diff options
Diffstat (limited to 'meta-agl/recipes-connectivity/rygel/files/0001-Add-LightMediaScanner-plugin.patch')
-rw-r--r-- | meta-agl/recipes-connectivity/rygel/files/0001-Add-LightMediaScanner-plugin.patch | 2541 |
1 files changed, 0 insertions, 2541 deletions
diff --git a/meta-agl/recipes-connectivity/rygel/files/0001-Add-LightMediaScanner-plugin.patch b/meta-agl/recipes-connectivity/rygel/files/0001-Add-LightMediaScanner-plugin.patch deleted file mode 100644 index eca08ee84..000000000 --- a/meta-agl/recipes-connectivity/rygel/files/0001-Add-LightMediaScanner-plugin.patch +++ /dev/null @@ -1,2541 +0,0 @@ -From 8bb9ae73464dd76f5fa94f2e9ba76b0bd88114df Mon Sep 17 00:00:00 2001 -From: Manuel Bachmann <manuel.bachmann@iot.bzh> -Date: Mon, 26 Oct 2015 04:18:33 +0000 -Subject: [PATCH] Add LightMediaScanner plugin - -Add a new plugin based on LightMediaScanner : -https://github.com/profusion/lightmediascanner - -Shorty put, this plugin does not do the indexing itself as -"media-export" does, but defers this task to the -"lightmediascannerd" daemon, with which it communicates -via a D-Bus interface. -The remote indexing daemon installs itself as a separate -package, and is designed to be ultra-lightweight and fast. - -This commit is the rebase and fusion of all work done -first on Maemo (lms.garage.maemo.org), then on Tizen IVI -(review.tizen.org/git/?p=profile/ivi/rygel.git) and right -now on AGL (automotivelinux.org) where several components -depend on LightMediaScanner. -A splitted version (13 commits) can also be seen on : -https://github.com/Tarnyko/rygel - -It difffers mostly in that it lets "media-export" plugin -as the default, just adding itself in "rygel.conf" as an -alternative. - -(note : reporter is not code Author, see patch for details) -(note 2 : rebased on top of 0.26.1) - -Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -Author: Alexander Kanavin <alex.kanavin@gmail.com> -Signed-off-by: Manuel Bachmann <manuel.bachmann@iot.bzh> ---- - configure.ac | 18 + - data/rygel.conf | 6 +- - src/plugins/Makefile.am | 5 + - src/plugins/lms/Makefile.am | 46 +++ - src/plugins/lms/README | 17 + - src/plugins/lms/lms.plugin.in | 7 + - src/plugins/lms/rygel-lms-album.vala | 173 +++++++++ - src/plugins/lms/rygel-lms-albums.vala | 175 +++++++++ - src/plugins/lms/rygel-lms-all-images.vala | 95 +++++ - src/plugins/lms/rygel-lms-all-music.vala | 169 ++++++++ - src/plugins/lms/rygel-lms-all-videos.vala | 123 ++++++ - src/plugins/lms/rygel-lms-artist.vala | 75 ++++ - src/plugins/lms/rygel-lms-artists.vala | 62 +++ - src/plugins/lms/rygel-lms-category-container.vala | 428 +++++++++++++++++++++ - src/plugins/lms/rygel-lms-collate.c | 49 +++ - src/plugins/lms/rygel-lms-database.vala | 294 ++++++++++++++ - src/plugins/lms/rygel-lms-dbus-interfaces.vala | 30 ++ - src/plugins/lms/rygel-lms-image-root.vala | 35 ++ - src/plugins/lms/rygel-lms-image-year.vala | 114 ++++++ - src/plugins/lms/rygel-lms-image-years.vala | 59 +++ - src/plugins/lms/rygel-lms-music-root.vala | 36 ++ - src/plugins/lms/rygel-lms-plugin-factory.vala | 40 ++ - src/plugins/lms/rygel-lms-plugin.vala | 35 ++ - src/plugins/lms/rygel-lms-root-container.vala | 58 +++ - src/plugins/lms/rygel-lms-sql-function.vala | 31 ++ - src/plugins/lms/rygel-lms-sql-operator.vala | 73 ++++ - 26 files changed, 2252 insertions(+), 1 deletion(-) - create mode 100644 src/plugins/lms/Makefile.am - create mode 100644 src/plugins/lms/README - create mode 100644 src/plugins/lms/lms.plugin.in - create mode 100644 src/plugins/lms/rygel-lms-album.vala - create mode 100644 src/plugins/lms/rygel-lms-albums.vala - create mode 100644 src/plugins/lms/rygel-lms-all-images.vala - create mode 100644 src/plugins/lms/rygel-lms-all-music.vala - create mode 100644 src/plugins/lms/rygel-lms-all-videos.vala - create mode 100644 src/plugins/lms/rygel-lms-artist.vala - create mode 100644 src/plugins/lms/rygel-lms-artists.vala - create mode 100644 src/plugins/lms/rygel-lms-category-container.vala - create mode 100644 src/plugins/lms/rygel-lms-collate.c - create mode 100644 src/plugins/lms/rygel-lms-database.vala - create mode 100644 src/plugins/lms/rygel-lms-dbus-interfaces.vala - create mode 100644 src/plugins/lms/rygel-lms-image-root.vala - create mode 100644 src/plugins/lms/rygel-lms-image-year.vala - create mode 100644 src/plugins/lms/rygel-lms-image-years.vala - create mode 100644 src/plugins/lms/rygel-lms-music-root.vala - create mode 100644 src/plugins/lms/rygel-lms-plugin-factory.vala - create mode 100644 src/plugins/lms/rygel-lms-plugin.vala - create mode 100644 src/plugins/lms/rygel-lms-root-container.vala - create mode 100644 src/plugins/lms/rygel-lms-sql-function.vala - create mode 100644 src/plugins/lms/rygel-lms-sql-operator.vala - -diff --git a/configure.ac b/configure.ac -index 7ae1105..275fd99 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -170,6 +170,18 @@ AS_IF([test "x$enable_ruih_plugin" = "xyes"], - libxml-2.0 >= $LIBXML_REQUIRED]) - ]) - -+ -+RYGEL_ADD_PLUGIN([lms],[LightMediaScanner],[yes]) -+AS_IF([test "x$enable_lms_plugin" = "xyes"], -+ [ -+ PKG_CHECK_MODULES([RYGEL_PLUGIN_LMS_DEPS], -+ [$RYGEL_COMMON_MODULES -+ gio-2.0 >= $GIO_REQUIRED -+ sqlite3 >= $LIBSQLITE3_REQUIRED]) -+ RYGEL_PLUGIN_LMS_DEPS_VALAFLAGS="$RYGEL_COMMON_MODULES_VALAFLAGS --pkg gio-2.0 --pkg gee-0.8 --pkg sqlite3" -+ AC_SUBST([RYGEL_PLUGIN_LMS_DEPS_VALAFLAGS]) -+ ]) -+ - AS_IF([test "x$with_media_engine" = "xgstreamer"], - [ - RYGEL_ADD_PLUGIN([playbin],[GStreamer playbin],[yes]) -@@ -332,6 +344,11 @@ then - fi - fi - -+dnl Check additional requirements for LMS plugin -+if test "x$enable_lms_plugin" = "xyes"; -+then -+ RYGEL_CHECK_PACKAGES([sqlite3]) -+fi - - RYGEL_ADD_PLUGIN([tracker],[Tracker],[yes]) - AS_IF([test "x$enable_tracker_plugin" = "xyes"], -@@ -513,6 +530,7 @@ echo " - version: ${tracker_api_version} - mediathek: ${enable_mediathek_plugin} - media-export ${enable_media_export_plugin} -+ lightmediascanner ${enable_lms_plugin} - external: ${enable_external_plugin} - MPRIS2: ${enable_mpris_plugin} - gst-launch: ${enable_gst_launch_plugin} -diff --git a/data/rygel.conf b/data/rygel.conf -index 6b1c1c4..8677a0d 100644 ---- a/data/rygel.conf -+++ b/data/rygel.conf -@@ -99,7 +99,7 @@ strict-sharing=false - title=@REALNAME@'s media on @PRETTY_HOSTNAME@ - - [MediaExport] --enabled=true -+enabled=false - title=@REALNAME@'s media on @PRETTY_HOSTNAME@ - # List of URIs to export. Following variables are automatically substituted by - # the appropriate XDG standard media folders by Rygel for you. -@@ -114,6 +114,10 @@ monitor-changes=true - monitor-grace-timeout=5 - virtual-folders=true - -+[LightMediaScanner] -+enabled=true -+title=My Media -+ - [Playbin] - enabled=true - title=Audio/Video playback on @PRETTY_HOSTNAME@ -diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am -index d116f09..40791f0 100644 ---- a/src/plugins/Makefile.am -+++ b/src/plugins/Makefile.am -@@ -10,6 +10,10 @@ if BUILD_MEDIA_EXPORT_PLUGIN - MEDIA_EXPORT_PLUGIN = media-export - endif - -+if BUILD_LMS_PLUGIN -+LMS_PLUGIN = lms -+endif -+ - if BUILD_EXTERNAL_PLUGIN - EXTERNAL_PLUGIN = external - endif -@@ -33,6 +37,7 @@ endif - SUBDIRS = $(TRACKER_PLUGIN) \ - $(MEDIATHEK_PLUGIN) \ - $(MEDIA_EXPORT_PLUGIN) \ -+ $(LMS_PLUGIN) \ - $(EXTERNAL_PLUGIN) \ - $(MPRIS_PLUGIN) \ - $(GST_LAUNCH_PLUGIN) \ -diff --git a/src/plugins/lms/Makefile.am b/src/plugins/lms/Makefile.am -new file mode 100644 -index 0000000..f96a2ab ---- /dev/null -+++ b/src/plugins/lms/Makefile.am -@@ -0,0 +1,46 @@ -+include $(top_srcdir)/common.am -+ -+plugin_LTLIBRARIES = librygel-lms.la -+plugin_DATA = lms.plugin -+ -+librygel_lms_la_SOURCES = \ -+ rygel-lms-plugin.vala \ -+ rygel-lms-plugin-factory.vala \ -+ rygel-lms-root-container.vala \ -+ rygel-lms-music-root.vala \ -+ rygel-lms-image-root.vala \ -+ rygel-lms-category-container.vala \ -+ rygel-lms-all-music.vala \ -+ rygel-lms-album.vala \ -+ rygel-lms-albums.vala \ -+ rygel-lms-artist.vala \ -+ rygel-lms-artists.vala \ -+ rygel-lms-all-videos.vala \ -+ rygel-lms-database.vala \ -+ rygel-lms-all-images.vala \ -+ rygel-lms-image-years.vala \ -+ rygel-lms-image-year.vala \ -+ rygel-lms-sql-function.vala \ -+ rygel-lms-sql-operator.vala \ -+ rygel-lms-collate.c \ -+ rygel-lms-dbus-interfaces.vala -+ -+librygel_lms_la_VALAFLAGS = \ -+ --enable-experimental \ -+ $(RYGEL_PLUGIN_LMS_DEPS_VALAFLAGS) \ -+ $(RYGEL_COMMON_LIBRYGEL_SERVER_VALAFLAGS) \ -+ $(RYGEL_COMMON_VALAFLAGS) -+ -+librygel_lms_la_CFLAGS = \ -+ $(RYGEL_PLUGIN_LMS_DEPS_CFLAGS) \ -+ $(RYGEL_COMMON_LIBRYGEL_SERVER_CFLAGS) \ -+ -DG_LOG_DOMAIN='"Lms"' -+ -+librygel_lms_la_LIBADD = \ -+ $(RYGEL_PLUGIN_LMS_DEPS_LIBS) \ -+ $(RYGEL_COMMON_LIBRYGEL_SERVER_LIBS) -+ -+librygel_lms_la_LDFLAGS = \ -+ $(RYGEL_PLUGIN_LINKER_FLAGS) -+ -+EXTRA_DIST = lms.plugin.in -diff --git a/src/plugins/lms/README b/src/plugins/lms/README -new file mode 100644 -index 0000000..b741806 ---- /dev/null -+++ b/src/plugins/lms/README -@@ -0,0 +1,17 @@ -+rygel-lms -+========= -+ -+A rygel mediaserver plugin that exposes a lightmediascanner database -+as a Mediaserver. -+ -+Configuration in rygel.conf: -+ -+ [LightMediaScanner] -+ db-path=/path/to/lightmediascannerd.sqlite3 -+ title=My Media -+ -+* Supports browsing and searching (but in many cases searches will -+ still fall back to the inefficient simple_search()). -+* UpdateIDs are not yet supported as lightmediascanner seems to have -+ not change signal support yet -+* No real DLNA CTT testing has been done so far -diff --git a/src/plugins/lms/lms.plugin.in b/src/plugins/lms/lms.plugin.in -new file mode 100644 -index 0000000..9db9895 ---- /dev/null -+++ b/src/plugins/lms/lms.plugin.in -@@ -0,0 +1,7 @@ -+[Plugin] -+Version = @VERSION@ -+Module = lms -+Name = LMS -+License = LGPL -+Description = LMS DMS plugin for Rygel -+Copyright = Copyright © Intel -diff --git a/src/plugins/lms/rygel-lms-album.vala b/src/plugins/lms/rygel-lms-album.vala -new file mode 100644 -index 0000000..4fea17a ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-album.vala -@@ -0,0 +1,173 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.Album : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL_TEMPLATE = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name as artist, " + -+ "audio_albums.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = %s " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT_TEMPLATE = -+ "SELECT COUNT(audios.id) " + -+ "FROM audios, files " + -+ "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = %s;"; -+ -+ private static const string SQL_COUNT_WITH_FILTER_TEMPLATE = -+ "SELECT COUNT(audios.id), audios.title as title, " + -+ "audio_artists.name as artist, " + -+ "audio_albums.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = %s;"; -+ -+ private static const string SQL_FIND_OBJECT_TEMPLATE = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name, " + -+ "audio_albums.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "WHERE dtime = 0 AND files.id = ? AND audios.id = files.id AND audios.album_id = %s;"; -+ -+ private static const string SQL_ADDED_TEMPLATE = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name as artist, " + -+ "audio_albums.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = %s " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ private static const string SQL_REMOVED_TEMPLATE = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name as artist, " + -+ "audio_albums.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "WHERE dtime <> 0 AND audios.id = files.id AND audios.album_id = %s " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ var id = statement.column_int (0); -+ var path = statement.column_text (1); -+ var mime_type = statement.column_text(10); -+ -+ if (mime_type == null || mime_type.length == 0) { -+ /* TODO is this correct? */ -+ debug ("Music item %d (%s) has no MIME type", -+ id, -+ path); -+ } -+ -+ var title = statement.column_text(3); -+ var song_id = this.build_child_id (id); -+ var song = new MusicItem (song_id, this, title); -+ song.ref_id = this.build_reference_id (id); -+ song.size = statement.column_int(2); -+ song.track_number = statement.column_int(4); -+ song.duration = statement.column_int(5); -+ song.channels = statement.column_int(6); -+ song.sample_freq = statement.column_int(7); -+ song.bitrate = statement.column_int(8); -+ song.dlna_profile = statement.column_text(9); -+ song.mime_type = mime_type; -+ song.artist = statement.column_text(11); -+ song.album = statement.column_text(12); -+ File file = File.new_for_path (path); -+ song.add_uri (file.get_uri ()); -+ -+ return song; -+ } -+ -+ private static string get_sql_all (string db_id) { -+ return (SQL_ALL_TEMPLATE.printf (db_id)); -+ } -+ private static string get_sql_find_object (string db_id) { -+ return (SQL_FIND_OBJECT_TEMPLATE.printf (db_id)); -+ } -+ private static string get_sql_count (string db_id) { -+ return (SQL_COUNT_TEMPLATE.printf (db_id)); -+ } -+ private static string get_sql_added (string db_id) { -+ return (SQL_ADDED_TEMPLATE.printf (db_id)); -+ } -+ private static string get_sql_removed (string db_id) { -+ return (SQL_REMOVED_TEMPLATE.printf (db_id)); -+ } -+ -+ protected override string get_sql_all_with_filter (string filter) { -+ if (filter.length == 0) { -+ return this.sql_all; -+ } -+ var filter_str = "%s AND %s".printf (this.db_id, filter); -+ return (SQL_ALL_TEMPLATE.printf (filter_str)); -+ } -+ -+ protected override string get_sql_count_with_filter (string filter) { -+ if (filter.length == 0) { -+ return this.sql_count; -+ } -+ var filter_str = "%s AND %s".printf (this.db_id, filter); -+ return (SQL_COUNT_WITH_FILTER_TEMPLATE.printf (filter_str)); -+ } -+ -+ public Album (string db_id, -+ MediaContainer parent, -+ string title, -+ LMS.Database lms_db) { -+ base (db_id, -+ parent, -+ title, -+ lms_db, -+ get_sql_all (db_id), -+ get_sql_find_object (db_id), -+ get_sql_count (db_id), -+ get_sql_added (db_id), -+ get_sql_removed (db_id) -+ ); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-albums.vala b/src/plugins/lms/rygel-lms-albums.vala -new file mode 100644 -index 0000000..309a352 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-albums.vala -@@ -0,0 +1,175 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.Albums : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL = -+ "SELECT audio_albums.id, audio_albums.name as title, " + -+ "audio_artists.name as artist " + -+ "FROM audio_albums " + -+ "LEFT JOIN audio_artists " + -+ "ON audio_albums.artist_id = audio_artists.id " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_ALL_WITH_FILTER_TEMPLATE = -+ "SELECT audio_albums.id, audio_albums.name as title, " + -+ "audio_artists.name as artist " + -+ "FROM audio_albums " + -+ "LEFT JOIN audio_artists " + -+ "ON audio_albums.artist_id = audio_artists.id " + -+ "WHERE %s " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT = -+ "SELECT COUNT(audio_albums.id) " + -+ "FROM audio_albums;"; -+ -+ private static const string SQL_COUNT_WITH_FILTER_TEMPLATE = -+ "SELECT COUNT(audio_albums.id), audio_albums.name as title, " + -+ "audio_artists.name as artist " + -+ "FROM audio_albums " + -+ "LEFT JOIN audio_artists " + -+ "ON audio_albums.artist_id = audio_artists.id " + -+ "WHERE %s;"; -+ -+ /* count songs inside albums */ -+ private static const string SQL_CHILD_COUNT_WITH_FILTER_TEMPLATE = -+ "SELECT COUNT(audios.id), audios.title as title, " + -+ "audio_artists.name as artist " + -+ "FROM audios, files, audio_albums " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = audio_albums.id %s;"; -+ -+ /* select songs inside albums */ -+ private static const string SQL_CHILD_ALL_WITH_FILTER_TEMPLATE = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name as artist, " + -+ "audio_albums.name, audio_albums.id " + -+ "FROM audios, files, audio_albums " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = audio_albums.id %s " + -+ "LIMIT ? OFFSET ?;"; -+ -+ -+ private static const string SQL_FIND_OBJECT = -+ "SELECT audio_albums.id, audio_albums.name " + -+ "FROM audio_albums " + -+ "WHERE audio_albums.id = ?;"; -+ -+ protected override string get_sql_all_with_filter (string filter) { -+ if (filter.length == 0) { -+ return Albums.SQL_ALL; -+ } -+ return (Albums.SQL_ALL_WITH_FILTER_TEMPLATE.printf (filter)); -+ } -+ -+ protected override string get_sql_count_with_filter (string filter) { -+ if (filter.length == 0) { -+ return Albums.SQL_COUNT; -+ } -+ return (Albums.SQL_COUNT_WITH_FILTER_TEMPLATE.printf (filter)); -+ } -+ -+ protected override uint get_child_count_with_filter (string where_filter, -+ ValueArray args) -+ { -+ -+ /* search the children (albums) as usual */ -+ var count = base.get_child_count_with_filter (where_filter, args); -+ -+ /* now search the album contents */ -+ var filter = ""; -+ if (where_filter.length > 0) { -+ filter = "AND %s".printf (where_filter); -+ } -+ var query = Albums.SQL_CHILD_COUNT_WITH_FILTER_TEMPLATE.printf (filter); -+ try { -+ var stmt = this.lms_db.prepare_and_init (query, args.values); -+ if (stmt.step () == Sqlite.ROW) { -+ count += stmt.column_int (0); -+ } -+ } catch (DatabaseError e) { -+ warning ("Query failed: %s", e.message); -+ } -+ -+ return count; -+ } -+ -+ protected override MediaObjects? get_children_with_filter (string where_filter, -+ ValueArray args, -+ string sort_criteria, -+ uint offset, -+ uint max_count) { -+ var children = base. get_children_with_filter (where_filter, -+ args, -+ sort_criteria, -+ offset, -+ max_count); -+ var filter = ""; -+ if (where_filter.length > 0) { -+ filter = "AND %s".printf (where_filter); -+ } -+ var query = Albums.SQL_CHILD_ALL_WITH_FILTER_TEMPLATE.printf (filter); -+ try { -+ var stmt = this.lms_db.prepare_and_init (query, args.values); -+ while (Database.get_children_step (stmt)) { -+ var album_id = stmt.column_text (13); -+ var album = new Album (album_id, this, "", this.lms_db); -+ -+ var song = album.object_from_statement (stmt); -+ song.parent_ref = song.parent; -+ children.add (song); -+ -+ } -+ } catch (DatabaseError e) { -+ warning ("Query failed: %s", e.message); -+ } -+ -+ return children; -+ } -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ var id = "%d".printf (statement.column_int (0)); -+ LMS.Album album = new LMS.Album (id, -+ this, -+ statement.column_text (1), -+ this.lms_db); -+ return album; -+ } -+ -+ public Albums (MediaContainer parent, -+ LMS.Database lms_db) { -+ base ("albums", -+ parent, -+ _("Albums"), -+ lms_db, -+ Albums.SQL_ALL, -+ Albums.SQL_FIND_OBJECT, -+ Albums.SQL_COUNT, -+ null, null); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-all-images.vala b/src/plugins/lms/rygel-lms-all-images.vala -new file mode 100644 -index 0000000..0b54c7f ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-all-images.vala -@@ -0,0 +1,95 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.AllImages : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL = -+ "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime " + -+ "FROM images, files " + -+ "WHERE dtime = 0 AND images.id = files.id " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT = -+ "SELECT count(images.id) " + -+ "FROM images, files " + -+ "WHERE dtime = 0 AND images.id = files.id;"; -+ -+ private static const string SQL_FIND_OBJECT = -+ "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime " + -+ "FROM images, files " + -+ "WHERE dtime = 0 AND files.id = ? AND images.id = files.id;"; -+ -+ private static const string SQL_ADDED = -+ "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime " + -+ "FROM images, files " + -+ "WHERE dtime = 0 AND images.id = files.id " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ private static const string SQL_REMOVED = -+ "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime " + -+ "FROM images, files " + -+ "WHERE dtime <> 0 AND images.id = files.id " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ var id = statement.column_int(0); -+ var path = statement.column_text(6); -+ var mime_type = statement.column_text(9); -+ -+ if (mime_type == null || mime_type.length == 0){ -+ /* TODO is this correct? */ -+ debug ("Image item %d (%s) has no MIME type", -+ id, -+ path); -+ } -+ -+ var title = statement.column_text(1); -+ var image = new ImageItem(this.build_child_id (id), this, title); -+ image.creator = statement.column_text(2); -+ TimeVal tv = { (long) statement.column_int(3), (long) 0 }; -+ image.date = tv.to_iso8601 (); -+ image.width = statement.column_int(4); -+ image.height = statement.column_int(5); -+ image.size = statement.column_int(7); -+ image.mime_type = mime_type; -+ image.dlna_profile = statement.column_text(8); -+ File file = File.new_for_path(path); -+ image.add_uri (file.get_uri ()); -+ -+ return image; -+ } -+ -+ public AllImages (MediaContainer parent, LMS.Database lms_db) { -+ base ("all", -+ parent, -+ _("All"), -+ lms_db, -+ AllImages.SQL_ALL, -+ AllImages.SQL_FIND_OBJECT, -+ AllImages.SQL_COUNT, -+ AllImages.SQL_ADDED, -+ AllImages.SQL_REMOVED -+ ); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-all-music.vala b/src/plugins/lms/rygel-lms-all-music.vala -new file mode 100644 -index 0000000..2a7226f ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-all-music.vala -@@ -0,0 +1,169 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.AllMusic : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL_TEMPLATE = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name as artist, " + -+ "audio_albums.name, " + -+ "files.mtime, " + -+ "audio_genres.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "LEFT JOIN audio_genres " + -+ "ON audios.genre_id = audio_genres.id " + -+ "WHERE dtime = 0 AND audios.id = files.id %s " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT = -+ "SELECT COUNT(audios.id) " + -+ "FROM audios, files " + -+ "WHERE dtime = 0 AND audios.id = files.id;"; -+ -+ private static const string SQL_COUNT_WITH_FILTER_TEMPLATE = -+ "SELECT COUNT(audios.id), audios.title as title, " + -+ "audio_artists.name as artist " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "WHERE dtime = 0 AND audios.id = files.id %s;"; -+ -+ private static const string SQL_FIND_OBJECT = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name, " + -+ "audio_albums.name, " + -+ "files.mtime, " + -+ "audio_genres.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "LEFT JOIN audio_genres " + -+ "ON audios.genre_id = audio_genres.id " + -+ "WHERE dtime = 0 AND files.id = ? AND audios.id = files.id;"; -+ -+ private static const string SQL_ADDED = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name as artist, " + -+ "audio_albums.name, " + -+ "files.mtime, " + -+ "audio_genres.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "LEFT JOIN audio_genres " + -+ "ON audios.genre_id = audio_genres.id " + -+ "WHERE dtime = 0 AND audios.id = files.id " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ private static const string SQL_REMOVED = -+ "SELECT files.id, files.path, files.size, " + -+ "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + -+ "audio_artists.name as artist, " + -+ "audio_albums.name, " + -+ "files.mtime, " + -+ "audio_genres.name " + -+ "FROM audios, files " + -+ "LEFT JOIN audio_artists " + -+ "ON audios.artist_id = audio_artists.id " + -+ "LEFT JOIN audio_albums " + -+ "ON audios.album_id = audio_albums.id " + -+ "LEFT JOIN audio_genres " + -+ "ON audios.genre_id = audio_genres.id " + -+ "WHERE dtime <> 0 AND audios.id = files.id " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ protected override string get_sql_all_with_filter (string filter) { -+ if (filter.length == 0) { -+ return this.sql_all; -+ } -+ var filter_str = "AND %s".printf (filter); -+ return (AllMusic.SQL_ALL_TEMPLATE.printf (filter_str)); -+ } -+ -+ protected override string get_sql_count_with_filter (string filter) { -+ if (filter.length == 0) { -+ return this.sql_count; -+ } -+ var filter_str = "AND %s".printf (filter); -+ return (AllMusic.SQL_COUNT_WITH_FILTER_TEMPLATE.printf (filter_str)); -+ } -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ var id = statement.column_int (0); -+ var path = statement.column_text (1); -+ var mime_type = statement.column_text(10); -+ -+ if (mime_type == null || mime_type.length == 0) { -+ /* TODO is this correct? */ -+ debug ("Music item %d (%s) has no MIME type", -+ id, -+ path); -+ } -+ -+ var title = statement.column_text(3); -+ var song_id = this.build_child_id (id); -+ var song = new MusicItem (song_id, this, title); -+ song.size = statement.column_int(2); -+ song.track_number = statement.column_int(4); -+ song.duration = statement.column_int(5); -+ song.channels = statement.column_int(6); -+ song.sample_freq = statement.column_int(7); -+ song.bitrate = statement.column_int(8); -+ song.dlna_profile = statement.column_text(9); -+ song.mime_type = mime_type; -+ song.artist = statement.column_text(11); -+ song.album = statement.column_text(12); -+ TimeVal tv = { (long) statement.column_int(13), (long) 0 }; -+ song.date = tv.to_iso8601 (); -+ song.genre = statement.column_text(14); -+ File file = File.new_for_path (path); -+ song.add_uri (file.get_uri ()); -+ -+ return song; -+ } -+ -+ public AllMusic (MediaContainer parent, LMS.Database lms_db) { -+ base("all", -+ parent, -+ _("All"), -+ lms_db, -+ AllMusic.SQL_ALL_TEMPLATE.printf (""), -+ AllMusic.SQL_FIND_OBJECT, -+ AllMusic.SQL_COUNT, -+ AllMusic.SQL_ADDED, -+ AllMusic.SQL_REMOVED -+ ); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-all-videos.vala b/src/plugins/lms/rygel-lms-all-videos.vala -new file mode 100644 -index 0000000..dbde0db ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-all-videos.vala -@@ -0,0 +1,123 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.AllVideos : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL = -+ "SELECT videos.id, title, artist, length, path, mtime, size, dlna_profile, dlna_mime " + -+ "FROM videos, files " + -+ "WHERE dtime = 0 AND videos.id = files.id " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT = -+ "SELECT count(videos.id) " + -+ "FROM videos, files " + -+ "WHERE dtime = 0 AND videos.id = files.id;"; -+ -+ private static const string SQL_FIND_OBJECT = -+ "SELECT videos.id, title, artist, length, path, mtime, size, dlna_profile, dlna_mime " + -+ "FROM videos, files " + -+ "WHERE dtime = 0 AND files.id = ? AND videos.id = files.id;"; -+ -+ private static const string SQL_ADDED = -+ "SELECT videos.id, title, artist, length, path, mtime, size, dlna_profile, dlna_mime " + -+ "FROM videos, files " + -+ "WHERE dtime = 0 AND videos.id = files.id " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ private static const string SQL_REMOVED = -+ "SELECT videos.id, title, artist, length, path, mtime, size, dlna_profile, dlna_mime " + -+ "FROM videos, files " + -+ "WHERE dtime <> 0 AND videos.id = files.id " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ var id = statement.column_int(0); -+ var mime_type = statement.column_text(8); -+ var path = statement.column_text(4); -+ var file = File.new_for_path(path); -+ -+ /* TODO: Temporary code to extract the MIME TYPE. LMS does not seem -+ to compute the mime type of videos. Don't know why. */ -+ -+/* if (mime_type == null || mime_type.length == 0) { -+ try { -+ FileInfo info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE, -+ FileQueryInfoFlags.NONE, null); -+ mime_type = info.get_content_type(); -+ } catch {} -+ } -+*/ -+ -+ if (mime_type == null || mime_type.length == 0) { -+ /* TODO is this correct? */ -+ debug ("Video item %d (%s) has no MIME type", -+ id, -+ path); -+ } -+ -+ var title = statement.column_text(1); -+ var video = new VideoItem(this.build_child_id (id), this, title); -+ video.creator = statement.column_text(2); -+ video.duration = statement.column_int(3); -+ TimeVal tv = { (long) statement.column_int(5), (long) 0 }; -+ video.date = tv.to_iso8601 (); -+ video.size = statement.column_int(6); -+ video.dlna_profile = statement.column_text(7); -+ video.mime_type = mime_type; -+ video.add_uri (file.get_uri ()); -+ -+ // Rygel does not support multiple video and audio tracks in a single file, -+ // so we just take the first one -+ var video_data = "select videos_videos.bitrate + videos_audios.bitrate, width, height, channels, sampling_rate " + -+ "from videos, videos_audios, videos_videos where videos.id = ? " + -+ "and videos.id = videos_audios.video_id and videos.id = videos_videos.video_id;"; -+ try { -+ var stmt = this.lms_db.prepare(video_data); -+ Rygel.LMS.Database.find_object("%d".printf(id), stmt); -+ video.bitrate = stmt.column_int(0) / 8; //convert bits per second into bytes per second -+ video.width = stmt.column_int(1); -+ video.height = stmt.column_int(2); -+ video.channels = stmt.column_int(3); -+ video.sample_freq = stmt.column_int(4); -+ } catch (DatabaseError e) { -+ warning ("Query failed: %s", e.message); -+ } -+ -+ return video; -+ } -+ -+ public AllVideos (string id, MediaContainer parent, string title, LMS.Database lms_db){ -+ base (id, -+ parent, -+ title, -+ lms_db, -+ AllVideos.SQL_ALL, -+ AllVideos.SQL_FIND_OBJECT, -+ AllVideos.SQL_COUNT, -+ AllVideos.SQL_ADDED, -+ AllVideos.SQL_REMOVED -+ ); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-artist.vala b/src/plugins/lms/rygel-lms-artist.vala -new file mode 100644 -index 0000000..31e9070 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-artist.vala -@@ -0,0 +1,75 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.Artist : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL_TEMPLATE = -+ "SELECT audio_albums.id, audio_albums.name " + -+ "FROM audio_albums " + -+ "WHERE audio_albums.artist_id = %s " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT_TEMPLATE = -+ "SELECT COUNT(audio_albums.id) " + -+ "FROM audio_albums " + -+ "WHERE audio_albums.artist_id = %s"; -+ -+ private static const string SQL_FIND_OBJECT_TEMPLATE = -+ "SELECT audio_albums.id, audio_albums.name " + -+ "FROM audio_albums " + -+ "WHERE audio_albums.id = ? AND audio_albums.artist_id = %s;"; -+ -+ private static string get_sql_all (string id) { -+ return (SQL_ALL_TEMPLATE.printf (id)); -+ } -+ private static string get_sql_find_object (string id) { -+ return (SQL_FIND_OBJECT_TEMPLATE.printf (id)); -+ } -+ private static string get_sql_count (string id) { -+ return (SQL_COUNT_TEMPLATE.printf (id)); -+ } -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ var db_id = "%d".printf (statement.column_int (0)); -+ var title = statement.column_text (1); -+ return new LMS.Album (db_id, this, title, this.lms_db); -+ } -+ -+ public Artist (string id, -+ MediaContainer parent, -+ string title, -+ LMS.Database lms_db) { -+ -+ base (id, -+ parent, -+ title, -+ lms_db, -+ get_sql_all (id), -+ get_sql_find_object (id), -+ get_sql_count (id), -+ null, // LMS does not track adding or removing albums -+ null -+ ); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-artists.vala b/src/plugins/lms/rygel-lms-artists.vala -new file mode 100644 -index 0000000..a00b2ce ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-artists.vala -@@ -0,0 +1,62 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.Artists : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL = -+ "SELECT audio_artists.id, audio_artists.name " + -+ "FROM audio_artists " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT = -+ "SELECT COUNT(audio_artists.id) " + -+ "FROM audio_artists;"; -+ -+ private static const string SQL_FIND_OBJECT = -+ "SELECT audio_artists.id, audio_artists.name " + -+ "FROM audio_artists " + -+ "WHERE audio_artists.id = ?;"; -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ var db_id = "%d".printf (statement.column_int (0)); -+ var title = statement.column_text (1); -+ -+ return new LMS.Artist (db_id, this, title, this.lms_db); -+ } -+ -+ public Artists (string id, -+ MediaContainer parent, -+ string title, -+ LMS.Database lms_db) { -+ base (id, -+ parent, -+ title, -+ lms_db, -+ Artists.SQL_ALL, -+ Artists.SQL_FIND_OBJECT, -+ Artists.SQL_COUNT, -+ null, null -+ ); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-category-container.vala b/src/plugins/lms/rygel-lms-category-container.vala -new file mode 100644 -index 0000000..e5430d1 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-category-container.vala -@@ -0,0 +1,428 @@ -+/* -+ * Copyright (C) 2009,2010 Jens Georg <mail@jensge.org>, -+ * (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Gee; -+using Sqlite; -+ -+public errordomain Rygel.LMS.CategoryContainerError { -+ SQLITE_ERROR, -+ GENERAL_ERROR, -+ INVALID_TYPE, -+ UNSUPPORTED_SEARCH -+} -+ -+public abstract class Rygel.LMS.CategoryContainer : Rygel.MediaContainer, -+ Rygel.TrackableContainer, -+ Rygel.SearchableContainer { -+ public ArrayList<string> search_classes { get; set; } -+ -+ public unowned LMS.Database lms_db { get; construct; } -+ -+ public string db_id { get; construct; } -+ -+ public string sql_all { get; construct; } -+ public string sql_find_object { get; construct; } -+ public string sql_count { get; construct; } -+ public string sql_added { get; construct; } -+ public string sql_removed { get; construct; } -+ -+ protected Statement stmt_all; -+ protected Statement stmt_find_object; -+ protected Statement stmt_added; -+ protected Statement stmt_removed; -+ -+ protected string child_prefix; -+ protected string ref_prefix; -+ -+ protected abstract MediaObject? object_from_statement (Statement statement); -+ -+ /* TODO these should be abstract */ -+ protected virtual string get_sql_all_with_filter (string filter) { -+ return this.sql_all; -+ } -+ protected virtual string get_sql_count_with_filter (string filter) { -+ return this.sql_count; -+ } -+ -+ private static string? map_operand_to_column (string operand, -+ out string? collate = null, -+ bool for_sort = false) -+ throws Error { -+ string column = null; -+ bool use_collation = false; -+ -+ // TODO add all used aliases to sql queries -+ switch (operand) { -+ case "dc:title": -+ column = "title"; -+ use_collation = true; -+ break; -+ case "upnp:artist": -+ column = "artist"; -+ use_collation = true; -+ break; -+ case "dc:creator": -+ column = "creator"; -+ use_collation = true; -+ break; -+ default: -+ var message = "Unsupported column %s".printf (operand); -+ -+ throw new CategoryContainerError.UNSUPPORTED_SEARCH (message); -+ } -+ -+ if (use_collation) { -+ collate = "COLLATE CASEFOLD"; -+ } else { -+ collate = ""; -+ } -+ -+ return column; -+ } -+ -+ private static string? relational_expression_to_sql -+ (RelationalExpression exp, -+ GLib.ValueArray args) -+ throws Error { -+ GLib.Value? v = null; -+ string collate = null; -+ -+ string column = CategoryContainer.map_operand_to_column (exp.operand1, -+ out collate); -+ SqlOperator operator; -+ -+ switch (exp.op) { -+ case GUPnP.SearchCriteriaOp.EXISTS: -+ string sql_function; -+ if (exp.operand2 == "true") { -+ sql_function = "%s IS NOT NULL AND %s != ''"; -+ } else { -+ sql_function = "%s IS NULL OR %s = ''"; -+ } -+ -+ return sql_function.printf (column, column); -+ case GUPnP.SearchCriteriaOp.EQ: -+ case GUPnP.SearchCriteriaOp.NEQ: -+ case GUPnP.SearchCriteriaOp.LESS: -+ case GUPnP.SearchCriteriaOp.LEQ: -+ case GUPnP.SearchCriteriaOp.GREATER: -+ case GUPnP.SearchCriteriaOp.GEQ: -+ v = exp.operand2; -+ operator = new SqlOperator.from_search_criteria_op -+ (exp.op, column, collate); -+ break; -+ case GUPnP.SearchCriteriaOp.CONTAINS: -+ operator = new SqlFunction ("contains", column); -+ v = exp.operand2; -+ break; -+ case GUPnP.SearchCriteriaOp.DOES_NOT_CONTAIN: -+ operator = new SqlFunction ("NOT contains", column); -+ v = exp.operand2; -+ break; -+ case GUPnP.SearchCriteriaOp.DERIVED_FROM: -+ operator = new SqlOperator ("LIKE", column); -+ v = "%s%%".printf (exp.operand2); -+ break; -+ default: -+ warning ("Unsupported op %d", exp.op); -+ return null; -+ } -+ -+ if (v != null) { -+ args.append (v); -+ } -+ -+ return operator.to_string (); -+ } -+ -+ private static string logical_expression_to_sql -+ (LogicalExpression expression, -+ GLib.ValueArray args) -+ throws Error { -+ string left_sql_string = CategoryContainer.search_expression_to_sql -+ (expression.operand1, -+ args); -+ string right_sql_string = CategoryContainer.search_expression_to_sql -+ (expression.operand2, -+ args); -+ unowned string operator_sql_string = "OR"; -+ -+ if (expression.op == LogicalOperator.AND) { -+ operator_sql_string = "AND"; -+ } -+ -+ return "(%s %s %s)".printf (left_sql_string, -+ operator_sql_string, -+ right_sql_string); -+ } -+ -+ private static string? search_expression_to_sql -+ (SearchExpression? expression, -+ GLib.ValueArray args) -+ throws Error { -+ if (expression == null) { -+ return ""; -+ } -+ -+ if (expression is LogicalExpression) { -+ return CategoryContainer.logical_expression_to_sql -+ (expression as LogicalExpression, args); -+ } else { -+ return CategoryContainer.relational_expression_to_sql -+ (expression as RelationalExpression, -+ args); -+ } -+ } -+ -+ protected virtual uint get_child_count_with_filter (string where_filter, -+ ValueArray args) -+ { -+ var query = this.get_sql_count_with_filter (where_filter); -+ try { -+ var stmt = this.lms_db.prepare_and_init (query, args.values); -+ if (stmt.step () != Sqlite.ROW) { -+ return 0; -+ } -+ return stmt.column_int (0); -+ } catch (DatabaseError e) { -+ warning ("Query failed: %s", e.message); -+ return 0; -+ } -+ } -+ -+ protected virtual MediaObjects? get_children_with_filter (string where_filter, -+ ValueArray args, -+ string sort_criteria, -+ uint offset, -+ uint max_count) { -+ var children = new MediaObjects (); -+ GLib.Value v = max_count; -+ args.append (v); -+ v = offset; -+ args.append (v); -+ -+ var query = this.get_sql_all_with_filter (where_filter); -+ try { -+ var stmt = this.lms_db.prepare_and_init (query, args.values); -+ while (Database.get_children_step (stmt)) { -+ children.add (this.object_from_statement (stmt)); -+ } -+ } catch (DatabaseError e) { -+ warning ("Query failed: %s", e.message); -+ } -+ -+ return children; -+ } -+ -+ public async MediaObjects? search (SearchExpression? expression, -+ uint offset, -+ uint max_count, -+ out uint total_matches, -+ string sort_criteria, -+ Cancellable? cancellable) -+ throws Error { -+ debug ("search()"); -+ try { -+ var args = new GLib.ValueArray (0); -+ var filter = CategoryContainer.search_expression_to_sql (expression, -+ args); -+ total_matches = this.get_child_count_with_filter (filter, args); -+ -+ if (expression != null) { -+ debug (" Original search: %s", expression.to_string ()); -+ debug (" Parsed search expression: %s", filter); -+ debug (" Filtered cild count is %u", total_matches); -+ } -+ -+ if (max_count == 0) { -+ max_count = uint.MAX; -+ } -+ return this.get_children_with_filter (filter, -+ args, -+ sort_criteria, -+ offset, -+ max_count); -+ } catch (Error e) { -+ debug (" Falling back to simple_search(): %s", e.message); -+ return yield this.simple_search (expression, -+ offset, -+ max_count, -+ out total_matches, -+ sort_criteria, -+ cancellable); -+ } -+ } -+ -+ public async override MediaObjects? get_children (uint offset, -+ uint max_count, -+ string sort_criteria, -+ Cancellable? cancellable) -+ throws Error { -+ MediaObjects retval = new MediaObjects (); -+ -+ Database.get_children_init (this.stmt_all, -+ offset, -+ max_count, -+ sort_criteria); -+ while (Database.get_children_step (this.stmt_all)) { -+ retval.add (this.object_from_statement (this.stmt_all)); -+ } -+ -+ return retval; -+ } -+ -+ public async override MediaObject? find_object (string id, -+ Cancellable? cancellable) -+ throws Error { -+ if (!id.has_prefix (this.child_prefix)) { -+ /* can't match anything in this container */ -+ return null; -+ } -+ -+ MediaObject object = null; -+ -+ /* remove parent section from id */ -+ var real_id = id.substring (this.child_prefix.length); -+ /* remove grandchildren from id */ -+ var index = real_id.index_of_char (':'); -+ if (index > 0) { -+ real_id = real_id.slice (0, index); -+ } -+ -+ try { -+ Database.find_object (real_id, this.stmt_find_object); -+ var child = this.object_from_statement (this.stmt_find_object); -+ if (index < 0) { -+ object = child; -+ } else { -+ /* try grandchildren */ -+ var container = child as CategoryContainer; -+ object = yield container.find_object (id, cancellable); -+ -+ /* tell object to keep a reference to the parent -- -+ * otherwise parent is freed before object is serialized */ -+ object.parent_ref = object.parent; -+ } -+ } catch (DatabaseError e) { -+ debug ("find_object %s in %s: %s", id, this.id, e.message); -+ /* Happens e.g. if id is not an integer */ -+ } -+ -+ return object; -+ } -+ -+ protected string build_child_id (int db_id) { -+ return "%s%d".printf (this.child_prefix, db_id); -+ } -+ -+ protected string build_reference_id (int db_id) { -+ return "%s%d".printf (this.ref_prefix, db_id); -+ } -+ -+ protected async void add_child (MediaObject object) { -+ } -+ -+ protected async void remove_child (MediaObject object) { -+ } -+ -+ private void on_db_updated(uint64 old_id, uint64 new_id) { -+ try { -+ var stmt_count = this.lms_db.prepare (this.sql_count); -+ -+ if (stmt_count.step () == Sqlite.ROW) { -+ this.child_count = stmt_count.column_int (0); -+ } -+ -+ Database.get_children_with_update_id_init (this.stmt_added, -+ old_id, -+ new_id); -+ while (Database.get_children_step (this.stmt_added)) { -+ this.add_child_tracked.begin(this.object_from_statement (this.stmt_added)); -+ } -+ -+ Database.get_children_with_update_id_init (this.stmt_removed, -+ old_id, -+ new_id); -+ while (Database.get_children_step (this.stmt_removed)) { -+ this.remove_child_tracked.begin(this.object_from_statement (this.stmt_removed)); -+ } -+ -+ } catch (DatabaseError e) { -+ warning ("Can't perform container update: %s", e.message); -+ } -+ -+ } -+ -+ public CategoryContainer (string db_id, -+ MediaContainer parent, -+ string title, -+ LMS.Database lms_db, -+ string sql_all, -+ string sql_find_object, -+ string sql_count, -+ string? sql_added, -+ string? sql_removed -+ ) { -+ Object (id : "%s:%s".printf (parent.id, db_id), -+ db_id : db_id, -+ parent : parent, -+ title : title, -+ lms_db : lms_db, -+ sql_all : sql_all, -+ sql_find_object : sql_find_object, -+ sql_count : sql_count, -+ sql_added : sql_added, -+ sql_removed: sql_removed -+ ); -+ } -+ -+ construct { -+ this.search_classes = new ArrayList<string> (); -+ -+ this.child_prefix = "%s:".printf (this.id); -+ -+ var index = this.id.index_of_char (':'); -+ this.ref_prefix = this.id.slice (0, index) + ":all:"; -+ -+ try { -+ this.stmt_all = this.lms_db.prepare (this.sql_all); -+ this.stmt_find_object = this.lms_db.prepare (this.sql_find_object); -+ var stmt_count = this.lms_db.prepare (this.sql_count); -+ -+ if (stmt_count.step () == Sqlite.ROW) { -+ this.child_count = stmt_count.column_int (0); -+ } -+ // some container implementations don't have a reasonable way to provide -+ // id-based statements to fetch added or removed items -+ if (this.sql_added != null && this.sql_removed != null) { -+ this.stmt_added = this.lms_db.prepare (this.sql_added); -+ this.stmt_removed = this.lms_db.prepare (this.sql_removed); -+ lms_db.db_updated.connect(this.on_db_updated); -+ } -+ } catch (DatabaseError e) { -+ warning ("Container %s: %s", this.title, e.message); -+ } -+ -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-collate.c b/src/plugins/lms/rygel-lms-collate.c -new file mode 100644 -index 0000000..8eee80b ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-collate.c -@@ -0,0 +1,49 @@ -+/* -+ * Copyright (C) 2012 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. -+ */ -+ -+#include <glib.h> -+ -+#ifdef HAVE_UNISTRING -+# include <unistr.h> -+#endif -+ -+gint rygel_lms_utf8_collate_str (const char *a, gsize alen, -+ const char *b, gsize blen) -+{ -+ char *a_str, *b_str; -+ gint result; -+ -+ /* Make sure the passed strings are null terminated */ -+ a_str = g_strndup (a, alen); -+ b_str = g_strndup (b, blen); -+ -+#ifdef HAVE_UNISTRING -+ result = u8_strcoll (a_str, b_str); -+#else -+ return g_utf8_collate (a_str, b_str); -+#endif -+ -+ g_free (a_str); -+ g_free (b_str); -+ -+ return result; -+} -diff --git a/src/plugins/lms/rygel-lms-database.vala b/src/plugins/lms/rygel-lms-database.vala -new file mode 100644 -index 0000000..e898d66 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-database.vala -@@ -0,0 +1,294 @@ -+/* -+ * Copyright (C) 2009,2011 Jens Georg <mail@jensge.org>, -+ * (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Gee; -+using Sqlite; -+ -+public errordomain Rygel.LMS.DatabaseError { -+ OPEN, -+ PREPARE, -+ BIND, -+ STEP, -+ NOT_FOUND -+} -+ -+namespace Rygel.LMS { -+ extern static int utf8_collate_str (uint8[] a, uint8[] b); -+} -+ -+public class Rygel.LMS.Database { -+ -+ public signal void db_updated(uint64 old_update_id, uint64 new_update_id); -+ -+ private Sqlite.Database db; -+ private LMS.DBus lms_proxy; -+ private uint64 update_id; -+ -+ /** -+ * Function to implement the custom SQL function 'contains' -+ */ -+ private static void utf8_contains (Sqlite.Context context, -+ Sqlite.Value[] args) -+ requires (args.length == 2) { -+ if (args[0].to_text () == null || -+ args[1].to_text () == null) { -+ context.result_int (0); -+ -+ return; -+ } -+ -+ var pattern = Regex.escape_string (args[1].to_text ()); -+ if (Regex.match_simple (pattern, -+ args[0].to_text (), -+ RegexCompileFlags.CASELESS)) { -+ context.result_int (1); -+ } else { -+ context.result_int (0); -+ } -+ } -+ -+ /** -+ * 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; -+ _a.length = alen; -+ -+ unowned uint8[] _b = (uint8[]) b; -+ _b.length = blen; -+ -+ return LMS.utf8_collate_str (_a, _b); -+ } -+ -+ public Database () throws DatabaseError { -+ string db_path; -+ try { -+ lms_proxy = Bus.get_proxy_sync (BusType.SESSION, -+ "org.lightmediascanner", -+ "/org/lightmediascanner/Scanner1"); -+ db_path = lms_proxy.data_base_path; -+ debug ("Got db path %s from LMS over dbus", db_path); -+ update_id = lms_proxy.update_id; -+ debug ("Got updated id %lld from LMS over dbus", update_id); -+ lms_proxy.g_properties_changed.connect (this.on_lms_properties_changed); -+ -+ } catch (IOError e) { -+ warning("Couldn't get LMS Dbus proxy: %s", e.message); -+ db_path = Environment.get_user_config_dir() + -+ "/lightmediascannerd/db.sqlite3"; -+ debug ("Using default sqlite database location %s", db_path); -+ } -+ -+ Sqlite.Database.open (db_path, out this.db); -+ if (this.db.errcode () != Sqlite.OK) { -+ throw new DatabaseError.OPEN ("Failed to open '%s': %d", -+ db_path, -+ this.db.errcode () ); -+ } -+ -+ this.db.create_function ("contains", -+ 2, -+ Sqlite.UTF8, -+ null, -+ LMS.Database.utf8_contains, -+ null, -+ null); -+ -+ this.db.create_collation ("CASEFOLD", -+ Sqlite.UTF8, -+ LMS.Database.utf8_collate); -+ -+ } -+ -+ private void on_lms_properties_changed (DBusProxy lms_proxy, -+ Variant changed, -+ string[] invalidated) { -+ if (!changed.get_type().equal (VariantType.VARDICT)) { -+ return; -+ } -+ -+ foreach (var changed_prop in changed) { -+ var key = (string) changed_prop.get_child_value (0); -+ var value = changed_prop.get_child_value (1).get_child_value (0); -+ -+ debug ("LMS property %s changed value to %s", key, value.print(true)); -+ -+ switch (key) { -+ case "UpdateID": -+ db_updated(update_id, (uint64)value); -+ update_id = (uint64)value; -+ break; -+ } -+ } -+ } -+ -+ -+ public Statement prepare (string query_string) throws DatabaseError { -+ Statement statement; -+ -+ var err = this.db.prepare_v2 (query_string, -1, out statement); -+ if (err != Sqlite.OK) -+ throw new DatabaseError.PREPARE ("Unable to create statement '%s': %d", -+ query_string, -+ err); -+ return statement; -+ } -+ -+ -+ public Statement prepare_and_init (string query, -+ GLib.Value[]? arguments) -+ throws DatabaseError { -+ -+ Statement statement; -+ -+ var err = this.db.prepare_v2 (query, -1, out statement); -+ if (err != Sqlite.OK) -+ throw new DatabaseError.PREPARE ("Unable to create statement '%s': %d", -+ query, -+ err); -+ -+ for (var i = 1; i <= arguments.length; ++i) { -+ int sqlite_err; -+ unowned GLib.Value current_value = arguments[i - 1]; -+ -+ if (current_value.holds (typeof (int))) { -+ sqlite_err = statement.bind_int (i, current_value.get_int ()); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind value %d", -+ sqlite_err); -+ } else if (current_value.holds (typeof (int64))) { -+ sqlite_err = statement.bind_int64 (i, current_value.get_int64 ()); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind value %d", -+ sqlite_err); -+ } else if (current_value.holds (typeof (uint64))) { -+ sqlite_err = statement.bind_int64 (i, (int64) current_value.get_uint64 ()); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind value %d", -+ sqlite_err); -+ } else if (current_value.holds (typeof (long))) { -+ sqlite_err = statement.bind_int64 (i, current_value.get_long ()); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind value %d", -+ sqlite_err); -+ } else if (current_value.holds (typeof (uint))) { -+ sqlite_err = statement.bind_int64 (i, current_value.get_uint ()); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind value %d", -+ sqlite_err); -+ } else if (current_value.holds (typeof (string))) { -+ sqlite_err = statement.bind_text (i, current_value.get_string ()); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind value %d", -+ sqlite_err); -+ } else if (current_value.holds (typeof (void *))) { -+ if (current_value.peek_pointer () == null) { -+ sqlite_err = statement.bind_null (i); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind value %d", -+ sqlite_err); -+ } else { -+ assert_not_reached (); -+ } -+ } else { -+ var type = current_value.type (); -+ warning (_("Unsupported type %s"), type.name ()); -+ assert_not_reached (); -+ } -+ } -+ -+ return statement; -+ } -+ -+ public static void find_object(string id, Statement stmt) throws DatabaseError { -+ -+ (void) stmt.reset(); -+ -+ int integer_id = int.parse(id); -+ int sqlite_err = stmt.bind_int(1, integer_id); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind id %d", sqlite_err); -+ -+ sqlite_err = stmt.step(); -+ if (sqlite_err != Sqlite.ROW) -+ throw new DatabaseError.STEP("Unable to find id %s", id); -+ } -+ -+ public static void get_children_init (Statement stmt, -+ uint offset, uint max_count, string sort_criteria) throws DatabaseError { -+ -+ int sqlite_err; -+ -+ (void) stmt.reset(); -+ -+ sqlite_err = stmt.bind_int(1, (int) max_count); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind max_count %d", -+ sqlite_err); -+ -+ sqlite_err = stmt.bind_int(2, (int) offset); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind offset %d", -+ sqlite_err); -+ } -+ -+ public static void get_children_with_update_id_init (Statement stmt, -+ uint64 old_id, uint64 new_id) throws DatabaseError { -+ -+ int sqlite_err; -+ -+ (void) stmt.reset(); -+ -+ if (new_id < old_id) // id value wrapped over -+ sqlite_err = stmt.bind_int64(1, 0); -+ else -+ sqlite_err = stmt.bind_int64(1, (int64)old_id); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind old_id %d", -+ sqlite_err); -+ -+ sqlite_err = stmt.bind_int64(2, (int64)new_id); -+ if (sqlite_err != Sqlite.OK) -+ throw new DatabaseError.BIND("Unable to bind new_id %d", -+ sqlite_err); -+ } -+ -+ public static bool get_children_step(Statement stmt) throws DatabaseError { -+ -+ bool retval; -+ int sqlite_err; -+ -+ sqlite_err = stmt.step(); -+ retval = sqlite_err == Sqlite.ROW; -+ -+ if (!retval && (sqlite_err != Sqlite.DONE)) -+ throw new DatabaseError.STEP("Error iterating through rows %d", -+ sqlite_err); -+ -+ return retval; -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-dbus-interfaces.vala b/src/plugins/lms/rygel-lms-dbus-interfaces.vala -new file mode 100644 -index 0000000..13f00cb ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-dbus-interfaces.vala -@@ -0,0 +1,30 @@ -+/* -+ * Copyright (C) 2014 Intel Corporation. -+ * -+ * Author: Alexander Kanavin <alex.kanavin@gmail.com> -+ * -+ * 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. -+ */ -+ -+[DBus (name = "org.lightmediascanner.Scanner1")] -+interface Rygel.LMS.DBus : DBusProxy { -+ public abstract string data_base_path { owned get; } -+ [DBus (name = "UpdateID")] -+ public abstract uint64 update_id { get; } -+ -+ //TODO: add all the other API items which are currently unused -+} -\ No newline at end of file -diff --git a/src/plugins/lms/rygel-lms-image-root.vala b/src/plugins/lms/rygel-lms-image-root.vala -new file mode 100644 -index 0000000..466bbe2 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-image-root.vala -@@ -0,0 +1,35 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+ -+public class Rygel.LMS.ImageRoot : Rygel.SimpleContainer { -+ public ImageRoot (string id, -+ MediaContainer parent, -+ string title, -+ LMS.Database lms_db) { -+ base (id, parent, title); -+ -+ this.add_child_container (new AllImages (this, lms_db)); -+ this.add_child_container (new ImageYears (this, lms_db)); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-image-year.vala b/src/plugins/lms/rygel-lms-image-year.vala -new file mode 100644 -index 0000000..a7768f0 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-image-year.vala -@@ -0,0 +1,114 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.ImageYear : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL_TEMPLATE = -+ "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime, strftime('%Y', date, 'unixepoch') as year " + -+ "FROM images, files " + -+ "WHERE dtime = 0 AND images.id = files.id AND year = '%s' " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT_TEMPLATE = -+ "SELECT count(images.id), strftime('%Y', date, 'unixepoch') as year " + -+ "FROM images, files " + -+ "WHERE dtime = 0 AND images.id = files.id AND year = '%s';"; -+ -+ private static const string SQL_FIND_OBJECT_TEMPLATE = -+ "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime, strftime('%Y', date, 'unixepoch') as year " + -+ "FROM images, files " + -+ "WHERE dtime = 0 AND files.id = ? AND images.id = files.id AND year = '%s';"; -+ -+ private static const string SQL_ADDED_TEMPLATE = -+ "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime, strftime('%Y', date, 'unixepoch') as year " + -+ "FROM images, files " + -+ "WHERE dtime = 0 AND images.id = files.id AND year = '%s' " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ private static const string SQL_REMOVED_TEMPLATE = -+ "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime, strftime('%Y', date, 'unixepoch') as year " + -+ "FROM images, files " + -+ "WHERE dtime <> 0 AND images.id = files.id AND year = '%s' " + -+ "AND update_id > ? AND update_id <= ?;"; -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ var id = statement.column_int(0); -+ var path = statement.column_text(6); -+ var mime_type = statement.column_text(9); -+ -+ if (mime_type == null || mime_type.length == 0){ -+ /* TODO is this correct? */ -+ debug ("Image item %d (%s) has no MIME type", -+ id, -+ path); -+ } -+ -+ var title = statement.column_text(1); -+ var image = new ImageItem(this.build_child_id (id), this, title); -+ image.ref_id = this.build_reference_id (id); -+ image.creator = statement.column_text(2); -+ TimeVal tv = { (long) statement.column_int(3), (long) 0 }; -+ image.date = tv.to_iso8601 (); -+ image.width = statement.column_int(4); -+ image.height = statement.column_int(5); -+ image.size = statement.column_int(7); -+ image.mime_type = mime_type; -+ image.dlna_profile = statement.column_text(8); -+ File file = File.new_for_path(path); -+ image.add_uri (file.get_uri ()); -+ -+ return image; -+ } -+ -+ private static string get_sql_all (string year) { -+ return (SQL_ALL_TEMPLATE.printf (year)); -+ } -+ private static string get_sql_find_object (string year) { -+ return (SQL_FIND_OBJECT_TEMPLATE.printf (year)); -+ } -+ private static string get_sql_count (string year) { -+ return (SQL_COUNT_TEMPLATE.printf (year)); -+ } -+ private static string get_sql_added (string year) { -+ return (SQL_ADDED_TEMPLATE.printf (year)); -+ } -+ private static string get_sql_removed (string year) { -+ return (SQL_REMOVED_TEMPLATE.printf (year)); -+ } -+ -+ public ImageYear (MediaContainer parent, -+ string year, -+ LMS.Database lms_db) { -+ base ("%s".printf (year), -+ parent, -+ year, -+ lms_db, -+ get_sql_all (year), -+ get_sql_find_object (year), -+ get_sql_count (year), -+ get_sql_added (year), -+ get_sql_removed (year) -+ ); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-image-years.vala b/src/plugins/lms/rygel-lms-image-years.vala -new file mode 100644 -index 0000000..636f4d1 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-image-years.vala -@@ -0,0 +1,59 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+using Sqlite; -+ -+public class Rygel.LMS.ImageYears : Rygel.LMS.CategoryContainer { -+ private static const string SQL_ALL = -+ "SELECT DISTINCT(strftime('%Y', images.date, 'unixepoch')) as year " + -+ "FROM images " + -+ "LIMIT ? OFFSET ?;"; -+ -+ private static const string SQL_COUNT = -+ "SELECT COUNT(DISTINCT(strftime('%Y', images.date, 'unixepoch'))) " + -+ "FROM images;"; -+ -+ /* actually returns multiple times the same result (because no DISTINCT) */ -+ /* Casting the year is a workaround so we can keep using -+ * Database.find_object() without making the argument a variant or something like it*/ -+ private static const string SQL_FIND_OBJECT = -+ "SELECT strftime('%Y', images.date, 'unixepoch') as year " + -+ "FROM images " + -+ "WHERE year = CAST(? AS TEXT)"; -+ -+ protected override MediaObject? object_from_statement (Statement statement) { -+ return new LMS.ImageYear (this, statement.column_text (0), this.lms_db); -+ } -+ -+ public ImageYears (MediaContainer parent, LMS.Database lms_db) { -+ base ("years", -+ parent, -+ _("Years"), -+ lms_db, -+ ImageYears.SQL_ALL, -+ ImageYears.SQL_FIND_OBJECT, -+ ImageYears.SQL_COUNT, -+ null, null -+ ); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-music-root.vala b/src/plugins/lms/rygel-lms-music-root.vala -new file mode 100644 -index 0000000..7b1eb0f ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-music-root.vala -@@ -0,0 +1,36 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+ -+public class Rygel.LMS.MusicRoot : Rygel.SimpleContainer { -+ public MusicRoot (string id, -+ MediaContainer parent, -+ string title, -+ LMS.Database lms_db) { -+ base (id, parent, title); -+ -+ this.add_child_container (new AllMusic (this, lms_db)); -+ this.add_child_container (new Artists ("artists", this, _("Artists"), lms_db)); -+ this.add_child_container (new Albums (this, lms_db)); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-plugin-factory.vala b/src/plugins/lms/rygel-lms-plugin-factory.vala -new file mode 100644 -index 0000000..9fa8ccd ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-plugin-factory.vala -@@ -0,0 +1,40 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+ -+private Rygel.LMS.PluginFactory plugin_factory; -+ -+public void module_init(PluginLoader loader) { -+ plugin_factory = new Rygel.LMS.PluginFactory(loader); -+} -+ -+public class Rygel.LMS.PluginFactory { -+ -+ PluginLoader loader; -+ -+ public PluginFactory(PluginLoader loader) { -+ this.loader = loader; -+ this.loader.add_plugin(new LMS.Plugin()); -+ } -+ -+} -diff --git a/src/plugins/lms/rygel-lms-plugin.vala b/src/plugins/lms/rygel-lms-plugin.vala -new file mode 100644 -index 0000000..8bf1284 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-plugin.vala -@@ -0,0 +1,35 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+ -+public class Rygel.LMS.Plugin : Rygel.MediaServerPlugin { -+ public const string NAME = "LMS"; -+ -+ private static RootContainer root; -+ -+ public Plugin() { -+ if (root == null) -+ root = new RootContainer(); -+ base(root, Plugin.NAME, null, PluginCapabilities.TRACK_CHANGES); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-root-container.vala b/src/plugins/lms/rygel-lms-root-container.vala -new file mode 100644 -index 0000000..1623fa3 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-root-container.vala -@@ -0,0 +1,58 @@ -+/* -+ * Copyright (C) 2013 Intel Corporation. -+ * -+ * Author: Jussi Kukkonen <jussi.kukkonen@intel.com> -+ * -+ * 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 Rygel; -+ -+using Sqlite; -+ -+public class Rygel.LMS.RootContainer : Rygel.SimpleContainer { -+ -+ private LMS.Database lms_db = null; -+ -+ public RootContainer() { -+ var config = MetaConfig.get_default (); -+ -+ var title = _("Shared media"); -+ try { -+ title = config.get_string ("LightMediaScanner", "title"); -+ } catch (GLib.Error error) {} -+ -+ base.root(title); -+ -+ try { -+ this.lms_db = new LMS.Database (); -+ -+ this.add_child_container (new MusicRoot ("music", this, _("Music"), this.lms_db)); -+ this.add_child_container (new AllVideos ("all-videos", this, _("Videos"), this.lms_db)); -+ this.add_child_container (new ImageRoot ("images", this, _("Pictures"), this.lms_db)); -+ -+ } catch (DatabaseError e) { -+ warning ("%s\n", e.message); -+ -+ /* TODO if db does not exist we should -+ wait for it to be created and then add folders. Best to wait for the -+ LMS notification API. */ -+ } -+ -+ } -+ -+} -diff --git a/src/plugins/lms/rygel-lms-sql-function.vala b/src/plugins/lms/rygel-lms-sql-function.vala -new file mode 100644 -index 0000000..e8580cc ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-sql-function.vala -@@ -0,0 +1,31 @@ -+/* -+ * Copyright (C) 2010 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. -+ */ -+ -+internal class Rygel.LMS.SqlFunction : SqlOperator { -+ public SqlFunction (string name, string arg) { -+ base (name, arg); -+ } -+ -+ public override string to_string () { -+ return "%s(%s,?)".printf (name, arg); -+ } -+} -diff --git a/src/plugins/lms/rygel-lms-sql-operator.vala b/src/plugins/lms/rygel-lms-sql-operator.vala -new file mode 100644 -index 0000000..fc4e907 ---- /dev/null -+++ b/src/plugins/lms/rygel-lms-sql-operator.vala -@@ -0,0 +1,73 @@ -+/* -+ * Copyright (C) 2010 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 GUPnP; -+ -+internal class Rygel.LMS.SqlOperator : GLib.Object { -+ protected string name; -+ protected string arg; -+ protected string collate; -+ -+ public SqlOperator (string name, -+ string arg, -+ string collate = "") { -+ this.name = name; -+ this.arg = arg; -+ this.collate = collate; -+ } -+ -+ public SqlOperator.from_search_criteria_op (SearchCriteriaOp op, -+ string arg, -+ string collate) { -+ string sql = null; -+ switch (op) { -+ case SearchCriteriaOp.EQ: -+ sql = "="; -+ break; -+ case SearchCriteriaOp.NEQ: -+ sql = "!="; -+ break; -+ case SearchCriteriaOp.LESS: -+ sql = "<"; -+ break; -+ case SearchCriteriaOp.LEQ: -+ sql = "<="; -+ break; -+ case SearchCriteriaOp.GREATER: -+ sql = ">"; -+ break; -+ case SearchCriteriaOp.GEQ: -+ sql = ">="; -+ break; -+ default: -+ assert_not_reached (); -+ } -+ -+ this (sql, arg, collate); -+ } -+ -+ public virtual string to_string () { -+ return "(%s %s ? %s)".printf (arg, name, collate); -+ } -+} -+ -+ --- -1.7.10.4 - |