diff options
author | Carlos Garnacho <carlosg@gnome.org> | 2020-05-05 19:00:30 +0200 |
---|---|---|
committer | Jean Felder <jean.felder@gmail.com> | 2020-08-21 15:05:10 +0000 |
commit | 89975911057d99eb639add25e348558672771eb5 (patch) | |
tree | 707508e567c0224087dad9d0fceada5681d1be5f | |
parent | 5511ad119f89853312d893ffcf286ef00a2c0418 (diff) | |
download | grilo-plugins-89975911057d99eb639add25e348558672771eb5.tar.gz |
plugins: Add tracker3 plugin
This is a separate plugin as it requires a version bump, different
queries, and can do some niceties supported in the new version.
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | meson_options.txt | 1 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-request-queue.c | 227 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-request-queue.h | 84 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source-api.c | 1468 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source-api.h | 77 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source-cache.c | 190 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source-cache.h | 47 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source-notif.c | 234 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source-notif.h | 55 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source-priv.h | 87 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source.c | 316 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-source.h | 97 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-utils.c | 879 | ||||
-rw-r--r-- | src/tracker3/grl-tracker-utils.h | 95 | ||||
-rw-r--r-- | src/tracker3/grl-tracker.c | 316 | ||||
-rw-r--r-- | src/tracker3/grl-tracker.h | 37 | ||||
-rw-r--r-- | src/tracker3/meson.build | 37 |
18 files changed, 4249 insertions, 0 deletions
diff --git a/meson.build b/meson.build index f858a20..1713d02 100644 --- a/meson.build +++ b/meson.build @@ -77,6 +77,7 @@ sqlite3_dep = dependency('sqlite3', required: false) totem_plparser_dep = dependency('totem-plparser', version: '>= 3.4.1', required: false) totem_plparser_mini_dep = dependency('totem-plparser-mini', version: '>= 3.4.1', required: false) tracker_sparql_dep = dependency('tracker-sparql-2.0', version: '>= 2.3.0', required: false) +tracker3_dep = dependency('tracker-sparql-3.0', required: false) lua_dep = dependency('lua', version: '>= 5.3.0', required: false) if not lua_dep.found() @@ -128,6 +129,7 @@ plugins = [ # NAME, REQ_DEPS, OPT_DEPS ['thetvdb', [grilo_net_dep, libxml_dep, libarchive_dep, gom_dep], []], ['tmdb', [json_glib_dep, libsoup_dep, grilo_net_dep], []], ['tracker', [tracker_sparql_dep], []], + ['tracker3', [tracker3_dep], []], ['vimeo', [grilo_net_dep, libxml_dep, totem_plparser_dep], []], ['youtube', [grilo_net_dep, libxml_dep, libgdata_dep, totem_plparser_dep], []], ] diff --git a/meson_options.txt b/meson_options.txt index ec324f6..77558ae 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -20,5 +20,6 @@ option('enable-shoutcast', type: 'combo', choices: [ 'auto', 'yes', 'no' ], valu option('enable-thetvdb', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 'Enable Thetvdb plugin') option('enable-tmdb', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 'Enable TMDb plugin') option('enable-tracker', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 'Enable Tracker plugin') +option('enable-tracker3', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 'Enable Tracker3 plugin') option('enable-vimeo', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 'Enable Vimeo plugin') option('enable-youtube', type: 'combo', choices: [ 'auto', 'yes', 'no' ], value: 'auto', description: 'Enable YouTube plugin') diff --git a/src/tracker3/grl-tracker-request-queue.c b/src/tracker3/grl-tracker-request-queue.c new file mode 100644 index 0000000..fb7d59e --- /dev/null +++ b/src/tracker3/grl-tracker-request-queue.c @@ -0,0 +1,227 @@ +/* + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include "grl-tracker.h" +#include "grl-tracker-request-queue.h" + +/**/ + +struct _GrlTrackerQueue { + GList *head; + GList *tail; + GHashTable *operations; + GHashTable *operations_ids; +}; + +/**/ + +static void +grl_tracker_op_terminate (GrlTrackerOp *os) +{ + if (os == NULL) + return; + + g_clear_object (&os->cursor); + g_object_unref (os->cancel); + g_free (os->request); + + g_slice_free (GrlTrackerOp, os); +} + +static GrlTrackerOp * +grl_tracker_op_initiate (gchar *request, + GAsyncReadyCallback callback, + gpointer data) +{ + GrlTrackerOp *os = g_slice_new0 (GrlTrackerOp); + + os->request = request; + os->callback = callback; + os->data = data; + os->cancel = g_cancellable_new (); + + return os; +} + +GrlTrackerOp * +grl_tracker_op_initiate_query (guint operation_id, + gchar *request, + GAsyncReadyCallback callback, + gpointer data) +{ + GrlTrackerOp *os = grl_tracker_op_initiate (request, + callback, + data); + + os->type = GRL_TRACKER_OP_TYPE_QUERY; + os->operation_id = operation_id; + + /* g_hash_table_insert (grl_tracker_operations, */ + /* GSIZE_TO_POINTER (operation_id), os); */ + + return os; +} + +GrlTrackerOp * +grl_tracker_op_initiate_metadata (gchar *request, + GAsyncReadyCallback callback, + gpointer data) +{ + GrlTrackerOp *os = grl_tracker_op_initiate (request, + callback, + data); + + os->type = GRL_TRACKER_OP_TYPE_QUERY; + + return os; +} + +GrlTrackerOp * +grl_tracker_op_initiate_set_metadata (gchar *request, + GAsyncReadyCallback callback, + gpointer data) +{ + GrlTrackerOp *os = grl_tracker_op_initiate (request, + callback, + data); + + os->type = GRL_TRACKER_OP_TYPE_UPDATE; + + return os; +} + +static void +grl_tracker_op_start (GrlTrackerOp *os) +{ + switch (os->type) { + case GRL_TRACKER_OP_TYPE_QUERY: + tracker_sparql_connection_query_async (grl_tracker_connection, + os->request, + NULL, + os->callback, + os); + break; + + case GRL_TRACKER_OP_TYPE_UPDATE: + tracker_sparql_connection_update_async (grl_tracker_connection, + os->request, + G_PRIORITY_DEFAULT, + NULL, + os->callback, + os); + break; + + default: + g_assert_not_reached(); + break; + } +} + +/**/ + +GrlTrackerQueue * +grl_tracker_queue_new (void) +{ + GrlTrackerQueue *queue = g_new0 (GrlTrackerQueue, 1); + + queue->operations = g_hash_table_new (g_direct_hash, g_direct_equal); + queue->operations_ids = g_hash_table_new (g_direct_hash, g_direct_equal); + + return queue; +} + +void +grl_tracker_queue_push (GrlTrackerQueue *queue, + GrlTrackerOp *os) +{ + gboolean first = FALSE; + + queue->tail = g_list_append (queue->tail, os); + if (queue->tail->next) + queue->tail = queue->tail->next; + else { + queue->head = queue->tail; + first = TRUE; + } + + g_assert (queue->tail->next == NULL); + + g_hash_table_insert (queue->operations, os, queue->tail); + if (os->operation_id != 0) + g_hash_table_insert (queue->operations_ids, + GSIZE_TO_POINTER (os->operation_id), os); + + if (first) + grl_tracker_op_start (os); +} + +void +grl_tracker_queue_cancel (GrlTrackerQueue *queue, + GrlTrackerOp *os) +{ + GList *item = g_hash_table_lookup (queue->operations, os); + + if (!item) + return; + + g_cancellable_cancel (os->cancel); + + g_hash_table_remove (queue->operations, os); + if (os->operation_id != 0) + g_hash_table_remove (queue->operations_ids, + GSIZE_TO_POINTER (os->operation_id)); + + if (item == queue->head) { + queue->head = queue->head->next; + } + if (item == queue->tail) { + queue->tail = queue->tail->prev; + } + + if (item->prev) + item->prev->next = item->next; + if (item->next) + item->next->prev = item->prev; + + item->next = NULL; + item->prev = NULL; + g_list_free (item); +} + +void +grl_tracker_queue_done (GrlTrackerQueue *queue, + GrlTrackerOp *os) +{ + GrlTrackerOp *next_os; + + grl_tracker_queue_cancel (queue, os); + grl_tracker_op_terminate (os); + + if (!queue->head) + return; + + next_os = queue->head->data; + + grl_tracker_op_start (next_os); +} diff --git a/src/tracker3/grl-tracker-request-queue.h b/src/tracker3/grl-tracker-request-queue.h new file mode 100644 index 0000000..8805b9c --- /dev/null +++ b/src/tracker3/grl-tracker-request-queue.h @@ -0,0 +1,84 @@ +/* + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _GRL_TRACKER_REQUEST_QUEUE_H_ +#define _GRL_TRACKER_REQUEST_QUEUE_H_ + +#include <grilo.h> +#include <tracker-sparql.h> + +/**/ + +typedef enum { + GRL_TRACKER_OP_TYPE_QUERY, + GRL_TRACKER_OP_TYPE_UPDATE, +} GrlTrackerOpType; + +typedef struct { + GrlTrackerOpType type; + GAsyncReadyCallback callback; + GCancellable *cancel; + TrackerSparqlConnection *connection; + gchar *request; + const GList *keys; + gpointer data; + + TrackerSparqlCursor *cursor; + + guint operation_id; + + guint skip; + guint count; + guint current; + GrlTypeFilter type_filter; +} GrlTrackerOp; + +typedef struct _GrlTrackerQueue GrlTrackerQueue; + +/**/ + +GrlTrackerOp *grl_tracker_op_initiate_query (guint operation_id, + gchar *request, + GAsyncReadyCallback callback, + gpointer data); + +GrlTrackerOp *grl_tracker_op_initiate_metadata (gchar *request, + GAsyncReadyCallback callback, + gpointer data); + +GrlTrackerOp *grl_tracker_op_initiate_set_metadata (gchar *request, + GAsyncReadyCallback callback, + gpointer data); + +/**/ + +GrlTrackerQueue *grl_tracker_queue_new (void); + +void grl_tracker_queue_push (GrlTrackerQueue *queue, GrlTrackerOp *os); + +void grl_tracker_queue_cancel (GrlTrackerQueue *queue, GrlTrackerOp *os); + +void grl_tracker_queue_done (GrlTrackerQueue *queue, GrlTrackerOp *os); + +#endif /* _GRL_TRACKER_REQUEST_QUEUE_H_ */ diff --git a/src/tracker3/grl-tracker-source-api.c b/src/tracker3/grl-tracker-source-api.c new file mode 100644 index 0000000..5abf3a0 --- /dev/null +++ b/src/tracker3/grl-tracker-source-api.c @@ -0,0 +1,1468 @@ +/* + * Copyright (C) 2011-2012 Igalia S.L. + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <gio/gio.h> +#include <tracker-sparql.h> + +#include "grl-tracker.h" +#include "grl-tracker-source-api.h" +#include "grl-tracker-source-cache.h" +#include "grl-tracker-source-priv.h" +#include "grl-tracker-request-queue.h" +#include "grl-tracker-utils.h" + +/* --------- Logging -------- */ + +#define GRL_LOG_DOMAIN_DEFAULT tracker_source_request_log_domain + +GRL_LOG_DOMAIN_STATIC(tracker_source_request_log_domain); +GRL_LOG_DOMAIN_STATIC(tracker_source_result_log_domain); + +/* Inputs/requests */ +#define GRL_IDEBUG(args...) \ + GRL_LOG (tracker_source_request_log_domain, \ + GRL_LOG_LEVEL_DEBUG, args) + +/* Outputs/results */ +#define GRL_ODEBUG(args...) \ + GRL_LOG (tracker_source_result_log_domain, \ + GRL_LOG_LEVEL_DEBUG, args) + +/* ------- Definitions ------- */ + +#define TRACKER_QUERY_LIMIT \ + "OFFSET %u " \ + "LIMIT %u" + +#define TRACKER_QUERY_PARTIAL_REQUEST \ + "SELECT rdf:type(?urn) %s " \ + "WHERE { %s . %s } " \ + "ORDER BY DESC(nfo:fileLastModified(?urn)) " \ + TRACKER_QUERY_LIMIT + +#define TRACKER_QUERY_FULL_REQUEST \ + "%s " \ + TRACKER_QUERY_LIMIT + +#define TRACKER_SEARCH_REQUEST \ + "SELECT DISTINCT rdf:type(?urn) %s " \ + "WHERE " \ + "{ " \ + "%s " \ + "?urn tracker:available ?tr . " \ + "?urn fts:match \"%s\" . " \ + "%s %s " \ + "} " \ + "ORDER BY DESC(nfo:fileLastModified(?urn)) " \ + "OFFSET %u " \ + "LIMIT %u" + +#define TRACKER_SEARCH_ALL_REQUEST \ + "SELECT DISTINCT rdf:type(?urn) %s " \ + "WHERE " \ + "{ " \ + "%s " \ + "?urn tracker:available ?tr . " \ + "%s %s " \ + "} " \ + "ORDER BY DESC(nfo:fileLastModified(?urn)) " \ + "OFFSET %u " \ + "LIMIT %u" + +#define TRACKER_BROWSE_SHOW_DOCUMENTS \ + "{ ?urn a nfo:Document } UNION" + +#define TRACKER_BROWSE_CATEGORY_REQUEST \ + "SELECT rdf:type(?urn) %s " \ + "WHERE " \ + "{ " \ + "?urn a %s . " \ + "?urn nie:isStoredAs ?file . " \ + "?file tracker:available ?tr . " \ + "%s " \ + "%s " \ + "} " \ + "ORDER BY DESC(nfo:fileLastModified(?urn)) " \ + "OFFSET %u " \ + "LIMIT %u" + +#define TRACKER_BROWSE_FILESYSTEM_ROOT_REQUEST \ + "SELECT DISTINCT rdf:type(?urn) %s " \ + "WHERE " \ + "{ " \ + "%s " \ + "{ ?urn a nfo:Folder } %s " \ + "%s " \ + "FILTER (!bound(nfo:belongsToContainer(?urn))) " \ + "} " \ + "ORDER BY DESC(nfo:fileLastModified(?urn)) " \ + "OFFSET %u " \ + "LIMIT %u" + +#define TRACKER_BROWSE_FILESYSTEM_REQUEST \ + "SELECT DISTINCT rdf:type(?urn) %s " \ + "WHERE " \ + "{ " \ + "%s " \ + "{ ?urn a nfo:Folder } %s " \ + "%s " \ + "FILTER(tracker:id(nfo:belongsToContainer(?urn)) = %s) " \ + "} " \ + "ORDER BY DESC(nfo:fileLastModified(?urn)) " \ + "OFFSET %u " \ + "LIMIT %u" + +#define TRACKER_RESOLVE_REQUEST \ + "SELECT %s " \ + "WHERE " \ + "{ " \ + "?urn a nie:InformationElement ; " \ + " nie:isStoredAs ?file . " \ + "FILTER (tracker:id(?urn) = %s) " \ + "}" + +#define TRACKER_RESOLVE_URL_REQUEST \ + "SELECT %s " \ + "WHERE " \ + "{ " \ + "?urn a nie:DataObject . " \ + "?urn nie:url \"%s\" " \ + "}" + +#define TRACKER_DELETE_REQUEST \ + "DELETE { <%s> %s } WHERE { <%s> a nfo:Media . %s }" + +#define TRACKER_SAVE_REQUEST \ + "DELETE { <%s> %s } WHERE { <%s> a nfo:Media . %s } " \ + "INSERT { <%s> a nfo:Media ; %s . }" + +#define TRACKER_TEST_MEDIA_FROM_URI_REQUEST \ + "SELECT ?urn " \ + "WHERE " \ + "{ " \ + "?urn nie:url \"%s\" ; " \ + "tracker:available true ; " \ + "a nfo:Media . " \ + "%s " \ + "}" + +#define TRACKER_TEST_MEDIA_FROM_URI_REQUEST_WITH_DOCUMENTS \ + "SELECT ?urn " \ + "WHERE " \ + "{ " \ + "?urn nie:url \"%s\" ; " \ + "tracker:available true . " \ + "%s " \ + "FILTER (?type IN ( nfo:Media, nfo:Document ))" \ + "}" + +#define TRACKER_MEDIA_FROM_URI_REQUEST \ + "SELECT rdf:type(?urn) %s " \ + "WHERE " \ + "{ " \ + "?urn nie:url \"%s\" ; " \ + "tracker:available ?tr . " \ + "%s " \ + "} " \ + +/**/ + +/**/ + +static GrlKeyID grl_metadata_key_tracker_category; +static GHashTable *grl_tracker_operations; + +/**/ + + +/**/ + +/**/ + +static void +set_title_from_filename (GrlMedia *media) +{ + const gchar *url; + gchar *path, *display_name, *ext, *title; + guint suffix_len; + + url = grl_media_get_url (media); + if (url == NULL) + return; + + path = g_filename_from_uri (url, NULL, NULL); + if (!path) + return; + display_name = g_filename_display_basename (path); + g_free (path); + ext = strrchr (display_name, '.'); + if (!ext) + goto out; + + suffix_len = strlen (ext); + if (suffix_len != 4 && suffix_len != 5) + goto out; + + title = g_strndup (display_name, ext - display_name); + if (g_strcmp0 (grl_media_get_title (media), title) == 0) + grl_data_set_boolean (GRL_DATA (media), GRL_METADATA_KEY_TITLE_FROM_FILENAME, TRUE); + g_free (title); + +out: + g_free (display_name); +} + +static void +fill_grilo_media_from_sparql (GrlTrackerSource *source, + GrlMedia *media, + TrackerSparqlCursor *cursor, + gint column) +{ + const gchar *sparql_key = tracker_sparql_cursor_get_variable_name (cursor, + column); + tracker_grl_sparql_t *assoc = + grl_tracker_get_mapping_from_sparql (sparql_key); + union { + gint64 int_val; + gdouble double_val; + const gchar *str_val; + } val; + + GrlKeyID grl_key; + + if (assoc == NULL) { + /* Maybe the user is setting the key */ + GrlRegistry *registry = grl_registry_get_default (); + grl_key = grl_registry_lookup_metadata_key (registry, sparql_key); + if (grl_key == GRL_METADATA_KEY_INVALID) { + return; + } + } else { + grl_key = assoc->grl_key; + } + + GRL_ODEBUG ("\tSetting media prop (col=%i/var=%s/prop=%s) %s", + column, + sparql_key, + GRL_METADATA_KEY_GET_NAME (grl_key), + tracker_sparql_cursor_get_string (cursor, column, NULL)); + + if (tracker_sparql_cursor_is_bound (cursor, column) == FALSE) { + GRL_ODEBUG ("\t\tDropping, no data"); + return; + } + + if (grl_data_has_key (GRL_DATA (media), grl_key)) { + GRL_ODEBUG ("\t\tDropping, already here"); + return; + } + + if (assoc && assoc->set_value) { + assoc->set_value (cursor, column, media, assoc->grl_key); + } else { + GType grl_type = GRL_METADATA_KEY_GET_TYPE (grl_key); + if (grl_type == G_TYPE_STRING) { + /* Cache the source associated to this result. */ + if (grl_key == GRL_METADATA_KEY_ID) { + grl_tracker_source_cache_add_item (grl_tracker_item_cache, + tracker_sparql_cursor_get_integer (cursor, + column), + source); + } + val.str_val = tracker_sparql_cursor_get_string (cursor, column, NULL); + if (val.str_val != NULL) + grl_data_set_string (GRL_DATA (media), grl_key, val.str_val); + } else if (grl_type == G_TYPE_INT) { + val.int_val = tracker_sparql_cursor_get_integer (cursor, column); + grl_data_set_int (GRL_DATA (media), grl_key, val.int_val); + } else if (grl_type == G_TYPE_INT64) { + val.int_val = tracker_sparql_cursor_get_integer (cursor, column); + grl_data_set_int64 (GRL_DATA (media), grl_key, val.int_val); + } else if (grl_type == G_TYPE_FLOAT) { + val.double_val = tracker_sparql_cursor_get_double (cursor, column); + grl_data_set_float (GRL_DATA (media), grl_key, (gfloat) val.double_val); + } else if (grl_type == G_TYPE_DATE_TIME) { + val.str_val = tracker_sparql_cursor_get_string (cursor, column, NULL); + GDateTime *date_time = grl_date_time_from_iso8601 (val.str_val); + grl_data_set_boxed (GRL_DATA (media), grl_key, date_time); + g_date_time_unref (date_time); + } else { + GRL_ODEBUG ("\t\tUnexpected data type"); + } + } +} + +static gchar * +get_sparql_type_filter (GrlOperationOptions *options, + gboolean prepend_union) +{ + GrlTypeFilter filter = grl_operation_options_get_type_filter (options); + GString *sparql_filter = g_string_new (""); + + if (filter & GRL_TYPE_FILTER_AUDIO) { + if (prepend_union) { + sparql_filter = g_string_append (sparql_filter, + "UNION { ?urn a nfo:Audio } "); + } else { + sparql_filter = g_string_append (sparql_filter, + "{ ?urn a nfo:Audio } "); + prepend_union = TRUE; + } + } + if (filter & GRL_TYPE_FILTER_VIDEO) { + if (prepend_union) { + sparql_filter = g_string_append (sparql_filter, + "UNION { ?urn a nmm:Video } "); + } else { + sparql_filter = g_string_append (sparql_filter, + "{ ?urn a nmm:Video } "); + prepend_union = TRUE; + } + } + if (filter & GRL_TYPE_FILTER_IMAGE) { + if (prepend_union) { + sparql_filter = g_string_append (sparql_filter, + "UNION { ?urn a nmm:Photo } "); + } else { + sparql_filter = g_string_append (sparql_filter, + "{ ?urn a nmm:Photo } "); + } + } + + sparql_filter = g_string_append_c (sparql_filter, '.'); + + return g_string_free (sparql_filter, FALSE); +} + +/* I can haz templatze ?? */ +#define TRACKER_QUERY_CB(spec_type,name,error) \ + \ + static void \ + tracker_##name##_result_cb (GObject *source_object, \ + GAsyncResult *result, \ + GrlTrackerOp *os) \ + { \ + gint col; \ + const gchar *sparql_type; \ + GError *tracker_error = NULL, *error = NULL; \ + GrlMedia *media; \ + spec_type *spec = \ + (spec_type *) os->data; \ + \ + GRL_ODEBUG ("%s", __FUNCTION__); \ + \ + if (g_cancellable_is_cancelled (os->cancel)) { \ + GRL_ODEBUG ("\tOperation %u cancelled", spec->operation_id); \ + spec->callback (spec->source, \ + spec->operation_id, \ + NULL, 0, \ + spec->user_data, NULL); \ + grl_tracker_queue_done (grl_tracker_queue, os); \ + \ + return; \ + } \ + \ + if (!tracker_sparql_cursor_next_finish (os->cursor, \ + result, \ + &tracker_error)) { \ + if (tracker_error != NULL) { \ + GRL_WARNING ("\terror in parsing query id=%u : %s", \ + spec->operation_id, tracker_error->message); \ + \ + error = g_error_new (GRL_CORE_ERROR, \ + GRL_CORE_ERROR_##error##_FAILED, \ + _("Failed to query: %s"), \ + tracker_error->message); \ + \ + spec->callback (spec->source, \ + spec->operation_id, \ + NULL, 0, \ + spec->user_data, error); \ + \ + g_error_free (error); \ + g_error_free (tracker_error); \ + } else { \ + GRL_ODEBUG ("\tend of parsing id=%u :)", spec->operation_id); \ + \ + /* Only emit this last one if more result than expected */ \ + if (os->count > 1) \ + spec->callback (spec->source, \ + spec->operation_id, \ + NULL, 0, \ + spec->user_data, NULL); \ + } \ + \ + grl_tracker_queue_done (grl_tracker_queue, os); \ + return; \ + } \ + \ + sparql_type = tracker_sparql_cursor_get_string (os->cursor, \ + 0, \ + NULL); \ + \ + GRL_ODEBUG ("\tParsing line %i of type %s", \ + os->current, sparql_type); \ + \ + media = grl_tracker_build_grilo_media (sparql_type, os->type_filter);\ + \ + if (media != NULL) { \ + for (col = 1 ; \ + col < tracker_sparql_cursor_get_n_columns (os->cursor) ; \ + col++) { \ + fill_grilo_media_from_sparql (GRL_TRACKER_SOURCE (spec->source), \ + media, os->cursor, col); \ + } \ + set_title_from_filename (media); \ + \ + spec->callback (spec->source, \ + spec->operation_id, \ + media, \ + --os->count, \ + spec->user_data, \ + NULL); \ + } \ + \ + /* Schedule the next line to parse */ \ + os->current++; \ + if (os->count < 1) \ + grl_tracker_queue_done (grl_tracker_queue, os); \ + else \ + tracker_sparql_cursor_next_async (os->cursor, os->cancel, \ + (GAsyncReadyCallback) tracker_##name##_result_cb, \ + (gpointer) os); \ + } \ + \ + static void \ + tracker_##name##_cb (GObject *source_object, \ + GAsyncResult *result, \ + GrlTrackerOp *os) \ + { \ + GError *tracker_error = NULL, *error = NULL; \ + spec_type *spec = (spec_type *) os->data; \ + TrackerSparqlConnection *connection = \ + grl_tracker_source_get_tracker_connection (GRL_TRACKER_SOURCE (spec->source)); \ + \ + GRL_ODEBUG ("%s", __FUNCTION__); \ + \ + os->cursor = \ + tracker_sparql_connection_query_finish (connection, \ + result, &tracker_error); \ + \ + if (tracker_error) { \ + GRL_WARNING ("Could not execute sparql query id=%u: %s", \ + spec->operation_id, tracker_error->message); \ + \ + error = g_error_new (GRL_CORE_ERROR, \ + GRL_CORE_ERROR_##error##_FAILED, \ + _("Failed to query: %s"), \ + tracker_error->message); \ + \ + spec->callback (spec->source, spec->operation_id, NULL, 0, \ + spec->user_data, error); \ + \ + g_error_free (tracker_error); \ + g_error_free (error); \ + grl_tracker_queue_done (grl_tracker_queue, os); \ + \ + return; \ + } \ + \ + /* Start parsing results */ \ + os->current = 0; \ + tracker_sparql_cursor_next_async (os->cursor, NULL, \ + (GAsyncReadyCallback) tracker_##name##_result_cb, \ + (gpointer) os); \ + } + +TRACKER_QUERY_CB(GrlSourceQuerySpec, query, QUERY) +TRACKER_QUERY_CB(GrlSourceBrowseSpec, browse, BROWSE) +TRACKER_QUERY_CB(GrlSourceSearchSpec, search, SEARCH) + +static void +tracker_resolve_cb (GObject *source_object, + GAsyncResult *result, + GrlTrackerOp *os) +{ + GrlSourceResolveSpec *rs = (GrlSourceResolveSpec *) os->data; + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (rs->source); + gint col; + GError *tracker_error = NULL, *error = NULL; + TrackerSparqlCursor *cursor; + + GRL_ODEBUG ("%s", __FUNCTION__); + + cursor = tracker_sparql_connection_query_finish (priv->tracker_connection, + result, &tracker_error); + + if (tracker_error) { + GRL_WARNING ("Could not execute sparql resolve query : %s", + tracker_error->message); + + error = g_error_new (GRL_CORE_ERROR, + GRL_CORE_ERROR_RESOLVE_FAILED, + _("Failed to resolve: %s"), + tracker_error->message); + + rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, error); + + g_error_free (tracker_error); + g_error_free (error); + + goto end_operation; + } + + + if (tracker_sparql_cursor_next (cursor, NULL, NULL)) { + /* Translate Sparql result into Grilo result */ + for (col = 0 ; col < tracker_sparql_cursor_get_n_columns (cursor) ; col++) { + fill_grilo_media_from_sparql (GRL_TRACKER_SOURCE (rs->source), + rs->media, cursor, col); + } + set_title_from_filename (rs->media); + + rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL); + } else { + rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL); + } + + end_operation: + g_clear_object (&cursor); + + grl_tracker_queue_done (grl_tracker_queue, os); +} + +static void +tracker_media_from_uri_cb (GObject *source_object, + GAsyncResult *result, + GrlTrackerOp *os) +{ + GrlSourceMediaFromUriSpec *mfus = (GrlSourceMediaFromUriSpec *) os->data; + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (mfus->source); + GError *tracker_error = NULL, *error = NULL; + GrlMedia *media; + TrackerSparqlCursor *cursor; + const gchar *sparql_type; + gint col; + + GRL_ODEBUG ("%s", __FUNCTION__); + + cursor = tracker_sparql_connection_query_finish (priv->tracker_connection, + result, &tracker_error); + + if (tracker_error) { + GRL_WARNING ("Could not execute sparql media from uri query : %s", + tracker_error->message); + + error = g_error_new (GRL_CORE_ERROR, + GRL_CORE_ERROR_MEDIA_FROM_URI_FAILED, + _("Failed to get media from uri: %s"), + tracker_error->message); + + mfus->callback (mfus->source, mfus->operation_id, NULL, mfus->user_data, error); + + g_error_free (tracker_error); + g_error_free (error); + + goto end_operation; + } + + + if (tracker_sparql_cursor_next (cursor, NULL, NULL)) { + /* Build grilo media */ + sparql_type = tracker_sparql_cursor_get_string (cursor, 0, NULL); + media = grl_tracker_build_grilo_media (sparql_type, GRL_TYPE_FILTER_NONE); + + /* Translate Sparql result into Grilo result */ + for (col = 0 ; col < tracker_sparql_cursor_get_n_columns (cursor) ; col++) { + fill_grilo_media_from_sparql (GRL_TRACKER_SOURCE (mfus->source), + media, cursor, col); + } + set_title_from_filename (media); + + mfus->callback (mfus->source, mfus->operation_id, media, mfus->user_data, NULL); + } else { + mfus->callback (mfus->source, mfus->operation_id, NULL, mfus->user_data, NULL); + } + + end_operation: + g_clear_object (&cursor); + + grl_tracker_queue_done (grl_tracker_queue, os); +} + +static void +tracker_store_metadata_cb (GObject *source_object, + GAsyncResult *result, + GrlTrackerOp *os) +{ + GrlSourceStoreMetadataSpec *sms = + (GrlSourceStoreMetadataSpec *) os->data; + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (sms->source); + GError *tracker_error = NULL, *error = NULL; + + tracker_sparql_connection_update_finish (priv->tracker_connection, + result, + &tracker_error); + + if (tracker_error) { + GRL_WARNING ("Could not execute sparql update : %s", + tracker_error->message); + + error = g_error_new (GRL_CORE_ERROR, + GRL_CORE_ERROR_STORE_METADATA_FAILED, + _("Failed to update metadata: %s"), + tracker_error->message); + + sms->callback (sms->source, sms->media, NULL, sms->user_data, error); + + g_error_free (tracker_error); + g_error_free (error); + } else { + sms->callback (sms->source, sms->media, NULL, sms->user_data, error); + } + + grl_tracker_queue_done (grl_tracker_queue, os); +} + +/**/ + +const GList * +grl_tracker_source_writable_keys (GrlSource *source) +{ + static GList *keys = NULL; + GrlRegistry *registry; + GrlKeyID grl_metadata_key_chromaprint; + + if (!keys) { + registry = grl_registry_get_default (); + grl_metadata_key_chromaprint = grl_registry_lookup_metadata_key (registry, "chromaprint"); + + keys = grl_metadata_key_list_new (GRL_METADATA_KEY_PLAY_COUNT, + GRL_METADATA_KEY_LAST_PLAYED, + GRL_METADATA_KEY_LAST_POSITION, + GRL_METADATA_KEY_FAVOURITE, + GRL_METADATA_KEY_TITLE, + GRL_METADATA_KEY_TRACK_NUMBER, + GRL_METADATA_KEY_CREATION_DATE, + grl_metadata_key_chromaprint, + NULL); + } + return keys; +} + +static void +grl_tracker_source_get_duration_min_max (GrlOperationOptions *options, + int *min, + int *max) +{ + GValue *min_val, *max_val; + + grl_operation_options_get_key_range_filter (options, GRL_METADATA_KEY_DURATION, + &min_val, &max_val); + if (min_val) + *min = g_value_get_int (min_val); + else + *min = -1; + if (max_val) + *max = g_value_get_int (max_val); + else + *max = -1; +} + +static char * +grl_tracker_source_create_constraint (int min, int max) +{ + if (min <= 0 && max <= 0) + return g_strdup (""); + if (max <= 0) { + return g_strdup_printf ("?urn a nfo:FileDataObject . " + "OPTIONAL {" + " ?urn nfo:duration ?duration " + "} . " + "FILTER(?duration > %d || !BOUND(?duration))", + min); + } + if (min <= 0) { + return g_strdup_printf ("?urn a nfo:FileDataObject . " + "OPTIONAL {" + " ?urn nfo:duration ?duration " + "} . " + "FILTER(?duration < %d || !BOUND(?duration))", + max); + } + return g_strdup_printf ("?urn a nfo:FileDataObject . " + "OPTIONAL {" + " ?urn nfo:duration ?duration " + "} . " + "FILTER(?duration < %d || ?duration > %d || !BOUND(?duration))", + max, min); +} + +/** + * Query is a SPARQL query. + * + * Columns must be named with the Grilo key name that the column + * represent. Unnamed or unknown columns will be ignored. + * + * First column must be the media type, and it does not need to be named. It + * must match with any value supported in rdf:type() property, or + * grilo#Container. Types understood are: + * + * <itemizedlist> + * <listitem> + * <para> + * <literal>nmm#MusicPiece</literal> + * </para> + * </listitem> + * <listitem> + * <para> + * <literal>nmm#Video</literal> + * </para> + * </listitem> + * <listitem> + * <para> + * <literal>nmm#Photo</literal> + * </para> + * </listitem> + * <listitem> + * <para> + * <literal>nmm#Artist</literal> + * </para> + * </listitem> + * <listitem> + * <para> + * <literal>nmm#MusicAlbum</literal> + * </para> + * </listitem> + * <listitem> + * <para> + * <literal>grilo#Container</literal> + * </para> + * </listitem> + * </itemizedlist> + * + * An example for searching all songs: + * + * <informalexample> + * <programlisting> + * SELECT rdf:type(?song) + * ?song AS ?id + * nie:title(?song) AS ?title + * nie:url(?song) AS ?url + * WHERE { ?song a nmm:MusicPiece } + * </programlisting> + * </informalexample> + * + * Alternatively, we can use a partial SPARQL query: just specify the sentence + * in the WHERE part. In this case, "?urn" is the ontology concept to be used in + * the clause. + * + * An example of such partial query: + * + * <informalexample> + * <programlisting> + * ?urn a nfo:Media + * </programlisting> + * </informalexample> + * + * In this case, all data required to build a full SPARQL query will be get from + * the query spec. + */ +void +grl_tracker_source_query (GrlSource *source, + GrlSourceQuerySpec *qs) +{ + GError *error = NULL; + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + gchar *constraint; + gchar *sparql_final; + gchar *sparql_select; + GrlTrackerOp *os; + gint count = grl_operation_options_get_count (qs->options); + guint skip = grl_operation_options_get_skip (qs->options); + + GRL_IDEBUG ("%s: id=%u", __FUNCTION__, qs->operation_id); + + if (!qs->query || qs->query[0] == '\0') { + error = g_error_new_literal (GRL_CORE_ERROR, + GRL_CORE_ERROR_QUERY_FAILED, + _("Empty query")); + goto send_error; + } + + /* Check if it is a full sparql query */ + if (g_ascii_strncasecmp (qs->query, "select ", 7) != 0) { + constraint = grl_tracker_source_get_device_constraint (priv); + sparql_select = grl_tracker_source_get_select_string (qs->keys); + sparql_final = g_strdup_printf (TRACKER_QUERY_PARTIAL_REQUEST, + sparql_select, + qs->query, + constraint, + skip, + count); + g_free (constraint); + g_free (qs->query); + g_free (sparql_select); + qs->query = sparql_final; + } else { + /* Append offset and limit */ + sparql_final = g_strdup_printf (TRACKER_QUERY_FULL_REQUEST, + qs->query, + skip, + count); + g_free (qs->query); + qs->query = sparql_final; + } + + os = grl_tracker_op_initiate_query (qs->operation_id, + g_strdup (qs->query), + (GAsyncReadyCallback) tracker_query_cb, + qs); + + os->keys = qs->keys; + os->skip = skip; + os->count = count; + os->type_filter = grl_operation_options_get_type_filter (qs->options); + os->data = qs; + /* os->cb.sr = qs->callback; */ + /* os->user_data = qs->user_data; */ + + grl_tracker_queue_push (grl_tracker_queue, os); + + return; + + send_error: + qs->callback (qs->source, qs->operation_id, NULL, 0, qs->user_data, error); + g_error_free (error); +} + +void +grl_tracker_source_resolve (GrlSource *source, + GrlSourceResolveSpec *rs) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + gchar *constraint = NULL, *sparql_select, *sparql_final; + gchar *sparql_type_filter = NULL; + const gchar *url = grl_media_get_url (rs->media); + GrlTrackerOp *os; + + GRL_IDEBUG ("%s: id=%i", __FUNCTION__, rs->operation_id); + + /* Check if the media comes from this source or another */ + if (g_strcmp0 (priv->tracker_datasource, grl_source_get_id (rs->source)) == 0) { + if (grl_media_get_id (rs->media) == NULL) { + rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL); + return; + } else { + sparql_select = grl_tracker_source_get_select_string (rs->keys); + sparql_final = g_strdup_printf (TRACKER_RESOLVE_REQUEST, sparql_select, + grl_media_get_id (rs->media)); + } + } else { + if (url) { + sparql_select = grl_tracker_source_get_select_string (rs->keys); + sparql_final = g_strdup_printf (TRACKER_RESOLVE_URL_REQUEST, + sparql_select, url); + } else { + rs->callback (rs->source, rs->operation_id, rs->media, rs->user_data, NULL); + return; + } + } + + GRL_IDEBUG ("\request: '%s'", sparql_final); + + os = grl_tracker_op_initiate_metadata (sparql_final, + (GAsyncReadyCallback) tracker_resolve_cb, + rs); + os->keys = rs->keys; + + grl_tracker_queue_push (grl_tracker_queue, os); + + g_clear_pointer (&sparql_type_filter, g_free); + g_clear_pointer (&constraint, g_free); + g_clear_pointer (&sparql_select, g_free); +} + +gboolean +grl_tracker_source_may_resolve (GrlSource *source, + GrlMedia *media, + GrlKeyID key_id, + GList **missing_keys) +{ + GRL_IDEBUG ("%s: key=%s", __FUNCTION__, GRL_METADATA_KEY_GET_NAME (key_id)); + + if (media && grl_tracker_source_find_source (grl_media_get_source (media))) { + return TRUE; + } + + if (!grl_tracker_key_is_supported (key_id)) { + return FALSE; + } + + if (media) { + if (grl_media_get_url (media)) { + return TRUE; + } else { + if (missing_keys) { + *missing_keys = g_list_append (*missing_keys, + GRLKEYID_TO_POINTER (GRL_METADATA_KEY_URL)); + } + } + } + + return FALSE; +} + +void +grl_tracker_source_store_metadata (GrlSource *source, + GrlSourceStoreMetadataSpec *sms) +{ + gchar *sparql_delete, *sparql_cdelete, *sparql_insert, *sparql_final; + const gchar *urn = grl_data_get_string (GRL_DATA (sms->media), + grl_metadata_key_tracker_urn); + GrlTrackerOp *os; + + GRL_IDEBUG ("%s: urn=%s", G_STRFUNC, urn); + + sparql_delete = grl_tracker_get_delete_string (sms->keys); + sparql_cdelete = grl_tracker_get_delete_conditional_string (urn, sms->keys); + sparql_insert = grl_tracker_tracker_get_insert_string (sms->media, sms->keys); + + if (g_strcmp0 (sparql_insert, "") == 0) { + sparql_final = g_strdup_printf (TRACKER_DELETE_REQUEST, + urn, sparql_delete, + urn, sparql_cdelete); + } else { + sparql_final = g_strdup_printf (TRACKER_SAVE_REQUEST, + urn, sparql_delete, + urn, sparql_cdelete, + urn, sparql_insert); + } + + os = grl_tracker_op_initiate_set_metadata (sparql_final, + (GAsyncReadyCallback) tracker_store_metadata_cb, + sms); + os->keys = sms->keys; + + GRL_IDEBUG ("\trequest: '%s'", sparql_final); + + grl_tracker_queue_push (grl_tracker_queue, os); + + g_free (sparql_delete); + g_free (sparql_cdelete); + g_free (sparql_insert); +} + +void +grl_tracker_source_search (GrlSource *source, GrlSourceSearchSpec *ss) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + gchar *constraint; + gchar *sparql_select; + gchar *sparql_final; + gchar *sparql_type_filter; + gchar *escaped_text; + GrlTrackerOp *os; + gint count = grl_operation_options_get_count (ss->options); + guint skip = grl_operation_options_get_skip (ss->options); + int min_dur, max_dur; + char *duration_constraint; + GRL_IDEBUG ("%s: id=%u", __FUNCTION__, ss->operation_id); + + constraint = grl_tracker_source_get_device_constraint (priv); + sparql_select = grl_tracker_source_get_select_string (ss->keys); + sparql_type_filter = get_sparql_type_filter (ss->options, FALSE); + grl_tracker_source_get_duration_min_max (ss->options, &min_dur, &max_dur); + duration_constraint = grl_tracker_source_create_constraint (min_dur, max_dur); + if (!ss->text || ss->text[0] == '\0') { + /* Search all */ + sparql_final = g_strdup_printf (TRACKER_SEARCH_ALL_REQUEST, sparql_select, + constraint, duration_constraint, sparql_type_filter, + skip, count); + } else { + escaped_text = tracker_sparql_escape_string (ss->text); + sparql_final = g_strdup_printf (TRACKER_SEARCH_REQUEST, sparql_select, + sparql_type_filter, escaped_text, + constraint, duration_constraint, skip, count); + g_free (escaped_text); + } + + GRL_IDEBUG ("\tselect: '%s'", sparql_final); + + os = grl_tracker_op_initiate_query (ss->operation_id, + sparql_final, + (GAsyncReadyCallback) tracker_search_cb, + ss); + os->keys = ss->keys; + os->skip = skip; + os->count = count; + os->type_filter = grl_operation_options_get_type_filter (ss->options); + + grl_tracker_queue_push (grl_tracker_queue, os); + + g_free (constraint); + g_free (sparql_select); + g_free (sparql_type_filter); + g_free (duration_constraint); +} + +static gboolean +is_root_box (GrlMedia *container) +{ + if (container == NULL) + return TRUE; + if (!grl_media_get_id (container)) + return TRUE; + return FALSE; +} + +static void +grl_tracker_source_browse_category (GrlSource *source, + GrlSourceBrowseSpec *bs) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + gchar *constraint; + gchar *sparql_select; + gchar *sparql_final; + GrlTrackerOp *os; + GrlMedia *media; + const gchar *category; + gint remaining; + gint count = grl_operation_options_get_count (bs->options); + guint skip = grl_operation_options_get_skip (bs->options); + GrlTypeFilter filter = grl_operation_options_get_type_filter (bs->options); + int min_dur, max_dur; + char *duration_constraint; + + GRL_IDEBUG ("%s: id=%u", __FUNCTION__, bs->operation_id); + + /* If the category is missing, try to get it from the + * container's ID */ + if (!is_root_box (bs->container) && + !grl_data_has_key (GRL_DATA (bs->container), + grl_metadata_key_tracker_category)) { + const char *id; + + id = grl_media_get_id (bs->container); + if (g_strcmp0 (id, "documents") == 0) + category = "nfo:Document"; + else if (g_strcmp0 (id, "music") == 0) + category = "nmm:MusicPiece"; + else if (g_strcmp0 (id, "photos") == 0) + category = "nmm:Photo"; + else if (g_strcmp0 (id, "videos") == 0) + category = "nmm:Video"; + else { + GError *error; + + error = g_error_new (GRL_CORE_ERROR, + GRL_CORE_ERROR_BROWSE_FAILED, + _("ID “%s” is not known in this source"), + id); + + bs->callback (bs->source, bs->operation_id, NULL, 0, + bs->user_data, error); + + g_error_free (error); + return; + } + + grl_data_set_string (GRL_DATA (bs->container), + grl_metadata_key_tracker_category, + category); + } + + if (is_root_box (bs->container) || + !grl_data_has_key (GRL_DATA (bs->container), + grl_metadata_key_tracker_category)) { + /* Hardcoded categories */ + if (filter == GRL_TYPE_FILTER_ALL) { + remaining = 3; + if (grl_tracker_show_documents) { + remaining++; + } + } else { + remaining = 0; + if (filter & GRL_TYPE_FILTER_AUDIO) { + remaining++; + } + if (filter & GRL_TYPE_FILTER_VIDEO) { + remaining++; + } + if (filter & GRL_TYPE_FILTER_IMAGE) { + remaining++; + } + } + + if (remaining == 0) { + bs->callback (bs->source, bs->operation_id, NULL, 0, + bs->user_data, NULL); + return; + } + + /* Special case: if everthing is filtered except one category, then skip the + intermediate level and go straightly to the elements */ + if (remaining == 1) { + if (filter & GRL_TYPE_FILTER_AUDIO) { + category = "nmm:MusicPiece"; + } else if (filter & GRL_TYPE_FILTER_IMAGE) { + category = "nmm:Photo"; + } else { + category = "nmm:Video"; + } + } else { + if (remaining == 4) { + media = grl_media_container_new (); + grl_media_set_title (media, "Documents"); + grl_media_set_id (media, "documents"); + grl_data_set_string (GRL_DATA (media), + grl_metadata_key_tracker_category, + "nfo:Document"); + bs->callback (bs->source, bs->operation_id, media, --remaining, + bs->user_data, NULL); + } + + if (filter & GRL_TYPE_FILTER_AUDIO) { + media = grl_media_container_new (); + grl_media_set_title (media, "Music"); + grl_media_set_id (media, "music"); + grl_data_set_string (GRL_DATA (media), + grl_metadata_key_tracker_category, + "nmm:MusicPiece"); + bs->callback (bs->source, bs->operation_id, media, --remaining, + bs->user_data, NULL); + } + + if (filter & GRL_TYPE_FILTER_IMAGE) { + media = grl_media_container_new (); + grl_media_set_title (media, "Photos"); + grl_media_set_id (media, "photos"); + grl_data_set_string (GRL_DATA (media), + grl_metadata_key_tracker_category, + "nmm:Photo"); + bs->callback (bs->source, bs->operation_id, media, --remaining, + bs->user_data, NULL); + } + + if (filter & GRL_TYPE_FILTER_VIDEO) { + media = grl_media_container_new (); + grl_media_set_title (media, "Videos"); + grl_media_set_id (media, "videos"); + grl_data_set_string (GRL_DATA (media), + grl_metadata_key_tracker_category, + "nmm:Video"); + bs->callback (bs->source, bs->operation_id, media, --remaining, + bs->user_data, NULL); + } + return; + } + } else { + category = grl_data_get_string (GRL_DATA (bs->container), + grl_metadata_key_tracker_category); + } + + grl_tracker_source_get_duration_min_max (bs->options, &min_dur, &max_dur); + duration_constraint = grl_tracker_source_create_constraint (min_dur, max_dur); + constraint = grl_tracker_source_get_device_constraint (priv); + sparql_select = grl_tracker_source_get_select_string (bs->keys); + sparql_final = g_strdup_printf (TRACKER_BROWSE_CATEGORY_REQUEST, + sparql_select, + category, + constraint, + duration_constraint, + skip, count); + + GRL_IDEBUG ("\tselect: '%s'", sparql_final); + + os = grl_tracker_op_initiate_query (bs->operation_id, + sparql_final, + (GAsyncReadyCallback) tracker_browse_cb, + bs); + os->keys = bs->keys; + os->skip = skip; + os->count = count; + os->type_filter = grl_operation_options_get_type_filter (bs->options); + + grl_tracker_queue_push (grl_tracker_queue, os); + + g_free (constraint); + g_free (sparql_select); + g_free (duration_constraint); +} + +static void +grl_tracker_source_browse_filesystem (GrlSource *source, + GrlSourceBrowseSpec *bs) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + gchar *constraint; + gchar *sparql_select; + gchar *sparql_final; + gchar *sparql_type_filter; + GrlTrackerOp *os; + gint count = grl_operation_options_get_count (bs->options); + guint skip = grl_operation_options_get_skip (bs->options); + int min_dur, max_dur; + char *duration_constraint; + + GRL_IDEBUG ("%s: id=%u", __FUNCTION__, bs->operation_id); + + sparql_select = grl_tracker_source_get_select_string (bs->keys); + constraint = grl_tracker_source_get_device_constraint (priv); + sparql_type_filter = get_sparql_type_filter (bs->options, TRUE); + grl_tracker_source_get_duration_min_max (bs->options, &min_dur, &max_dur); + duration_constraint = grl_tracker_source_create_constraint (min_dur, max_dur); + + if (bs->container == NULL || + !grl_media_get_id (bs->container)) { + sparql_final = g_strdup_printf (TRACKER_BROWSE_FILESYSTEM_ROOT_REQUEST, + sparql_select, + grl_tracker_show_documents? TRACKER_BROWSE_SHOW_DOCUMENTS: "", + sparql_type_filter, + constraint, + skip, count); + + } else { + sparql_final = g_strdup_printf (TRACKER_BROWSE_FILESYSTEM_REQUEST, + sparql_select, + grl_tracker_show_documents? TRACKER_BROWSE_SHOW_DOCUMENTS: "", + sparql_type_filter, + constraint, + grl_media_get_id (bs->container), + skip, count); + } + + GRL_IDEBUG ("\tselect: '%s'", sparql_final); + + os = grl_tracker_op_initiate_query (bs->operation_id, + sparql_final, + (GAsyncReadyCallback) tracker_browse_cb, + bs); + os->keys = bs->keys; + os->skip = skip; + os->count = count; + os->type_filter = grl_operation_options_get_type_filter (bs->options); + + grl_tracker_queue_push (grl_tracker_queue, os); + + g_free (sparql_type_filter); + g_free (constraint); + g_free (sparql_select); + g_free (duration_constraint); +} + +void +grl_tracker_source_browse (GrlSource *source, + GrlSourceBrowseSpec *bs) +{ + /* Ensure GRL_METADATA_KEY_ID is always requested */ + if (!g_list_find (bs->keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ID))) + bs->keys = g_list_prepend (bs->keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ID)); + + if (grl_tracker_browse_filesystem) + grl_tracker_source_browse_filesystem (source, bs); + else + grl_tracker_source_browse_category (source, bs); +} + +void +grl_tracker_source_cancel (GrlSource *source, guint operation_id) +{ + GrlTrackerOp *os; + + GRL_IDEBUG ("%s: id=%u", __FUNCTION__, operation_id); + + os = g_hash_table_lookup (grl_tracker_operations, + GSIZE_TO_POINTER (operation_id)); + + if (os != NULL) + grl_tracker_queue_cancel (grl_tracker_queue, os); +} + +gboolean +grl_tracker_source_change_start (GrlSource *source, GError **error) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + + priv->notify_changes = TRUE; + + return TRUE; +} + +gboolean +grl_tracker_source_change_stop (GrlSource *source, GError **error) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + + priv->notify_changes = FALSE; + + return TRUE; +} + +void +grl_tracker_source_init_requests (void) +{ + GrlRegistry *registry = grl_registry_get_default (); + + grl_metadata_key_tracker_category = + grl_registry_lookup_metadata_key (registry, "tracker-category"); + + grl_tracker_operations = g_hash_table_new (g_direct_hash, g_direct_equal); + + GRL_LOG_DOMAIN_INIT (tracker_source_request_log_domain, + "tracker-source-request"); + GRL_LOG_DOMAIN_INIT (tracker_source_result_log_domain, + "tracker-source-result"); +} + +GrlCaps * +grl_tracker_source_get_caps (GrlSource *source, + GrlSupportedOps operation) +{ + static GrlCaps *caps; + + if (!caps) { + GList *range_list; + caps = grl_caps_new (); + grl_caps_set_type_filter (caps, GRL_TYPE_FILTER_ALL); + range_list = grl_metadata_key_list_new (GRL_METADATA_KEY_DURATION, + GRL_METADATA_KEY_INVALID); + grl_caps_set_key_range_filter (caps, range_list); + g_list_free (range_list); + } + + return caps; +} + +GrlSupportedOps +grl_tracker_source_supported_operations (GrlSource *source) +{ + gboolean is_extractor; + GrlSupportedOps ops; + + /* Always supported operations. */ + ops = GRL_OP_RESOLVE | GRL_OP_MEDIA_FROM_URI | GRL_OP_SEARCH | GRL_OP_QUERY | + GRL_OP_STORE_METADATA | GRL_OP_NOTIFY_CHANGE; + + /* The extractor doesn’t support browsing; only resolving. */ + is_extractor = g_str_has_prefix (grl_source_get_id (source), + "http://www.tracker-project.org" + "/ontologies/tracker" + "#extractor-data-source,"); + if (!is_extractor) { + ops |= GRL_OP_BROWSE; + } + + return ops; +} + +gboolean +grl_tracker_source_test_media_from_uri (GrlSource *source, + const gchar *uri) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + GError *error = NULL; + TrackerSparqlCursor *cursor; + gboolean empty; + gchar *constraint; + gchar *sparql_final; + + constraint = grl_tracker_source_get_device_constraint (priv); + if (grl_tracker_show_documents) { + sparql_final = g_strdup_printf (TRACKER_TEST_MEDIA_FROM_URI_REQUEST_WITH_DOCUMENTS, + uri, + constraint); + } else { + sparql_final = g_strdup_printf (TRACKER_TEST_MEDIA_FROM_URI_REQUEST, + uri, + constraint); + } + + cursor = tracker_sparql_connection_query (grl_tracker_connection, + sparql_final, + NULL, + &error); + g_free (constraint); + g_free (sparql_final); + + if (error) { + GRL_WARNING ("Error when executig sparql query: %s", + error->message); + g_error_free (error); + return FALSE; + } + + /* Check if there are results */ + if (tracker_sparql_cursor_next (cursor, NULL, NULL)) { + empty = FALSE; + } else { + empty = TRUE; + } + + g_object_unref (cursor); + + return !empty; +} + +void +grl_tracker_source_get_media_from_uri (GrlSource *source, + GrlSourceMediaFromUriSpec *mfus) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + gchar *constraint; + gchar *sparql_select; + gchar *sparql_final; + GrlTrackerOp *os; + + GRL_IDEBUG ("%s: id=%u", __FUNCTION__, mfus->operation_id); + + /* Ensure GRL_METADATA_KEY_ID is always requested */ + if (!g_list_find (mfus->keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ID))) + mfus->keys = g_list_prepend (mfus->keys, GRLKEYID_TO_POINTER (GRL_METADATA_KEY_ID)); + + constraint = grl_tracker_source_get_device_constraint (priv); + sparql_select = grl_tracker_source_get_select_string (mfus->keys); + sparql_final = g_strdup_printf (TRACKER_MEDIA_FROM_URI_REQUEST, + sparql_select, + mfus->uri, + constraint); + + GRL_IDEBUG ("\tselect: '%s'", sparql_final); + + os = grl_tracker_op_initiate_metadata (sparql_final, + (GAsyncReadyCallback) tracker_media_from_uri_cb, + mfus); + os->keys = mfus->keys; + + grl_tracker_queue_push (grl_tracker_queue, os); + + g_free (constraint); + g_free (sparql_select); +} diff --git a/src/tracker3/grl-tracker-source-api.h b/src/tracker3/grl-tracker-source-api.h new file mode 100644 index 0000000..f452e75 --- /dev/null +++ b/src/tracker3/grl-tracker-source-api.h @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2011-2012 Igalia S.L. + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _GRL_TRACKER_SOURCE_API_H_ +#define _GRL_TRACKER_SOURCE_API_H_ + +#include "grl-tracker-source.h" + +/**/ + +void grl_tracker_source_init_requests (void); + +const GList *grl_tracker_source_writable_keys (GrlSource *source); + +void grl_tracker_source_query (GrlSource *source, + GrlSourceQuerySpec *qs); + +void grl_tracker_source_resolve (GrlSource *source, + GrlSourceResolveSpec *rs); + +gboolean grl_tracker_source_may_resolve (GrlSource *source, + GrlMedia *media, + GrlKeyID key_id, + GList **missing_keys); + +void grl_tracker_source_store_metadata (GrlSource *source, + GrlSourceStoreMetadataSpec *sms); + +void grl_tracker_source_cancel (GrlSource *source, guint operation_id); + +void grl_tracker_source_search (GrlSource *source, + GrlSourceSearchSpec *ss); + +void grl_tracker_source_browse (GrlSource *source, + GrlSourceBrowseSpec *bs); + +gboolean grl_tracker_source_change_start (GrlSource *source, + GError **error); + +gboolean grl_tracker_source_change_stop (GrlSource *source, + GError **error); + +GrlCaps *grl_tracker_source_get_caps (GrlSource *source, + GrlSupportedOps operation); + +GrlSupportedOps grl_tracker_source_supported_operations (GrlSource *source); + +gboolean grl_tracker_source_test_media_from_uri (GrlSource *source, + const gchar *uri); + +void grl_tracker_source_get_media_from_uri (GrlSource *source, + GrlSourceMediaFromUriSpec *mfus); + +#endif /* _GRL_TRACKER_SOURCE_API_H_ */ diff --git a/src/tracker3/grl-tracker-source-cache.c b/src/tracker3/grl-tracker-source-cache.c new file mode 100644 index 0000000..0ad04b2 --- /dev/null +++ b/src/tracker3/grl-tracker-source-cache.c @@ -0,0 +1,190 @@ +/* + * Copyright (C) 2011 Intel Corporation. + * Copyright (C) 2011 Igalia S.L. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <glib.h> + +#include "grl-tracker-source-cache.h" + +typedef struct { + GrlTrackerSource *source; + + GHashTable *id_table; +} GrlTrackerCacheSource; + +struct _GrlTrackerCache { + gsize size_limit; + gsize size_current; + + GHashTable *id_table; + GHashTable *source_table; + GList *id_list; +}; + +static GrlTrackerCacheSource * +grl_tracker_cache_source_new (GrlTrackerSource *source) +{ + GrlTrackerCacheSource *csource = g_slice_new0 (GrlTrackerCacheSource); + + csource->source = source; + csource->id_table = g_hash_table_new (g_direct_hash, g_direct_equal); + + return csource; +} + +static void +grl_tracker_cache_source_free (GrlTrackerCacheSource *csource) +{ + g_hash_table_destroy (csource->id_table); + + g_slice_free (GrlTrackerCacheSource, csource); +} + +/**/ + +GrlTrackerCache * +grl_tracker_source_cache_new (gsize size) +{ + GrlTrackerCache *cache; + + g_return_val_if_fail (size > 0, NULL); + + cache = g_slice_new0 (GrlTrackerCache); + + if (!cache) + return NULL; + + cache->size_limit = size; + cache->id_table = g_hash_table_new (g_direct_hash, g_direct_equal); + cache->source_table = g_hash_table_new (g_direct_hash, g_direct_equal); + + return cache; +} + +void +grl_tracker_source_cache_free (GrlTrackerCache *cache) +{ + GHashTableIter iter; + gpointer key, value; + + g_return_if_fail (cache != NULL); + + g_hash_table_iter_init (&iter, cache->source_table); + while (g_hash_table_iter_next (&iter, &key, &value)) { + grl_tracker_source_cache_del_source (cache, key); + } + + if (cache->id_list) { + g_warning ("Memleak detected"); + g_list_free (cache->id_list); + } + g_hash_table_destroy (cache->id_table); + g_hash_table_destroy (cache->source_table); + + g_slice_free (GrlTrackerCache, cache); +} + +void +grl_tracker_source_cache_add_item (GrlTrackerCache *cache, + guint id, + GrlTrackerSource *source) +{ + GList *lid; + GrlTrackerCacheSource *csource; + + g_return_if_fail (cache != NULL); + + if (g_hash_table_lookup (cache->id_table, GSIZE_TO_POINTER (id)) != NULL) + return; /* TODO: is it worth to have an LRU ? */ + + csource = g_hash_table_lookup (cache->source_table, source); + + if (!csource) { + csource = grl_tracker_cache_source_new (source); + g_hash_table_insert (cache->source_table, source, csource); + } + + if (cache->size_current >= cache->size_limit) { + lid = g_list_last (cache->id_list); /* TODO: optimize that ! */ + g_hash_table_remove (cache->id_table, lid->data); + cache->id_list = g_list_remove_link (cache->id_list, lid); + + lid->data = GSIZE_TO_POINTER (id); + lid->next = cache->id_list; + cache->id_list->prev = lid; + cache->id_list = lid; + } else { + cache->id_list = g_list_prepend (cache->id_list, GSIZE_TO_POINTER (id)); + cache->size_current++; + } + + g_hash_table_insert (cache->id_table, GSIZE_TO_POINTER (id), csource); + g_hash_table_insert (csource->id_table, GSIZE_TO_POINTER (id), + cache->id_list); +} + +void +grl_tracker_source_cache_del_source (GrlTrackerCache *cache, + GrlTrackerSource *source) +{ + GrlTrackerCacheSource *csource; + GHashTableIter iter; + gpointer key, value; + + g_return_if_fail (cache != NULL); + g_return_if_fail (source != NULL); + + csource = g_hash_table_lookup (cache->source_table, source); + + if (!csource) + return; + + g_hash_table_iter_init (&iter, csource->id_table); + + while (g_hash_table_iter_next (&iter, &key, &value)) { + g_hash_table_remove (cache->id_table, key); + cache->id_list = g_list_delete_link (cache->id_list, value); + } + + g_hash_table_remove (cache->source_table, source); + grl_tracker_cache_source_free (csource); +} + +GrlTrackerSource * +grl_tracker_source_cache_get_source (GrlTrackerCache *cache, guint id) +{ + GrlTrackerCacheSource *csource; + + g_return_val_if_fail (cache != NULL, NULL); + + csource = (GrlTrackerCacheSource *) g_hash_table_lookup (cache->id_table, + GSIZE_TO_POINTER (id)); + + if (csource) { + return csource->source; + } + + return NULL; +} diff --git a/src/tracker3/grl-tracker-source-cache.h b/src/tracker3/grl-tracker-source-cache.h new file mode 100644 index 0000000..a84930b --- /dev/null +++ b/src/tracker3/grl-tracker-source-cache.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011 Intel Corporation. + * Copyright (C) 2011-2012 Igalia S.L. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _GRL_TRACKER_SOURCE_CACHE_H_ +#define _GRL_TRACKER_SOURCE_CACHE_H_ + +#include "grl-tracker-source.h" + +typedef struct _GrlTrackerCache GrlTrackerCache; + +GrlTrackerCache *grl_tracker_source_cache_new (gsize size); + +void grl_tracker_source_cache_free (GrlTrackerCache *cache); + +void grl_tracker_source_cache_add_item (GrlTrackerCache *cache, + guint id, + GrlTrackerSource *source); +void grl_tracker_source_cache_del_source (GrlTrackerCache *cache, + GrlTrackerSource *source); + +GrlTrackerSource *grl_tracker_source_cache_get_source (GrlTrackerCache *cache, + guint id); + +#endif /* _GRL_TRACKER_SOURCE_CACHE_H_ */ diff --git a/src/tracker3/grl-tracker-source-notif.c b/src/tracker3/grl-tracker-source-notif.c new file mode 100644 index 0000000..fa415be --- /dev/null +++ b/src/tracker3/grl-tracker-source-notif.c @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2011-2012 Igalia S.L. + * Copyright (C) 2011 Intel Corporation. + * Copyright (C) 2015 Collabora Ltd. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * Juan A. Suarez Romero <jasuarez@igalia.com> + * Xavier Claessens <xavier.claessens@collabora.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#include <libtracker-sparql/tracker-sparql.h> + +#include "grl-tracker.h" +#include "grl-tracker-source-notif.h" +#include "grl-tracker-source-priv.h" +#include "grl-tracker-utils.h" + +#define GRL_LOG_DOMAIN_DEFAULT tracker_notif_log_domain +GRL_LOG_DOMAIN_STATIC(tracker_notif_log_domain); + +#define GRL_TRACKER_TYPE_SOURCE_NOTIFY grl_tracker_source_notify_get_type () +G_DECLARE_FINAL_TYPE (GrlTrackerSourceNotify, grl_tracker_source_notify, GRL_TRACKER, SOURCE_NOTIFY, GObject) + +struct _GrlTrackerSourceNotify { + GObject parent; + TrackerSparqlConnection *connection; + TrackerNotifier *notifier; + guint events_signal_id; +}; + +enum { + PROP_0, + PROP_CONNECTION, + N_PROPS +}; + +static GParamSpec *props[N_PROPS] = { 0, }; + +G_DEFINE_TYPE (GrlTrackerSourceNotify, grl_tracker_source_notify, G_TYPE_OBJECT) + +static GrlTrackerSourceNotify *singleton = NULL; + +static GrlMedia * +media_for_event (GrlTrackerSourceNotify *self, + TrackerNotifierEvent *event) +{ + gchar *id_str; + GrlMedia *media; + + id_str = g_strdup_printf ("%" G_GINT64_FORMAT, tracker_notifier_event_get_id (event)); + // FIXME + media = grl_tracker_build_grilo_media (NULL,//tracker_notifier_event_get_type (event), + GRL_TYPE_FILTER_NONE); + grl_media_set_id (media, id_str); + grl_media_set_url (media, tracker_notifier_event_get_urn (event)); + + g_free (id_str); + + return media; +} + +static void +handle_changes (GrlTrackerSourceNotify *self, + GPtrArray *events, + TrackerNotifierEventType tracker_type, + GrlSourceChangeType change_type) +{ + GrlTrackerSource *source = NULL; + TrackerNotifierEvent *event; + GPtrArray *change_list; + GrlMedia *media; + gint i; + + source = grl_tracker_source_find (""); + + if (!source || !grl_tracker_source_can_notify (source)) + return; + + change_list = g_ptr_array_new (); + + for (i = 0; i < events->len; i++) { + event = g_ptr_array_index (events, i); + if (tracker_notifier_event_get_event_type (event) != tracker_type) + continue; + + media = media_for_event (self, event); + g_ptr_array_add (change_list, media); + } + + grl_source_notify_change_list (GRL_SOURCE (source), change_list, + change_type, FALSE); +} + +static void +notifier_event_cb (GrlTrackerSourceNotify *self, + const gchar *service, + const gchar *graph, + GPtrArray *events, + gpointer user_data) +{ + handle_changes (self, events, + TRACKER_NOTIFIER_EVENT_CREATE, + GRL_CONTENT_ADDED); + handle_changes (self, events, + TRACKER_NOTIFIER_EVENT_UPDATE, + GRL_CONTENT_CHANGED); + handle_changes (self, events, + TRACKER_NOTIFIER_EVENT_DELETE, + GRL_CONTENT_REMOVED); +} + +static void +grl_tracker_source_notify_get_property (GObject *object, + guint prop_id, + GValue *value, + GParamSpec *pspec) +{ + GrlTrackerSourceNotify *self = GRL_TRACKER_SOURCE_NOTIFY (object); + + switch (prop_id) { + case PROP_CONNECTION: + g_value_set_object (value, self->connection); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +grl_tracker_source_notify_set_property (GObject *object, + guint prop_id, + const GValue *value, + GParamSpec *pspec) +{ + GrlTrackerSourceNotify *self = GRL_TRACKER_SOURCE_NOTIFY (object); + + switch (prop_id) { + case PROP_CONNECTION: + self->connection = g_value_get_object (value); + break; + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); + break; + } +} + +static void +grl_tracker_source_notify_constructed (GObject *object) +{ + GrlTrackerSourceNotify *self = GRL_TRACKER_SOURCE_NOTIFY (object); + + self->notifier = + tracker_sparql_connection_create_notifier (self->connection); + self->events_signal_id = + g_signal_connect_swapped (self->notifier, "events", + G_CALLBACK (notifier_event_cb), object); + + G_OBJECT_CLASS (grl_tracker_source_notify_parent_class)->constructed (object); +} + +static void +grl_tracker_source_notify_finalize (GObject *object) +{ + GrlTrackerSourceNotify *self = GRL_TRACKER_SOURCE_NOTIFY (object); + + if (self->events_signal_id) + g_signal_handler_disconnect (self->notifier, self->events_signal_id); + g_clear_object (&self->notifier); + G_OBJECT_CLASS (grl_tracker_source_notify_parent_class)->finalize (object); +} + +static void +grl_tracker_source_notify_class_init (GrlTrackerSourceNotifyClass *klass) +{ + GObjectClass *object_class = (GObjectClass *) klass; + + GRL_LOG_DOMAIN_INIT (tracker_notif_log_domain, "tracker-notif"); + object_class->set_property = grl_tracker_source_notify_set_property; + object_class->get_property = grl_tracker_source_notify_get_property; + object_class->finalize = grl_tracker_source_notify_finalize; + object_class->constructed = grl_tracker_source_notify_constructed; + + props[PROP_CONNECTION] = + g_param_spec_object ("connection", + "SPARQL Connection", + "SPARQL Connection", + TRACKER_TYPE_SPARQL_CONNECTION, + G_PARAM_READWRITE | + G_PARAM_CONSTRUCT_ONLY | + G_PARAM_STATIC_STRINGS); + + g_object_class_install_properties (object_class, N_PROPS, props); +} + +static void +grl_tracker_source_notify_init (GrlTrackerSourceNotify *self) +{ +} + +void +grl_tracker_notify_init (TrackerSparqlConnection *sparql_conn) +{ + if (singleton != NULL) + return; + + singleton = g_object_new (GRL_TRACKER_TYPE_SOURCE_NOTIFY, + "connection", sparql_conn, + NULL); +} + +void +grl_tracker_notify_shutdown (void) +{ + g_clear_object (&singleton); +} diff --git a/src/tracker3/grl-tracker-source-notif.h b/src/tracker3/grl-tracker-source-notif.h new file mode 100644 index 0000000..dd78722 --- /dev/null +++ b/src/tracker3/grl-tracker-source-notif.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2011 Intel Corporation. + * Copyright (C) 2011-2012 Igalia S.L. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _GRL_TRACKER_SOURCE_NOTIF_H_ +#define _GRL_TRACKER_SOURCE_NOTIF_H_ + +#include "grl-tracker-source.h" + +/* ------- Definitions ------- */ + +#define TRACKER_DATASOURCES_REQUEST \ + "SELECT " \ + "(SELECT GROUP_CONCAT(rdf:type(?source), \":\") " \ + " WHERE { ?urn nie:dataSource ?source }) " \ + "nie:dataSource(?urn) " \ + "(SELECT GROUP_CONCAT(nie:title(?source), \":\") " \ + " WHERE { ?urn nie:dataSource ?source }) " \ + "(SELECT GROUP_CONCAT(nie:url(tracker:mountPoint(?source)), \":\") " \ + " WHERE { ?urn nie:dataSource ?source }) " \ + "tracker:available(?urn) " \ + "WHERE " \ + "{ " \ + "?urn a nfo:FileDataObject . FILTER (bound(nie:dataSource(?urn)))" \ + "} " \ + "GROUP BY (nie:dataSource(?urn))" + +/**/ + +void grl_tracker_notify_init (TrackerSparqlConnection *sparql_conn); +void grl_tracker_notify_shutdown (void); + +#endif /* _GRL_TRACKER_SOURCE_NOTIF_H_ */ diff --git a/src/tracker3/grl-tracker-source-priv.h b/src/tracker3/grl-tracker-source-priv.h new file mode 100644 index 0000000..95dc5d7 --- /dev/null +++ b/src/tracker3/grl-tracker-source-priv.h @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2011-2012 Igalia S.L. + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _GRL_TRACKER_SOURCE_PRIV_H_ +#define _GRL_TRACKER_SOURCE_PRIV_H_ + +#include "grl-tracker-source.h" +#include "grl-tracker-source-cache.h" + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <glib/gi18n-lib.h> +#include <tracker-sparql.h> + +/* ---- Source information ---- */ + +#define GRL_TRACKER_SOURCE_ID "grl-tracker3-source" +#define GRL_TRACKER_SOURCE_NAME "Tracker3" +#define GRL_TRACKER_SOURCE_DESC _("A plugin for searching multimedia content using Tracker3") + +#define GRL_TRACKER_AUTHOR "Igalia S.L." +#define GRL_TRACKER_LICENSE "LGPL" +#define GRL_TRACKER_SITE "http://www.igalia.com" + +/**/ + +#define GRL_TRACKER_SOURCE_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE((object), \ + GRL_TRACKER_SOURCE_TYPE, \ + GrlTrackerSourcePriv)) + +typedef enum { + GRL_TRACKER_SOURCE_STATE_INSERTING, + GRL_TRACKER_SOURCE_STATE_RUNNING, + GRL_TRACKER_SOURCE_STATE_DELETING, + GRL_TRACKER_SOURCE_STATE_DELETED, +} GrlTrackerSourceState; + +struct _GrlTrackerSourcePriv { + TrackerSparqlConnection *tracker_connection; + + GHashTable *operations; + + gchar *tracker_datasource; + gboolean notify_changes; + + GrlTrackerSourceState state; +}; + +/**/ + +extern GrlPlugin *grl_tracker_plugin; + +/* shared data across */ +extern GrlTrackerCache *grl_tracker_item_cache; +extern gboolean grl_tracker_upnp_present; + +/* tracker plugin config */ +extern gboolean grl_tracker_browse_filesystem; +extern gboolean grl_tracker_show_documents; + +#endif /* _GRL_TRACKER_SOURCE_PRIV_H_ */ diff --git a/src/tracker3/grl-tracker-source.c b/src/tracker3/grl-tracker-source.c new file mode 100644 index 0000000..78c3998 --- /dev/null +++ b/src/tracker3/grl-tracker-source.c @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2011-2012 Igalia S.L. + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <grilo.h> +#include <string.h> +#include <tracker-sparql.h> + +#include "grl-tracker.h" +#include "grl-tracker-source.h" +#include "grl-tracker-source-priv.h" +#include "grl-tracker-source-api.h" +#include "grl-tracker-source-cache.h" +#include "grl-tracker-source-notif.h" +#include "grl-tracker-utils.h" + +/* --------- Logging -------- */ + +#define GRL_LOG_DOMAIN_DEFAULT tracker_source_log_domain +GRL_LOG_DOMAIN_STATIC(tracker_source_log_domain); + +/* ------- Definitions ------- */ + +#define MEDIA_TYPE "grilo-media-type" + +#define TRACKER_ITEM_CACHE_SIZE (10000) + +/* --- Other --- */ + +enum { + PROP_0, + PROP_TRACKER_CONNECTION, + PROP_TRACKER_DATASOURCE, +}; + +static void grl_tracker_source_set_property (GObject *object, + guint propid, + const GValue *value, + GParamSpec *pspec); + +static void grl_tracker_source_finalize (GObject *object); + +/* ===================== Globals ================= */ + +/* shared data across */ +GrlTrackerCache *grl_tracker_item_cache; +GHashTable *grl_tracker_source_sources; + +/* ================== TrackerSource GObject ================ */ + +G_DEFINE_TYPE (GrlTrackerSource, grl_tracker_source, GRL_TYPE_SOURCE); + +static GrlTrackerSource * +grl_tracker_source_new (TrackerSparqlConnection *connection) +{ + GRL_DEBUG ("%s", __FUNCTION__); + + return g_object_new (GRL_TRACKER_SOURCE_TYPE, + "source-id", GRL_TRACKER_SOURCE_ID, + "source-name", GRL_TRACKER_SOURCE_NAME, + "source-desc", GRL_TRACKER_SOURCE_DESC, + "tracker-connection", connection, + "tracker-datasource", "", + NULL); +} + +static void +grl_tracker_source_class_init (GrlTrackerSourceClass * klass) +{ + GObjectClass *g_class = G_OBJECT_CLASS (klass); + GrlSourceClass *source_class = GRL_SOURCE_CLASS (klass); + + g_class->finalize = grl_tracker_source_finalize; + g_class->set_property = grl_tracker_source_set_property; + + source_class->cancel = grl_tracker_source_cancel; + source_class->supported_keys = grl_tracker_supported_keys; + source_class->writable_keys = grl_tracker_source_writable_keys; + source_class->store_metadata = grl_tracker_source_store_metadata; + source_class->query = grl_tracker_source_query; + source_class->resolve = grl_tracker_source_resolve; + source_class->may_resolve = grl_tracker_source_may_resolve; + source_class->search = grl_tracker_source_search; + source_class->browse = grl_tracker_source_browse; + source_class->notify_change_start = grl_tracker_source_change_start; + source_class->notify_change_stop = grl_tracker_source_change_stop; + source_class->supported_operations = grl_tracker_source_supported_operations; + source_class->get_caps = grl_tracker_source_get_caps; + source_class->test_media_from_uri = grl_tracker_source_test_media_from_uri; + source_class->media_from_uri = grl_tracker_source_get_media_from_uri; + + g_object_class_install_property (g_class, + PROP_TRACKER_CONNECTION, + g_param_spec_object ("tracker-connection", + "tracker connection", + "A Tracker connection", + TRACKER_SPARQL_TYPE_CONNECTION, + G_PARAM_WRITABLE + | G_PARAM_CONSTRUCT_ONLY + | G_PARAM_STATIC_NAME)); + + g_object_class_install_property (g_class, + PROP_TRACKER_DATASOURCE, + g_param_spec_string ("tracker-datasource", + "tracker datasource", + "A Tracker nie:DataSource URN", + NULL, + G_PARAM_WRITABLE + | G_PARAM_CONSTRUCT_ONLY + | G_PARAM_STATIC_NAME)); + + g_type_class_add_private (klass, sizeof (GrlTrackerSourcePriv)); +} + +static void +grl_tracker_source_init (GrlTrackerSource *source) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + + source->priv = priv; + + priv->operations = g_hash_table_new (g_direct_hash, g_direct_equal); +} + +static void +grl_tracker_source_finalize (GObject *object) +{ + GrlTrackerSource *self; + + self = GRL_TRACKER_SOURCE (object); + + g_clear_object (&self->priv->tracker_connection); + + G_OBJECT_CLASS (grl_tracker_source_parent_class)->finalize (object); +} + +static void +grl_tracker_source_set_property (GObject *object, + guint propid, + const GValue *value, + GParamSpec *pspec) + +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (object); + + switch (propid) { + case PROP_TRACKER_CONNECTION: + g_clear_object (&priv->tracker_connection); + priv->tracker_connection = g_object_ref (g_value_get_object (value)); + break; + + case PROP_TRACKER_DATASOURCE: + g_clear_pointer (&priv->tracker_datasource, g_free); + priv->tracker_datasource = g_strdup (g_value_get_string (value)); + break; + + default: + G_OBJECT_WARN_INVALID_PROPERTY_ID (object, propid, pspec); + } +} + +const gchar * +grl_tracker_source_get_tracker_source (GrlTrackerSource *source) +{ + GrlTrackerSourcePriv *priv; + + g_return_val_if_fail (GRL_IS_TRACKER_SOURCE (source), NULL); + + priv = source->priv; + + return priv->tracker_datasource; +} + +TrackerSparqlConnection * +grl_tracker_source_get_tracker_connection (GrlTrackerSource *source) +{ + GrlTrackerSourcePriv *priv; + + g_return_val_if_fail (GRL_IS_TRACKER_SOURCE (source), NULL); + + priv = source->priv; + + return priv->tracker_connection; +} + +/* =================== TrackerSource Plugin =============== */ + +void +grl_tracker_add_source (GrlTrackerSource *source) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + + GRL_DEBUG ("====================>add source '%s'", + grl_source_get_name (GRL_SOURCE (source))); + + g_hash_table_insert (grl_tracker_source_sources, + (gpointer) grl_tracker_source_get_tracker_source (source), + g_object_ref (source)); + priv->state = GRL_TRACKER_SOURCE_STATE_RUNNING; + grl_registry_register_source (grl_registry_get_default (), + grl_tracker_plugin, + GRL_SOURCE (g_object_ref (source)), + NULL); +} + +void +grl_tracker_del_source (GrlTrackerSource *source) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + + GRL_DEBUG ("==================>del source '%s'", + grl_source_get_name (GRL_SOURCE (source))); + + g_hash_table_remove (grl_tracker_source_sources, + grl_tracker_source_get_tracker_source (source)); + grl_tracker_source_cache_del_source (grl_tracker_item_cache, source); + priv->state = GRL_TRACKER_SOURCE_STATE_DELETED; + grl_registry_unregister_source (grl_registry_get_default (), + GRL_SOURCE (source), + NULL); +} + +gboolean +grl_tracker_source_can_notify (GrlTrackerSource *source) +{ + GrlTrackerSourcePriv *priv = GRL_TRACKER_SOURCE_GET_PRIVATE (source); + + if (priv->state == GRL_TRACKER_SOURCE_STATE_RUNNING) + return priv->notify_changes; + + return FALSE; +} + +GrlTrackerSource * +grl_tracker_source_find (const gchar *id) +{ + GrlTrackerSource *source; + + source = g_hash_table_lookup (grl_tracker_source_sources, id); + return source; +} + +static gboolean +match_plugin_id (gpointer key, + gpointer value, + gpointer user_data) +{ + if (g_strcmp0 (grl_source_get_id (GRL_SOURCE (value)), + (gchar *) user_data) == 0) { + return TRUE; + } + + return FALSE; +} + +/* Search for registered plugin with @id */ +GrlTrackerSource * +grl_tracker_source_find_source (const gchar *id) +{ + GrlTrackerSource *source; + + source = g_hash_table_find (grl_tracker_source_sources, + match_plugin_id, + (gpointer) id); + return source; +} + +void +grl_tracker_source_sources_init (void) +{ + + GRL_LOG_DOMAIN_INIT (tracker_source_log_domain, "tracker-source"); + + GRL_DEBUG ("%s", __FUNCTION__); + + grl_tracker_item_cache = + grl_tracker_source_cache_new (TRACKER_ITEM_CACHE_SIZE); + grl_tracker_source_sources = g_hash_table_new_full (g_str_hash, g_str_equal, + NULL, g_object_unref); + + if (grl_tracker_connection != NULL) { + GrlTrackerSource *source; + + grl_tracker_notify_init (grl_tracker_connection); + + /* One source to rule them all. */ + source = grl_tracker_source_new (grl_tracker_connection); + grl_tracker_add_source (source); + g_object_unref (source); + } +} diff --git a/src/tracker3/grl-tracker-source.h b/src/tracker3/grl-tracker-source.h new file mode 100644 index 0000000..a615726 --- /dev/null +++ b/src/tracker3/grl-tracker-source.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2011-2012 Igalia S.L. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _GRL_TRACKER_SOURCE_H_ +#define _GRL_TRACKER_SOURCE_H_ + +#include <grilo.h> +#include <tracker-sparql.h> + +#define GRL_TRACKER_SOURCE_TYPE \ + (grl_tracker_source_get_type ()) + +#define GRL_TRACKER_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST ((obj), \ + GRL_TRACKER_SOURCE_TYPE, \ + GrlTrackerSource)) + +#define GRL_IS_TRACKER_SOURCE(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE ((obj), \ + GRL_TRACKER_SOURCE_TYPE)) + +#define GRL_TRACKER_SOURCE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass), \ + GRL_TRACKER_SOURCE_TYPE, \ + GrlTrackerSourceClass)) + +#define GRL_IS_TRACKER_SOURCE_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass), \ + GRL_TRACKER_SOURCE_TYPE)) + +#define GRL_TRACKER_SOURCE_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj), \ + GRL_TRACKER_SOURCE_TYPE, \ + GrlTrackerSourceClass)) + +typedef struct _GrlTrackerSource GrlTrackerSource; +typedef struct _GrlTrackerSourcePriv GrlTrackerSourcePriv; + +struct _GrlTrackerSource { + + GrlSource parent; + + /*< private >*/ + GrlTrackerSourcePriv *priv; + +}; + +typedef struct _GrlTrackerSourceClass GrlTrackerSourceClass; + +struct _GrlTrackerSourceClass { + + GrlSourceClass parent_class; + +}; + +GType grl_tracker_source_get_type (void); + +gboolean grl_tracker_source_can_notify (GrlTrackerSource *source); + +const gchar *grl_tracker_source_get_tracker_source (GrlTrackerSource *source); + +TrackerSparqlConnection *grl_tracker_source_get_tracker_connection (GrlTrackerSource *source); + +/**/ + +void grl_tracker_source_sources_init (void); + +void grl_tracker_add_source (GrlTrackerSource *source); + +void grl_tracker_del_source (GrlTrackerSource *source); + +GrlTrackerSource *grl_tracker_source_find (const gchar *id); + +GrlTrackerSource *grl_tracker_source_find_source (const gchar *id); + +#endif /* _GRL_TRACKER_SOURCE_H_ */ diff --git a/src/tracker3/grl-tracker-utils.c b/src/tracker3/grl-tracker-utils.c new file mode 100644 index 0000000..e83ec5e --- /dev/null +++ b/src/tracker3/grl-tracker-utils.c @@ -0,0 +1,879 @@ +/* + * Copyright (C) 2011 Igalia S.L. + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "grl-tracker-utils.h" +#include <glib/gi18n-lib.h> + +/**/ + +static GHashTable *grl_to_sparql_mapping = NULL; +static GHashTable *sparql_to_grl_mapping = NULL; + +GrlKeyID grl_metadata_key_tracker_urn; +GrlKeyID grl_metadata_key_gibest_hash; + + +/**/ + +static gchar * +build_flavored_key (gchar *key, const gchar *flavor) +{ + gint i = 0; + + while (key[i] != '\0') { + if (!g_ascii_isalnum (key[i])) { + key[i] = '_'; + } + i++; + } + + return g_strdup_printf ("%s_%s", key, flavor); +} + +static void +set_orientation (TrackerSparqlCursor *cursor, + gint column, + GrlMedia *media, + GrlKeyID key) +{ + const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL); + + if (g_str_has_suffix (str, "nfo#orientation-top")) + grl_data_set_int (GRL_DATA (media), key, 0); + else if (g_str_has_suffix (str, "nfo#orientation-right")) + grl_data_set_int (GRL_DATA (media), key, 90); + else if (g_str_has_suffix (str, "nfo#orientation-bottom")) + grl_data_set_int (GRL_DATA (media), key, 180); + else if (g_str_has_suffix (str, "nfo#orientation-left")) + grl_data_set_int (GRL_DATA (media), key, 270); +} + +static void +set_date (TrackerSparqlCursor *cursor, + gint column, + GrlMedia *media, + GrlKeyID key) +{ + const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL); + if (key == GRL_METADATA_KEY_CREATION_DATE + || key == GRL_METADATA_KEY_LAST_PLAYED + || key == GRL_METADATA_KEY_MODIFICATION_DATE) { + GDateTime *date = grl_date_time_from_iso8601 (str); + if (date) { + grl_data_set_boxed (GRL_DATA (media), key, date); + g_date_time_unref (date); + } + } +} + +static void +set_favourite (TrackerSparqlCursor *cursor, + gint column, + GrlMedia *media, + GrlKeyID key) +{ + const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL); + gboolean is_favourite = FALSE; + + if (str != NULL && g_str_has_suffix (str, "predefined-tag-favorite")) + is_favourite = TRUE; + + grl_data_set_boolean (GRL_DATA (media), key, is_favourite); +} + +static void +set_title_from_filename (TrackerSparqlCursor *cursor, + gint column, + GrlMedia *media, + GrlKeyID key) +{ + const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL); + if (key == GRL_METADATA_KEY_TITLE) { + grl_data_set_boolean (GRL_DATA (media), GRL_METADATA_KEY_TITLE_FROM_FILENAME, TRUE); + grl_media_set_title (media, str); + } +} + +static void +set_title (TrackerSparqlCursor *cursor, + gint column, + GrlMedia *media, + GrlKeyID key) +{ + const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL); + grl_data_set_boolean (GRL_DATA (media), GRL_METADATA_KEY_TITLE_FROM_FILENAME, FALSE); + grl_media_set_title (media, str); +} + +static void +set_string_metadata_keys (TrackerSparqlCursor *cursor, + gint column, + GrlMedia *media, + GrlKeyID key) +{ + const gchar *str = tracker_sparql_cursor_get_string (cursor, column, NULL); + grl_data_set_string (GRL_DATA (media), key, str); +} + +static void +set_int_metadata_keys (TrackerSparqlCursor *cursor, + gint column, + GrlMedia *media, + GrlKeyID key) +{ + const gint64 value = tracker_sparql_cursor_get_integer (cursor, column); + grl_data_set_int (GRL_DATA (media), key, value); +} + +static tracker_grl_sparql_t * +insert_key_mapping (GrlKeyID grl_key, + const gchar *sparql_key_attr, + const gchar *sparql_key_attr_call, + const gchar *sparql_key_flavor) +{ + tracker_grl_sparql_t *assoc; + GList *assoc_list; + gchar *canon_name; + + g_return_val_if_fail (grl_key != GRL_METADATA_KEY_INVALID, NULL); + + assoc = g_new0 (tracker_grl_sparql_t, 1); + assoc_list = g_hash_table_lookup (grl_to_sparql_mapping, + GRLKEYID_TO_POINTER (grl_key)); + canon_name = g_strdup (GRL_METADATA_KEY_GET_NAME (grl_key)); + + assoc->grl_key = grl_key; + assoc->sparql_key_name = build_flavored_key (canon_name, + sparql_key_flavor); + assoc->sparql_key_name_canon = g_strdup (canon_name); + assoc->sparql_key_attr = sparql_key_attr; + assoc->sparql_key_attr_call = sparql_key_attr_call; + assoc->sparql_key_flavor = sparql_key_flavor; + + assoc_list = g_list_append (assoc_list, assoc); + + g_hash_table_insert (grl_to_sparql_mapping, + GRLKEYID_TO_POINTER (grl_key), + assoc_list); + g_hash_table_insert (sparql_to_grl_mapping, + (gpointer) assoc->sparql_key_name, + assoc); + g_hash_table_insert (sparql_to_grl_mapping, + (gpointer) GRL_METADATA_KEY_GET_NAME (grl_key), + assoc); + + /* Grilo maps key names to SPARQL variables. Key names can contain dashes, + * however SPARQL does not allow dashes in variable names. So use the to + * underscores converted canon_name as additional mapping. + */ + if (g_strrstr (assoc->sparql_key_name_canon, "_")) { + g_hash_table_insert (sparql_to_grl_mapping, + (gpointer) assoc->sparql_key_name_canon, + assoc); + } + + g_free (canon_name); + + return assoc; +} + +static tracker_grl_sparql_t * +insert_key_mapping_with_setter (GrlKeyID grl_key, + const gchar *sparql_key_attr, + const gchar *sparql_key_attr_call, + const gchar *sparql_key_flavor, + tracker_grl_sparql_setter_cb_t setter) +{ + tracker_grl_sparql_t *assoc; + + assoc = insert_key_mapping (grl_key, + sparql_key_attr, + sparql_key_attr_call, + sparql_key_flavor); + + assoc->set_value = setter; + + return assoc; +} + +void +grl_tracker_setup_key_mappings (void) +{ + GrlRegistry *registry = grl_registry_get_default (); + GrlKeyID grl_metadata_key_chromaprint; + + grl_metadata_key_tracker_urn = + grl_registry_lookup_metadata_key (registry, "tracker-urn"); + + grl_metadata_key_gibest_hash = + grl_registry_lookup_metadata_key (registry, "gibest-hash"); + + grl_metadata_key_chromaprint = + grl_registry_lookup_metadata_key (registry, "chromaprint"); + + grl_to_sparql_mapping = g_hash_table_new (g_direct_hash, g_direct_equal); + sparql_to_grl_mapping = g_hash_table_new (g_str_hash, g_str_equal); + + insert_key_mapping (grl_metadata_key_tracker_urn, + NULL, + "?urn", + "file"); + + insert_key_mapping (GRL_METADATA_KEY_ALBUM, + NULL, + "nie:title(nmm:musicAlbum(?urn))", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_ALBUM_DISC_NUMBER, + NULL, + "nmm:setNumber(nmm:musicAlbumDisc(?urn))", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_ARTIST, + NULL, + "nmm:artistName(nmm:performer(?urn))", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_ALBUM_ARTIST, + NULL, + "nmm:artistName(nmm:albumArtist(nmm:musicAlbum(?urn)))", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_AUTHOR, + NULL, + "nmm:artistName(nmm:performer(?urn))", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_BITRATE, + "nfo:averageBitrate", + "nfo:averageBitrate(?urn)", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_CHILDCOUNT, + "nfo:entryCounter", + "nfo:entryCounter(?urn)", + "directory"); + + insert_key_mapping (GRL_METADATA_KEY_COMPOSER, + NULL, + "nmm:artistName(nmm:composer(?urn))", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_SIZE, + NULL, + "nfo:fileSize(?urn)", + "file"); + + insert_key_mapping (grl_metadata_key_gibest_hash, + NULL, + "(select nfo:hashValue(?h) { ?urn nfo:hasHash ?h . ?h nfo:hashAlgorithm \"gibest\" })", + "video"); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_MODIFICATION_DATE, + "nfo:fileLastModified", + "nfo:fileLastModified(?urn)", + "file", + set_date); + + insert_key_mapping (GRL_METADATA_KEY_DURATION, + "nfo:duration", + "nfo:duration(?urn)", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_MB_TRACK_ID, + NULL, + "(SELECT tracker:referenceIdentifier(?t) AS ?t_id { ?urn tracker:hasExternalReference ?t . ?t tracker:referenceSource \"https://musicbrainz.org/doc/Track\" })", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_MB_ARTIST_ID, + NULL, + "(SELECT tracker:referenceIdentifier(?a) AS ?a_id { ?urn nmm:performer ?artist . ?artist tracker:hasExternalReference ?a . ?a tracker:referenceSource \"https://musicbrainz.org/doc/Artist\" })", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_MB_RECORDING_ID, + NULL, + "(SELECT tracker:referenceIdentifier(?r) AS ?r_id { ?urn tracker:hasExternalReference ?r . ?r tracker:referenceSource \"https://musicbrainz.org/doc/Recording\" })", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_MB_RELEASE_ID, + NULL, + "(SELECT tracker:referenceIdentifier(?re) AS ?re_id { ?urn nmm:musicAlbum ?album . ?album tracker:hasExternalReference ?re . ?re tracker:referenceSource \"https://musicbrainz.org/doc/Release\" })", + "audio"); + + insert_key_mapping (GRL_METADATA_KEY_MB_RELEASE_GROUP_ID, + NULL, + "(SELECT tracker:referenceIdentifier(?rg) AS ?rg_id { ?urn nmm:musicAlbum ?album . ?album tracker:hasExternalReference ?rg . ?rg tracker:referenceSource \"https://musicbrainz.org/doc/Release_Group\" })", + "audio"); + + if (grl_metadata_key_chromaprint != 0) { + insert_key_mapping_with_setter (grl_metadata_key_chromaprint, + NULL, + "(select nfo:hashValue(?h) { ?urn nfo:hasHash ?h . ?h nfo:hashAlgorithm \"chromaprint\" })", + "audio", + set_string_metadata_keys); + }; + + insert_key_mapping (GRL_METADATA_KEY_FRAMERATE, + "nfo:frameRate", + "nfo:frameRate(?urn)", + "video"); + + insert_key_mapping (GRL_METADATA_KEY_HEIGHT, + "nfo:height", + "nfo:height(?urn)", + "video"); + + insert_key_mapping (GRL_METADATA_KEY_ID, + "tracker:id", + "tracker:id(?urn)", + "file"); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_LAST_PLAYED, + "nfo:fileLastAccessed", + "nfo:fileLastAccessed(?urn)", + "file", + set_date); + + insert_key_mapping (GRL_METADATA_KEY_MIME, + "nie:mimeType", + "nie:mimeType(?urn)", + "file"); + + insert_key_mapping (GRL_METADATA_KEY_SITE, + "nie:url", + "nie:url(?urn)", + "file"); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_TITLE, + "nie:title", + "nie:title(?urn)", + "audio", + set_title); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_TITLE, + "nfo:fileName", + "nfo:fileName(?urn)", + "file", + set_title_from_filename); + + insert_key_mapping (GRL_METADATA_KEY_URL, + "nie:url", + "nie:url(?urn)", + "file"); + + insert_key_mapping (GRL_METADATA_KEY_WIDTH, + "nfo:width", + "nfo:width(?urn)", + "video"); + + insert_key_mapping (GRL_METADATA_KEY_SEASON, + "nmm:season", + "nmm:season(?urn)", + "video"); + + insert_key_mapping (GRL_METADATA_KEY_EPISODE, + "nmm:episodeNumber", + "nmm:episodeNumber(?urn)", + "video"); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_CREATION_DATE, + "nie:contentCreated", + "nie:contentCreated(?urn)", + "image", + set_date); + + insert_key_mapping (GRL_METADATA_KEY_CAMERA_MODEL, + NULL, + "nfo:model(nfo:equipment(?urn))", + "image"); + + insert_key_mapping (GRL_METADATA_KEY_FLASH_USED, + "nmm:flash", + "nmm:flash(?urn)", + "image"); + + insert_key_mapping (GRL_METADATA_KEY_EXPOSURE_TIME, + "nmm:exposureTime", + "nmm:exposureTime(?urn)", + "image"); + + insert_key_mapping (GRL_METADATA_KEY_ISO_SPEED, + "nmm:isoSpeed", + "nmm:isoSpeed(?urn)", + "image"); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_ORIENTATION, + "nfo:orientation", + "nfo:orientation(?urn)", + "image", + set_orientation); + + insert_key_mapping (GRL_METADATA_KEY_PLAY_COUNT, + "nie:usageCounter", + "nie:usageCounter(?urn)", + "media"); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_LAST_PLAYED, + "nie:contentAccessed", + "nie:contentAccessed(?urn)", + "media", + set_date); + + insert_key_mapping (GRL_METADATA_KEY_LAST_POSITION, + "nfo:lastPlayedPosition", + "nfo:lastPlayedPosition(?urn)", + "media"); + + insert_key_mapping (GRL_METADATA_KEY_START_TIME, + "nfo:audioOffset", + "nfo:audioOffset(?urn)", + "media"); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_TRACK_NUMBER, + "nmm:trackNumber", + "nmm:trackNumber(?urn)", + "audio", + set_int_metadata_keys); + + insert_key_mapping_with_setter (GRL_METADATA_KEY_FAVOURITE, + "nao:hasTag", + "nao:hasTag(?urn)", + "audio", + set_favourite); +} + +tracker_grl_sparql_t * +grl_tracker_get_mapping_from_sparql (const gchar *key) +{ + return (tracker_grl_sparql_t *) g_hash_table_lookup (sparql_to_grl_mapping, + key); +} + +static GList * +get_mapping_from_grl (const GrlKeyID key) +{ + return (GList *) g_hash_table_lookup (grl_to_sparql_mapping, + GRLKEYID_TO_POINTER (key)); +} + +gboolean +grl_tracker_key_is_supported (const GrlKeyID key) +{ + return g_hash_table_lookup (grl_to_sparql_mapping, + GRLKEYID_TO_POINTER (key)) != NULL; +} + +/**/ + +gchar * +grl_tracker_source_get_device_constraint (GrlTrackerSourcePriv *priv) +{ + if (priv->tracker_datasource == NULL || + priv->tracker_datasource[0] == '\0') + return g_strdup (""); + + return g_strdup_printf ("?urn nie:dataSource <%s> .", + priv->tracker_datasource); +} + +gchar * +grl_tracker_source_get_select_string (const GList *keys) +{ + const GList *key = keys; + GString *gstr = g_string_new (""); + GList *assoc_list; + tracker_grl_sparql_t *assoc; + + assoc_list = get_mapping_from_grl (grl_metadata_key_tracker_urn); + assoc = (tracker_grl_sparql_t *) assoc_list->data; + g_string_append_printf (gstr, "%s AS ?%s ", + assoc->sparql_key_attr_call, + assoc->sparql_key_name); + + while (key != NULL) { + assoc_list = get_mapping_from_grl (GRLPOINTER_TO_KEYID (key->data)); + while (assoc_list != NULL) { + assoc = (tracker_grl_sparql_t *) assoc_list->data; + if (assoc != NULL) { + g_string_append_printf (gstr, "%s AS ?%s ", + assoc->sparql_key_attr_call, + assoc->sparql_key_name); + } + assoc_list = assoc_list->next; + } + key = key->next; + } + + return g_string_free (gstr, FALSE); +} + +static void +gen_prop_insert_string (GString *gstr, + tracker_grl_sparql_t *assoc, + GrlData *data) +{ + gchar *tmp; + GType type = GRL_METADATA_KEY_GET_TYPE (assoc->grl_key); + + switch (type) { + case G_TYPE_STRING: + tmp = g_strescape (grl_data_get_string (data, assoc->grl_key), NULL); + g_string_append_printf (gstr, "%s \"%s\"", + assoc->sparql_key_attr, tmp); + g_free (tmp); + break; + + case G_TYPE_INT: + g_string_append_printf (gstr, "%s %i", + assoc->sparql_key_attr, + grl_data_get_int (data, assoc->grl_key)); + break; + + case G_TYPE_FLOAT: + g_string_append_printf (gstr, "%s %f", + assoc->sparql_key_attr, + grl_data_get_float (data, assoc->grl_key)); + break; + + case G_TYPE_BOOLEAN: + /* Special case for favourite tag, see comment in + * grl_tracker_tracker_get_insert_string for more details. + */ + if (assoc->grl_key == GRL_METADATA_KEY_FAVOURITE) { + g_string_append_printf (gstr, "%s nao:predefined-tag-favorite", + assoc->sparql_key_attr); + } + break; + + default: + if (type == G_TYPE_DATE_TIME) { + tmp = g_date_time_format (grl_data_get_boxed (data, assoc->grl_key), + "%FT%T%:z"); + g_string_append_printf (gstr, "%s '%s'", + assoc->sparql_key_attr, + tmp); + + g_free (tmp); + } + break; + } +} + +gchar * +grl_tracker_tracker_get_insert_string (GrlMedia *media, const GList *keys) +{ + gboolean first = TRUE; + const GList *key; + GString *gstr = g_string_new (""); + + for (key = keys; key != NULL; key = key->next) { + const GList *assoc_list; + GrlKeyID key_id = GRLPOINTER_TO_KEYID (key->data); + + for (assoc_list = get_mapping_from_grl (key_id); + assoc_list != NULL; + assoc_list = assoc_list->next) { + tracker_grl_sparql_t *assoc = assoc_list->data; + + if (assoc == NULL) + continue; + + /* The favourite key is really setting or deleting a tag + * in tracker, so in the case of setting it to false skip + * the insert string creation step for this key completely. + */ + if (assoc->grl_key == GRL_METADATA_KEY_FAVOURITE && + !grl_media_get_favourite (media)) + continue; + + if (!grl_data_has_key (GRL_DATA (media), key_id)) + continue; + + /* Special case for key title, nfo:fileName is read-only. + * It cannot be modified. + */ + if (assoc->grl_key == GRL_METADATA_KEY_TITLE && + g_strcmp0 (assoc->sparql_key_attr, "nfo:fileName") == 0) { + continue; + } + + if (!first) + g_string_append (gstr, " ; "); + + gen_prop_insert_string (gstr, assoc, GRL_DATA (media)); + first = FALSE; + } + } + + return g_string_free (gstr, FALSE); +} + +gchar * +grl_tracker_get_delete_string (const GList *keys) +{ + gboolean first = TRUE; + const GList *key = keys, *assoc_list; + tracker_grl_sparql_t *assoc; + GString *gstr = g_string_new (""); + gchar *ret; + gint var_n = 0; + + while (key != NULL) { + assoc_list = get_mapping_from_grl (GRLPOINTER_TO_KEYID (key->data)); + while (assoc_list != NULL) { + assoc = (tracker_grl_sparql_t *) assoc_list->data; + if (assoc != NULL) { + /* Special case for key title, nfo:fileName is read-only. + * It cannot be modified. + */ + if (assoc->grl_key == GRL_METADATA_KEY_TITLE && + g_strcmp0 (assoc->sparql_key_attr, "nfo:fileName") == 0) { + assoc_list = assoc_list->next; + continue; + } + + if (first) { + g_string_append_printf (gstr, "%s ?v%i", + assoc->sparql_key_attr, var_n); + first = FALSE; + } else { + g_string_append_printf (gstr, " ; %s ?v%i", + assoc->sparql_key_attr, var_n); + } + var_n++; + } + assoc_list = assoc_list->next; + } + key = key->next; + } + + ret = gstr->str; + g_string_free (gstr, FALSE); + + return ret; +} + +gchar * +grl_tracker_get_delete_conditional_string (const gchar *urn, + const GList *keys) +{ + gboolean first = TRUE; + const GList *key = keys, *assoc_list; + tracker_grl_sparql_t *assoc; + GString *gstr = g_string_new (""); + gchar *ret; + gint var_n = 0; + + while (key != NULL) { + assoc_list = get_mapping_from_grl (GRLPOINTER_TO_KEYID (key->data)); + while (assoc_list != NULL) { + assoc = (tracker_grl_sparql_t *) assoc_list->data; + if (assoc != NULL) { + /* Special case for key title, nfo:fileName is read-only. + * It cannot be modified. + */ + if (assoc->grl_key == GRL_METADATA_KEY_TITLE && + g_strcmp0 (assoc->sparql_key_attr, "nfo:fileName") == 0) { + assoc_list = assoc_list->next; + continue; + } + + if (first) { + g_string_append_printf (gstr, "OPTIONAL { <%s> %s ?v%i }", + urn, assoc->sparql_key_attr, var_n); + first = FALSE; + } else { + g_string_append_printf (gstr, " . OPTIONAL { <%s> %s ?v%i }", + urn, assoc->sparql_key_attr, var_n); + } + var_n++; + } + assoc_list = assoc_list->next; + } + key = key->next; + } + + ret = gstr->str; + g_string_free (gstr, FALSE); + + return ret; +} + +static GrlMedia * +grl_tracker_build_grilo_media_default (GHashTable *ht) +{ + if (g_hash_table_lookup (ht, RDF_TYPE_MUSIC)) { + return grl_media_audio_new (); + } else if (g_hash_table_lookup (ht, RDF_TYPE_VIDEO)) { + return grl_media_video_new (); + } else if (g_hash_table_lookup (ht, RDF_TYPE_IMAGE)) { + return grl_media_image_new (); + } else if (g_hash_table_lookup (ht, RDF_TYPE_ARTIST)) { + return grl_media_container_new (); + } else if (g_hash_table_lookup (ht, RDF_TYPE_ALBUM)) { + return grl_media_container_new (); + } else if (g_hash_table_lookup (ht, RDF_TYPE_CONTAINER)) { + return grl_media_container_new (); + } else if (g_hash_table_lookup (ht, RDF_TYPE_FOLDER)) { + return grl_media_container_new (); + } else if (g_hash_table_lookup (ht, RDF_TYPE_PLAYLIST)) { + return grl_media_container_new (); + } + + return NULL; +} + +/**/ + +/* Builds an appropriate GrlMedia based on ontology type returned by + tracker, or NULL if unknown */ +GrlMedia * +grl_tracker_build_grilo_media (const gchar *rdf_type, + GrlTypeFilter type_filter) +{ + GrlMedia *media = NULL; + gchar **rdf_single_type; + int i; + GHashTable *ht; + + if (!rdf_type) { + return NULL; + } + + /* As rdf_type can be formed by several types, split them */ + rdf_single_type = g_strsplit (rdf_type, ",", -1); + i = g_strv_length (rdf_single_type) - 1; + ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL); + for (; i>= 0; i--) + g_hash_table_insert (ht, g_path_get_basename (rdf_single_type[i]), GINT_TO_POINTER(TRUE)); + + if (type_filter == GRL_TYPE_FILTER_NONE || + type_filter == GRL_TYPE_FILTER_ALL) { + media = grl_tracker_build_grilo_media_default (ht); + } else if ((type_filter & GRL_TYPE_FILTER_AUDIO) && + g_hash_table_lookup (ht, RDF_TYPE_MUSIC)) { + media = grl_media_audio_new (); + } else if ((type_filter & GRL_TYPE_FILTER_VIDEO) && + g_hash_table_lookup (ht, RDF_TYPE_VIDEO)) { + media = grl_media_video_new (); + } else if ((type_filter & GRL_TYPE_FILTER_IMAGE) && + g_hash_table_lookup (ht, RDF_TYPE_IMAGE)) { + media = grl_media_image_new (); + } else { + media = grl_tracker_build_grilo_media_default (ht); + } + + g_hash_table_destroy (ht); + g_strfreev (rdf_single_type); + + if (!media) + media = grl_media_new (); + + return media; +} + +/**/ + +static gchar * +get_tracker_volume_name (const gchar *uri, + const gchar *datasource) +{ + gchar *source_name = NULL; + GVolumeMonitor *volume_monitor; + GList *mounts, *mount; + GFile *file; + + if (uri != NULL && *uri != '\0') { + volume_monitor = g_volume_monitor_get (); + mounts = g_volume_monitor_get_mounts (volume_monitor); + file = g_file_new_for_uri (uri); + + mount = mounts; + while (mount != NULL) { + GFile *m_file = g_mount_get_root (G_MOUNT (mount->data)); + + if (g_file_equal (m_file, file)) { + gchar *m_name = g_mount_get_name (G_MOUNT (mount->data)); + g_object_unref (G_OBJECT (m_file)); + source_name = g_strdup_printf (_("Removable — %s"), m_name); + g_free (m_name); + break; + } + g_object_unref (G_OBJECT (m_file)); + + mount = mount->next; + } + g_list_free_full (mounts, g_object_unref); + g_object_unref (G_OBJECT (file)); + g_object_unref (G_OBJECT (volume_monitor)); + } else { + source_name = g_strdup (_("Local files")); + } + + return source_name; +} + +gchar * +grl_tracker_get_source_name (const gchar *rdf_type, + const gchar *uri, + const gchar *datasource, + const gchar *datasource_name) +{ + gchar *source_name = NULL; + gchar **rdf_single_type; + gint i; + + /* As rdf_type can be formed by several types, split them */ + rdf_single_type = g_strsplit (rdf_type, ",", -1); + i = g_strv_length (rdf_single_type) - 1; + + while (i >= 0) { + if (g_str_has_suffix (rdf_single_type[i], RDF_TYPE_VOLUME)) { + source_name = get_tracker_volume_name (uri, datasource); + break; + } + i--; + } + + g_strfreev (rdf_single_type); + + return source_name; +} + +const GList * +grl_tracker_supported_keys (GrlSource *source) +{ + static GList *supported_keys = NULL; + + if (!supported_keys) { + supported_keys = g_hash_table_get_keys (grl_to_sparql_mapping); + } + + return supported_keys; +} diff --git a/src/tracker3/grl-tracker-utils.h b/src/tracker3/grl-tracker-utils.h new file mode 100644 index 0000000..bf6c0ef --- /dev/null +++ b/src/tracker3/grl-tracker-utils.h @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2011 Igalia S.L. + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _GRL_TRACKER_UTILS_H_ +#define _GRL_TRACKER_UTILS_H_ + +#include "grl-tracker-source-priv.h" + +/* ------- Definitions ------- */ + +#define RDF_TYPE_ALBUM "nmm#MusicAlbum" +#define RDF_TYPE_ARTIST "nmm#Artist" +#define RDF_TYPE_AUDIO "nfo#Audio" +#define RDF_TYPE_MUSIC "nmm#MusicPiece" +#define RDF_TYPE_IMAGE "nmm#Photo" +#define RDF_TYPE_VIDEO "nmm#Video" +#define RDF_TYPE_FOLDER "nfo#Folder" +#define RDF_TYPE_DOCUMENT "nfo#Document" +#define RDF_TYPE_CONTAINER "grilo#Container" +#define RDF_TYPE_PLAYLIST "nmm#Playlist" + +#define RDF_TYPE_VOLUME "tracker#Volume" +#define RDF_TYPE_UPNP "upnp#ContentDirectory" + +/**/ + +typedef void (*tracker_grl_sparql_setter_cb_t) (TrackerSparqlCursor *cursor, + gint column, + GrlMedia *media, + GrlKeyID key); + +typedef struct { + GrlKeyID grl_key; + const gchar *sparql_key_name; + const gchar *sparql_key_name_canon; + const gchar *sparql_key_attr; + const gchar *sparql_key_attr_call; + const gchar *sparql_key_flavor; + + tracker_grl_sparql_setter_cb_t set_value; +} tracker_grl_sparql_t; + +extern GrlKeyID grl_metadata_key_tracker_urn; + +const GList *grl_tracker_supported_keys (GrlSource *source); + +gboolean grl_tracker_key_is_supported (const GrlKeyID key); + +void grl_tracker_setup_key_mappings (void); + +tracker_grl_sparql_t *grl_tracker_get_mapping_from_sparql (const gchar *key); + +GrlMedia *grl_tracker_build_grilo_media (const gchar *rdf_type, + GrlTypeFilter type_filter); + +gchar *grl_tracker_source_get_device_constraint (GrlTrackerSourcePriv *priv); + +gchar *grl_tracker_source_get_select_string (const GList *keys); + +gchar *grl_tracker_tracker_get_insert_string (GrlMedia *media, + const GList *keys); + +gchar *grl_tracker_get_delete_string (const GList *keys); + +gchar *grl_tracker_get_delete_conditional_string (const gchar *urn, + const GList *keys); + +gchar *grl_tracker_get_source_name (const gchar *rdf_type, + const gchar *uri, + const gchar *datasource, + const gchar *datasource_name); + +#endif /* _GRL_TRACKER_UTILS_H_ */ diff --git a/src/tracker3/grl-tracker.c b/src/tracker3/grl-tracker.c new file mode 100644 index 0000000..652e77b --- /dev/null +++ b/src/tracker3/grl-tracker.c @@ -0,0 +1,316 @@ +/* + * Copyright (C) 2011-2012 Igalia S.L. + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Juan A. Suarez Romero <jasuarez@igalia.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include <grilo.h> +#include <string.h> +#include <libtracker-sparql/tracker-sparql.h> + +#include "grl-tracker.h" +#include "grl-tracker-source.h" +#include "grl-tracker-source-api.h" +#include "grl-tracker-source-notif.h" +#include "grl-tracker-request-queue.h" +#include "grl-tracker-utils.h" + +/* --------- Logging -------- */ + +#define GRL_LOG_DOMAIN_DEFAULT tracker_general_log_domain +GRL_LOG_DOMAIN_STATIC(tracker_general_log_domain); + +/* ------- Definitions ------- */ + +#define TRACKER_FOLDER_CLASS_REQUEST \ + "SELECT ?urn WHERE " \ + "{ " \ + "?urn a rdfs:Class . " \ + "FILTER(fn:ends-with(?urn,\"nfo#Folder\")) " \ + "}" + +#define TRACKER_UPNP_CLASS_REQUEST \ + "SELECT ?urn WHERE " \ + "{ " \ + "?urn a rdfs:Class . " \ + "FILTER(fn:ends-with(?urn,\"upnp#UPnPDataObject\")) " \ + "}" + +#define TRACKER_NOTIFY_FOLDER_UPDATE \ + "INSERT " \ + "{ " \ + "<%s> tracker:notify true " \ + "}" + +/* --- Other --- */ + +gboolean grl_tracker3_plugin_init (GrlRegistry *registry, + GrlPlugin *plugin, + GList *configs); + +/* ===================== Globals ================= */ + +TrackerSparqlConnection *grl_tracker_connection = NULL; +GrlPlugin *grl_tracker_plugin; +GCancellable *grl_tracker_plugin_init_cancel = NULL; +gboolean grl_tracker_upnp_present = FALSE; +GrlTrackerQueue *grl_tracker_queue = NULL; + +/* tracker plugin config */ +gboolean grl_tracker_browse_filesystem = FALSE; +gboolean grl_tracker_show_documents = FALSE; + +/* =================== Tracker Plugin =============== */ + +static void +init_sources (void) +{ + grl_tracker_setup_key_mappings (); + + grl_tracker_queue = grl_tracker_queue_new (); + + if (grl_tracker_connection != NULL) { + grl_tracker_notify_init (grl_tracker_connection); + + grl_tracker_source_sources_init (); + } +} + +static void +tracker_update_folder_class_cb (GObject *object, + GAsyncResult *result, + gpointer data) +{ + init_sources (); +} + +static void +tracker_get_folder_class_cb (GObject *object, + GAsyncResult *result, + gpointer data) +{ + GError *error = NULL; + TrackerSparqlCursor *cursor; + + GRL_DEBUG ("%s", __FUNCTION__); + + cursor = tracker_sparql_connection_query_finish (grl_tracker_connection, + result, &error); + + if (error) { + GRL_INFO ("Could not execute sparql query for folder class: %s", + error->message); + g_error_free (error); + } + + if (!cursor) { + init_sources (); + return; + } + + if (tracker_sparql_cursor_next (cursor, NULL, NULL)) { + gchar *update = g_strdup_printf (TRACKER_NOTIFY_FOLDER_UPDATE, + tracker_sparql_cursor_get_string (cursor, + 0, + NULL)); + + GRL_DEBUG ("\tupdate query: '%s'", update); + + tracker_sparql_connection_update_async (grl_tracker_connection, + update, + G_PRIORITY_DEFAULT, + NULL, + tracker_update_folder_class_cb, + NULL); + + g_free (update); + } + + g_object_unref (cursor); +} + +static void +tracker_get_upnp_class_cb (GObject *object, + GAsyncResult *result, + gpointer data) +{ + GError *error = NULL; + TrackerSparqlCursor *cursor; + + GRL_DEBUG ("%s", G_STRFUNC); + + cursor = tracker_sparql_connection_query_finish (grl_tracker_connection, + result, &error); + if (error) { + GRL_INFO ("Could not execute sparql query for upnp class: %s", + error->message); + g_error_free (error); + } else { + if (tracker_sparql_cursor_next (cursor, NULL, NULL)) { + GRL_DEBUG ("\tUPnP ontology present"); + grl_tracker_upnp_present = TRUE; + } + } + + g_clear_object (&cursor); + + if (grl_tracker_browse_filesystem) + tracker_sparql_connection_query_async (grl_tracker_connection, + TRACKER_FOLDER_CLASS_REQUEST, + grl_tracker_plugin_init_cancel, + tracker_get_folder_class_cb, + NULL); + else + init_sources (); +} + +static void +tracker_new_connection_cb (GObject *object, + GAsyncResult *res, + GrlPlugin *plugin) +{ + GError *error = NULL; + + GRL_DEBUG ("%s", __FUNCTION__); + + grl_tracker_connection = tracker_sparql_connection_new_finish (res, &error); + + if (error) { + GRL_INFO ("Could not get connection to Tracker: %s", error->message); + g_error_free (error); + return; + } + + GRL_DEBUG ("\trequest : '%s'", TRACKER_UPNP_CLASS_REQUEST); + + tracker_sparql_connection_query_async (grl_tracker_connection, + TRACKER_UPNP_CLASS_REQUEST, + grl_tracker_plugin_init_cancel, + tracker_get_upnp_class_cb, + NULL); +} + +gboolean +grl_tracker3_plugin_init (GrlRegistry *registry, + GrlPlugin *plugin, + GList *configs) +{ + GrlConfig *config; + gint config_count; + GFile *ontology; + + GRL_LOG_DOMAIN_INIT (tracker_general_log_domain, "tracker3-general"); + + /* Initialize i18n */ + bindtextdomain (GETTEXT_PACKAGE, LOCALEDIR); + bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8"); + + grl_tracker_source_init_requests (); + + grl_tracker_plugin = plugin; + + if (!configs) { + GRL_INFO ("\tConfiguration not provided! Using default configuration."); + } else { + config_count = g_list_length (configs); + if (config_count > 1) { + GRL_INFO ("\tProvided %i configs, but will only use one", config_count); + } + + config = GRL_CONFIG (configs->data); + + grl_tracker_browse_filesystem = + grl_config_get_boolean (config, "browse-filesystem"); + grl_tracker_show_documents = + grl_config_get_boolean (config, "show-documents"); + } + + grl_tracker_plugin_init_cancel = g_cancellable_new (); + ontology = tracker_sparql_get_ontology_nepomuk (); + tracker_sparql_connection_new_async (TRACKER_SPARQL_CONNECTION_FLAGS_NONE, + NULL, + ontology, + grl_tracker_plugin_init_cancel, + (GAsyncReadyCallback) tracker_new_connection_cb, + plugin); + g_object_unref (ontology); + + return TRUE; +} + +static void +grl_tracker3_plugin_deinit (GrlPlugin *plugin) +{ + g_cancellable_cancel (grl_tracker_plugin_init_cancel); + g_clear_object (&grl_tracker_plugin_init_cancel); + g_clear_object (&grl_tracker_connection); +} + +static void +grl_tracker3_plugin_register_keys (GrlRegistry *registry, + GrlPlugin *plugin) +{ + grl_registry_register_metadata_key (grl_registry_get_default (), + g_param_spec_string ("tracker-category", + "Tracker category", + "Category a media belongs to", + NULL, + G_PARAM_STATIC_STRINGS | + G_PARAM_READWRITE), + GRL_METADATA_KEY_INVALID, + NULL); + grl_registry_register_metadata_key (grl_registry_get_default (), + g_param_spec_string ("gibest-hash", + "Gibest hash", + "Gibest hash of the video file", + NULL, + G_PARAM_STATIC_STRINGS | + G_PARAM_READWRITE), + GRL_METADATA_KEY_INVALID, + NULL); + grl_registry_register_metadata_key (grl_registry_get_default (), + g_param_spec_string ("tracker-urn", + "Tracker URN", + "Universal resource number in Tracker's store", + NULL, + G_PARAM_STATIC_STRINGS | + G_PARAM_READWRITE), + GRL_METADATA_KEY_INVALID, + NULL); +} + +GRL_PLUGIN_DEFINE (GRL_MAJOR, + GRL_MINOR, + GRL_TRACKER_PLUGIN_ID, + "Tracker3", + "A plugin for searching multimedia content using Tracker Miners 3.x", + "Igalia S.L.", + VERSION, + "LGPL", + "http://www.igalia.com", + grl_tracker3_plugin_init, + grl_tracker3_plugin_deinit, + grl_tracker3_plugin_register_keys); diff --git a/src/tracker3/grl-tracker.h b/src/tracker3/grl-tracker.h new file mode 100644 index 0000000..847ecd2 --- /dev/null +++ b/src/tracker3/grl-tracker.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2011 Intel Corporation. + * + * Contact: Iago Toral Quiroga <itoral@igalia.com> + * + * Authors: Lionel Landwerlin <lionel.g.landwerlin@linux.intel.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; version 2.1 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA + * 02110-1301 USA + * + */ + +#ifndef _GRL_TRACKER_H_ +#define _GRL_TRACKER_H_ + +#include "grl-tracker-request-queue.h" + +/* ---- Plugin information --- */ + +#define GRL_TRACKER_PLUGIN_ID TRACKER3_PLUGIN_ID + +extern GrlTrackerQueue *grl_tracker_queue; +extern TrackerSparqlConnection *grl_tracker_connection; + +#endif /* _GRL_TRACKER_H_ */ diff --git a/src/tracker3/meson.build b/src/tracker3/meson.build new file mode 100644 index 0000000..4ce507c --- /dev/null +++ b/src/tracker3/meson.build @@ -0,0 +1,37 @@ +# +# meson.build +# +# Author: Juan A. Suarez Romero <jasuarez@igalia.com> +# +# Copyright (C) 2016 Igalia S.L. All rights reserved. + +tracker_sources = [ + 'grl-tracker-request-queue.c', + 'grl-tracker-request-queue.h', + 'grl-tracker-source-api.c', + 'grl-tracker-source-api.h', + 'grl-tracker-source-cache.c', + 'grl-tracker-source-cache.h', + 'grl-tracker-source-notif.c', + 'grl-tracker-source-notif.h', + 'grl-tracker-source-priv.h', + 'grl-tracker-source.c', + 'grl-tracker-source.h', + 'grl-tracker-utils.c', + 'grl-tracker-utils.h', + 'grl-tracker.c', + 'grl-tracker.h', +] + +configure_file(output: 'config.h', + configuration: cdata) + +shared_library('grltracker3', + sources: tracker_sources, + install: true, + install_dir: pluginsdir, + dependencies: must_deps + plugins[tracker3_idx][REQ_DEPS] + plugins[tracker3_idx][OPT_DEPS], + c_args: [ + '-DG_LOG_DOMAIN="GrlTracker3"', + '-DHAVE_CONFIG_H', + ]) |