[rygel] media-export: Get rid of callbacks in database



commit 592bee9125faf9ba0b5b1449857c6554435c8f29
Author: Jens Georg <mail jensge org>
Date:   Fri Feb 25 00:55:30 2011 +0100

    media-export: Get rid of callbacks in database
    
    Actually most of the code using the callback approach was more like a
    loop body. Now we have a database cursor that can be used with vala's
    foreach syntax, removing some indirection.
    
    Note: This also drops SQL debugging which will be re-introduced later.

 configure.ac                                       |    7 -
 src/plugins/media-export/Makefile.am               |    2 +
 .../rygel-media-export-database-cursor.vala        |  143 +++++++++++++++
 .../media-export/rygel-media-export-database.vala  |  190 +++++++-------------
 .../rygel-media-export-media-cache-upgrader.vala   |   59 +++----
 .../rygel-media-export-media-cache.vala            |  163 ++++++-----------
 .../rygel-media-export-sqlite-wrapper.vala         |   80 ++++++++
 7 files changed, 365 insertions(+), 279 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 0d371c5..80a620d 100644
--- a/configure.ac
+++ b/configure.ac
@@ -74,12 +74,6 @@ dnl glib-genmarshal
 GLIB_GENMARSHAL=`pkg-config --variable=glib_genmarshal glib-2.0`
 AC_SUBST(GLIB_GENMARSHAL)
 
-AC_ARG_ENABLE(sql-debugging,
-    AS_HELP_STRING([--enable-sql-debugging],[enable SQL statement debugging]),,
-        enable_sql_debugging=no)
-AS_IF([test x$enable_sql_debugging = xyes],
-      [RYGEL_ADD_VALAFLAGS([-D RYGEL_DEBUG_SQL])])
-
 dnl Add plugins
 RYGEL_ADD_PLUGIN([test],[Test],[no])
 RYGEL_ADD_PLUGIN([tracker],[Tracker],[yes])
@@ -231,7 +225,6 @@ echo "
         VALAFLAGS:              ${VALAFLAGS}
         uninstalled:            ${enable_uninstalled}
         preferences ui:         ${BUILD_UI}
-        SQL debugging:          ${enable_sql_debugging}
     Plugins:
         test:                   ${enable_test_plugin}
         tracker:                ${enable_tracker_plugin}
diff --git a/src/plugins/media-export/Makefile.am b/src/plugins/media-export/Makefile.am
index c5ec8c8..70e9fb9 100644
--- a/src/plugins/media-export/Makefile.am
+++ b/src/plugins/media-export/Makefile.am
@@ -12,6 +12,8 @@ AM_CFLAGS += \
 librygel_media_export_la_SOURCES = \
 	rygel-media-export-plugin.vala \
 	rygel-media-export-database.vala \
+	rygel-media-export-database-cursor.vala \
+	rygel-media-export-sqlite-wrapper.vala \
 	rygel-media-export-db-container.vala \
 	rygel-media-export-sql-factory.vala \
 	rygel-media-export-media-cache.vala \
