From 8bb9ae73464dd76f5fa94f2e9ba76b0bd88114df Mon Sep 17 00:00:00 2001 From: Manuel Bachmann 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 Author: Alexander Kanavin Signed-off-by: Manuel Bachmann --- 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 , + * (C) 2013 Intel Corporation. + * + * Author: Jussi Kukkonen + * + * 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 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 (); + + 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 . + * + * Author: Jens Georg + * + * 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 + +#ifdef HAVE_UNISTRING +# include +#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 , + * (C) 2013 Intel Corporation. + * + * Author: Jussi Kukkonen + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 + * + * 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 . + * + * Author: Jens Georg + * + * 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 . + * + * Author: Jens Georg + * + * 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