diff options
author | Sam Thursfield <sam@afuera.me.uk> | 2020-05-03 10:38:15 +0200 |
---|---|---|
committer | Sam Thursfield <sam@afuera.me.uk> | 2020-05-11 22:23:44 +0200 |
commit | cc79656af8de2136a9613a0e34db241c05df057c (patch) | |
tree | 674c8d33757c8d2b93fca060bb4661d1ffc02f63 | |
parent | fedff503b57cec716d60c09e6d32737019f0a156 (diff) | |
download | nautilus-cc79656af8de2136a9613a0e34db241c05df057c.tar.gz |
tag-manager: Store starred files in a private database
Until now, starred file information was stored in the tracker-miner-fs
database. This has some downsides, firstly the data is deleted if
someone runs `tracker reset --hard`, secondly it isn't possible to
do this from inside a Flatpak sandbox with Tracker 3.0.
This commit changes the NautilusTagManager to set up a private
database inside XDG_DATA_HOME/nautilus/tags. This stores the starred
file information. The database is managed with Tracker, which allows us
to continue using the rename-tracking that tracker-miner-fs provides.
The same limitations apply as before that only files in indexed
locations can be starred.
-rw-r--r-- | data/meson.build | 2 | ||||
-rw-r--r-- | data/ontology/meson.build | 8 | ||||
-rw-r--r-- | data/ontology/nautilus.description | 9 | ||||
-rw-r--r-- | data/ontology/nautilus.ontology | 14 | ||||
-rw-r--r-- | src/nautilus-tag-manager.c | 249 | ||||
-rw-r--r-- | test/automated/displayless/meson.build | 7 | ||||
-rw-r--r-- | test/meson.build | 1 |
7 files changed, 197 insertions, 93 deletions
diff --git a/data/meson.build b/data/meson.build index 7218b84e0..e51ed50b1 100644 --- a/data/meson.build +++ b/data/meson.build @@ -137,3 +137,5 @@ if appstream_util.found() ] ) endif + +subdir('ontology') diff --git a/data/ontology/meson.build b/data/ontology/meson.build new file mode 100644 index 000000000..cb80e86a1 --- /dev/null +++ b/data/ontology/meson.build @@ -0,0 +1,8 @@ +ontology_data = files( + 'nautilus.description', + 'nautilus.ontology', +) + +install_data(ontology_data, + install_dir: join_paths(datadir, 'nautilus', 'ontology') +) diff --git a/data/ontology/nautilus.description b/data/ontology/nautilus.description new file mode 100644 index 000000000..42f4102c3 --- /dev/null +++ b/data/ontology/nautilus.description @@ -0,0 +1,9 @@ +@prefix dsc: <http://tracker.api.gnome.org/ontology/v3/dsc#> . + +<virtual-ontology-uri:nautilus.ontology> a dsc:Ontology ; + dsc:title "Nautilus ontology" ; + dsc:description "Tracker database schema for Nautilus private data." ; + + dsc:localPrefix "nautilus" ; + dsc:baseUrl "https://gitlab.gnome.org/GNOME/nautilus#" ; + dsc:relativePath "./nautilus.ontology" ; diff --git a/data/ontology/nautilus.ontology b/data/ontology/nautilus.ontology new file mode 100644 index 000000000..2a677da13 --- /dev/null +++ b/data/ontology/nautilus.ontology @@ -0,0 +1,14 @@ +@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . +@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . +@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . +@prefix nautilus: <https://gitlab.gnome.org/GNOME/nautilus#> . +@prefix tracker: <http://tracker.api.gnome.org/ontology/v3/tracker#> . + +nautilus: a tracker:Namespace, tracker:Ontology ; + tracker:prefix "nautilus" ; + tracker:lastModified "2020-05-03T10:00:00Z" . + +nautilus:starred a rdf:Property ; + rdfs:comment "Marks resources that are starred in Nautilus." ; + rdfs:domain rdfs:Resource ; + rdfs:range xsd:boolean . diff --git a/src/nautilus-tag-manager.c b/src/nautilus-tag-manager.c index 8b5ad22ab..70cb37027 100644 --- a/src/nautilus-tag-manager.c +++ b/src/nautilus-tag-manager.c @@ -1,6 +1,7 @@ /* nautilus-tag-manager.c * * Copyright (C) 2017 Alexandru Pandelea <alexandru.pandelea@gmail.com> + * Copyright (C) 2020 Sam Thursfield <sam@afuera.me.uk> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,14 +21,20 @@ #include "nautilus-file.h" #include "nautilus-file-undo-operations.h" #include "nautilus-file-undo-manager.h" +#define DEBUG_FLAG NAUTILUS_DEBUG_TAG_MANAGER +#include "nautilus-debug.h" #include <tracker-sparql.h> +#include "config.h" + struct _NautilusTagManager { GObject object; - TrackerSparqlConnection *connection; + gboolean tracker_ok; + TrackerSparqlConnection *local; + TrackerSparqlConnection *miner_fs; TrackerNotifier *notifier; TrackerSparqlStatement *query_starred_files; @@ -76,21 +83,24 @@ enum #define TRACKER_MINER_FS_BUSNAME "org.freedesktop.Tracker3.Miner.Files" -#define STARRED_TAG "<urn:gnome:nautilus:starred>" - #define QUERY_STARRED_FILES \ - "SELECT ?url tracker:id(?urn) " \ - "{ " \ - " ?urn nie:url ?url ; " \ - " nao:hasTag " STARRED_TAG \ - "}" + "SELECT ?file_url ?content_id " \ + "{ " \ + " ?content_urn nautilus:starred true . " \ + " SERVICE <dbus:" TRACKER_MINER_FS_BUSNAME "> { " \ + " ?content_urn nie:isStoredAs ?file_url . " \ + " BIND (tracker:id (?content_urn) AS ?content_id) " \ + " } " \ + "}" #define QUERY_UPDATED_FILE_URL \ - "SELECT ?url EXISTS { ?urn nao:hasTag " STARRED_TAG "} AS ?starred" \ - "{ " \ - " ?urn nie:url ?url . " \ - " FILTER (tracker:id(?urn) = ~id) " \ - "}" + "SELECT ?file_url EXISTS { ?content_urn nautilus:starred true } AS ?starred" \ + "{ " \ + " SERVICE <dbus:" TRACKER_MINER_FS_BUSNAME "> { " \ + " ?content_urn nie:isStoredAs ?file_url . " \ + " FILTER (tracker:id(?content_urn) = ~id) " \ + " }" \ + "}" static guint signals[LAST_SIGNAL]; @@ -113,7 +123,7 @@ add_selection_filter (GList *selection, NautilusFile *file; GList *l; - g_string_append (query, " FILTER(?url IN ("); + g_string_append (query, " FILTER(?file_url IN ("); for (l = selection; l != NULL; l = l->next) { @@ -123,8 +133,7 @@ add_selection_filter (GList *selection, file = l->data; uri = nautilus_file_get_uri (file); - escaped_uri = tracker_sparql_escape_string (uri); - g_string_append_printf (query, "'%s'", escaped_uri); + g_string_append_printf (query, "<%s>", uri); if (l->next != NULL) { @@ -138,7 +147,7 @@ add_selection_filter (GList *selection, } static void -start_query_or_update (TrackerSparqlConnection *connection, +start_query_or_update (TrackerSparqlConnection *local, GString *query, GAsyncReadyCallback callback, gpointer user_data, @@ -147,7 +156,7 @@ start_query_or_update (TrackerSparqlConnection *connection, { g_autoptr (GError) error = NULL; - if (!connection) + if (!local) { g_message ("nautilus-tag-manager: No Tracker connection"); return; @@ -155,7 +164,7 @@ start_query_or_update (TrackerSparqlConnection *connection, if (is_query) { - tracker_sparql_connection_query_async (connection, + tracker_sparql_connection_query_async (local, query->str, cancellable, callback, @@ -163,7 +172,7 @@ start_query_or_update (TrackerSparqlConnection *connection, } else { - tracker_sparql_connection_update_async (connection, + tracker_sparql_connection_update_async (local, query->str, G_PRIORITY_DEFAULT, cancellable, @@ -177,7 +186,7 @@ on_update_callback (GObject *object, GAsyncResult *result, gpointer user_data) { - TrackerSparqlConnection *connection; + TrackerSparqlConnection *local; GError *error; UpdateData *data; gint64 *new_id; @@ -188,9 +197,9 @@ on_update_callback (GObject *object, error = NULL; - connection = TRACKER_SPARQL_CONNECTION (object); + local = TRACKER_SPARQL_CONNECTION (object); - tracker_sparql_connection_update_finish (connection, result, &error); + tracker_sparql_connection_update_finish (local, result, &error); if (error == NULL || (error != NULL && error->code == G_IO_ERROR_CANCELLED)) @@ -213,11 +222,16 @@ on_update_callback (GObject *object, new_id, nautilus_file_get_uri (NAUTILUS_FILE (l->data))); } + else + { + g_message ("Ignoring star request for %s as we didn't get the resource ID from Tracker.", uri); + } } else { new_id = g_hash_table_lookup (data->tag_manager->starred_file_uris, uri); - if (new_id) { + if (new_id) + { g_hash_table_remove (data->tag_manager->starred_file_uris, uri); g_hash_table_remove (data->tag_manager->starred_file_ids, new_id); } @@ -247,9 +261,9 @@ on_update_callback (GObject *object, } else { + g_warning ("error updating tags: %s", error->message); g_task_return_error (data->task, error); g_object_unref (data->task); - g_warning ("error updating tags: %s", error->message); } if (data->ids) @@ -390,7 +404,7 @@ static void nautilus_tag_manager_query_starred_files (NautilusTagManager *self, GCancellable *cancellable) { - if (!self->connection) + if (!self->tracker_ok) { g_message ("nautilus-tag-manager: No Tracker connection"); return; @@ -420,12 +434,14 @@ nautilus_tag_manager_delete_tag (NautilusTagManager *self, GString *query) { g_string_append (query, - "DELETE { ?urn nao:hasTag " STARRED_TAG " }" - "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url ."); + "DELETE { ?content_urn nautilus:starred true } " + "WHERE { " + " SERVICE <dbus:" TRACKER_MINER_FS_BUSNAME "> { " + " ?content_urn nie:isStoredAs ?file_url . "); query = add_selection_filter (selection, query); - g_string_append (query, "}\n"); + g_string_append (query, " } }\n"); return query; } @@ -436,13 +452,16 @@ nautilus_tag_manager_insert_tag (NautilusTagManager *self, GString *query) { g_string_append (query, - "INSERT DATA { " STARRED_TAG " a nao:Tag .}\n" - "INSERT { ?urn nao:hasTag " STARRED_TAG " }" - "WHERE { ?urn a nfo:FileDataObject ; nie:url ?url ."); + "INSERT { " + " ?content_urn a rdfs:Resource . " + " ?content_urn nautilus:starred true . " + "} WHERE { " + " SERVICE <dbus:" TRACKER_MINER_FS_BUSNAME "> { " + " ?content_urn nie:isStoredAs ?file_url . "); query = add_selection_filter (selection, query); - g_string_append (query, "}\n"); + g_string_append (query, "} }\n"); return query; } @@ -516,15 +535,13 @@ on_get_file_ids_for_urls_query_callback (GObject *object, { TrackerSparqlCursor *cursor; g_autoptr (GError) error = NULL; - TrackerSparqlConnection *connection; + TrackerSparqlConnection *local; GTask *task; - connection = TRACKER_SPARQL_CONNECTION (object); + local = TRACKER_SPARQL_CONNECTION (object); task = user_data; - cursor = tracker_sparql_connection_query_finish (connection, - result, - &error); + cursor = tracker_sparql_connection_query_finish (local, result, &error); if (error != NULL) { @@ -539,7 +556,7 @@ on_get_file_ids_for_urls_query_callback (GObject *object, { tracker_sparql_cursor_next_async (cursor, g_task_get_cancellable (task), - on_get_file_ids_for_urls_query_callback, + on_get_file_ids_for_urls_cursor_callback, user_data); } } @@ -551,13 +568,17 @@ nautilus_tag_manager_get_file_ids_for_urls (NautilusTagManager *self, { GString *query; - query = g_string_new ("SELECT ?url tracker:id(?urn) WHERE { ?urn nie:url ?url; ."); + query = g_string_new ("SELECT ?file_url ?content_id " + "WHERE { " + " SERVICE <dbus:" TRACKER_MINER_FS_BUSNAME "> { " + " ?content_urn nie:isStoredAs ?file_url . " + " BIND (tracker:id (?content_urn) AS ?content_id) "); query = add_selection_filter (selection, query); - g_string_append (query, "}\n"); + g_string_append (query, "} }\n"); - start_query_or_update (self->connection, + start_query_or_update (self->local, query, on_get_file_ids_for_urls_query_callback, task, @@ -603,7 +624,7 @@ on_star_files_callback (GObject *object, */ destroy_insert_task_data (data); - start_query_or_update (self->connection, + start_query_or_update (self->local, query, on_update_callback, update_data, @@ -633,6 +654,8 @@ nautilus_tag_manager_star_files (NautilusTagManager *self, data->object = object; data->cancellable = cancellable; + DEBUG ("Starring %i files", g_list_length (selection)); + task = g_task_new (self, cancellable, on_star_files_callback, NULL); g_task_set_task_data (task, data, @@ -652,6 +675,8 @@ nautilus_tag_manager_unstar_files (NautilusTagManager *self, GTask *task; UpdateData *update_data; + DEBUG ("Unstarring %i files", g_list_length (selection)); + task = g_task_new (object, cancellable, callback, NULL); query = g_string_new (""); @@ -666,7 +691,7 @@ nautilus_tag_manager_unstar_files (NautilusTagManager *self, update_data->selection = nautilus_file_list_copy (selection); update_data->star = FALSE; - start_query_or_update (self->connection, + start_query_or_update (self->local, query, on_update_callback, update_data, @@ -686,11 +711,11 @@ on_tracker_notifier_events (TrackerNotifier *notifier, TrackerNotifierEvent *event; NautilusTagManager *self; int i; - const gchar *location_uri; - const gchar *new_location_uri; + const gchar *file_url; + const gchar *new_file_url; GError *error = NULL; TrackerSparqlCursor *cursor; - gboolean query_has_results; + gboolean query_has_results = FALSE; gboolean is_starred; gint64 id, *new_id; GList *changed_files; @@ -702,79 +727,82 @@ on_tracker_notifier_events (TrackerNotifier *notifier, { event = g_ptr_array_index (events, i); + id = tracker_notifier_event_get_id (event); + file_url = g_hash_table_lookup (self->starred_file_ids, &id); + changed_file = NULL; + + DEBUG ("Got event for tracker resource id %li", id); + tracker_sparql_statement_bind_int (self->query_updated_file_url, "id", tracker_notifier_event_get_id (event)); cursor = tracker_sparql_statement_execute (self->query_updated_file_url, NULL, &error); - if (error || !cursor) + if (cursor) { - g_printerr ("Couldn't query the Tracker Store: '%s'", error->message); + query_has_results = tracker_sparql_cursor_next (cursor, NULL, &error); + } + if (error || !cursor) + { + g_warning ("Couldn't query the Tracker Store: '%s'", error ? error->message : "(null error)"); g_clear_error (&error); - return; } - changed_file = NULL; - - id = tracker_notifier_event_get_id (event); - location_uri = g_hash_table_lookup (self->starred_file_ids, &id); - - query_has_results = tracker_sparql_cursor_next (cursor, NULL, &error); - if (!query_has_results) { if (g_hash_table_contains (self->starred_file_ids, &id)) { - /* The file must have been deleted from the Tracker index. */ - location_uri = g_hash_table_lookup (self->starred_file_ids, &id); + /* The file was deleted from the filesystem or is no longer indexed by Tracker. */ + file_url = g_hash_table_lookup (self->starred_file_ids, &id); + DEBUG ("Removing %s from starred files list, as id %li no longer present in Tracker index. ", file_url, id); - changed_file = nautilus_file_get_by_uri (location_uri); + changed_file = nautilus_file_get_by_uri (file_url); g_hash_table_remove (self->starred_file_ids, &id); - g_hash_table_remove (self->starred_file_uris, location_uri); + g_hash_table_remove (self->starred_file_uris, file_url); } } else { - new_location_uri = tracker_sparql_cursor_get_string (cursor, 0, NULL); + new_file_url = tracker_sparql_cursor_get_string (cursor, 0, NULL); is_starred = tracker_sparql_cursor_get_boolean (cursor, 1); if (g_hash_table_contains (self->starred_file_ids, &id)) { - if (is_starred && strcmp (location_uri, new_location_uri) != 0) + if (is_starred && strcmp (file_url, new_file_url) != 0) { new_id = g_new0 (gint64, 1); *new_id = id; - /* File URI changed. */ - g_hash_table_remove (self->starred_file_uris, location_uri); + DEBUG ("Starred file changed URI from %s to %s.", file_url, new_file_url); + g_hash_table_remove (self->starred_file_uris, file_url); - g_hash_table_insert (self->starred_file_ids, &id, g_strdup (new_location_uri)); - g_hash_table_insert (self->starred_file_uris, g_strdup (new_location_uri), new_id); + g_hash_table_insert (self->starred_file_ids, new_id, g_strdup (new_file_url)); + g_hash_table_insert (self->starred_file_uris, g_strdup (new_file_url), new_id); - changed_file = nautilus_file_get_by_uri (new_location_uri); + changed_file = nautilus_file_get_by_uri (new_file_url); } else if (!is_starred) { - /* File is no longer starred */ - g_hash_table_remove (self->starred_file_uris, location_uri); + DEBUG ("File is no longer starred: %s", file_url); + g_hash_table_remove (self->starred_file_uris, file_url); g_hash_table_remove (self->starred_file_ids, &id); - changed_file = nautilus_file_get_by_uri (new_location_uri); + changed_file = nautilus_file_get_by_uri (new_file_url); } } - else + else if (is_starred) { - /* File is newly starred */ + DEBUG ("File is now starred: %s", new_file_url); new_id = g_new0 (gint64, 1); *new_id = id; - g_hash_table_insert (self->starred_file_ids, &id, g_strdup (new_location_uri)); - g_hash_table_insert (self->starred_file_uris, g_strdup (new_location_uri), new_id); + g_hash_table_insert (self->starred_file_ids, new_id, g_strdup (new_file_url)); + g_hash_table_insert (self->starred_file_uris, g_strdup (new_file_url), new_id); - changed_file = nautilus_file_get_by_uri (new_location_uri); + changed_file = nautilus_file_get_by_uri (new_file_url); } } @@ -806,8 +834,9 @@ nautilus_tag_manager_finalize (GObject *object) } g_clear_object (&self->notifier); - g_clear_object (&self->connection); - g_clear_object (&self->query_has_results); + g_clear_object (&self->local); + g_clear_object (&self->miner_fs); + g_clear_object (&self->query_updated_file_url); g_clear_object (&self->query_starred_files); g_hash_table_destroy (self->starred_file_ids); @@ -854,21 +883,57 @@ nautilus_tag_manager_get (void) } static gboolean -setup_tracker_connection (NautilusTagManager *self, - GCancellable *cancellable, - GError **error) +setup_tracker_connections (NautilusTagManager *self, + GCancellable *cancellable, + GError **error) { - self->connection = tracker_sparql_connection_bus_new (TRACKER_MINER_FS_BUSNAME, - NULL, - NULL, - error); + const gchar *datadir; + g_autofree gchar *store_path = NULL; + g_autofree gchar *ontology_path = NULL; + g_autoptr (GFile) store = NULL; + g_autoptr (GFile) ontology = NULL; + + /* Connect to private database to store nautilus:starred property. */ + + if (g_getenv ("NAUTILUS_TEST_DATADIR")) + { + datadir = g_getenv ("NAUTILUS_TEST_DATADIR"); + } + else + { + datadir = NAUTILUS_DATADIR; + } + + store_path = g_build_filename (g_get_user_data_dir (), "nautilus", "tags", NULL); + ontology_path = g_build_filename (datadir, "ontology", NULL); + + store = g_file_new_for_path (store_path); + ontology = g_file_new_for_path (ontology_path); + + self->local = tracker_sparql_connection_new (TRACKER_SPARQL_CONNECTION_FLAGS_NONE, + store, + ontology, + cancellable, + error); if (*error) { return FALSE; } - self->query_updated_file_url = tracker_sparql_connection_query_statement (self->connection, + /* Connect to Tracker filesystem index to follow file renames. */ + self->miner_fs = tracker_sparql_connection_bus_new (TRACKER_MINER_FS_BUSNAME, + NULL, + NULL, + error); + + if (*error) + { + return FALSE; + } + + /* Prepare reusable queries. */ + self->query_updated_file_url = tracker_sparql_connection_query_statement (self->local, QUERY_UPDATED_FILE_URL, cancellable, error); @@ -878,7 +943,7 @@ setup_tracker_connection (NautilusTagManager *self, return FALSE; } - self->query_starred_files = tracker_sparql_connection_query_statement (self->connection, + self->query_starred_files = tracker_sparql_connection_query_statement (self->local, QUERY_STARRED_FILES, cancellable, error); @@ -898,17 +963,19 @@ nautilus_tag_manager_set_cancellable (NautilusTagManager *self, { g_autoptr (GError) error = NULL; - setup_tracker_connection (self, cancellable, &error); + self->tracker_ok = setup_tracker_connections (self, cancellable, &error); - if (error) { + if (error) + { g_warning ("Unable to initialize tag manager: %s", error->message); return; } + self->notifier = tracker_sparql_connection_create_notifier (self->miner_fs, + TRACKER_NOTIFIER_FLAG_NONE); + nautilus_tag_manager_query_starred_files (self, cancellable); - self->notifier = tracker_sparql_connection_create_notifier (self->connection, - TRACKER_NOTIFIER_FLAG_NONE); g_signal_connect (self->notifier, "events", G_CALLBACK (on_tracker_notifier_events), diff --git a/test/automated/displayless/meson.build b/test/automated/displayless/meson.build index fccc61b30..64b23bd2a 100644 --- a/test/automated/displayless/meson.build +++ b/test/automated/displayless/meson.build @@ -37,7 +37,7 @@ tests = [ tracker_tests = [ ['test-nautilus-search-engine-tracker', [ - 'test-nautilus-search-engine-tracker.c' + 'test-nautilus-search-engine-tracker.c', ]], ] @@ -54,13 +54,16 @@ foreach t: tests ) endforeach + + # Tests that read and write from the Tracker index are run using 'tracker-sandbox' # script to use a temporary instance of tracker-miner-fs instead of the session one. foreach t: tracker_tests + test_exe = executable(t[0], t[1], files('test-utilities.c'), dependencies: libnautilus_dep) test( t[0], tracker_sandbox, - args: [executable(t[0], t[1], files('test-utilities.c'), dependencies: libnautilus_dep)], + args: ['--store-tmpdir', '--index-recursive-tmpdir', test_exe], env: [ test_env, 'G_TEST_BUILDDIR=@0@'.format(meson.current_build_dir()), diff --git a/test/meson.build b/test/meson.build index ab588efd3..36e5aca0a 100644 --- a/test/meson.build +++ b/test/meson.build @@ -15,6 +15,7 @@ # the test sources are scattered. test_env = [ 'GSETTINGS_SCHEMA_DIR=@0@'.format(join_paths(meson.build_root(), 'data')), + 'NAUTILUS_TEST_DATADIR=@0@'.format(join_paths(meson.source_root(), 'data')), 'RUNNING_TESTS=@0@'.format('TRUE') ] |