diff --git a/src/plugins/media-export/rygel-media-export-database-cursor.vala b/src/plugins/media-export/rygel-media-export-database-cursor.vala
new file mode 100644
index 0000000..6c3f0dc
--- /dev/null
+++ b/src/plugins/media-export/rygel-media-export-database-cursor.vala
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2011 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel 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 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Sqlite;
+
+internal class Rygel.MediaExport.DatabaseCursor : SqliteWrapper {
+    private Statement statement;
+    private int current_state = -1;
+    private bool dirty = true;
+
+    /**
+     * Prepare a SQLite statement from a SQL string
+     *
+     * This function uses the type of the GValue passed in values to determine
+     * which _bind function to use.
+     *
+     * Supported types are: int, long, int64, uint64, string and pointer.
+     * @note the only pointer supported is the null pointer as provided by
+     * Database  null  This is a special value to bind a column to NULL
+     *
+     * @param db SQLite database this cursor belongs to
+     * @param sql statement to execute
+     * @param values array of values to bind to the SQL statement or null if
+     * none
+     */
+    public DatabaseCursor (Sqlite.Database   db,
+                           string            sql,
+                           GLib.Value[]?     arguments) throws DatabaseError {
+        base.wrap (db);
+
+        this.throw_if_code_is_error (db.prepare_v2 (sql,
+                                                    -1,
+                                                    out this.statement,
+                                                    null));
+        if (arguments == null) {
+            return;
+        }
+
+        for (var i = 1; i <= arguments.length; ++i) {
+            unowned GLib.Value current_value = arguments[i - 1];
+
+            if (current_value.holds (typeof (int))) {
+                statement.bind_int (i, current_value.get_int ());
+            } else if (current_value.holds (typeof (int64))) {
+                statement.bind_int64 (i, current_value.get_int64 ());
+            } else if (current_value.holds (typeof (uint64))) {
+                statement.bind_int64 (i, (int64) current_value.get_uint64 ());
+            } else if (current_value.holds (typeof (long))) {
+                statement.bind_int64 (i, current_value.get_long ());
+            } else if (current_value.holds (typeof (string))) {
+                statement.bind_text (i, current_value.get_string ());
+            } else if (current_value.holds (typeof (void *))) {
+                if (current_value.peek_pointer () == null) {
+                    statement.bind_null (i);
+                } else {
+                    assert_not_reached ();
+                }
+            } else {
+                var type = current_value.type ();
+                warning (_("Unsupported type %s"), type.name ());
+                assert_not_reached ();
+            }
+
+            this.throw_if_db_has_error ();
+        }
+    }
+
+    /**
+     * Check if the cursor has more rows left
+     *
+     * @return true if more rows left, false otherwise
+     */
+    public bool has_next () {
+        if (this.dirty) {
+            this.current_state = this.statement.step ();
+            this.dirty = false;
+        }
+
+        return this.current_state == Sqlite.ROW || this.current_state == -1;
+    }
+
+    /**
+     * Get the next row of this cursor.
+     *
+     * This function uses pointers instead of unowned because var doesn't work
+     * with unowned.
+     *
+     * @return a pointer to the current row
+     */
+    public Statement* next () throws DatabaseError {
+        this.has_next ();
+        this.throw_if_code_is_error (this.current_state);
+        this.dirty = true;
+
+        return this.statement;
+    }
+
+    // convenience functions for "foreach"
+
+    /**
+     * Return a iterator to the cursor to use with foreach
+     *
+     * @return an iterator wrapping the cursor
+     */
+    public Iterator iterator () {
+        return new Iterator (this);
+    }
+
+    public class Iterator {
+        public DatabaseCursor cursor;
+
+        public Iterator (DatabaseCursor cursor) {
+            this.cursor = cursor;
+        }
+
+        public bool next () {
+            return this.cursor.has_next ();
+        }
+
+        public unowned Statement @get () throws DatabaseError {
+            return this.cursor.next ();
+        }
+    }
+}
diff --git a/src/plugins/media-export/rygel-media-export-database.vala b/src/plugins/media-export/rygel-media-export-database.vala
index e45b312..e35cf85 100644
--- a/src/plugins/media-export/rygel-media-export-database.vala
+++ b/src/plugins/media-export/rygel-media-export-database.vala
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2009 Jens Georg <mail jensge org>.
+ * Copyright (C) 2009,2011 Jens Georg <mail jensge org>.
  *
  * Author: Jens Georg <mail jensge org>
  *
