* Boston, MA 02111-1307, USA.
*/
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
#include <locale.h>
#include <string.h>
#include "encoding-target.h"
*
* GKeyFile style.
*
- * [_gstencodingtarget_]
+ * [GStreamer Encoding Target]
* name : <name>
* category : <category>
* description : <description> #translatable
* variableframerate : <variableframerate>
* */
-#define GST_ENCODING_TARGET_HEADER "_gstencodingtarget_"
+/*
+ * Location of profile files
+ *
+ * $GST_DATADIR/gstreamer-GST_MAJORMINOR/encoding-profile
+ * $HOME/gstreamer-GST_MAJORMINOR/encoding-profile
+ *
+ * Naming convention
+ * $(target.category)/$(target.name).gep
+ *
+ * Naming restrictions:
+ * lowercase ASCII letter for the first character
+ * Same for all other characters + numerics + hyphens
+ */
+
+
+#define GST_ENCODING_TARGET_HEADER "GStreamer Encoding Target"
+#define GST_ENCODING_TARGET_DIRECTORY "encoding-profiles"
+#define GST_ENCODING_TARGET_SUFFIX ".gep"
struct _GstEncodingTarget
{
- GstMiniObject parent;
+ GObject parent;
gchar *name;
gchar *category;
gchar *keyfile;
};
-G_DEFINE_TYPE (GstEncodingTarget, gst_encoding_target, GST_TYPE_MINI_OBJECT);
+G_DEFINE_TYPE (GstEncodingTarget, gst_encoding_target, G_TYPE_OBJECT);
static void
gst_encoding_target_init (GstEncodingTarget * target)
}
static void
-gst_encoding_target_finalize (GstEncodingTarget * target)
+gst_encoding_target_finalize (GObject * object)
{
+ GstEncodingTarget *target = (GstEncodingTarget *) object;
+
GST_DEBUG ("Finalizing");
if (target->name)
if (target->description)
g_free (target->description);
- g_list_foreach (target->profiles, (GFunc) gst_mini_object_unref, NULL);
+ g_list_foreach (target->profiles, (GFunc) g_object_unref, NULL);
g_list_free (target->profiles);
}
static void
-gst_encoding_target_class_init (GstMiniObjectClass * klass)
+gst_encoding_target_class_init (GObjectClass * klass)
{
- klass->finalize =
- (GstMiniObjectFinalizeFunction) gst_encoding_target_finalize;
+ klass->finalize = gst_encoding_target_finalize;
}
/**
*
* Since: 0.10.32
*
- * Returns: (transfer none): The category of the @target.
+ * Returns: (transfer none): The category of the @target. For example:
+ * #GST_ENCODING_CATEGORY_DEVICE.
*/
const gchar *
gst_encoding_target_get_category (GstEncodingTarget * target)
return target->profiles;
}
+/**
+ * gst_encoding_target_get_profile:
+ * @target: a #GstEncodingTarget
+ * @name: the name of the profile to retrieve
+ *
+ * Since: 0.10.32
+ *
+ * Returns: (transfer full): The matching #GstEncodingProfile, or %NULL.
+ */
+GstEncodingProfile *
+gst_encoding_target_get_profile (GstEncodingTarget * target, const gchar * name)
+{
+ GList *tmp;
+
+ g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), NULL);
+ g_return_val_if_fail (name != NULL, NULL);
+
+ for (tmp = target->profiles; tmp; tmp = tmp->next) {
+ GstEncodingProfile *tprof = (GstEncodingProfile *) tmp->data;
+
+ if (!g_strcmp0 (gst_encoding_profile_get_name (tprof), name)) {
+ gst_encoding_profile_ref (tprof);
+ return tprof;
+ }
+ }
+
+ return NULL;
+}
+
static inline gboolean
validate_name (const gchar * name)
{
* gst_encoding_target_new:
* @name: The name of the target.
* @category: (transfer none): The name of the category to which this @target
- * belongs.
+ * belongs. For example: #GST_ENCODING_CATEGORY_DEVICE.
* @description: (transfer none): A description of #GstEncodingTarget in the
* current locale.
* @profiles: (transfer none) (element-type Gst.EncodingProfile): A #GList of
* first character, followed by either lowercase ASCII letters, digits or
* hyphens ('-').
*
+ * The @category <emphasis>should</emphasis> be one of the existing
+ * well-defined categories, like #GST_ENCODING_CATEGORY_DEVICE, but it
+ * <emphasis>can</emphasis> be a application or user specific category if
+ * needed.
+ *
* Since: 0.10.32
*
* Returns: (transfer full): The newly created #GstEncodingTarget or %NULL if
if (!validate_name (category))
goto invalid_category;
- res = (GstEncodingTarget *) gst_mini_object_new (GST_TYPE_ENCODING_TARGET);
+ res = (GstEncodingTarget *) g_object_new (GST_TYPE_ENCODING_TARGET, NULL);
res->name = g_strdup (name);
res->category = g_strdup (category);
res->description = g_strdup (description);
invalid_name:
{
- GST_ERROR ("Invalid name for encoding category : '%s'", name);
+ GST_ERROR ("Invalid name for encoding target : '%s'", name);
return NULL;
}
invalid_category:
{
- GST_ERROR ("Invalid category for encoding category : '%s'", category);
+ GST_ERROR ("Invalid name for encoding category : '%s'", category);
return NULL;
}
}
* @target: the #GstEncodingTarget to add a profile to
* @profile: (transfer full): the #GstEncodingProfile to add
*
- * Adds the given @profile to the @target.
+ * Adds the given @profile to the @target. Each added profile must have
+ * a unique name within the profile.
*
* The @target will steal a reference to the @profile. If you wish to use
* the profile after calling this method, you should increase its reference
return TRUE;
}
+static gchar *
+get_locale (void)
+{
+ const char *loc = NULL;
+ gchar *ret;
+
+#ifdef ENABLE_NLS
+#if defined(LC_MESSAGES)
+ loc = setlocale (LC_MESSAGES, NULL);
+ GST_LOG ("LC_MESSAGES: %s", GST_STR_NULL (loc));
+#elif defined(LC_ALL)
+ loc = setlocale (LC_ALL, NULL);
+ GST_LOG ("LC_ALL: %s", GST_STR_NULL (loc));
+#else
+ GST_LOG ("Neither LC_ALL nor LC_MESSAGES defined");
+#endif
+#else /* !ENABLE_NLS */
+ GST_LOG ("i18n disabled");
+#endif
+
+ if (loc == NULL || g_ascii_strncasecmp (loc, "en", 2) == 0)
+ return NULL;
+
+ /* en_GB.UTF-8 => en */
+ ret = g_ascii_strdown (loc, -1);
+ ret = g_strcanon (ret, "abcdefghijklmnopqrstuvwxyz", '\0');
+ GST_LOG ("using locale: %s", ret);
+ return ret;
+}
+
/* Serialize the top-level profiles
* Note: They don't have to be containerprofiles */
static gboolean
const GList *tmp;
guint i;
const gchar *profname, *profdesc, *profpreset, *proftype;
- const GstCaps *profformat, *profrestriction;
+ const GstCaps *profformat;
profname = gst_encoding_profile_get_name (prof);
profdesc = gst_encoding_profile_get_description (prof);
profformat = gst_encoding_profile_get_format (prof);
profpreset = gst_encoding_profile_get_preset (prof);
proftype = gst_encoding_profile_get_type_nick (prof);
- profrestriction = gst_encoding_profile_get_restriction (prof);
profgroupname = g_strdup_printf ("profile-%s", profname);
g_key_file_set_string (out, profgroupname, "name", profname);
- g_key_file_set_value (out, profgroupname, "type",
- gst_encoding_profile_get_type_nick (prof));
+ g_key_file_set_value (out, profgroupname, "type", proftype);
+
+ if (profdesc) {
+ gchar *locale;
- if (profdesc)
- g_key_file_set_locale_string (out, profgroupname, "description",
- setlocale (LC_ALL, NULL), profdesc);
+ locale = get_locale ();
+ if (locale != NULL) {
+ g_key_file_set_locale_string (out, profgroupname, "description",
+ locale, profdesc);
+ g_free (locale);
+ } else {
+ g_key_file_set_string (out, profgroupname, "description", profdesc);
+ }
+ }
if (profformat) {
gchar *tmpc = gst_caps_to_string (profformat);
g_key_file_set_string (out, profgroupname, "format", tmpc);
pname = g_key_file_get_value (in, profilename, "name", NULL);
/* First try to get localized description */
- description =
- g_key_file_get_locale_string (in, profilename, "description",
- setlocale (LC_ALL, NULL), NULL);
- if (description == NULL)
- description = g_key_file_get_value (in, profilename, "description", NULL);
+ {
+ gchar *locale;
+
+ locale = get_locale ();
+ if (locale != NULL) {
+ /* will try to fall back to untranslated string if no translation found */
+ description = g_key_file_get_locale_string (in, profilename,
+ "description", locale, NULL);
+ g_free (locale);
+ } else {
+ description =
+ g_key_file_get_string (in, profilename, "description", NULL);
+ }
+ }
+
+ /* Note: a missing description is normal for non-container profiles */
+ if (description == NULL) {
+ GST_LOG ("Missing 'description' field for streamprofile %s", profilename);
+ }
/* Parse the remaining fields */
proftype = g_key_file_get_value (in, profilename, "type", NULL);
{
GKeyFile *in;
gboolean res;
+ GError *key_error = NULL;
+
+ g_return_val_if_fail (error == NULL || *error == NULL, NULL);
in = g_key_file_new ();
res =
g_key_file_load_from_file (in, path,
- G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, error);
- if (!res || error != NULL)
+ G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, &key_error);
+ if (!res || key_error != NULL)
goto load_error;
+ key_error = NULL;
*targetname =
- g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "name", error);
+ g_key_file_get_value (in, GST_ENCODING_TARGET_HEADER, "name", &key_error);
if (!*targetname)
goto empty_name;
load_error:
{
GST_WARNING ("Unable to read GstEncodingTarget file %s: %s",
- path, (*error)->message);
+ path, key_error->message);
+ g_propagate_error (error, key_error);
g_key_file_free (in);
return NULL;
}
empty_name:
{
- GST_WARNING ("Wrong header in file %s: %s", path, (*error)->message);
+ GST_WARNING ("Wrong header in file %s: %s", path, key_error->message);
+ g_propagate_error (error, key_error);
g_key_file_free (in);
return NULL;
}
}
/**
- * gst_encoding_target_load_from:
- * @path: The file to load the #GstEncodingTarget from
+ * gst_encoding_target_load_from_file:
+ * @filepath: The file location to load the #GstEncodingTarget from
* @error: If an error occured, this field will be filled in.
*
* Opens the provided file and returns the contained #GstEncodingTarget.
*/
GstEncodingTarget *
-gst_encoding_target_load_from (const gchar * path, GError ** error)
+gst_encoding_target_load_from_file (const gchar * filepath, GError ** error)
{
GKeyFile *in;
gchar *targetname, *categoryname, *description;
GstEncodingTarget *res = NULL;
- in = load_file_and_read_header (path, &targetname, &categoryname,
+ in = load_file_and_read_header (filepath, &targetname, &categoryname,
&description, error);
if (!in)
goto beach;
return res;
}
+/*
+ * returned list contents must be freed
+ */
+static GList *
+get_matching_filenames (gchar * path, gchar * filename)
+{
+ GList *res = NULL;
+ GDir *topdir;
+ const gchar *subdirname;
+
+ topdir = g_dir_open (path, 0, NULL);
+ if (G_UNLIKELY (topdir == NULL))
+ return NULL;
+
+ while ((subdirname = g_dir_read_name (topdir))) {
+ gchar *ltmp = g_build_filename (path, subdirname, NULL);
+
+ if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
+ gchar *tmp = g_build_filename (path, subdirname, filename, NULL);
+ /* Test to see if we have a file named like that in that directory */
+ if (g_file_test (tmp, G_FILE_TEST_EXISTS))
+ res = g_list_append (res, tmp);
+ else
+ g_free (tmp);
+ }
+ g_free (ltmp);
+ }
+
+ g_dir_close (topdir);
+
+ return res;
+}
+
+static GstEncodingTarget *
+gst_encoding_target_subload (gchar * path, const gchar * category,
+ gchar * lfilename, GError ** error)
+{
+ GstEncodingTarget *target = NULL;
+
+ if (category) {
+ gchar *filename;
+
+ filename = g_build_filename (path, category, lfilename, NULL);
+ target = gst_encoding_target_load_from_file (filename, error);
+ g_free (filename);
+ } else {
+ GList *tmp, *tries = get_matching_filenames (path, lfilename);
+
+ /* Try to find a file named %s.gstprofile in any subdirectories */
+ for (tmp = tries; tmp; tmp = tmp->next) {
+ target = gst_encoding_target_load_from_file ((gchar *) tmp->data, NULL);
+ if (target)
+ break;
+ }
+ g_list_foreach (tries, (GFunc) g_free, NULL);
+ if (tries)
+ g_list_free (tries);
+ }
+
+ return target;
+}
+
/**
* gst_encoding_target_load:
* @name: the name of the #GstEncodingTarget to load.
+ * @category: (allow-none): the name of the target category, like
+ * #GST_ENCODING_CATEGORY_DEVICE. Can be %NULL
* @error: If an error occured, this field will be filled in.
*
* Searches for the #GstEncodingTarget with the given name, loads it
* and returns it.
*
- * Warning: NOT IMPLEMENTED.
+ * If the category name is specified only targets from that category will be
+ * searched for.
*
* Since: 0.10.32
*
* Returns: (transfer full): The #GstEncodingTarget if available, else %NULL.
*/
-
GstEncodingTarget *
-gst_encoding_target_load (const gchar * name, GError ** error)
+gst_encoding_target_load (const gchar * name, const gchar * category,
+ GError ** error)
{
- /* FIXME : IMPLEMENT */
- return NULL;
-}
+ gchar *lfilename, *tldir;
+ GstEncodingTarget *target = NULL;
-/**
- * gst_encoding_target_save:
- * @target: a #GstEncodingTarget
- * @error: If an error occured, this field will be filled in.
- *
- * Saves the @target to the default location.
- *
- * Warning: NOT IMPLEMENTED.
- *
- * Since: 0.10.32
- *
- * Returns: %TRUE if the target was correctly saved, else %FALSE.
- **/
+ g_return_val_if_fail (name != NULL, NULL);
-gboolean
-gst_encoding_target_save (GstEncodingTarget * target, GError ** error)
-{
- g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
+ if (!validate_name (name))
+ goto invalid_name;
+
+ if (category && !validate_name (category))
+ goto invalid_category;
+
+ lfilename = g_strdup_printf ("%s" GST_ENCODING_TARGET_SUFFIX, name);
+
+ /* Try from local profiles */
+ tldir =
+ g_build_filename (g_get_home_dir (), ".gstreamer-" GST_MAJORMINOR,
+ GST_ENCODING_TARGET_DIRECTORY, NULL);
+ target = gst_encoding_target_subload (tldir, category, lfilename, error);
+ g_free (tldir);
+
+ if (target == NULL) {
+ /* Try from system-wide profiles */
+ tldir =
+ g_build_filename (GST_DATADIR, "gstreamer-" GST_MAJORMINOR,
+ GST_ENCODING_TARGET_DIRECTORY, NULL);
+ target = gst_encoding_target_subload (tldir, category, lfilename, error);
+ g_free (tldir);
+ }
+
+ g_free (lfilename);
- /* FIXME : IMPLEMENT */
- return FALSE;
+ return target;
+
+invalid_name:
+ {
+ GST_ERROR ("Invalid name for encoding target : '%s'", name);
+ return NULL;
+ }
+invalid_category:
+ {
+ GST_ERROR ("Invalid name for encoding category : '%s'", category);
+ return NULL;
+ }
}
/**
- * gst_encoding_target_save_to:
+ * gst_encoding_target_save_to_file:
* @target: a #GstEncodingTarget
- * @path: the location to store the @target at.
+ * @filepath: the location to store the @target at.
* @error: If an error occured, this field will be filled in.
*
- * Saves the @target to the provided location.
+ * Saves the @target to the provided file location.
*
* Since: 0.10.32
*
**/
gboolean
-gst_encoding_target_save_to (GstEncodingTarget * target, const gchar * path,
- GError ** error)
+gst_encoding_target_save_to_file (GstEncodingTarget * target,
+ const gchar * filepath, GError ** error)
{
GKeyFile *out;
gchar *data;
gsize data_size;
g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
- g_return_val_if_fail (path != NULL, FALSE);
+ g_return_val_if_fail (filepath != NULL, FALSE);
- /* FIXME : Check path is valid and writable
+ /* FIXME : Check filepath is valid and writable
* FIXME : Strip out profiles already present in system target */
/* Get unique name... */
if (!(data = g_key_file_to_data (out, &data_size, error)))
goto convert_failed;
- if (!g_file_set_contents (path, data, data_size, error))
+ if (!g_file_set_contents (filepath, data, data_size, error))
goto write_failed;
g_key_file_free (out);
write_failed:
{
- GST_ERROR ("Unable to write file %s: %s", path, (*error)->message);
+ GST_ERROR ("Unable to write file %s: %s", filepath, (*error)->message);
g_key_file_free (out);
g_free (data);
return FALSE;
}
}
+
+/**
+ * gst_encoding_target_save:
+ * @target: a #GstEncodingTarget
+ * @error: If an error occured, this field will be filled in.
+ *
+ * Saves the @target to a default user-local directory.
+ *
+ * Since: 0.10.32
+ *
+ * Returns: %TRUE if the target was correctly saved, else %FALSE.
+ **/
+
+gboolean
+gst_encoding_target_save (GstEncodingTarget * target, GError ** error)
+{
+ gchar *filename;
+ gchar *lfilename;
+
+ g_return_val_if_fail (GST_IS_ENCODING_TARGET (target), FALSE);
+ g_return_val_if_fail (target->category != NULL, FALSE);
+
+ lfilename = g_strdup_printf ("%s" GST_ENCODING_TARGET_SUFFIX, target->name);
+ filename =
+ g_build_filename (g_get_home_dir (), ".gstreamer-" GST_MAJORMINOR,
+ GST_ENCODING_TARGET_DIRECTORY, target->category, lfilename, NULL);
+ g_free (lfilename);
+
+ gst_encoding_target_save_to_file (target, filename, error);
+ g_free (filename);
+
+ return TRUE;
+}
+
+static GList *
+get_categories (gchar * path)
+{
+ GList *res = NULL;
+ GDir *topdir;
+ const gchar *subdirname;
+
+ topdir = g_dir_open (path, 0, NULL);
+ if (G_UNLIKELY (topdir == NULL))
+ return NULL;
+
+ while ((subdirname = g_dir_read_name (topdir))) {
+ gchar *ltmp = g_build_filename (path, subdirname, NULL);
+
+ if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
+ res = g_list_append (res, (gpointer) g_strdup (subdirname));
+ }
+ g_free (ltmp);
+ }
+
+ g_dir_close (topdir);
+
+ return res;
+}
+
+/**
+ * gst_encoding_list_available_categories:
+ *
+ * Lists all #GstEncodingTarget categories present on disk.
+ *
+ * Returns: (transfer full) (element-type gchar*): A list
+ * of #GstEncodingTarget categories.
+ *
+ * Since: 0.10.32
+ */
+GList *
+gst_encoding_list_available_categories (void)
+{
+ GList *res = NULL;
+ GList *tmp1, *tmp2;
+ gchar *topdir;
+
+ /* First try user-local categories */
+ topdir = g_build_filename (g_get_home_dir (), ".gstreamer-" GST_MAJORMINOR,
+ GST_ENCODING_TARGET_DIRECTORY, NULL);
+ res = get_categories (topdir);
+ g_free (topdir);
+
+ /* Extend with system-wide categories */
+ topdir = g_build_filename (GST_DATADIR, "gstreamer-" GST_MAJORMINOR,
+ GST_ENCODING_TARGET_DIRECTORY, NULL);
+ tmp1 = get_categories (topdir);
+ g_free (topdir);
+
+ for (tmp2 = tmp1; tmp2; tmp2 = tmp2->next) {
+ gchar *name = (gchar *) tmp2->data;
+ if (!g_list_find_custom (res, name, (GCompareFunc) g_strcmp0))
+ res = g_list_append (res, (gpointer) name);
+ else
+ g_free (name);
+ }
+ g_free (tmp1);
+
+ return res;
+}
+
+static inline GList *
+sub_get_all_targets (gchar * subdir)
+{
+ GList *res = NULL;
+ const gchar *filename;
+ GDir *dir;
+ GstEncodingTarget *target;
+
+ dir = g_dir_open (subdir, 0, NULL);
+ if (G_UNLIKELY (dir == NULL))
+ return NULL;
+
+ while ((filename = g_dir_read_name (dir))) {
+ gchar *fullname;
+
+ /* Only try files ending with .gstprofile */
+ if (!g_str_has_suffix (filename, GST_ENCODING_TARGET_SUFFIX))
+ continue;
+
+ fullname = g_build_filename (subdir, filename, NULL);
+ target = gst_encoding_target_load_from_file (fullname, NULL);
+ if (target) {
+ res = g_list_append (res, target);
+ } else
+ GST_WARNING ("Failed to get a target from %s", fullname);
+ g_free (fullname);
+ }
+ g_dir_close (dir);
+
+ return res;
+}
+
+static inline GList *
+get_all_targets (gchar * topdir, const gchar * categoryname)
+{
+ GList *res = NULL;
+
+ if (categoryname) {
+ gchar *subdir = g_build_filename (topdir, categoryname, NULL);
+ /* Try to open the directory */
+ res = sub_get_all_targets (subdir);
+ g_free (subdir);
+ } else {
+ const gchar *subdirname;
+ GDir *dir = g_dir_open (topdir, 0, NULL);
+
+ if (G_UNLIKELY (dir == NULL))
+ return NULL;
+
+ while ((subdirname = g_dir_read_name (dir))) {
+ gchar *ltmp = g_build_filename (topdir, subdirname, NULL);
+
+ if (g_file_test (ltmp, G_FILE_TEST_IS_DIR)) {
+ res = g_list_concat (res, sub_get_all_targets (ltmp));
+ }
+ g_free (ltmp);
+ }
+ g_dir_close (dir);
+ }
+
+ return res;
+}
+
+static guint
+compare_targets (const GstEncodingTarget * ta, const GstEncodingTarget * tb)
+{
+ if (!g_strcmp0 (ta->name, tb->name)
+ && !g_strcmp0 (ta->category, tb->category))
+ return -1;
+
+ return 0;
+}
+
+/**
+ * gst_encoding_list_all_targets:
+ * @categoryname: (allow-none): The category, for ex: #GST_ENCODING_CATEGORY_DEVICE.
+ * Can be %NULL.
+ *
+ * List all available #GstEncodingTarget for the specified category, or all categories
+ * if @categoryname is %NULL.
+ *
+ * Returns: (transfer full) (element-type GstEncodingTarget): The list of #GstEncodingTarget
+ *
+ * Since: 0.10.32
+ */
+GList *
+gst_encoding_list_all_targets (const gchar * categoryname)
+{
+ GList *res;
+ GList *tmp1, *tmp2;
+ gchar *topdir;
+
+ /* Get user-locals */
+ topdir = g_build_filename (g_get_home_dir (), ".gstreamer-" GST_MAJORMINOR,
+ GST_ENCODING_TARGET_DIRECTORY, NULL);
+ res = get_all_targets (topdir, categoryname);
+ g_free (topdir);
+
+ /* Get system-wide */
+ topdir = g_build_filename (GST_DATADIR, "gstreamer-" GST_MAJORMINOR,
+ GST_ENCODING_TARGET_DIRECTORY, NULL);
+ tmp1 = get_all_targets (topdir, categoryname);
+ g_free (topdir);
+
+ /* Merge system-wide targets */
+ /* FIXME : We should merge the system-wide profiles into the user-locals
+ * instead of stopping at identical target names */
+ for (tmp2 = tmp1; tmp2; tmp2 = tmp2->next) {
+ GstEncodingTarget *target = (GstEncodingTarget *) tmp2->data;
+ if (g_list_find_custom (res, target, (GCompareFunc) compare_targets))
+ gst_encoding_target_unref (target);
+ else
+ res = g_list_append (res, target);
+ }
+ g_list_free (tmp1);
+
+ return res;
+}