summaryrefslogtreecommitdiffstats
path: root/meta-agl/recipes-connectivity/rygel/files/0001-Add-LightMediaScanner-plugin.patch
diff options
context:
space:
mode:
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.patch2541
1 files changed, 2541 insertions, 0 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
new file mode 100644
index 000000000..eca08ee84
--- /dev/null
+++ b/meta-agl/recipes-connectivity/rygel/files/0001-Add-LightMediaScanner-plugin.patch
@@ -0,0 +1,2541 @@
+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
+