@@ -33,16 +33,11 @@ public errordomain Rygel.MediaExport.DatabaseError {
  * It adds statement preparation based on GValue and a cancellable exec
  * function.
  */
-internal class Rygel.MediaExport.Database : Object {
-    private Sqlite.Database db;
+internal class Rygel.MediaExport.Database : SqliteWrapper {
 
     /**
-     * Callback to pass to exec
-     *
-     * @return true, if you want the query to continue, false otherwise
+     * Function to implement the custom SQL function 'contains'
      */
-    public delegate bool RowCallback (Sqlite.Statement stmt);
-
     private static void utf8_contains (Sqlite.Context context,
                                        Sqlite.Value[] args)
                                        requires (args.length == 2) {
@@ -62,6 +57,11 @@ internal class Rygel.MediaExport.Database : Object {
         }
     }
 
+    /**
+     * Function to implement the custom SQLite collation 'CASEFOLD'.
+     *
+     * Uses utf8 case-fold to compare the strings.
+     */
     private static int utf8_collate (int alen, void* a, int blen, void* b) {
         // unowned to prevent array copy
         unowned uint8[] _a = (uint8[]) a;
@@ -87,18 +87,14 @@ internal class Rygel.MediaExport.Database : Object {
                                            "rygel");
         DirUtils.create_with_parents (dirname, 0750);
         var db_file = Path.build_filename (dirname, "%s.db".printf (name));
+
+        base (db_file);
+
         debug ("Using database file %s", db_file);
-        var rc = Sqlite.Database.open (db_file, out this.db);
-        if (rc != Sqlite.OK) {
-            throw new DatabaseError.IO_ERROR
-                                        (_("Failed to open database: %d (%s)"),
-                                         rc,
-                                         db.errmsg ());
-        }
 
-        this.db.exec ("PRAGMA synchronous = OFF");
-        this.db.exec ("PRAGMA temp_store = MEMORY");
-        this.db.exec ("PRAGMA count_changes = OFF");
+        this.exec ("PRAGMA synchronous = OFF");
+        this.exec ("PRAGMA temp_store = MEMORY");
+        this.exec ("PRAGMA count_changes = OFF");
 
         this.db.create_function ("contains",
                                  2,
@@ -114,55 +110,58 @@ internal class Rygel.MediaExport.Database : Object {
     }
 
     /**
-     * Execute a cancellable SQL statement.
+     * SQL query function.
      *
-     * The supplied values are bound to the SQL statement and the RowCallback
-     * is called on every row of the resultset.
+     * Use for all queries that return a result set.
      *
-     * @param sql statement to execute
-     * @param values array of values to bind to the SQL statement or null if
-     * none
-     * @param callback to call on each row of the result set or null if none
-     * necessary
-     * @param cancellable to cancel the running query or null if none
-     * necessary
+     * @param sql The SQL query to run.
+     * @param args Values to bind in the SQL query or null.
+     * @throws DatabaseError if the underlying SQLite operation fails.
      */
-    public int exec (string        sql,
-                     GLib.Value[]? values      = null,
-                     RowCallback?  callback    = null,
-                     Cancellable?  cancellable = null) throws DatabaseError {
-        #if RYGEL_DEBUG_SQL
-        var t = new Timer ();
-        #endif
-        int rc;
-
-        if (values == null && callback == null && cancellable == null) {
-            rc = this.db.exec (sql);
-        } else {
-            var statement = prepare_statement (sql, values);
-            while ((rc = statement.step ()) == Sqlite.ROW) {
-                if (cancellable != null && cancellable.is_cancelled ()) {
-                    break;
-                }
+    public DatabaseCursor exec_cursor (string        sql,
+                                       GLib.Value[]? arguments = null)
+                                       throws DatabaseError {
+        return new DatabaseCursor (this.db, sql, arguments);
+    }
 
-                if (callback != null) {
-                    if (!callback (statement)) {
-                        rc = Sqlite.DONE;
+    /**
+     * Simple SQL query execution function.
+     *
+     * Use for all queries that don't return anything.
+     *
+     * @param sql The SQL query to run.
+     * @param args Values to bind in the SQL query or null.
+     * @throws DatabaseError if the underlying SQLite operation fails.
+     */
+    public void exec (string        sql,
+                      GLib.Value[]? arguments = null)
+                      throws DatabaseError {
+        if (arguments == null) {
+            this.throw_if_code_is_error (this.db.exec (sql));
 
-                        break;
-                    }
-                }
-            }
+            return;
         }
 
-        if (rc != Sqlite.DONE && rc != Sqlite.OK) {
-            throw new DatabaseError.SQLITE_ERROR (db.errmsg ());
+        var cursor = this.exec_cursor (sql, arguments);
+        while (cursor.has_next ()) {
+            cursor.next ();
         }
-        #if RYGEL_DEBUG_SQL
-        debug ("Query: %s, Time: %f", sql, t.elapsed ());
-        #endif
+    }
 
-        return rc;
+    /**
+     * Execute a SQL query that returns a single number.
+     *
+     * @param sql The SQL query to run.
+     * @param args Values to bind in the SQL query or null.
+     * @return The contents of the first row's column as an int.
+     * @throws DatabaseError if the underlying SQLite operation fails.
+     */
+    public int query_value (string        sql,
+                             GLib.Value[]? args = null)
+                             throws DatabaseError {
+        var cursor = this.exec_cursor (sql, args);
+        var statement = cursor.next ();
+        return statement->column_int (0);
     }
 
     /**
@@ -173,7 +172,7 @@ internal class Rygel.MediaExport.Database : Object {
     }
 
     /**
-     * Special GValue to pass to exec or prepare_statement to bind a column to
+     * Special GValue to pass to exec or exec_cursor to bind a column to
      * NULL
      */
     public static GLib.Value @null () {
@@ -187,14 +186,14 @@ internal class Rygel.MediaExport.Database : Object {
      * Start a transaction
      */
     public void begin () throws DatabaseError {
-        this.single_statement ("BEGIN");
+        this.exec ("BEGIN");
     }
 
     /**
      * Commit a transaction
      */
     public void commit () throws DatabaseError {
-        this.single_statement ("COMMIT");
+        this.exec ("COMMIT");
     }
 
     /**
@@ -202,77 +201,10 @@ internal class Rygel.MediaExport.Database : Object {
      */
     public void rollback () {
         try {
-            this.single_statement ("ROLLBACK");
+            this.exec ("ROLLBACK");
         } catch (DatabaseError error) {
             critical (_("Failed to roll back transaction: %s"),
                       error.message);
         }
     }
-
-    /**
-     * Execute a single SQL statement and throw an exception on error
-     *
-     * @param sql SQL statement to execute
-     * @throws DatabaseError if SQL statement fails
-     */
-    private void single_statement (string sql) throws DatabaseError {
-        if (this.db.exec (sql) != Sqlite.OK) {
-            throw new DatabaseError.SQLITE_ERROR (db.errmsg ());
-        }
-    }
-
-    /**
-     * Prepare a SQLite statement from a SQL string
-     *
-     * This function uses the type of the GValue passed in values to determine
-     * which _bind function to use.
-     *
-     * Supported types are: int, long, int64, uint64, string and pointer.
-     * @note the only pointer supported is the null pointer as provided by
-     * Database  null  This is a special value to bind a column to NULL
-     *
-     * @param sql statement to execute
-     * @param values array of values to bind to the SQL statement or null if
-     * none
-     */
-    private Statement prepare_statement (string        sql,
-                                         GLib.Value[]? values = null)
-                                         throws DatabaseError {
-        Statement statement;
-        var rc = db.prepare_v2 (sql, -1, out statement, null);
-        if (rc != Sqlite.OK) {
-            throw new DatabaseError.SQLITE_ERROR (db.errmsg ());
-        }
-
-        if (values != null) {
-            for (int i = 0; i < values.length; i++) {
-                if (values[i].holds (typeof (int))) {
-                    rc = statement.bind_int (i + 1, values[i].get_int ());
-                } else if (values[i].holds (typeof (int64))) {
-                    rc = statement.bind_int64 (i + 1, values[i].get_int64 ());
-                } else if (values[i].holds (typeof (uint64))) {
-                    rc = statement.bind_int64 (i + 1, (int64) values[i].get_uint64 ());
-                } else if (values[i].holds (typeof (long))) {
-                    rc = statement.bind_int64 (i + 1, values[i].get_long ());
-                } else if (values[i].holds (typeof (string))) {
-                    rc = statement.bind_text (i + 1, values[i].get_string ());
-                } else if (values[i].holds (typeof (void *))) {
-                    if (values[i].peek_pointer () == null) {
-                        rc = statement.bind_null (i + 1);
-                    } else {
-                        assert_not_reached ();
-                    }
-                } else {
-                    var t = values[i].type ();
-                    warning (_("Unsupported type %s"), t.name ());
-                        assert_not_reached ();
-                }
-                if (rc != Sqlite.OK) {
-                    throw new DatabaseError.SQLITE_ERROR (db.errmsg ());
-                }
-            }
-        }
-
-        return statement;
-    }
 }
diff --git a/src/plugins/media-export/rygel-media-export-media-cache-upgrader.vala b/src/plugins/media-export/rygel-media-export-media-cache-upgrader.vala
index c997230..192d159 100644
--- a/src/plugins/media-export/rygel-media-export-media-cache-upgrader.vala
+++ b/src/plugins/media-export/rygel-media-export-media-cache-upgrader.vala
@@ -41,34 +41,19 @@ internal class Rygel.MediaExport.MediaCacheUpgrader {
     }
 
     public bool needs_upgrade (out int current_version) throws Error {
-        // cannot capture out parameters in closure
-        int current_version_temp = 0;
-
-        this.database.exec ("SELECT version FROM schema_info",
-                            null,
-                            (statement) => {
-                                current_version_temp = statement.column_int (0);
-
-                                return false;
-                            });
-        current_version = current_version_temp;
+        current_version = this.database.query_value (
+                                        "SELECT version FROM schema_info");
 
         return current_version < int.parse (SQLFactory.schema_version);
     }
 
     public void fix_schema () throws Error {
-        bool schema_ok = true;
-
-        database.exec ("SELECT count(*) FROM sqlite_master WHERE sql " +
-                       "LIKE 'CREATE TABLE Meta_Data%object_fk TEXT " +
-                       "UNIQUE%'",
-                       null,
-                       (statement) => {
-                           schema_ok = statement.column_int (0) == 1;
-
-                           return false;
-                       });
-        if (!schema_ok) {
+        var matching_schema_count = this.database.query_value (
+                                        "SELECT count(*) FROM " +
+                                        "sqlite_master WHERE sql " +
+                                        "LIKE 'CREATE TABLE Meta_Data" +
+                                        "%object_fk TEXT UNIQUE%'");
+        if (matching_schema_count == 0) {
             try {
                 message ("Found faulty schema, forcing full reindex");
                 database.begin ();
@@ -182,13 +167,13 @@ internal class Rygel.MediaExport.MediaCacheUpgrader {
             queue.offer ("0");
             while (!queue.is_empty) {
                 GLib.Value[] args = { queue.poll () };
-                database.exec ("SELECT upnp_id FROM _Object WHERE parent = ?",
-                               args,
-                               (statement) => {
-                                   queue.offer (statement.column_text (0));
-
-                                   return true;
-                              });
+                var cursor = this.database.exec_cursor (
+                                        "SELECT upnp_id FROM _Object WHERE " +
+                                        "parent = ?",
+                                        args);
+                foreach (var statement in cursor) {
+                    queue.offer (statement.column_text (0));
+                }
 
                 database.exec ("INSERT INTO Object SELECT * FROM _OBJECT " +
                                "WHERE parent = ?",
@@ -329,13 +314,13 @@ internal class Rygel.MediaExport.MediaCacheUpgrader {
             queue.offer ("0");
             while (!queue.is_empty) {
                 GLib.Value[] args = { queue.poll () };
-                database.exec ("SELECT upnp_id FROM _Object WHERE parent = ?",
-                               args,
-                               (statement) => {
-                                   queue.offer (statement.column_text (0));
-
-                                   return true;
-                              });
+                var cursor = this.database.exec_cursor (
+                                        "SELECT upnp_id FROM _Object WHERE " +
+                                        "parent = ?",
+                                        args);
+                foreach (var statement in cursor) {
+                    queue.offer (statement.column_text (0));
+                }
 
                 database.exec ("INSERT INTO Object SELECT * FROM _Object " +
                                "WHERE parent = ?",
diff --git a/src/plugins/media-export/rygel-media-export-media-cache.vala b/src/plugins/media-export/rygel-media-export-media-cache.vala
index f0572c6..4004a51 100644
--- a/src/plugins/media-export/rygel-media-export-media-cache.vala
+++ b/src/plugins/media-export/rygel-media-export-media-cache.vala
@@ -102,7 +102,9 @@ public class Rygel.MediaExport.MediaCache : Object {
         GLib.Value[] values = { object_id };
         MediaObject parent = null;
 
-        Database.RowCallback cb = (statement) => {
+        var cursor = this.exec_cursor (SQLString.GET_OBJECT, values);
+
+        foreach (var statement in cursor) {
             var parent_container = parent as MediaContainer;
             var id = statement.column_text (DetailColumn.ID);
             var object = get_object_from_statement
@@ -111,11 +113,7 @@ public class Rygel.MediaExport.MediaCache : Object {
                                          statement);
             object.parent_ref = parent_container;
             parent = object;
-
-            return true;
-        };
-
-        this.db.exec (this.sql.make (SQLString.GET_OBJECT), values, cb);
+        }
 
         return parent;
     }
@@ -146,44 +144,28 @@ public class Rygel.MediaExport.MediaCache : Object {
     }
 
     public int get_child_count (string container_id) throws DatabaseError {
-        int count = 0;
         GLib.Value[] values = { container_id };
 
-        this.db.exec (this.sql.make (SQLString.CHILD_COUNT),
-                      values,
-                      (statement) => {
-                          count = statement.column_int (0);
-
-                          return false;
-                      });
-
-        return count;
+        return this.query_value (SQLString.CHILD_COUNT, values);
     }
 
 
     private void get_exists_cache () throws DatabaseError {
         this.exists_cache = new HashMap<string, ExistsCacheEntry?> ();
-        this.db.exec (this.sql.make (SQLString.EXISTS_CACHE),
-                      null,
-                      (statement) => {
-                          var entry = ExistsCacheEntry ();
-                          entry.mtime = statement.column_int64 (1);
-                          entry.size = statement.column_int64 (0);
-                          this.exists_cache.set (statement.column_text (2),
-                                                 entry);
-
-                          return true;
-                      });
+        var cursor = this.exec_cursor (SQLString.EXISTS_CACHE);
+        foreach (var statement in cursor) {
+            var entry = ExistsCacheEntry ();
+            entry.mtime = statement.column_int64 (1);
+            entry.size = statement.column_int64 (0);
+            this.exists_cache.set (statement.column_text (2), entry);
+        }
     }
 
     public bool exists (File      file,
                         out int64 timestamp,
                         out int64 size) throws DatabaseError {
-        var exists = false;
         var uri = file.get_uri ();
         GLib.Value[] values = { uri };
-        int64 tmp_timestamp = 0;
-        int64 tmp_size = 0;
 
         if (this.exists_cache.has_key (uri)) {
             var entry = this.exists_cache.get (uri);
@@ -194,21 +176,12 @@ public class Rygel.MediaExport.MediaCache : Object {
             return true;
         }
 
-        this.db.exec (this.sql.make (SQLString.EXISTS),
-                      values,
-                      (statement) => {
-                          exists = statement.column_int (0) == 1;
-                          tmp_timestamp = statement.column_int64 (1);
-                          tmp_size = statement.column_int64 (2);
-
-                          return false;
-                      });
+        var cursor = this.exec_cursor (SQLString.EXISTS, values);
+        var statement = cursor.next ();
+        timestamp = statement->column_int64 (1);
+        size = statement->column_int64 (2);
 
-        // out parameters are not allowed to be captured
-        timestamp = tmp_timestamp;
-        size = tmp_size;
-
-        return exists;
+        return statement->column_int (0) == 1;
     }
 
     public MediaObjects get_children (MediaContainer container,
@@ -220,19 +193,16 @@ public class Rygel.MediaExport.MediaCache : Object {
         GLib.Value[] values = { container.id,
                                 (int64) offset,
                                 (int64) max_count };
-        Database.RowCallback callback = (statement) => {
+
+        var cursor = this.exec_cursor (SQLString.GET_CHILDREN, values);
+
+        foreach (var statement in cursor) {
             var child_id = statement.column_text (DetailColumn.ID);
             children.add (get_object_from_statement (container,
                                                      child_id,
                                                      statement));
             children.last ().parent_ref = container;
-
-            return true;
-        };
-
-        this.db.exec (this.sql.make (SQLString.GET_CHILDREN),
-                      values,
-                      callback);
+        }
 
         return children;
     }
@@ -304,24 +274,12 @@ public class Rygel.MediaExport.MediaCache : Object {
                                          throws Error {
         GLib.Value v = container_id;
         args.prepend (v);
-        long count = 0;
 
         debug ("Parameters to bind: %u", args.n_values);
-
-        Database.RowCallback callback = (statement) => {
-            count = statement.column_int (0);
-
-            return false;
-        };
-
-        unowned string sql = this.sql.make
+        unowned string pattern = this.sql.make
                                         (SQLString.GET_OBJECT_COUNT_BY_FILTER);
 
-        this.db.exec (sql.printf (filter),
-                      args.values,
-                      callback);
-
-        return count;
+        return this.db.query_value (pattern.printf (filter), args.values);
     }
 
 
@@ -340,7 +298,9 @@ public class Rygel.MediaExport.MediaCache : Object {
 
         debug ("Parameters to bind: %u", args.n_values);
 
-        Database.RowCallback callback = (statement) => {
+        unowned string sql = this.sql.make (SQLString.GET_OBJECTS_BY_FILTER);
+        var cursor = this.db.exec_cursor (sql.printf (filter), args.values);
+        foreach (var statement in cursor) {
             var child_id = statement.column_text (DetailColumn.ID);
             var parent_id = statement.column_text (DetailColumn.PARENT);
 
@@ -360,14 +320,7 @@ public class Rygel.MediaExport.MediaCache : Object {
                          child_id,
                          parent_id);
             }
-
-            return true;
-        };
-
-        var sql = this.sql.make (SQLString.GET_OBJECTS_BY_FILTER);
-        this.db.exec (sql.printf (filter),
-                      args.values,
-                      callback);
+        }
 
         return children;
     }
@@ -413,15 +366,8 @@ public class Rygel.MediaExport.MediaCache : Object {
             debug ("Could not find schema version;" +
                    " checking for empty database...");
             try {
-                int rows = -1;
-                this.db.exec ("SELECT count(type) FROM sqlite_master " +
-                              "WHERE rowid=1",
-                              null,
-                              (statement) => {
-                                  rows = statement.column_int (0);
-
-                                  return false;
-                              });
+                var rows = this.db.query_value ("SELECT count(type) FROM " +
+                                                "sqlite_master WHERE rowid=1");
                 if (rows == 0) {
                     debug ("Empty database, creating new schema version %s",
                             SQLFactory.schema_version);
@@ -639,13 +585,10 @@ public class Rygel.MediaExport.MediaCache : Object {
         ArrayList<string> children = new ArrayList<string> (str_equal);
         GLib.Value[] values = { container_id  };
 
-        this.db.exec (this.sql.make (SQLString.CHILD_IDS),
-                      values,
-                      (statement) => {
-                          children.add (statement.column_text (0));
-
-                          return true;
-                      });
+        var cursor = this.exec_cursor (SQLString.CHILD_IDS, values);
+        foreach (var statement in cursor) {
+            children.add (statement.column_text (0));
+        }
 
         return children;
     }
@@ -827,16 +770,13 @@ public class Rygel.MediaExport.MediaCache : Object {
         args.append (v);
 
         var data = new ArrayList<string> ();
-        Database.RowCallback callback = (statement) => {
-            data.add (statement.column_text (0));
 
-            return true;
-        };
-
-        var sql = this.sql.make (SQLString.GET_META_DATA_COLUMN);
-        this.db.exec (sql.printf (column, filter),
-                      args.values,
-                      callback);
+        unowned string sql = this.sql.make (SQLString.GET_META_DATA_COLUMN);
+        var cursor = this.db.exec_cursor (sql.printf (column, filter),
+                                          args.values);
+        foreach (var statement in cursor) {
+            data.add (statement.column_text (0));
+        }
 
         return data;
     }
@@ -871,15 +811,26 @@ public class Rygel.MediaExport.MediaCache : Object {
 
     public Gee.List<string> get_flagged_uris (string flag) throws Error {
         var uris = new ArrayList<string> ();
+        const string query = "SELECT uri FROM object WHERE flags = ?";
+
         GLib.Value[] args = { flag };
-        this.db.exec ("SELECT uri FROM object WHERE flags = ?",
-                      args,
-                      (statement) => {
-                          uris.add (statement.column_text (0));
 
-                          return true;
-                      });
+        var cursor = this.db.exec_cursor (query, args);
+        foreach (var statement in cursor) {
+            uris.add (statement.column_text (0));
+        }
 
         return uris;
     }
+    private DatabaseCursor exec_cursor (SQLString      id,
+                                        GLib.Value[]?  values = null)
+                                        throws DatabaseError {
+        return this.db.exec_cursor (this.sql.make (id), values);
+    }
+
+    private int query_value (SQLString      id,
+                             GLib.Value[]?  values = null)
+                             throws DatabaseError {
+        return this.db.query_value (this.sql.make (id), values);
+    }
 }
diff --git a/src/plugins/media-export/rygel-media-export-sqlite-wrapper.vala b/src/plugins/media-export/rygel-media-export-sqlite-wrapper.vala
new file mode 100644
index 0000000..84a288b
--- /dev/null
+++ b/src/plugins/media-export/rygel-media-export-sqlite-wrapper.vala
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2011 Jens Georg <mail jensge org>.
+ *
+ * Author: Jens Georg <mail jensge org>
+ *
+ * This file is part of Rygel.
+ *
+ * Rygel 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 of the License, or
+ * (at your option) any later version.
+ *
+ * Rygel 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 program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+using Sqlite;
+
+internal class Rygel.MediaExport.SqliteWrapper : Object {
+    private Sqlite.Database database = null;
+    private Sqlite.Database *reference = null;
+
+    /**
+     * Property to access the wrapped database
+     */
+    protected unowned Sqlite.Database db {
+        get { return reference; }
+    }
+
+    /**
+     * Wrap an existing SQLite Database object.
+     *
+     * The SqliteWrapper doesn't take ownership of the passed db
+     */
+    public SqliteWrapper.wrap (Sqlite.Database db) {
+        this.reference = db;
+    }
+
+    /**
+     * Create or open a new SQLite database in path.
+     *
+     * @note: Path may also be ":memory:" for temporary databases
+     */
+    public SqliteWrapper (string path) throws DatabaseError {
+        Sqlite.Database.open (path, out this.database);
+        this.reference = this.database;
+        this.throw_if_db_has_error ();
+    }
+
+    /**
+     * Convert a SQLite return code to a DatabaseError
+     */
+    protected void throw_if_code_is_error (int sqlite_error)
+                                           throws DatabaseError {
+        switch (sqlite_error) {
+            case Sqlite.OK:
+            case Sqlite.DONE:
+            case Sqlite.ROW:
+                return;
+            default:
+                throw new DatabaseError.SQLITE_ERROR
+                                        ("SQLite error %d: %s",
+                                         sqlite_error,
+                                         this.reference->errmsg ());
+        }
+    }
+
+    /**
+     * Check if the last operation on the database was an error
+     */
+    protected void throw_if_db_has_error () throws DatabaseError {
+        this.throw_if_code_is_error (this.reference->errcode ());
+    }
+}



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