mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-11-27 12:11:13 +00:00
96cf1f8c8e
When requesting an asset from different threads we had no guarantee that during the time we lookup an asset (which didn't exist) and the time we create the asset with the same type/ID another thread could not end up doing the same thing. In turns we could end up with 2 different threads loading the exact same asset and the cache basically forgetting about one of the entries meaning that the user would never get notified about one of those being ready to be used. There was also the case when requesting "sync" where the user was requesting an asset while another thread is creating it so it was still in "ASSET_INITIALIZING" state, meaning that the returned asset would be NULL which would be considered as an error in apps. Since the cache lock is recursive we can just take it during the whole ges_asset_request_async call and have other method still hold it. Part-of: <https://gitlab.freedesktop.org/gstreamer/gstreamer/-/merge_requests/5732>
1679 lines
50 KiB
C
1679 lines
50 KiB
C
/* GStreamer Editing Services
|
|
*
|
|
* Copyright (C) 2012-2015 Thibault Saunier <thibault.saunier@collabora.com>
|
|
* Copyright (C) 2012 Volodymyr Rudyi <vladimir.rudoy@gmail.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Library General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2 of the License, or (at your option) any later version.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Library General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Library General Public
|
|
* License along with this library; if not, write to the
|
|
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
* Boston, MA 02111-1307, USA.
|
|
*/
|
|
/**
|
|
* SECTION: gesasset
|
|
* @title: GESAsset
|
|
* @short_description: Represents usable resources inside the GStreamer
|
|
* Editing Services
|
|
*
|
|
* A #GESAsset in the GStreamer Editing Services represents a resources
|
|
* that can be used. In particular, any class that implements the
|
|
* #GESExtractable interface may have some associated assets with a
|
|
* corresponding #GESAsset:extractable-type, from which its objects can be
|
|
* extracted using ges_asset_extract(). Some examples would be
|
|
* #GESClip, #GESFormatter and #GESTrackElement.
|
|
*
|
|
* All assets that are created within GES are stored in a cache; one per
|
|
* each #GESAsset:id and #GESAsset:extractable-type pair. These assets can
|
|
* be fetched, and initialized if they do not yet exist in the cache,
|
|
* using ges_asset_request().
|
|
*
|
|
* ``` c
|
|
* GESAsset *effect_asset;
|
|
* GESEffect *effect;
|
|
*
|
|
* // You create an asset for an effect
|
|
* effect_asset = ges_asset_request (GES_TYPE_EFFECT, "agingtv", NULL);
|
|
*
|
|
* // And now you can extract an instance of GESEffect from that asset
|
|
* effect = GES_EFFECT (ges_asset_extract (effect_asset));
|
|
*
|
|
* ```
|
|
*
|
|
* The advantage of using assets, rather than simply creating the object
|
|
* directly, is that the currently loaded resources can be listed with
|
|
* ges_list_assets() and displayed to an end user. For example, to show
|
|
* which media files have been loaded, and a standard list of effects. In
|
|
* fact, the GES library already creates assets for #GESTransitionClip and
|
|
* #GESFormatter, which you can use to list all the available transition
|
|
* types and supported formats.
|
|
*
|
|
* The other advantage is that #GESAsset implements #GESMetaContainer, so
|
|
* metadata can be set on the asset, with some subclasses automatically
|
|
* creating this metadata on initiation.
|
|
*
|
|
* For example, to display information about the supported formats, you
|
|
* could do the following:
|
|
* |[
|
|
* GList *formatter_assets, *tmp;
|
|
*
|
|
* // List all the transitions
|
|
* formatter_assets = ges_list_assets (GES_TYPE_FORMATTER);
|
|
*
|
|
* // Print some infos about the formatter GESAsset
|
|
* for (tmp = formatter_assets; tmp; tmp = tmp->next) {
|
|
* gst_print ("Name of the formatter: %s, file extension it produces: %s",
|
|
* ges_meta_container_get_string (
|
|
* GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_NAME),
|
|
* ges_meta_container_get_string (
|
|
* GES_META_CONTAINER (tmp->data), GES_META_FORMATTER_EXTENSION));
|
|
* }
|
|
*
|
|
* g_list_free (transition_assets);
|
|
*
|
|
* ]|
|
|
*
|
|
* ## ID
|
|
*
|
|
* Each asset is uniquely defined in the cache by its
|
|
* #GESAsset:extractable-type and #GESAsset:id. Depending on the
|
|
* #GESAsset:extractable-type, the #GESAsset:id can be used to parametrise
|
|
* the creation of the object upon extraction. By default, a class that
|
|
* implements #GESExtractable will only have a single associated asset,
|
|
* with an #GESAsset:id set to the type name of its objects. However, this
|
|
* is overwritten by some implementations, which allow a class to have
|
|
* multiple associated assets. For example, for #GESTransitionClip the
|
|
* #GESAsset:id will be a nickname of the #GESTransitionClip:vtype. You
|
|
* should check the documentation for each extractable type to see if they
|
|
* differ from the default.
|
|
*
|
|
* Moreover, each #GESAsset:extractable-type may also associate itself
|
|
* with a specific asset subclass. In such cases, when their asset is
|
|
* requested, an asset of this subclass will be returned instead.
|
|
*
|
|
* ## Managing
|
|
*
|
|
* You can use a #GESProject to easily manage the assets of a
|
|
* #GESTimeline.
|
|
*
|
|
* ## Proxies
|
|
*
|
|
* Some assets can (temporarily) act as the #GESAsset:proxy of another
|
|
* asset. When the original asset is requested from the cache, the proxy
|
|
* will be returned in its place. This can be useful if, say, you want
|
|
* to substitute a #GESUriClipAsset corresponding to a high resolution
|
|
* media file with the asset of a lower resolution stand in.
|
|
*
|
|
* An asset may even have several proxies, the first of which will act as
|
|
* its default and be returned on requests, but the others will be ordered
|
|
* to take its place once it is removed. You can add a proxy to an asset,
|
|
* or set its default, using ges_asset_set_proxy(), and you can remove
|
|
* them with ges_asset_unproxy().
|
|
*/
|
|
#ifdef HAVE_CONFIG_H
|
|
#include "config.h"
|
|
#endif
|
|
|
|
#include "ges.h"
|
|
#include "ges-internal.h"
|
|
|
|
#define GLIB_DISABLE_DEPRECATION_WARNINGS
|
|
|
|
#include <gst/gst.h>
|
|
|
|
GST_DEBUG_CATEGORY_STATIC (ges_asset_debug);
|
|
#undef GST_CAT_DEFAULT
|
|
#define GST_CAT_DEFAULT ges_asset_debug
|
|
|
|
enum
|
|
{
|
|
PROP_0,
|
|
PROP_TYPE,
|
|
PROP_ID,
|
|
PROP_PROXY,
|
|
PROP_PROXY_TARGET,
|
|
PROP_LAST
|
|
};
|
|
|
|
typedef enum
|
|
{
|
|
ASSET_NOT_INITIALIZED,
|
|
ASSET_INITIALIZING, ASSET_INITIALIZED_WITH_ERROR,
|
|
ASSET_PROXIED,
|
|
ASSET_NEEDS_RELOAD,
|
|
ASSET_INITIALIZED
|
|
} GESAssetState;
|
|
|
|
static GParamSpec *_properties[PROP_LAST];
|
|
|
|
struct _GESAssetPrivate
|
|
{
|
|
gchar *id;
|
|
GESAssetState state;
|
|
GType extractable_type;
|
|
|
|
/* used internally by try_proxy to pre-set a proxy whilst an asset is
|
|
* still loading. It can be used later to set the proxy for the asset
|
|
* once it has finished loading */
|
|
char *proxied_asset_id;
|
|
|
|
/* actual list of proxies */
|
|
GList *proxies;
|
|
/* the asset whose proxies list we belong to */
|
|
GESAsset *proxy_target;
|
|
|
|
/* The error that occurred when an asset has been initialized with error */
|
|
GError *error;
|
|
};
|
|
|
|
/* Internal structure to help avoid full loading
|
|
* of one asset several times
|
|
*/
|
|
typedef struct
|
|
{
|
|
GList *results;
|
|
GESAsset *asset;
|
|
} GESAssetCacheEntry;
|
|
|
|
/* We are mapping entries by types and ID, such as:
|
|
*
|
|
* {
|
|
* first_extractable_type_name1 :
|
|
* {
|
|
* "some ID": GESAssetCacheEntry,
|
|
* "some other ID": GESAssetCacheEntry 2
|
|
* },
|
|
* second_extractable_type_name :
|
|
* {
|
|
* "some ID": GESAssetCacheEntry,
|
|
* "some other ID": GESAssetCacheEntry 2
|
|
* }
|
|
* }
|
|
*
|
|
* (The first extractable type is the type of the class that implemented
|
|
* the GESExtractable interface ie: GESClip, GESTimeline,
|
|
* GESFomatter, etc... but not subclasses)
|
|
*
|
|
* This is in order to be able to have 2 Asset with the same ID but
|
|
* different extractable types.
|
|
**/
|
|
static GHashTable *type_entries_table = NULL;
|
|
/* Protect all the entries in the cache */
|
|
static GRecMutex asset_cache_lock;
|
|
#define LOCK_CACHE (g_rec_mutex_lock (&asset_cache_lock))
|
|
#define UNLOCK_CACHE (g_rec_mutex_unlock (&asset_cache_lock))
|
|
|
|
static gchar *
|
|
_check_and_update_parameters (GType * extractable_type, const gchar * id,
|
|
GError ** error)
|
|
{
|
|
gchar *real_id;
|
|
GType old_type = *extractable_type;
|
|
|
|
*extractable_type =
|
|
ges_extractable_get_real_extractable_type_for_id (*extractable_type, id);
|
|
|
|
if (*extractable_type == G_TYPE_NONE) {
|
|
GST_WARNING ("No way to create a Asset for ID: %s, type: %s", id,
|
|
g_type_name (old_type));
|
|
|
|
if (error && *error == NULL)
|
|
g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID,
|
|
"Wrong ID, can not find any extractable_type");
|
|
return NULL;
|
|
}
|
|
|
|
real_id = ges_extractable_type_check_id (*extractable_type, id, error);
|
|
if (real_id == NULL) {
|
|
GST_WARNING ("Wrong ID %s, can not create asset", id);
|
|
|
|
g_free (real_id);
|
|
if (error && *error == NULL)
|
|
g_set_error (error, GES_ERROR, GES_ERROR_ASSET_WRONG_ID, "Wrong ID");
|
|
|
|
return NULL;
|
|
}
|
|
|
|
return real_id;
|
|
}
|
|
|
|
/* FIXME: why are we not accepting a GError ** error argument, which we
|
|
* could pass to ges_asset_cache_set_loaded ()? Which would allow the
|
|
* error to be set for the GInitable init method below */
|
|
static gboolean
|
|
start_loading (GESAsset * asset)
|
|
{
|
|
GInitableIface *iface;
|
|
|
|
iface = g_type_interface_peek (GES_ASSET_GET_CLASS (asset), G_TYPE_INITABLE);
|
|
|
|
if (!iface->init) {
|
|
GST_INFO_OBJECT (asset, "Can not start loading sync, as no ->init vmethod");
|
|
return FALSE;
|
|
}
|
|
|
|
ges_asset_cache_put (gst_object_ref (asset), NULL);
|
|
return ges_asset_cache_set_loaded (asset->priv->extractable_type,
|
|
asset->priv->id, NULL);
|
|
}
|
|
|
|
static gboolean
|
|
initable_init (GInitable * initable, GCancellable * cancellable,
|
|
GError ** error)
|
|
{
|
|
/* FIXME: Is there actually a reason to be freeing the GError that
|
|
* error points to? */
|
|
g_clear_error (error);
|
|
|
|
return start_loading (GES_ASSET (initable));
|
|
}
|
|
|
|
static void
|
|
async_initable_init_async (GAsyncInitable * initable, gint io_priority,
|
|
GCancellable * cancellable, GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
GTask *task;
|
|
|
|
GError *error = NULL;
|
|
GESAsset *asset = GES_ASSET (initable);
|
|
|
|
task = g_task_new (asset, cancellable, callback, user_data);
|
|
|
|
ges_asset_cache_put (gst_object_ref (asset), task);
|
|
switch (GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error)) {
|
|
case GES_ASSET_LOADING_ERROR:
|
|
{
|
|
if (error == NULL)
|
|
g_set_error (&error, GES_ERROR, GES_ERROR_ASSET_LOADING,
|
|
"Could not start loading asset");
|
|
|
|
/* FIXME Define error code */
|
|
ges_asset_cache_set_loaded (asset->priv->extractable_type,
|
|
asset->priv->id, error);
|
|
g_error_free (error);
|
|
return;
|
|
}
|
|
case GES_ASSET_LOADING_OK:
|
|
{
|
|
ges_asset_cache_set_loaded (asset->priv->extractable_type,
|
|
asset->priv->id, error);
|
|
return;
|
|
}
|
|
case GES_ASSET_LOADING_ASYNC:
|
|
/* If Async.... let it go */
|
|
/* FIXME: how are user subclasses that implement ->start_loading
|
|
* to return GES_ASSET_LOADING_ASYNC meant to invoke the private
|
|
* method ges_asset_cache_set_loaded once they finish initializing?
|
|
*/
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void
|
|
async_initable_iface_init (GAsyncInitableIface * async_initable_iface)
|
|
{
|
|
async_initable_iface->init_async = async_initable_init_async;
|
|
}
|
|
|
|
static void
|
|
initable_iface_init (GInitableIface * initable_iface)
|
|
{
|
|
initable_iface->init = initable_init;
|
|
}
|
|
|
|
G_DEFINE_TYPE_WITH_CODE (GESAsset, ges_asset, G_TYPE_OBJECT,
|
|
G_ADD_PRIVATE (GESAsset)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_ASYNC_INITABLE, async_initable_iface_init)
|
|
G_IMPLEMENT_INTERFACE (G_TYPE_INITABLE, initable_iface_init)
|
|
G_IMPLEMENT_INTERFACE (GES_TYPE_META_CONTAINER, NULL));
|
|
|
|
/* GESAsset virtual methods default implementation */
|
|
static GESAssetLoadingReturn
|
|
ges_asset_start_loading_default (GESAsset * asset, GError ** error)
|
|
{
|
|
return GES_ASSET_LOADING_OK;
|
|
}
|
|
|
|
G_GNUC_BEGIN_IGNORE_DEPRECATIONS;
|
|
static GESExtractable *
|
|
ges_asset_extract_default (GESAsset * asset, GError ** error)
|
|
{
|
|
guint n_params;
|
|
GParameter *params;
|
|
GESAssetPrivate *priv = asset->priv;
|
|
GESExtractable *n_extractable;
|
|
gint i;
|
|
GValue *values;
|
|
const gchar **names;
|
|
|
|
params = ges_extractable_type_get_parameters_from_id (priv->extractable_type,
|
|
priv->id, &n_params);
|
|
|
|
|
|
values = g_malloc0 (sizeof (GValue) * n_params);
|
|
names = g_malloc0 (sizeof (gchar *) * n_params);
|
|
|
|
for (i = 0; i < n_params; i++) {
|
|
values[i] = params[i].value;
|
|
names[i] = params[i].name;
|
|
}
|
|
|
|
n_extractable =
|
|
GES_EXTRACTABLE (g_object_new_with_properties (priv->extractable_type,
|
|
n_params, names, values));
|
|
g_free (names);
|
|
g_free (values);
|
|
|
|
while (n_params--)
|
|
g_value_unset (¶ms[n_params].value);
|
|
|
|
g_free (params);
|
|
|
|
return n_extractable;
|
|
}
|
|
|
|
G_GNUC_END_IGNORE_DEPRECATIONS;
|
|
|
|
static gboolean
|
|
ges_asset_request_id_update_default (GESAsset * self, gchar ** proposed_new_id,
|
|
GError * error)
|
|
{
|
|
return FALSE;
|
|
}
|
|
|
|
/* GObject virtual methods implementation */
|
|
static void
|
|
ges_asset_get_property (GObject * object, guint property_id,
|
|
GValue * value, GParamSpec * pspec)
|
|
{
|
|
GESAsset *asset = GES_ASSET (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_TYPE:
|
|
g_value_set_gtype (value, asset->priv->extractable_type);
|
|
break;
|
|
case PROP_ID:
|
|
g_value_set_string (value, asset->priv->id);
|
|
break;
|
|
case PROP_PROXY:
|
|
g_value_set_object (value, ges_asset_get_proxy (asset));
|
|
break;
|
|
case PROP_PROXY_TARGET:
|
|
g_value_set_object (value, ges_asset_get_proxy_target (asset));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ges_asset_set_property (GObject * object, guint property_id,
|
|
const GValue * value, GParamSpec * pspec)
|
|
{
|
|
GESAsset *asset = GES_ASSET (object);
|
|
|
|
switch (property_id) {
|
|
case PROP_TYPE:
|
|
asset->priv->extractable_type = g_value_get_gtype (value);
|
|
/* NOTE: we calling this in the setter so metadata is set on the
|
|
* asset upon initiation, but before it has been loaded. */
|
|
ges_extractable_register_metas (asset->priv->extractable_type, asset);
|
|
break;
|
|
case PROP_ID:
|
|
asset->priv->id = g_value_dup_string (value);
|
|
break;
|
|
case PROP_PROXY:
|
|
ges_asset_set_proxy (asset, g_value_get_object (value));
|
|
break;
|
|
default:
|
|
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
|
|
}
|
|
}
|
|
|
|
static void
|
|
ges_asset_finalize (GObject * object)
|
|
{
|
|
GESAssetPrivate *priv = GES_ASSET (object)->priv;
|
|
|
|
GST_LOG_OBJECT (object, "finalizing");
|
|
|
|
if (priv->id)
|
|
g_free (priv->id);
|
|
|
|
if (priv->proxied_asset_id)
|
|
g_free (priv->proxied_asset_id);
|
|
|
|
if (priv->error)
|
|
g_error_free (priv->error);
|
|
|
|
if (priv->proxies)
|
|
g_list_free (priv->proxies);
|
|
|
|
G_OBJECT_CLASS (ges_asset_parent_class)->finalize (object);
|
|
}
|
|
|
|
void
|
|
ges_asset_class_init (GESAssetClass * klass)
|
|
{
|
|
GObjectClass *object_class = G_OBJECT_CLASS (klass);
|
|
|
|
object_class->get_property = ges_asset_get_property;
|
|
object_class->set_property = ges_asset_set_property;
|
|
object_class->finalize = ges_asset_finalize;
|
|
|
|
/**
|
|
* GESAsset:extractable-type:
|
|
*
|
|
* The #GESExtractable object type that can be extracted from the asset.
|
|
*/
|
|
_properties[PROP_TYPE] =
|
|
g_param_spec_gtype ("extractable-type", "Extractable type",
|
|
"The type of the Object that can be extracted out of the asset",
|
|
G_TYPE_OBJECT, G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
|
|
|
|
/**
|
|
* GESAsset:id:
|
|
*
|
|
* The ID of the asset. This should be unique amongst all assets with
|
|
* the same #GESAsset:extractable-type. Depending on the associated
|
|
* #GESExtractable implementation, this id may convey some information
|
|
* about the #GObject that should be extracted. Note that, as such, the
|
|
* ID will have an expected format, and you can not choose this value
|
|
* arbitrarily. By default, this will be set to the type name of the
|
|
* #GESAsset:extractable-type, but you should check the documentation
|
|
* of the extractable type to see whether they differ from the
|
|
* default behaviour.
|
|
*/
|
|
_properties[PROP_ID] =
|
|
g_param_spec_string ("id", "Identifier",
|
|
"The unique identifier of the asset", NULL,
|
|
G_PARAM_CONSTRUCT_ONLY | G_PARAM_READWRITE);
|
|
|
|
/**
|
|
* GESAsset:proxy:
|
|
*
|
|
* The default proxy for this asset, or %NULL if it has no proxy. A
|
|
* proxy will act as a substitute for the original asset when the
|
|
* original is requested (see ges_asset_request()).
|
|
*
|
|
* Setting this property will not usually remove the existing proxy, but
|
|
* will replace it as the default (see ges_asset_set_proxy()).
|
|
*/
|
|
_properties[PROP_PROXY] =
|
|
g_param_spec_object ("proxy", "Proxy",
|
|
"The asset default proxy.", GES_TYPE_ASSET, G_PARAM_READWRITE);
|
|
|
|
/**
|
|
* GESAsset:proxy-target:
|
|
*
|
|
* The asset that this asset is a proxy for, or %NULL if it is not a
|
|
* proxy for another asset.
|
|
*
|
|
* Note that even if this asset is acting as a proxy for another asset,
|
|
* but this asset is not the default #GESAsset:proxy, then @proxy-target
|
|
* will *still* point to this other asset. So you should check the
|
|
* #GESAsset:proxy property of @target-proxy before assuming it is the
|
|
* current default proxy for the target.
|
|
*
|
|
* Note that the #GObject::notify for this property is emitted after
|
|
* the #GESAsset:proxy #GObject::notify for the corresponding (if any)
|
|
* asset it is now the proxy of/no longer the proxy of.
|
|
*/
|
|
_properties[PROP_PROXY_TARGET] =
|
|
g_param_spec_object ("proxy-target", "Proxy target",
|
|
"The target of a proxy asset.", GES_TYPE_ASSET, G_PARAM_READABLE);
|
|
|
|
g_object_class_install_properties (object_class, PROP_LAST, _properties);
|
|
|
|
klass->start_loading = ges_asset_start_loading_default;
|
|
klass->extract = ges_asset_extract_default;
|
|
klass->request_id_update = ges_asset_request_id_update_default;
|
|
klass->inform_proxy = NULL;
|
|
|
|
GST_DEBUG_CATEGORY_INIT (ges_asset_debug, "ges-asset",
|
|
GST_DEBUG_FG_BLUE | GST_DEBUG_BOLD, "GES Asset");
|
|
}
|
|
|
|
void
|
|
ges_asset_init (GESAsset * self)
|
|
{
|
|
self->priv = ges_asset_get_instance_private (self);
|
|
|
|
self->priv->state = ASSET_INITIALIZING;
|
|
self->priv->proxied_asset_id = NULL;
|
|
}
|
|
|
|
/* Internal methods */
|
|
|
|
static inline const gchar *
|
|
_extractable_type_name (GType type)
|
|
{
|
|
/* We can use `ges_asset_request (GES_TYPE_FORMATTER);` */
|
|
if (g_type_is_a (type, GES_TYPE_FORMATTER))
|
|
return g_type_name (GES_TYPE_FORMATTER);
|
|
|
|
return g_type_name (type);
|
|
}
|
|
|
|
static void
|
|
ges_asset_cache_init_unlocked (void)
|
|
{
|
|
if (type_entries_table)
|
|
return;
|
|
|
|
type_entries_table = g_hash_table_new_full (g_str_hash, g_str_equal,
|
|
g_free, (GDestroyNotify) g_hash_table_unref);
|
|
|
|
_init_formatter_assets ();
|
|
_init_standard_transition_assets ();
|
|
}
|
|
|
|
|
|
/* WITH LOCK_CACHE */
|
|
static GHashTable *
|
|
_get_type_entries (void)
|
|
{
|
|
if (type_entries_table)
|
|
return type_entries_table;
|
|
|
|
ges_asset_cache_init_unlocked ();
|
|
|
|
return type_entries_table;
|
|
}
|
|
|
|
/* WITH LOCK_CACHE */
|
|
static inline GESAssetCacheEntry *
|
|
_lookup_entry (GType extractable_type, const gchar * id)
|
|
{
|
|
GHashTable *entries_table;
|
|
|
|
entries_table = g_hash_table_lookup (_get_type_entries (),
|
|
_extractable_type_name (extractable_type));
|
|
if (entries_table)
|
|
return g_hash_table_lookup (entries_table, id);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
_free_entries (gpointer entry)
|
|
{
|
|
GESAssetCacheEntry *data = (GESAssetCacheEntry *) entry;
|
|
if (data->asset)
|
|
gst_object_unref (data->asset);
|
|
g_free (entry);
|
|
}
|
|
|
|
static void
|
|
_gtask_return_error (GTask * task, GError * error)
|
|
{
|
|
g_task_return_error (task, g_error_copy (error));
|
|
}
|
|
|
|
static void
|
|
_gtask_return_true (GTask * task, gpointer udata)
|
|
{
|
|
g_task_return_boolean (task, TRUE);
|
|
}
|
|
|
|
/**
|
|
* ges_asset_cache_lookup:
|
|
*
|
|
* @id String identifier of asset
|
|
*
|
|
* Looks for asset with specified id in cache and it's completely loaded.
|
|
*
|
|
* Returns: (transfer none) (nullable): The #GESAsset found or %NULL
|
|
*/
|
|
GESAsset *
|
|
ges_asset_cache_lookup (GType extractable_type, const gchar * id)
|
|
{
|
|
GESAsset *asset = NULL;
|
|
GESAssetCacheEntry *entry = NULL;
|
|
|
|
g_return_val_if_fail (id, NULL);
|
|
|
|
LOCK_CACHE;
|
|
entry = _lookup_entry (extractable_type, id);
|
|
if (entry)
|
|
asset = entry->asset;
|
|
UNLOCK_CACHE;
|
|
|
|
return asset;
|
|
}
|
|
|
|
static void
|
|
ges_asset_cache_append_task (GType extractable_type,
|
|
const gchar * id, GTask * task)
|
|
{
|
|
GESAssetCacheEntry *entry = NULL;
|
|
|
|
LOCK_CACHE;
|
|
if ((entry = _lookup_entry (extractable_type, id)))
|
|
entry->results = g_list_append (entry->results, task);
|
|
UNLOCK_CACHE;
|
|
}
|
|
|
|
gboolean
|
|
ges_asset_cache_set_loaded (GType extractable_type, const gchar * id,
|
|
GError * error)
|
|
{
|
|
GESAsset *asset;
|
|
GESAssetCacheEntry *entry = NULL;
|
|
GList *results = NULL;
|
|
GFunc user_func = NULL;
|
|
gpointer user_data = NULL;
|
|
|
|
LOCK_CACHE;
|
|
if ((entry = _lookup_entry (extractable_type, id)) == NULL) {
|
|
UNLOCK_CACHE;
|
|
GST_ERROR ("Calling but type %s ID: %s not in cached, "
|
|
"something massively screwed", g_type_name (extractable_type), id);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
asset = entry->asset;
|
|
GST_DEBUG_OBJECT (entry->asset, ": (extractable type: %s) loaded, calling %i "
|
|
"callback (Error: %s)", g_type_name (asset->priv->extractable_type),
|
|
g_list_length (entry->results), error ? error->message : "");
|
|
|
|
results = entry->results;
|
|
entry->results = NULL;
|
|
|
|
if (error) {
|
|
asset->priv->state = ASSET_INITIALIZED_WITH_ERROR;
|
|
if (asset->priv->error)
|
|
g_error_free (asset->priv->error);
|
|
asset->priv->error = g_error_copy (error);
|
|
|
|
/* In case of error we do not want to emit in idle as we need to recover
|
|
* if possible */
|
|
user_func = (GFunc) _gtask_return_error;
|
|
user_data = error;
|
|
GST_DEBUG_OBJECT (asset, "initialized with error");
|
|
} else {
|
|
asset->priv->state = ASSET_INITIALIZED;
|
|
user_func = (GFunc) _gtask_return_true;
|
|
GST_DEBUG_OBJECT (asset, "initialized");
|
|
}
|
|
UNLOCK_CACHE;
|
|
|
|
g_list_foreach (results, user_func, user_data);
|
|
g_list_free_full (results, g_object_unref);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* transfer full for both @asset and @task */
|
|
void
|
|
ges_asset_cache_put (GESAsset * asset, GTask * task)
|
|
{
|
|
GType extractable_type;
|
|
const gchar *asset_id;
|
|
GESAssetCacheEntry *entry;
|
|
|
|
/* Needing to work with the cache, taking the lock */
|
|
asset_id = ges_asset_get_id (asset);
|
|
extractable_type = asset->priv->extractable_type;
|
|
|
|
LOCK_CACHE;
|
|
if (!(entry = _lookup_entry (extractable_type, asset_id))) {
|
|
GHashTable *entries_table;
|
|
|
|
entries_table = g_hash_table_lookup (_get_type_entries (),
|
|
_extractable_type_name (extractable_type));
|
|
if (entries_table == NULL) {
|
|
entries_table = g_hash_table_new_full (g_str_hash, g_str_equal, g_free,
|
|
_free_entries);
|
|
|
|
g_hash_table_insert (_get_type_entries (),
|
|
g_strdup (_extractable_type_name (extractable_type)), entries_table);
|
|
}
|
|
|
|
entry = g_new0 (GESAssetCacheEntry, 1);
|
|
|
|
/* transfer asset to entry */
|
|
entry->asset = asset;
|
|
if (task)
|
|
entry->results = g_list_prepend (entry->results, task);
|
|
g_hash_table_insert (entries_table, (gpointer) g_strdup (asset_id),
|
|
(gpointer) entry);
|
|
} else {
|
|
/* give up the reference we were given */
|
|
gst_object_unref (asset);
|
|
if (task) {
|
|
GST_DEBUG ("%s already in cache, adding result %p", asset_id, task);
|
|
entry->results = g_list_prepend (entry->results, task);
|
|
}
|
|
}
|
|
UNLOCK_CACHE;
|
|
}
|
|
|
|
void
|
|
ges_asset_cache_init (void)
|
|
{
|
|
LOCK_CACHE;
|
|
ges_asset_cache_init_unlocked ();
|
|
UNLOCK_CACHE;
|
|
}
|
|
|
|
void
|
|
ges_asset_cache_deinit (void)
|
|
{
|
|
_deinit_formatter_assets ();
|
|
|
|
LOCK_CACHE;
|
|
g_hash_table_destroy (type_entries_table);
|
|
type_entries_table = NULL;
|
|
UNLOCK_CACHE;
|
|
}
|
|
|
|
gboolean
|
|
ges_asset_request_id_update (GESAsset * asset, gchar ** proposed_id,
|
|
GError * error)
|
|
{
|
|
g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
|
|
|
|
return GES_ASSET_GET_CLASS (asset)->request_id_update (asset, proposed_id,
|
|
error);
|
|
}
|
|
|
|
/* pre-set a proxy id whilst the asset is still loading. Once the proxy
|
|
* is loaded, call ges_asset_finish_proxy (proxy) */
|
|
gboolean
|
|
ges_asset_try_proxy (GESAsset * asset, const gchar * new_id)
|
|
{
|
|
GESAssetClass *class;
|
|
|
|
g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
|
|
|
|
if (g_strcmp0 (asset->priv->id, new_id) == 0) {
|
|
GST_WARNING_OBJECT (asset, "Trying to proxy to itself (%s),"
|
|
" NOT possible", new_id);
|
|
|
|
return FALSE;
|
|
} else if (g_strcmp0 (asset->priv->proxied_asset_id, new_id) == 0) {
|
|
GST_WARNING_OBJECT (asset,
|
|
"Trying to proxy to same currently set proxy: %s -- %s",
|
|
asset->priv->proxied_asset_id, new_id);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
g_free (asset->priv->proxied_asset_id);
|
|
asset->priv->state = ASSET_PROXIED;
|
|
asset->priv->proxied_asset_id = g_strdup (new_id);
|
|
|
|
/* FIXME: inform_proxy is not used consistently. For example, it is
|
|
* not called in set_proxy. However, it is still used by GESUriAsset.
|
|
* We should find some other method */
|
|
class = GES_ASSET_GET_CLASS (asset);
|
|
if (class->inform_proxy)
|
|
GES_ASSET_GET_CLASS (asset)->inform_proxy (asset, new_id);
|
|
|
|
GST_DEBUG_OBJECT (asset, "Trying to proxy to %s", new_id);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static gboolean
|
|
_lookup_proxied_asset (const gchar * id, GESAssetCacheEntry * entry,
|
|
const gchar * asset_id)
|
|
{
|
|
return !g_strcmp0 (asset_id, entry->asset->priv->proxied_asset_id);
|
|
}
|
|
|
|
/* find the assets that called try_proxy for the asset id of @proxy
|
|
* and set @proxy as their proxy */
|
|
gboolean
|
|
ges_asset_finish_proxy (GESAsset * proxy)
|
|
{
|
|
GESAsset *proxied_asset;
|
|
GHashTable *entries_table;
|
|
GESAssetCacheEntry *entry;
|
|
|
|
LOCK_CACHE;
|
|
entries_table = g_hash_table_lookup (_get_type_entries (),
|
|
_extractable_type_name (proxy->priv->extractable_type));
|
|
entry = g_hash_table_find (entries_table, (GHRFunc) _lookup_proxied_asset,
|
|
(gpointer) ges_asset_get_id (proxy));
|
|
|
|
if (!entry) {
|
|
UNLOCK_CACHE;
|
|
GST_DEBUG_OBJECT (proxy, "Not proxying any asset %s", proxy->priv->id);
|
|
return FALSE;
|
|
}
|
|
|
|
proxied_asset = entry->asset;
|
|
UNLOCK_CACHE;
|
|
|
|
/* If the asset with the matching ->proxied_asset_id is already proxied
|
|
* by another asset, we actually want @proxy to proxy this instead */
|
|
while (proxied_asset->priv->proxies)
|
|
proxied_asset = proxied_asset->priv->proxies->data;
|
|
|
|
/* unless it is ourselves. I.e. it is already proxied by us */
|
|
if (proxied_asset == proxy)
|
|
return FALSE;
|
|
|
|
GST_INFO_OBJECT (proxied_asset,
|
|
"%s Making sure the proxy chain is fully set.",
|
|
ges_asset_get_id (entry->asset));
|
|
if (g_strcmp0 (proxied_asset->priv->proxied_asset_id, proxy->priv->id) ||
|
|
g_strcmp0 (proxied_asset->priv->id, proxy->priv->proxied_asset_id))
|
|
ges_asset_finish_proxy (proxied_asset);
|
|
return ges_asset_set_proxy (proxied_asset, proxy);
|
|
}
|
|
|
|
static gboolean
|
|
_contained_in_proxy_tree (GESAsset * node, GESAsset * search)
|
|
{
|
|
GList *tmp;
|
|
if (node == search)
|
|
return TRUE;
|
|
for (tmp = node->priv->proxies; tmp; tmp = tmp->next) {
|
|
if (_contained_in_proxy_tree (tmp->data, search))
|
|
return TRUE;
|
|
}
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_set_proxy:
|
|
* @asset: The #GESAsset to proxy
|
|
* @proxy: (allow-none): A new default proxy for @asset
|
|
*
|
|
* Sets the #GESAsset:proxy for the asset.
|
|
*
|
|
* If @proxy is among the existing proxies of the asset (see
|
|
* ges_asset_list_proxies()) it will be moved to become the default
|
|
* proxy. Otherwise, if @proxy is not %NULL, it will be added to the list
|
|
* of proxies, as the new default. The previous default proxy will become
|
|
* 'next in line' for if the new one is removed, and so on. As such, this
|
|
* will **not** actually remove the previous default proxy (use
|
|
* ges_asset_unproxy() for that).
|
|
*
|
|
* Note that an asset can only act as a proxy for one other asset.
|
|
*
|
|
* As a special case, if @proxy is %NULL, then this method will actually
|
|
* remove **all** proxies from the asset.
|
|
*
|
|
* Returns: %TRUE if @proxy was successfully set as the default for
|
|
* @asset.
|
|
*/
|
|
gboolean
|
|
ges_asset_set_proxy (GESAsset * asset, GESAsset * proxy)
|
|
{
|
|
GESAsset *current_target;
|
|
g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
|
|
g_return_val_if_fail (proxy == NULL || GES_IS_ASSET (proxy), FALSE);
|
|
g_return_val_if_fail (asset != proxy, FALSE);
|
|
|
|
if (!proxy) {
|
|
GList *tmp, *proxies;
|
|
if (asset->priv->error) {
|
|
GST_ERROR_OBJECT (asset,
|
|
"Asset was loaded with error (%s), it should not be 'unproxied'",
|
|
asset->priv->error->message);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
GST_DEBUG_OBJECT (asset, "Removing all proxies");
|
|
proxies = asset->priv->proxies;
|
|
asset->priv->proxies = NULL;
|
|
|
|
for (tmp = proxies; tmp; tmp = tmp->next) {
|
|
GESAsset *proxy = tmp->data;
|
|
proxy->priv->proxy_target = NULL;
|
|
}
|
|
asset->priv->state = ASSET_INITIALIZED;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
|
|
for (tmp = proxies; tmp; tmp = tmp->next)
|
|
g_object_notify_by_pspec (G_OBJECT (tmp->data),
|
|
_properties[PROP_PROXY_TARGET]);
|
|
|
|
g_list_free (proxies);
|
|
return TRUE;
|
|
}
|
|
current_target = proxy->priv->proxy_target;
|
|
|
|
if (current_target && current_target != asset) {
|
|
GST_ERROR_OBJECT (asset,
|
|
"Trying to use '%s' as a proxy, but it is already proxying '%s'",
|
|
proxy->priv->id, current_target->priv->id);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (_contained_in_proxy_tree (proxy, asset)) {
|
|
GST_ERROR_OBJECT (asset, "Trying to setup a circular proxying dependency!");
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
if (g_list_find (asset->priv->proxies, proxy)) {
|
|
GST_INFO_OBJECT (asset,
|
|
"%" GST_PTR_FORMAT " already marked as proxy, moving to first", proxy);
|
|
asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
|
|
}
|
|
|
|
GST_INFO ("%s is now proxied by %s", asset->priv->id, proxy->priv->id);
|
|
asset->priv->proxies = g_list_prepend (asset->priv->proxies, proxy);
|
|
|
|
proxy->priv->proxy_target = asset;
|
|
asset->priv->state = ASSET_PROXIED;
|
|
|
|
g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
|
|
if (current_target != asset)
|
|
g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
|
|
|
|
/* FIXME: ->inform_proxy is not called. We should figure out what the
|
|
* purpose of ->inform_proxy should be generically. Currently, it is
|
|
* only called in ges_asset_try_proxy! */
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_unproxy:
|
|
* @asset: The #GESAsset to no longer proxy with @proxy
|
|
* @proxy: An existing proxy of @asset
|
|
*
|
|
* Removes the proxy from the available list of proxies for the asset. If
|
|
* the given proxy is the default proxy of the list, then the next proxy
|
|
* in the available list (see ges_asset_list_proxies()) will become the
|
|
* default. If there are no other proxies, then the asset will no longer
|
|
* have a default #GESAsset:proxy.
|
|
*
|
|
* Returns: %TRUE if @proxy was successfully removed from @asset's proxy
|
|
* list.
|
|
*/
|
|
gboolean
|
|
ges_asset_unproxy (GESAsset * asset, GESAsset * proxy)
|
|
{
|
|
gboolean removing_default;
|
|
gboolean last_proxy;
|
|
g_return_val_if_fail (GES_IS_ASSET (asset), FALSE);
|
|
g_return_val_if_fail (GES_IS_ASSET (proxy), FALSE);
|
|
g_return_val_if_fail (asset != proxy, FALSE);
|
|
|
|
/* also tests if the list is NULL */
|
|
if (!g_list_find (asset->priv->proxies, proxy)) {
|
|
GST_INFO_OBJECT (asset, "'%s' is not a proxy.", proxy->priv->id);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
last_proxy = (asset->priv->proxies->next == NULL);
|
|
if (last_proxy && asset->priv->error) {
|
|
GST_ERROR_OBJECT (asset,
|
|
"Asset was loaded with error (%s), its last proxy '%s' should "
|
|
"not be removed", asset->priv->error->message, proxy->priv->id);
|
|
return FALSE;
|
|
}
|
|
|
|
removing_default = (asset->priv->proxies->data == proxy);
|
|
|
|
asset->priv->proxies = g_list_remove (asset->priv->proxies, proxy);
|
|
|
|
if (last_proxy)
|
|
asset->priv->state = ASSET_INITIALIZED;
|
|
proxy->priv->proxy_target = NULL;
|
|
|
|
if (removing_default)
|
|
g_object_notify_by_pspec (G_OBJECT (asset), _properties[PROP_PROXY]);
|
|
g_object_notify_by_pspec (G_OBJECT (proxy), _properties[PROP_PROXY_TARGET]);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_list_proxies:
|
|
* @asset: A #GESAsset
|
|
*
|
|
* Get all the proxies that the asset has. The first item of the list will
|
|
* be the default #GESAsset:proxy. The second will be the proxy that is
|
|
* 'next in line' to be default, and so on.
|
|
*
|
|
* Returns: (element-type GESAsset) (transfer none): The list of proxies
|
|
* that @asset has.
|
|
*/
|
|
GList *
|
|
ges_asset_list_proxies (GESAsset * asset)
|
|
{
|
|
g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
|
|
|
|
return asset->priv->proxies;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_get_proxy:
|
|
* @asset: A #GESAsset
|
|
*
|
|
* Gets the default #GESAsset:proxy of the asset.
|
|
*
|
|
* Returns: (transfer none) (nullable): The default proxy of @asset.
|
|
*/
|
|
GESAsset *
|
|
ges_asset_get_proxy (GESAsset * asset)
|
|
{
|
|
g_return_val_if_fail (GES_IS_ASSET (asset), NULL);
|
|
|
|
if (asset->priv->state == ASSET_PROXIED && asset->priv->proxies) {
|
|
return asset->priv->proxies->data;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_get_proxy_target:
|
|
* @proxy: A #GESAsset
|
|
*
|
|
* Gets the #GESAsset:proxy-target of the asset.
|
|
*
|
|
* Note that the proxy target may have loaded with an error, so you should
|
|
* call ges_asset_get_error() on the returned target.
|
|
*
|
|
* Returns: (transfer none) (nullable): The asset that @proxy is a proxy
|
|
* of.
|
|
*/
|
|
GESAsset *
|
|
ges_asset_get_proxy_target (GESAsset * proxy)
|
|
{
|
|
g_return_val_if_fail (GES_IS_ASSET (proxy), NULL);
|
|
|
|
return proxy->priv->proxy_target;
|
|
}
|
|
|
|
/* Caution, this method should be used in rare cases (ie: for the project
|
|
* as we can change its ID from a useless one to a proper URI). In most
|
|
* cases you want to update the ID creating a proxy
|
|
*/
|
|
void
|
|
ges_asset_set_id (GESAsset * asset, const gchar * id)
|
|
{
|
|
GHashTable *entries;
|
|
|
|
gpointer orig_id = NULL;
|
|
GESAssetCacheEntry *entry = NULL;
|
|
GESAssetPrivate *priv = NULL;
|
|
|
|
g_return_if_fail (GES_IS_ASSET (asset));
|
|
|
|
priv = asset->priv;
|
|
|
|
if (priv->state != ASSET_INITIALIZED) {
|
|
GST_WARNING_OBJECT (asset, "Trying to rest ID on an object that is"
|
|
" not properly loaded");
|
|
return;
|
|
}
|
|
|
|
if (g_strcmp0 (id, priv->id) == 0) {
|
|
GST_DEBUG_OBJECT (asset, "ID is already %s", id);
|
|
|
|
return;
|
|
}
|
|
|
|
LOCK_CACHE;
|
|
entries = g_hash_table_lookup (_get_type_entries (),
|
|
_extractable_type_name (asset->priv->extractable_type));
|
|
|
|
g_return_if_fail (g_hash_table_lookup_extended (entries, priv->id, &orig_id,
|
|
(gpointer *) & entry));
|
|
|
|
g_hash_table_steal (entries, priv->id);
|
|
g_hash_table_insert (entries, g_strdup (id), entry);
|
|
|
|
GST_DEBUG_OBJECT (asset, "Changing id from %s to %s", priv->id, id);
|
|
g_free (priv->id);
|
|
g_free (orig_id);
|
|
priv->id = g_strdup (id);
|
|
UNLOCK_CACHE;
|
|
}
|
|
|
|
static GESAsset *
|
|
_ensure_asset_for_wrong_id (const gchar * wrong_id, GType extractable_type,
|
|
GError * error)
|
|
{
|
|
GESAsset *asset;
|
|
|
|
if ((asset = ges_asset_cache_lookup (extractable_type, wrong_id)))
|
|
return asset;
|
|
|
|
/* It is a dummy GESAsset, we just bruteforce its creation */
|
|
asset = g_object_new (GES_TYPE_ASSET, "id", wrong_id,
|
|
"extractable-type", extractable_type, NULL);
|
|
|
|
/* transfer ownership to the cache */
|
|
ges_asset_cache_put (asset, NULL);
|
|
ges_asset_cache_set_loaded (extractable_type, wrong_id, error);
|
|
|
|
return asset;
|
|
}
|
|
|
|
/**********************************
|
|
* *
|
|
* API implementation *
|
|
* *
|
|
**********************************/
|
|
|
|
/**
|
|
* ges_asset_get_extractable_type:
|
|
* @self: The #GESAsset
|
|
*
|
|
* Gets the #GESAsset:extractable-type of the asset.
|
|
*
|
|
* Returns: The extractable type of @self.
|
|
*/
|
|
GType
|
|
ges_asset_get_extractable_type (GESAsset * self)
|
|
{
|
|
g_return_val_if_fail (GES_IS_ASSET (self), G_TYPE_INVALID);
|
|
|
|
return self->priv->extractable_type;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_request:
|
|
* @extractable_type: The #GESAsset:extractable-type of the asset
|
|
* @id: (allow-none): The #GESAsset:id of the asset
|
|
* @error: (allow-none): An error to be set if the requested asset has
|
|
* loaded with an error, or %NULL to ignore
|
|
*
|
|
* Returns an asset with the given properties. If such an asset already
|
|
* exists in the cache (it has been previously created in GES), then a
|
|
* reference to the existing asset is returned. Otherwise, a newly created
|
|
* asset is returned, and also added to the cache.
|
|
*
|
|
* If the requested asset has been loaded with an error, then @error is
|
|
* set, if given, and %NULL will be returned instead.
|
|
*
|
|
* Note that the given @id may not be exactly the #GESAsset:id that is
|
|
* set on the returned asset. For instance, it may be adjusted into a
|
|
* standard format. Or, if a #GESExtractable type does not have its
|
|
* extraction parametrised, as is the case by default, then the given @id
|
|
* may be ignored entirely and the #GESAsset:id set to some standard, in
|
|
* which case a %NULL @id can be given.
|
|
*
|
|
* Similarly, the given @extractable_type may not be exactly the
|
|
* #GESAsset:extractable-type that is set on the returned asset. Instead,
|
|
* the actual extractable type may correspond to a subclass of the given
|
|
* @extractable_type, depending on the given @id.
|
|
*
|
|
* Moreover, depending on the given @extractable_type, the returned asset
|
|
* may belong to a subclass of #GESAsset.
|
|
*
|
|
* Finally, if the requested asset has a #GESAsset:proxy, then the proxy
|
|
* that is found at the end of the chain of proxies is returned (a proxy's
|
|
* proxy will take its place, and so on, unless it has no proxy).
|
|
*
|
|
* Some asset subclasses only support asynchronous construction of its
|
|
* assets, such as #GESUriClip. For such assets this method will fail, and
|
|
* you should use ges_asset_request_async() instead. In the case of
|
|
* #GESUriClip, you can use ges_uri_clip_asset_request_sync() if you only
|
|
* want to wait for the request to finish.
|
|
*
|
|
* Returns: (transfer full) (allow-none): A reference to the requested
|
|
* asset, or %NULL if an error occurred.
|
|
*/
|
|
GESAsset *
|
|
ges_asset_request (GType extractable_type, const gchar * id, GError ** error)
|
|
{
|
|
gchar *real_id;
|
|
|
|
GError *lerr = NULL;
|
|
GESAsset *asset = NULL, *proxy;
|
|
gboolean proxied = TRUE;
|
|
|
|
g_return_val_if_fail (error == NULL || *error == NULL, NULL);
|
|
g_return_val_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT), NULL);
|
|
g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
|
|
NULL);
|
|
|
|
real_id = _check_and_update_parameters (&extractable_type, id, &lerr);
|
|
if (real_id == NULL) {
|
|
/* We create an asset for that wrong ID so we have a reference that the
|
|
* user requested it */
|
|
_ensure_asset_for_wrong_id (id, extractable_type, lerr);
|
|
real_id = g_strdup (id);
|
|
}
|
|
if (lerr)
|
|
g_error_free (lerr);
|
|
|
|
/* asset owned by cache */
|
|
LOCK_CACHE;
|
|
asset = ges_asset_cache_lookup (extractable_type, real_id);
|
|
if (asset) {
|
|
while (proxied) {
|
|
proxied = FALSE;
|
|
switch (asset->priv->state) {
|
|
case ASSET_INITIALIZED:
|
|
break;
|
|
case ASSET_INITIALIZING:
|
|
asset = NULL;
|
|
break;
|
|
case ASSET_PROXIED:
|
|
proxy = ges_asset_get_proxy (asset);
|
|
if (proxy == NULL) {
|
|
GST_ERROR ("Proxied against an asset we do not"
|
|
" have in cache, something massively screwed");
|
|
asset = NULL;
|
|
} else {
|
|
proxied = TRUE;
|
|
do {
|
|
asset = proxy;
|
|
} while ((proxy = ges_asset_get_proxy (asset)));
|
|
}
|
|
break;
|
|
case ASSET_NEEDS_RELOAD:
|
|
GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
|
|
if (!start_loading (asset)) {
|
|
GST_ERROR ("Failed to reload the asset for id %s", id);
|
|
asset = NULL;
|
|
}
|
|
break;
|
|
case ASSET_INITIALIZED_WITH_ERROR:
|
|
GST_WARNING_OBJECT (asset, "Initialized with error, not returning");
|
|
if (asset->priv->error && error)
|
|
*error = g_error_copy (asset->priv->error);
|
|
asset = NULL;
|
|
break;
|
|
default:
|
|
GST_WARNING ("Case %i not handle, returning", asset->priv->state);
|
|
asset = NULL;
|
|
break;
|
|
}
|
|
}
|
|
if (asset)
|
|
gst_object_ref (asset);
|
|
} else {
|
|
GObjectClass *klass;
|
|
GInitableIface *iface;
|
|
GType asset_type = ges_extractable_type_get_asset_type (extractable_type);
|
|
|
|
klass = g_type_class_ref (asset_type);
|
|
iface = g_type_interface_peek (klass, G_TYPE_INITABLE);
|
|
|
|
if (iface->init) {
|
|
/* FIXME: allow the error to be set, which GInitable is designed
|
|
* for! */
|
|
asset = g_initable_new (asset_type,
|
|
NULL, NULL, "id", real_id, "extractable-type",
|
|
extractable_type, NULL);
|
|
} else {
|
|
GST_INFO ("Tried to create an Asset for type %s but no ->init method",
|
|
g_type_name (extractable_type));
|
|
}
|
|
g_type_class_unref (klass);
|
|
}
|
|
UNLOCK_CACHE;
|
|
|
|
if (real_id)
|
|
g_free (real_id);
|
|
|
|
GST_DEBUG ("New asset created synchronously: %p", asset);
|
|
return asset;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_request_async:
|
|
* @extractable_type: The #GESAsset:extractable-type of the asset
|
|
* @id: (allow-none): The #GESAsset:id of the asset
|
|
* @cancellable: (allow-none): An object to allow cancellation of the
|
|
* asset request, or %NULL to ignore
|
|
* @callback: A function to call when the initialization is finished
|
|
* @user_data: Data to be passed to @callback
|
|
*
|
|
* Requests an asset with the given properties asynchronously (see
|
|
* ges_asset_request()). When the asset has been initialized or fetched
|
|
* from the cache, the given callback function will be called. The
|
|
* asset can then be retrieved in the callback using the
|
|
* ges_asset_request_finish() method on the given #GAsyncResult.
|
|
*
|
|
* Note that the source object passed to the callback will be the
|
|
* #GESAsset corresponding to the request, but it may not have loaded
|
|
* correctly and therefore can not be used as is. Instead,
|
|
* ges_asset_request_finish() should be used to fetch a usable asset, or
|
|
* indicate that an error occurred in the asset's creation.
|
|
*
|
|
* Note that the callback will be called in the #GMainLoop running under
|
|
* the same #GMainContext that ges_init() was called in. So, if you wish
|
|
* the callback to be invoked outside the default #GMainContext, you can
|
|
* call g_main_context_push_thread_default() in a new thread before
|
|
* calling ges_init().
|
|
*
|
|
* Example of an asynchronous asset request:
|
|
* ``` c
|
|
* // The request callback
|
|
* static void
|
|
* asset_loaded_cb (GESAsset * source, GAsyncResult * res, gpointer user_data)
|
|
* {
|
|
* GESAsset *asset;
|
|
* GError *error = NULL;
|
|
*
|
|
* asset = ges_asset_request_finish (res, &error);
|
|
* if (asset) {
|
|
* gst_print ("The file: %s is usable as a GESUriClip",
|
|
* ges_asset_get_id (asset));
|
|
* } else {
|
|
* gst_print ("The file: %s is *not* usable as a GESUriClip because: %s",
|
|
* ges_asset_get_id (source), error->message);
|
|
* }
|
|
*
|
|
* gst_object_unref (asset);
|
|
* }
|
|
*
|
|
* // The request:
|
|
* ges_asset_request_async (GES_TYPE_URI_CLIP, some_uri, NULL,
|
|
* (GAsyncReadyCallback) asset_loaded_cb, user_data);
|
|
* ```
|
|
*/
|
|
void
|
|
ges_asset_request_async (GType extractable_type,
|
|
const gchar * id, GCancellable * cancellable, GAsyncReadyCallback callback,
|
|
gpointer user_data)
|
|
{
|
|
gchar *real_id;
|
|
GESAsset *asset;
|
|
GError *error = NULL;
|
|
GTask *task = NULL;
|
|
|
|
g_return_if_fail (g_type_is_a (extractable_type, G_TYPE_OBJECT));
|
|
g_return_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE));
|
|
g_return_if_fail (callback);
|
|
|
|
GST_DEBUG ("Creating asset with extractable type %s and ID=%s",
|
|
g_type_name (extractable_type), id);
|
|
|
|
real_id = _check_and_update_parameters (&extractable_type, id, &error);
|
|
if (error) {
|
|
_ensure_asset_for_wrong_id (id, extractable_type, error);
|
|
real_id = g_strdup (id);
|
|
}
|
|
|
|
/* Check if we already have an asset for this ID */
|
|
LOCK_CACHE;
|
|
asset = ges_asset_cache_lookup (extractable_type, real_id);
|
|
GESAssetCacheEntry *entry = _lookup_entry (extractable_type, id);
|
|
if (entry)
|
|
asset = entry->asset;
|
|
if (asset) {
|
|
task = g_task_new (asset, NULL, callback, user_data);
|
|
|
|
/* In the case of proxied asset, we will loop until we find the
|
|
* last asset of the chain of proxied asset */
|
|
while (TRUE) {
|
|
switch (asset->priv->state) {
|
|
case ASSET_INITIALIZED:
|
|
GST_DEBUG_OBJECT (asset, "Asset in cache and initialized, "
|
|
"using it");
|
|
|
|
/* Takes its own references to @asset */
|
|
g_task_return_boolean (task, TRUE);
|
|
|
|
goto done;
|
|
case ASSET_INITIALIZING:
|
|
GST_DEBUG_OBJECT (asset, "Asset in cache but not "
|
|
"initialized, setting a new callback");
|
|
ges_asset_cache_append_task (extractable_type, real_id, task);
|
|
task = NULL;
|
|
|
|
goto done;
|
|
case ASSET_PROXIED:{
|
|
GESAsset *target = ges_asset_get_proxy (asset);
|
|
|
|
if (target == NULL) {
|
|
GST_ERROR ("Asset %s proxied against an asset (%s) we do not"
|
|
" have in cache, something massively screwed",
|
|
asset->priv->id, asset->priv->proxied_asset_id);
|
|
|
|
goto done;
|
|
}
|
|
asset = target;
|
|
break;
|
|
}
|
|
case ASSET_NEEDS_RELOAD:
|
|
GST_DEBUG_OBJECT (asset, "Asset in cache and needs reload");
|
|
ges_asset_cache_append_task (extractable_type, real_id, task);
|
|
task = NULL;
|
|
GES_ASSET_GET_CLASS (asset)->start_loading (asset, &error);
|
|
|
|
goto done;
|
|
case ASSET_INITIALIZED_WITH_ERROR:
|
|
g_task_return_error (task,
|
|
error ? g_error_copy (error) : g_error_copy (asset->priv->error));
|
|
|
|
g_clear_error (&error);
|
|
|
|
goto done;
|
|
default:
|
|
GST_WARNING ("Case %i not handle, returning", asset->priv->state);
|
|
UNLOCK_CACHE;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
g_async_initable_new_async (ges_extractable_type_get_asset_type
|
|
(extractable_type), G_PRIORITY_DEFAULT, cancellable, callback, user_data,
|
|
"id", real_id, "extractable-type", extractable_type, NULL);
|
|
|
|
done:
|
|
UNLOCK_CACHE;
|
|
if (task)
|
|
gst_object_unref (task);
|
|
if (real_id)
|
|
g_free (real_id);
|
|
}
|
|
|
|
/**
|
|
* ges_asset_needs_reload
|
|
* @extractable_type: The #GESAsset:extractable-type of the asset that
|
|
* needs reloading
|
|
* @id: (allow-none): The #GESAsset:id of the asset asset that needs
|
|
* reloading
|
|
*
|
|
* Indicate that an existing #GESAsset in the cache should be reloaded
|
|
* upon the next request. This can be used when some condition has
|
|
* changed, which may require that an existing asset should be updated.
|
|
* For example, if an external resource has changed or now become
|
|
* available.
|
|
*
|
|
* Note, the asset is not immediately changed, but will only actually
|
|
* reload on the next call to ges_asset_request() or
|
|
* ges_asset_request_async().
|
|
*
|
|
* Returns: %TRUE if the specified asset exists in the cache and could be
|
|
* marked for reloading.
|
|
*/
|
|
gboolean
|
|
ges_asset_needs_reload (GType extractable_type, const gchar * id)
|
|
{
|
|
gchar *real_id;
|
|
GESAsset *asset;
|
|
GError *error = NULL;
|
|
|
|
g_return_val_if_fail (g_type_is_a (extractable_type, GES_TYPE_EXTRACTABLE),
|
|
FALSE);
|
|
|
|
real_id = _check_and_update_parameters (&extractable_type, id, &error);
|
|
if (error) {
|
|
_ensure_asset_for_wrong_id (id, extractable_type, error);
|
|
real_id = g_strdup (id);
|
|
}
|
|
|
|
asset = ges_asset_cache_lookup (extractable_type, real_id);
|
|
|
|
if (real_id) {
|
|
g_free (real_id);
|
|
}
|
|
|
|
if (asset) {
|
|
GST_DEBUG_OBJECT (asset,
|
|
"Asset with id %s switch state to ASSET_NEEDS_RELOAD",
|
|
ges_asset_get_id (asset));
|
|
asset->priv->state = ASSET_NEEDS_RELOAD;
|
|
g_clear_error (&asset->priv->error);
|
|
return TRUE;
|
|
}
|
|
|
|
GST_DEBUG ("Asset with id %s not found in cache", id);
|
|
return FALSE;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_get_id:
|
|
* @self: A #GESAsset
|
|
*
|
|
* Gets the #GESAsset:id of the asset.
|
|
*
|
|
* Returns: The ID of @self.
|
|
*/
|
|
const gchar *
|
|
ges_asset_get_id (GESAsset * self)
|
|
{
|
|
g_return_val_if_fail (GES_IS_ASSET (self), NULL);
|
|
|
|
return self->priv->id;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_extract:
|
|
* @self: The #GESAsset to extract an object from
|
|
* @error: An error to be set in case something goes wrong,
|
|
* or %NULL to ignore
|
|
*
|
|
* Extracts a new #GESAsset:extractable-type object from the asset. The
|
|
* #GESAsset:id of the asset may determine the properties and state of the
|
|
* newly created object.
|
|
*
|
|
* Returns: (transfer floating): A newly created object, or %NULL if an
|
|
* error occurred.
|
|
*/
|
|
GESExtractable *
|
|
ges_asset_extract (GESAsset * self, GError ** error)
|
|
{
|
|
GESExtractable *extractable;
|
|
|
|
g_return_val_if_fail (GES_IS_ASSET (self), NULL);
|
|
g_return_val_if_fail (GES_ASSET_GET_CLASS (self)->extract, NULL);
|
|
|
|
GST_DEBUG_OBJECT (self, "Extracting asset of type %s",
|
|
g_type_name (self->priv->extractable_type));
|
|
extractable = GES_ASSET_GET_CLASS (self)->extract (self, error);
|
|
|
|
if (extractable == NULL)
|
|
return NULL;
|
|
|
|
if (ges_extractable_get_asset (extractable) == NULL)
|
|
ges_extractable_set_asset (extractable, self);
|
|
|
|
return extractable;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_request_finish:
|
|
* @res: The task result to fetch the asset from
|
|
* @error: An error to be set in case something goes wrong, or %NULL to ignore
|
|
*
|
|
* Fetches an asset requested by ges_asset_request_async(), which
|
|
* finalises the request.
|
|
*
|
|
* Returns: (transfer full): The requested asset, or %NULL if an error
|
|
* occurred.
|
|
*/
|
|
GESAsset *
|
|
ges_asset_request_finish (GAsyncResult * res, GError ** error)
|
|
{
|
|
GObject *object;
|
|
GObject *source_object;
|
|
|
|
g_return_val_if_fail (G_IS_ASYNC_RESULT (res), NULL);
|
|
|
|
source_object = g_async_result_get_source_object (res);
|
|
g_assert (source_object != NULL);
|
|
|
|
object = g_async_initable_new_finish (G_ASYNC_INITABLE (source_object),
|
|
res, error);
|
|
|
|
gst_object_unref (source_object);
|
|
|
|
return GES_ASSET (object);
|
|
}
|
|
|
|
/**
|
|
* ges_list_assets:
|
|
* @filter: The type of object that can be extracted from the asset
|
|
*
|
|
* List all the assets in the current cache whose
|
|
* #GESAsset:extractable-type are of the given type (including
|
|
* subclasses).
|
|
*
|
|
* Note that, since only a #GESExtractable can be extracted from an asset,
|
|
* using `GES_TYPE_EXTRACTABLE` as @filter will return all the assets in
|
|
* the current cache.
|
|
*
|
|
* Returns: (transfer container) (element-type GESAsset): A list of all
|
|
* #GESAsset-s currently in the cache whose #GESAsset:extractable-type is
|
|
* of the @filter type.
|
|
*/
|
|
GList *
|
|
ges_list_assets (GType filter)
|
|
{
|
|
GList *ret = NULL;
|
|
GESAsset *asset;
|
|
GHashTableIter iter, types_iter;
|
|
gpointer key, value, typename, assets;
|
|
|
|
g_return_val_if_fail (g_type_is_a (filter, GES_TYPE_EXTRACTABLE), NULL);
|
|
|
|
LOCK_CACHE;
|
|
g_hash_table_iter_init (&types_iter, _get_type_entries ());
|
|
while (g_hash_table_iter_next (&types_iter, &typename, &assets)) {
|
|
if (g_type_is_a (filter, g_type_from_name ((gchar *) typename)) == FALSE)
|
|
continue;
|
|
|
|
g_hash_table_iter_init (&iter, (GHashTable *) assets);
|
|
while (g_hash_table_iter_next (&iter, &key, &value)) {
|
|
asset = ((GESAssetCacheEntry *) value)->asset;
|
|
|
|
if (g_type_is_a (asset->priv->extractable_type, filter))
|
|
ret = g_list_append (ret, asset);
|
|
}
|
|
}
|
|
UNLOCK_CACHE;
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* ges_asset_get_error:
|
|
* @self: A #GESAsset
|
|
*
|
|
* Retrieve the error that was set on the asset when it was loaded.
|
|
*
|
|
* Returns: (transfer none) (nullable): The error set on @asset, or
|
|
* %NULL if no error occurred when @asset was loaded.
|
|
*
|
|
* Since: 1.8
|
|
*/
|
|
GError *
|
|
ges_asset_get_error (GESAsset * self)
|
|
{
|
|
g_return_val_if_fail (GES_IS_ASSET (self), NULL);
|
|
|
|
return self->priv->error;
|
|
}
|