[tracker/wip/sam/private-store: 3/3] libtracker-sparql: Add public API to open a private Tracker store



commit 94d8449b1c36a445047015de4997371803c39e44
Author: Sam Thursfield <sam afuera me uk>
Date:   Fri Jun 24 00:04:47 2016 +0100

    libtracker-sparql: Add public API to open a private Tracker store

 src/libtracker-direct/tracker-direct.vala          |   14 +++-
 src/libtracker-sparql-backend/tracker-backend.vala |   61 ++++++++++-----
 src/libtracker-sparql/tracker-connection.vala      |    4 +-
 src/libtracker-sparql/tracker-private-store.vala   |   80 ++++++++++++++++++++
 tests/libtracker-sparql/tracker-sparql-test.c      |   65 ++++++++++++++++-
 5 files changed, 199 insertions(+), 25 deletions(-)
---
diff --git a/src/libtracker-direct/tracker-direct.vala b/src/libtracker-direct/tracker-direct.vala
index 1cacf09..3f282b4 100644
--- a/src/libtracker-direct/tracker-direct.vala
+++ b/src/libtracker-direct/tracker-direct.vala
@@ -21,7 +21,12 @@ public class Tracker.Direct.Connection : Tracker.Sparql.Connection {
        static int use_count;
        bool initialized;
 
-       public Connection () throws Sparql.Error, IOError, DBusError {
+       public enum Flags {
+               NONE=0,
+               READONLY=1,
+       }
+
+       public Connection (string? store_path, string[]? ontologies, Flags flags) throws Sparql.Error, 
IOError, DBusError {
                DBManager.lock ();
 
                try {
@@ -40,7 +45,12 @@ public class Tracker.Direct.Connection : Tracker.Sparql.Connection {
                                        select_cache_size = int.parse (env_cache_size);
                                }
 
-                               Data.Manager.init (DBManagerFlags.READONLY, null, null, false, false, 
select_cache_size, 0, null, null);
+                               DBManagerFlags db_manager_flags = 0;
+                               if ((flags & Flags.READONLY) == Flags.READONLY) {
+                                       db_manager_flags |= DBManagerFlags.READONLY;
+                               }
+
+                               Data.Manager.init (store_path, ontologies, db_manager_flags, null, false, 
false, select_cache_size, 0, null, null);
                        }
 
                        use_count++;
diff --git a/src/libtracker-sparql-backend/tracker-backend.vala 
b/src/libtracker-sparql-backend/tracker-backend.vala
index 6f1219d..d7f5a9f 100644
--- a/src/libtracker-sparql-backend/tracker-backend.vala
+++ b/src/libtracker-sparql-backend/tracker-backend.vala
@@ -17,7 +17,7 @@
  * Boston, MA  02110-1301, USA.
  */
 
-static size_t log_initialized = false;
+static size_t log_initialized = 0;
 
 static void log_init () {
        if (GLib.Once.init_enter(&log_initialized)) {
@@ -63,7 +63,7 @@ static void log_init () {
                        GLib.Log.set_handler ("Tracker", remove_levels, remove_log_handler);
                }
 
-               GLib.Once.init_leave (&log_initialized, true);
+               GLib.Once.init_leave (&log_initialized, 1);
        }
 }
 
@@ -71,18 +71,22 @@ static void remove_log_handler (string? log_domain, LogLevelFlags log_level, str
        /* do nothing */
 }
 
-class Tracker.Sparql.Backend : Connection {
+/* The session-wide backend does reads in-process, but sends writes over D-Bus
+ * to the session-wide tracker-store daemon. This is the most efficient way to
+ * provide multiple process with read+write access to the same SQLite database.
+ */
+class Tracker.Sparql.SessionWideBackend : Connection {
        bool initialized;
        Tracker.Sparql.Connection direct = null;
        Tracker.Sparql.Connection bus = null;
-       enum Backend {
+       enum BackendType {
                AUTO,
                DIRECT,
                BUS
        }
        GLib.BusType bus_type = BusType.SESSION;
 
-       public Backend () throws Sparql.Error, IOError, DBusError, SpawnError {
+       public SessionWideBackend () throws Sparql.Error, IOError, DBusError, SpawnError {
                try {
                        // Important to make sure we check the right bus for the store
                        load_env ();
@@ -239,28 +243,28 @@ class Tracker.Sparql.Backend : Connection {
        // Plugin loading functions
        private void load_plugins () throws GLib.Error {
                string env_backend = Environment.get_variable ("TRACKER_SPARQL_BACKEND");
-               Backend backend = Backend.AUTO;
+               BackendType backend_type = BackendType.AUTO;
 
                if (env_backend != null) {
                        if (env_backend.ascii_casecmp ("direct") == 0) {
-                               backend = Backend.DIRECT;
+                               backend_type = BackendType.DIRECT;
                                debug ("Using backend = 'DIRECT'");
                        } else if (env_backend.ascii_casecmp ("bus") == 0) {
-                               backend = Backend.BUS;
+                               backend_type = BackendType.BUS;
                                debug ("Using backend = 'BUS'");
                        } else {
                                warning ("Environment variable TRACKER_SPARQL_BACKEND set to unknown value 
'%s'", env_backend);
                        }
                }
 
-               if (backend == Backend.AUTO) {
+               if (backend_type == BackendType.AUTO) {
                        debug ("Using backend = 'AUTO'");
                }
 
-               switch (backend) {
-               case Backend.AUTO:
+               switch (backend_type) {
+               case BackendType.AUTO:
                        try {
-                               direct = new Tracker.Direct.Connection ();
+                               direct = new Tracker.Direct.Connection (null, null, 
Tracker.Direct.Connection.Flags.READONLY);
                        } catch (Error e) {
                                debug ("Unable to initialize direct backend: " + e.message);
                        }
@@ -268,11 +272,11 @@ class Tracker.Sparql.Backend : Connection {
                        bus = new Tracker.Bus.Connection ();
                        break;
 
-               case Backend.DIRECT:
-                       direct = new Tracker.Direct.Connection ();
+               case BackendType.DIRECT:
+                       direct = new Tracker.Direct.Connection (null, null, 
Tracker.Direct.Connection.Flags.READONLY);
                        break;
 
-               case Backend.BUS:
+               case BackendType.BUS:
                        bus = new Tracker.Bus.Connection ();
                        break;
 
@@ -294,7 +298,7 @@ class Tracker.Sparql.Backend : Connection {
                        if (result == null) {
                                log_init ();
 
-                               result = new Tracker.Sparql.Backend ();
+                               result = new Tracker.Sparql.SessionWideBackend ();
 
                                if (cancellable != null && cancellable.is_cancelled ()) {
                                        throw new IOError.CANCELLED ("Operation was cancelled");
@@ -394,17 +398,34 @@ class Tracker.Sparql.Backend : Connection {
 }
 
 public async static Tracker.Sparql.Connection tracker_sparql_connection_get_async (Cancellable? cancellable 
= null) throws Tracker.Sparql.Error, IOError, DBusError, SpawnError {
-       return yield Tracker.Sparql.Backend.get_internal_async (cancellable);
+       return yield Tracker.Sparql.SessionWideBackend.get_internal_async (cancellable);
 }
 
 public static Tracker.Sparql.Connection tracker_sparql_connection_get (Cancellable? cancellable = null) 
throws Tracker.Sparql.Error, IOError, DBusError, SpawnError {
-       return Tracker.Sparql.Backend.get_internal (cancellable);
+       return Tracker.Sparql.SessionWideBackend.get_internal (cancellable);
 }
 
 public async static Tracker.Sparql.Connection tracker_sparql_connection_get_direct_async (Cancellable? 
cancellable = null) throws Tracker.Sparql.Error, IOError, DBusError, SpawnError {
-       return yield Tracker.Sparql.Backend.get_internal_async (cancellable);
+       return yield Tracker.Sparql.SessionWideBackend.get_internal_async (cancellable);
 }
 
 public static Tracker.Sparql.Connection tracker_sparql_connection_get_direct (Cancellable? cancellable = 
null) throws Tracker.Sparql.Error, IOError, DBusError, SpawnError {
-       return Tracker.Sparql.Backend.get_internal (cancellable);
+       return Tracker.Sparql.SessionWideBackend.get_internal (cancellable);
+}
+
+/* The private backend does both reads and writes in-process. This makes sense
+ * if only one process will be reading and writing. Performance will suffer if
+ * multiple processes are trying to write to the same SQLite database
+ * simultaneously.
+ */
+class Tracker.Sparql.PrivateBackend : Tracker.Direct.Connection {
+       public PrivateBackend (string store_path, string[]? ontologies) throws Sparql.Error, IOError {
+               log_init ();
+
+               base (store_path, ontologies, Flags.NONE);
+       }
+}
+
+public static Tracker.Sparql.Connection tracker_sparql_private_store_open (string store_path, string[]? 
ontologies) throws Tracker.Sparql.Error, IOError {
+    return new Tracker.Sparql.PrivateBackend (store_path, ontologies);
 }
diff --git a/src/libtracker-sparql/tracker-connection.vala b/src/libtracker-sparql/tracker-connection.vala
index 1e1749e..681a4a5 100644
--- a/src/libtracker-sparql/tracker-connection.vala
+++ b/src/libtracker-sparql/tracker-connection.vala
@@ -19,14 +19,14 @@
 
 /**
  * SECTION: tracker-sparql-connection
- * @short_description: Connecting to the Store
+ * @short_description: Connecting to the user's session-wide Store
  * @title: TrackerSparqlConnection
  * @stability: Stable
  * @include: tracker-sparql.h
  *
  * <para>
  * #TrackerSparqlConnection is an object which sets up connections to the
- * Tracker Store.
+ * user's session-wide Tracker Store.
  * </para>
  */
 
diff --git a/src/libtracker-sparql/tracker-private-store.vala 
b/src/libtracker-sparql/tracker-private-store.vala
new file mode 100644
index 0000000..7eade57
--- /dev/null
+++ b/src/libtracker-sparql/tracker-private-store.vala
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016, Sam Thursfield <sam afuera me uk>
+ *
+ * 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; either
+ * 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 Street, Fifth Floor,
+ * Boston, MA  02110-1301, USA.
+ */
+
+/**
+ * SECTION: tracker-sparql-private-store
+ * @short_description: Creating a private Store.
+ * @title: TrackerSparqlPrivateStore
+ * @stability: Stable
+ * @include: tracker-sparql.h
+ *
+ * <para>
+ * Use a private Tracker Store if your application wants to keep data
+ * separate from the user's session-wide Tracker Store.
+ *
+ * In general, we encourage storing data in the session-wide Tracker Store,
+ * so that other applications can make use of it. Use
+ * tracker_sparql_connection_get() to access the user's session-wide Store.
+ * There are some reasons why you might want to avoid the user's session-wide
+ * store, and use a private store instead.
+ *
+ * If you are writing automated tests, use a private store to ensure the tests
+ * can't interfere with the user's real data.
+ *
+ * If you want to use a different set of ontologies to those that ship
+ * by default with Tracker, you'll need to use a private store. It's very hard
+ * for the session-wide store to deal with changes to the ontologies, so we
+ * discourage installing new or changed ontologies for the system-wide Tracker
+ * instance. Using a private store works around this limitation.
+ *
+ * With a private Tracker store, all access to the database happens in-process.
+ * If multiple processes are writing to the same private Tracker store, you
+ * might performance issues. This is because SQLite needs to lock the entire
+ * database in order to do a write.
+ */
+
+/**
+ * TrackerSparqlPrivateStore:
+ *
+ * The <structname>TrackerSparqlPrivateStore</structname> object represents a
+ * connection to a private Tracker store.
+ */
+public class Tracker.Sparql.PrivateStore : Tracker.Sparql.Connection {
+       /**
+        * tracker_sparql_private_store_open:
+        * @store_path: Location of a new or existing Tracker database.
+        * @ontologies: An array of paths to ontologies to load, or %NULL to load all
+        *    system-provided ontologies.
+        * @error: #GError for error reporting.
+        *
+        * Opens a private Tracker store. See above for more information on private
+        * Tracker stores.
+        *
+        * All access to a private Tracker store happens in-process. No D-Bus
+        * communication is involved. For that reason there is no async variant of
+        * this function.
+        *
+        * Returns: a new #TrackerSparqlConnection. Call g_object_unref() on the
+        * object when no longer used.
+        *
+        * Since: 0.10
+        */
+       /* Implementation of this is in libtracker-sparql-backend/tracker-backend.vala */
+       public extern static new Connection open (String store_path, String? ontologies_path) throws 
Sparql.Error, IOError;
+}
diff --git a/tests/libtracker-sparql/tracker-sparql-test.c b/tests/libtracker-sparql/tracker-sparql-test.c
index 5cd4b7a..892bc06 100644
--- a/tests/libtracker-sparql/tracker-sparql-test.c
+++ b/tests/libtracker-sparql/tracker-sparql-test.c
@@ -191,6 +191,9 @@ test_tracker_sparql_cursor_next_async_cb (GObject      *source,
        }
 }
 
+/* This test could be using a connection to the user's real session-wide
+ * tracker-store, so it shouldn't do any writes.
+ */
 static void
 test_tracker_sparql_cursor_next_async_query (gint query)
 {
@@ -215,6 +218,9 @@ test_tracker_sparql_cursor_next_async_query (gint query)
                                          GINT_TO_POINTER(query));
 }
 
+/* This test could be using a connection to the user's real session-wide
+ * tracker-store, so it shouldn't do any writes.
+ */
 static void
 test_tracker_sparql_cursor_next_async (void)
 {
@@ -237,6 +243,9 @@ test_tracker_sparql_cursor_next_async (void)
 
 #endif /* HAVE_TRACKER_FTS */
 
+/* This test could be using a connection to the user's real session-wide
+ * tracker-store, so it shouldn't do any writes.
+ */
 static void
 test_tracker_sparql_connection_locking_sync (void)
 {
@@ -283,9 +292,15 @@ test_tracker_sparql_connection_locking_async_cb (GObject      *source,
        g_assert (connection_waiting == NULL);
 }
 
+/* This test could be using a connection to the user's real session-wide
+ * tracker-store, so it shouldn't do any writes.
+ */
 static void
 test_tracker_sparql_connection_locking_async (void)
 {
+       /* These could be a connection to the user's real session-wide
+        * tracker-store, so don't do anything crazy.
+        */
        tracker_sparql_connection_get_async (NULL, test_tracker_sparql_connection_locking_async_cb, c2);
        tracker_sparql_connection_get_async (NULL, test_tracker_sparql_connection_locking_async_cb, c3);
        c3 = tracker_sparql_connection_get (NULL, NULL);
@@ -320,9 +335,15 @@ test_tracker_sparql_nb237150_cb (GObject      *source_object,
        }
 }
 
+/* This test could be using a connection to the user's real session-wide
+ * tracker-store, so it shouldn't do any writes.
+ */
 static void
 test_tracker_sparql_nb237150_subprocess (void)
 {
+       /* These could be a connection to the user's real session-wide
+        * tracker-store, so don't do anything crazy.
+        */
        g_print ("\n");
        g_print ("Calling #1 - tracker_sparql_connection_get_async()\n");
        tracker_sparql_connection_get_async (NULL, test_tracker_sparql_nb237150_cb, GINT_TO_POINTER(1));
@@ -357,6 +378,9 @@ test_tracker_sparql_nb237150 (void)
        g_test_trap_assert_stdout ("*Called back ALL*");
 }
 
+/* This test could be using a connection to the user's real session-wide
+ * tracker-store, so it shouldn't do any writes.
+ */
 static void
 test_tracker_sparql_connection_interleaved (void)
 {
@@ -366,8 +390,11 @@ test_tracker_sparql_connection_interleaved (void)
        TrackerSparqlCursor *cursor2;
        TrackerSparqlConnection *connection;
 
-       const gchar* query = "select ?u {?u a rdfs:Resource .}";
+       const gchar * query = "select ?u {?u a rdfs:Resource .}";
 
+       /* This could be a connection to the user's real session-wide
+        * tracker-store, so don't do anything crazy.
+        */
        connection = tracker_sparql_connection_get (NULL, &error);
        g_assert_no_error (error);
 
@@ -389,6 +416,40 @@ test_tracker_sparql_connection_interleaved (void)
        g_object_unref(cursor1);
 }
 
+static void test_sparql_connection_private (void) {
+       GError *error = NULL;
+       TrackerSparqlConnection *connection;
+       TrackerSparqlCursor *cursor;
+       const gchar *store_location;
+       const gchar *cleanup_command;
+       const gchar *update = "insert { <http://www.example.com/> a nie:InformationElement; nie:title 
\"Boris\" . }";
+       const gchar *query = "select ?title { <http://www.example.com/> nie:title ?title .}";
+
+       store_location = g_mkdtemp ("tracker-sparql-test-XXXXXX");
+
+       connection = tracker_sparql_private_store_open (store_location, NULL, &error);
+       g_assert_no_error (error);
+
+       /* It's OK to do writes here, we have a private connection. */
+       tracker_sparql_connection_update (connection, update, 0, NULL, &error);
+       g_assert_no_error (error);
+
+       cursor = tracker_sparql_connection_query (connection, query, 0, &error);
+       g_assert_no_error (error);
+
+       tracker_sparql_cursor_next (cursor, NULL, &error);
+       g_assert_no_error (error);
+
+       g_assert_cmpstr (tracker_sparql_cursor_get_string (cursor, 0, NULL), ==, "Boris");
+
+       g_object_unref (connection);
+       g_object_unref (cursor);
+
+       cleanup_command = g_strdup_printf ("rm -Rf %s/", store_location);
+       g_spawn_command_line_sync (cleanup_command, NULL, NULL, NULL, NULL);
+       g_free (cleanup_command);
+}
+
 gint
 main (gint argc, gchar **argv)
 {
@@ -420,6 +481,8 @@ main (gint argc, gchar **argv)
                         test_tracker_sparql_connection_locking_sync);
        g_test_add_func ("/libtracker-sparql/tracker-sparql/tracker_sparql_connection_locking_async",
                         test_tracker_sparql_connection_locking_async);
+       g_test_add_func ("/libtracker-sparql/tracker-sparql/tracker_sparql_connection_private",
+                        test_tracker_sparql_connection_private);
 
 #if HAVE_TRACKER_FTS
        g_test_add_func ("/libtracker-sparql/tracker-sparql/tracker_sparql_cursor_next_async",


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]