Provide LightMediaScanner 0.5.1, Rygel LMS plugin
[AGL/meta-agl.git] / meta-agl / recipes-connectivity / rygel / files / 0001-Add-LightMediaScanner-plugin.patch
1 From 8bb9ae73464dd76f5fa94f2e9ba76b0bd88114df Mon Sep 17 00:00:00 2001
2 From: Manuel Bachmann <manuel.bachmann@iot.bzh>
3 Date: Mon, 26 Oct 2015 04:18:33 +0000
4 Subject: [PATCH] Add LightMediaScanner plugin
5
6 Add a new plugin based on LightMediaScanner :
7 https://github.com/profusion/lightmediascanner
8
9 Shorty put, this plugin does not do the indexing itself as
10 "media-export" does, but defers this task to the
11 "lightmediascannerd" daemon, with which it communicates
12 via a D-Bus interface.
13 The remote indexing daemon installs itself as a separate
14 package, and is designed to be ultra-lightweight and fast.
15
16 This commit is the rebase and fusion of all work done
17 first on Maemo (lms.garage.maemo.org), then on Tizen IVI
18 (review.tizen.org/git/?p=profile/ivi/rygel.git) and right
19 now on AGL (automotivelinux.org) where several components
20 depend on LightMediaScanner.
21 A splitted version (13 commits) can also be seen on :
22 https://github.com/Tarnyko/rygel
23
24 It difffers mostly in that it lets "media-export" plugin
25 as the default, just adding itself in "rygel.conf" as an
26 alternative.
27
28 (note : reporter is not code Author, see patch for details)
29 (note 2 : rebased on top of 0.26.1)
30
31 Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
32 Author: Alexander Kanavin <alex.kanavin@gmail.com>
33 Signed-off-by: Manuel Bachmann <manuel.bachmann@iot.bzh>
34 ---
35  configure.ac                                      |   18 +
36  data/rygel.conf                                   |    6 +-
37  src/plugins/Makefile.am                           |    5 +
38  src/plugins/lms/Makefile.am                       |   46 +++
39  src/plugins/lms/README                            |   17 +
40  src/plugins/lms/lms.plugin.in                     |    7 +
41  src/plugins/lms/rygel-lms-album.vala              |  173 +++++++++
42  src/plugins/lms/rygel-lms-albums.vala             |  175 +++++++++
43  src/plugins/lms/rygel-lms-all-images.vala         |   95 +++++
44  src/plugins/lms/rygel-lms-all-music.vala          |  169 ++++++++
45  src/plugins/lms/rygel-lms-all-videos.vala         |  123 ++++++
46  src/plugins/lms/rygel-lms-artist.vala             |   75 ++++
47  src/plugins/lms/rygel-lms-artists.vala            |   62 +++
48  src/plugins/lms/rygel-lms-category-container.vala |  428 +++++++++++++++++++++
49  src/plugins/lms/rygel-lms-collate.c               |  49 +++
50  src/plugins/lms/rygel-lms-database.vala           |  294 ++++++++++++++
51  src/plugins/lms/rygel-lms-dbus-interfaces.vala    |   30 ++
52  src/plugins/lms/rygel-lms-image-root.vala         |   35 ++
53  src/plugins/lms/rygel-lms-image-year.vala         |  114 ++++++
54  src/plugins/lms/rygel-lms-image-years.vala        |   59 +++
55  src/plugins/lms/rygel-lms-music-root.vala         |   36 ++
56  src/plugins/lms/rygel-lms-plugin-factory.vala     |   40 ++
57  src/plugins/lms/rygel-lms-plugin.vala             |   35 ++
58  src/plugins/lms/rygel-lms-root-container.vala     |   58 +++
59  src/plugins/lms/rygel-lms-sql-function.vala       |   31 ++
60  src/plugins/lms/rygel-lms-sql-operator.vala       |   73 ++++
61  26 files changed, 2252 insertions(+), 1 deletion(-)
62  create mode 100644 src/plugins/lms/Makefile.am
63  create mode 100644 src/plugins/lms/README
64  create mode 100644 src/plugins/lms/lms.plugin.in
65  create mode 100644 src/plugins/lms/rygel-lms-album.vala
66  create mode 100644 src/plugins/lms/rygel-lms-albums.vala
67  create mode 100644 src/plugins/lms/rygel-lms-all-images.vala
68  create mode 100644 src/plugins/lms/rygel-lms-all-music.vala
69  create mode 100644 src/plugins/lms/rygel-lms-all-videos.vala
70  create mode 100644 src/plugins/lms/rygel-lms-artist.vala
71  create mode 100644 src/plugins/lms/rygel-lms-artists.vala
72  create mode 100644 src/plugins/lms/rygel-lms-category-container.vala
73  create mode 100644 src/plugins/lms/rygel-lms-collate.c
74  create mode 100644 src/plugins/lms/rygel-lms-database.vala
75  create mode 100644 src/plugins/lms/rygel-lms-dbus-interfaces.vala
76  create mode 100644 src/plugins/lms/rygel-lms-image-root.vala
77  create mode 100644 src/plugins/lms/rygel-lms-image-year.vala
78  create mode 100644 src/plugins/lms/rygel-lms-image-years.vala
79  create mode 100644 src/plugins/lms/rygel-lms-music-root.vala
80  create mode 100644 src/plugins/lms/rygel-lms-plugin-factory.vala
81  create mode 100644 src/plugins/lms/rygel-lms-plugin.vala
82  create mode 100644 src/plugins/lms/rygel-lms-root-container.vala
83  create mode 100644 src/plugins/lms/rygel-lms-sql-function.vala
84  create mode 100644 src/plugins/lms/rygel-lms-sql-operator.vala
85
86 diff --git a/configure.ac b/configure.ac
87 index 7ae1105..275fd99 100644
88 --- a/configure.ac
89 +++ b/configure.ac
90 @@ -170,6 +170,18 @@ AS_IF([test "x$enable_ruih_plugin" = "xyes"],
91                         libxml-2.0 >= $LIBXML_REQUIRED])
92    ])
93  
94 +
95 +RYGEL_ADD_PLUGIN([lms],[LightMediaScanner],[yes])
96 +AS_IF([test "x$enable_lms_plugin" = "xyes"],
97 +  [
98 +    PKG_CHECK_MODULES([RYGEL_PLUGIN_LMS_DEPS],
99 +                      [$RYGEL_COMMON_MODULES
100 +                       gio-2.0 >= $GIO_REQUIRED
101 +                       sqlite3 >= $LIBSQLITE3_REQUIRED])
102 +    RYGEL_PLUGIN_LMS_DEPS_VALAFLAGS="$RYGEL_COMMON_MODULES_VALAFLAGS --pkg gio-2.0 --pkg gee-0.8 --pkg sqlite3"
103 +    AC_SUBST([RYGEL_PLUGIN_LMS_DEPS_VALAFLAGS])
104 +  ])
105 +
106  AS_IF([test "x$with_media_engine" = "xgstreamer"],
107        [
108          RYGEL_ADD_PLUGIN([playbin],[GStreamer playbin],[yes])
109 @@ -332,6 +344,11 @@ then
110      fi
111  fi
112  
113 +dnl Check additional requirements for LMS plugin
114 +if test "x$enable_lms_plugin" = "xyes";
115 +then
116 +    RYGEL_CHECK_PACKAGES([sqlite3])
117 +fi
118  
119  RYGEL_ADD_PLUGIN([tracker],[Tracker],[yes])
120  AS_IF([test "x$enable_tracker_plugin" = "xyes"],
121 @@ -513,6 +530,7 @@ echo "
122              version:            ${tracker_api_version}
123          mediathek:              ${enable_mediathek_plugin}
124          media-export            ${enable_media_export_plugin}
125 +        lightmediascanner       ${enable_lms_plugin}
126          external:               ${enable_external_plugin}
127          MPRIS2:                 ${enable_mpris_plugin}
128          gst-launch:             ${enable_gst_launch_plugin}
129 diff --git a/data/rygel.conf b/data/rygel.conf
130 index 6b1c1c4..8677a0d 100644
131 --- a/data/rygel.conf
132 +++ b/data/rygel.conf
133 @@ -99,7 +99,7 @@ strict-sharing=false
134  title=@REALNAME@'s media on @PRETTY_HOSTNAME@
135  
136  [MediaExport]
137 -enabled=true
138 +enabled=false
139  title=@REALNAME@'s media on @PRETTY_HOSTNAME@
140  # List of URIs to export. Following variables are automatically substituted by
141  # the appropriate XDG standard media folders by Rygel for you.
142 @@ -114,6 +114,10 @@ monitor-changes=true
143  monitor-grace-timeout=5
144  virtual-folders=true
145  
146 +[LightMediaScanner]
147 +enabled=true
148 +title=My Media
149 +
150  [Playbin]
151  enabled=true
152  title=Audio/Video playback on @PRETTY_HOSTNAME@
153 diff --git a/src/plugins/Makefile.am b/src/plugins/Makefile.am
154 index d116f09..40791f0 100644
155 --- a/src/plugins/Makefile.am
156 +++ b/src/plugins/Makefile.am
157 @@ -10,6 +10,10 @@ if BUILD_MEDIA_EXPORT_PLUGIN
158  MEDIA_EXPORT_PLUGIN = media-export
159  endif
160  
161 +if BUILD_LMS_PLUGIN
162 +LMS_PLUGIN = lms
163 +endif
164 +
165  if BUILD_EXTERNAL_PLUGIN
166  EXTERNAL_PLUGIN = external
167  endif
168 @@ -33,6 +37,7 @@ endif
169  SUBDIRS = $(TRACKER_PLUGIN) \
170           $(MEDIATHEK_PLUGIN) \
171           $(MEDIA_EXPORT_PLUGIN) \
172 +         $(LMS_PLUGIN) \
173           $(EXTERNAL_PLUGIN) \
174           $(MPRIS_PLUGIN) \
175           $(GST_LAUNCH_PLUGIN) \
176 diff --git a/src/plugins/lms/Makefile.am b/src/plugins/lms/Makefile.am
177 new file mode 100644
178 index 0000000..f96a2ab
179 --- /dev/null
180 +++ b/src/plugins/lms/Makefile.am
181 @@ -0,0 +1,46 @@
182 +include $(top_srcdir)/common.am
183 +
184 +plugin_LTLIBRARIES = librygel-lms.la
185 +plugin_DATA = lms.plugin
186 +
187 +librygel_lms_la_SOURCES = \
188 +       rygel-lms-plugin.vala \
189 +       rygel-lms-plugin-factory.vala \
190 +       rygel-lms-root-container.vala \
191 +       rygel-lms-music-root.vala \
192 +       rygel-lms-image-root.vala \
193 +       rygel-lms-category-container.vala \
194 +       rygel-lms-all-music.vala \
195 +       rygel-lms-album.vala \
196 +       rygel-lms-albums.vala \
197 +       rygel-lms-artist.vala \
198 +       rygel-lms-artists.vala \
199 +       rygel-lms-all-videos.vala \
200 +       rygel-lms-database.vala \
201 +       rygel-lms-all-images.vala \
202 +       rygel-lms-image-years.vala \
203 +       rygel-lms-image-year.vala \
204 +       rygel-lms-sql-function.vala \
205 +       rygel-lms-sql-operator.vala \
206 +       rygel-lms-collate.c \
207 +       rygel-lms-dbus-interfaces.vala
208 +
209 +librygel_lms_la_VALAFLAGS = \
210 +       --enable-experimental \
211 +       $(RYGEL_PLUGIN_LMS_DEPS_VALAFLAGS) \
212 +       $(RYGEL_COMMON_LIBRYGEL_SERVER_VALAFLAGS) \
213 +       $(RYGEL_COMMON_VALAFLAGS)
214 +
215 +librygel_lms_la_CFLAGS = \
216 +       $(RYGEL_PLUGIN_LMS_DEPS_CFLAGS) \
217 +       $(RYGEL_COMMON_LIBRYGEL_SERVER_CFLAGS) \
218 +       -DG_LOG_DOMAIN='"Lms"'
219 +
220 +librygel_lms_la_LIBADD = \
221 +       $(RYGEL_PLUGIN_LMS_DEPS_LIBS) \
222 +       $(RYGEL_COMMON_LIBRYGEL_SERVER_LIBS)
223 +
224 +librygel_lms_la_LDFLAGS = \
225 +       $(RYGEL_PLUGIN_LINKER_FLAGS)
226 +
227 +EXTRA_DIST = lms.plugin.in
228 diff --git a/src/plugins/lms/README b/src/plugins/lms/README
229 new file mode 100644
230 index 0000000..b741806
231 --- /dev/null
232 +++ b/src/plugins/lms/README
233 @@ -0,0 +1,17 @@
234 +rygel-lms
235 +=========
236 +
237 +A rygel mediaserver plugin that exposes a lightmediascanner database
238 +as a Mediaserver.
239 +
240 +Configuration in rygel.conf:
241 +
242 +    [LightMediaScanner]
243 +    db-path=/path/to/lightmediascannerd.sqlite3
244 +    title=My Media
245 +
246 +* Supports browsing and searching (but in many cases searches will
247 +  still fall back to the inefficient simple_search()).
248 +* UpdateIDs are not yet supported as lightmediascanner seems to have
249 +  not change signal support yet
250 +* No real DLNA CTT testing has been done so far
251 diff --git a/src/plugins/lms/lms.plugin.in b/src/plugins/lms/lms.plugin.in
252 new file mode 100644
253 index 0000000..9db9895
254 --- /dev/null
255 +++ b/src/plugins/lms/lms.plugin.in
256 @@ -0,0 +1,7 @@
257 +[Plugin]
258 +Version = @VERSION@
259 +Module = lms
260 +Name = LMS
261 +License = LGPL
262 +Description = LMS DMS plugin for Rygel
263 +Copyright = Copyright Â© Intel 
264 diff --git a/src/plugins/lms/rygel-lms-album.vala b/src/plugins/lms/rygel-lms-album.vala
265 new file mode 100644
266 index 0000000..4fea17a
267 --- /dev/null
268 +++ b/src/plugins/lms/rygel-lms-album.vala
269 @@ -0,0 +1,173 @@
270 +/*
271 + * Copyright (C) 2013 Intel Corporation.
272 + *
273 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
274 + *
275 + * This file is part of Rygel.
276 + *
277 + * Rygel is free software; you can redistribute it and/or modify
278 + * it under the terms of the GNU Lesser General Public License as published by
279 + * the Free Software Foundation; either version 2 of the License, or
280 + * (at your option) any later version.
281 + *
282 + * Rygel is distributed in the hope that it will be useful,
283 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
284 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
285 + * GNU Lesser General Public License for more details.
286 + *
287 + * You should have received a copy of the GNU Lesser General Public License
288 + * along with this program; if not, write to the Free Software Foundation,
289 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
290 + */
291 +
292 +using Rygel;
293 +using Sqlite;
294 +
295 +public class Rygel.LMS.Album : Rygel.LMS.CategoryContainer {
296 +    private static const string SQL_ALL_TEMPLATE = 
297 +        "SELECT files.id, files.path, files.size, " +
298 +               "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " +
299 +               "audio_artists.name as artist, " +
300 +               "audio_albums.name " +
301 +        "FROM audios, files " +
302 +        "LEFT JOIN audio_artists " +
303 +        "ON audios.artist_id = audio_artists.id " +
304 +        "LEFT JOIN audio_albums " +
305 +        "ON audios.album_id = audio_albums.id " +
306 +        "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = %s " +
307 +        "LIMIT ? OFFSET ?;";
308 +
309 +    private static const string SQL_COUNT_TEMPLATE =
310 +        "SELECT COUNT(audios.id) " +
311 +        "FROM audios, files " +
312 +        "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = %s;";
313 +
314 +    private static const string SQL_COUNT_WITH_FILTER_TEMPLATE =
315 +        "SELECT COUNT(audios.id), audios.title as title, " +
316 +               "audio_artists.name as artist, " +
317 +               "audio_albums.name " +
318 +        "FROM audios, files " +
319 +        "LEFT JOIN audio_artists " +
320 +        "ON audios.artist_id = audio_artists.id " +
321 +        "LEFT JOIN audio_albums " +
322 +        "ON audios.album_id = audio_albums.id " +
323 +        "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = %s;";
324 +
325 +    private static const string SQL_FIND_OBJECT_TEMPLATE =
326 +        "SELECT files.id, files.path, files.size, " +
327 +               "audios.title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + 
328 +               "audio_artists.name, " +
329 +               "audio_albums.name " +
330 +        "FROM audios, files " +
331 +        "LEFT JOIN audio_artists " +
332 +        "ON audios.artist_id = audio_artists.id " +
333 +        "LEFT JOIN audio_albums " +
334 +        "ON audios.album_id = audio_albums.id " +
335 +        "WHERE dtime = 0 AND files.id = ? AND audios.id = files.id AND audios.album_id = %s;";
336 +
337 +    private static const string SQL_ADDED_TEMPLATE =
338 +        "SELECT files.id, files.path, files.size, " +
339 +               "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " +
340 +               "audio_artists.name as artist, " +
341 +               "audio_albums.name " +
342 +        "FROM audios, files " +
343 +        "LEFT JOIN audio_artists " +
344 +        "ON audios.artist_id = audio_artists.id " +
345 +        "LEFT JOIN audio_albums " +
346 +        "ON audios.album_id = audio_albums.id " +
347 +        "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = %s " +
348 +        "AND update_id > ? AND update_id <= ?;";
349 +
350 +    private static const string SQL_REMOVED_TEMPLATE =
351 +        "SELECT files.id, files.path, files.size, " +
352 +               "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " +
353 +               "audio_artists.name as artist, " +
354 +               "audio_albums.name " +
355 +        "FROM audios, files " +
356 +        "LEFT JOIN audio_artists " +
357 +        "ON audios.artist_id = audio_artists.id " +
358 +        "LEFT JOIN audio_albums " +
359 +        "ON audios.album_id = audio_albums.id " +
360 +        "WHERE dtime <> 0 AND audios.id = files.id AND audios.album_id = %s " +
361 +        "AND update_id > ? AND update_id <= ?;";
362 +
363 +    protected override MediaObject? object_from_statement (Statement statement) {
364 +        var id = statement.column_int (0);
365 +        var path = statement.column_text (1);
366 +        var mime_type = statement.column_text(10);
367 +
368 +        if (mime_type == null || mime_type.length == 0) {
369 +            /* TODO is this correct? */
370 +            debug ("Music item %d (%s) has no MIME type",
371 +                   id,
372 +                   path);
373 +        }
374 +
375 +        var title = statement.column_text(3);
376 +        var song_id = this.build_child_id (id);
377 +        var song = new MusicItem (song_id, this, title);
378 +        song.ref_id = this.build_reference_id (id);
379 +        song.size = statement.column_int(2);
380 +        song.track_number = statement.column_int(4);
381 +        song.duration = statement.column_int(5);
382 +        song.channels = statement.column_int(6);
383 +        song.sample_freq = statement.column_int(7); 
384 +        song.bitrate = statement.column_int(8);
385 +        song.dlna_profile = statement.column_text(9);
386 +        song.mime_type = mime_type;
387 +        song.artist = statement.column_text(11);
388 +        song.album = statement.column_text(12);
389 +        File file = File.new_for_path (path);
390 +        song.add_uri (file.get_uri ());
391 +
392 +        return song;
393 +    }
394 +
395 +    private static string get_sql_all (string db_id) {
396 +        return (SQL_ALL_TEMPLATE.printf (db_id));
397 +    }
398 +    private static string get_sql_find_object (string db_id) {
399 +        return (SQL_FIND_OBJECT_TEMPLATE.printf (db_id));
400 +    }
401 +    private static string get_sql_count (string db_id) {
402 +        return (SQL_COUNT_TEMPLATE.printf (db_id));
403 +    }
404 +    private static string get_sql_added (string db_id) {
405 +        return (SQL_ADDED_TEMPLATE.printf (db_id));
406 +    }
407 +    private static string get_sql_removed (string db_id) {
408 +        return (SQL_REMOVED_TEMPLATE.printf (db_id));
409 +    }
410 +
411 +    protected override string get_sql_all_with_filter (string filter) {
412 +        if (filter.length == 0) {
413 +            return this.sql_all;
414 +        }
415 +        var filter_str = "%s AND %s".printf (this.db_id, filter);
416 +        return (SQL_ALL_TEMPLATE.printf (filter_str));
417 +    }
418 +
419 +    protected override string get_sql_count_with_filter (string filter) {
420 +        if (filter.length == 0) {
421 +            return this.sql_count;
422 +        }
423 +        var filter_str = "%s AND %s".printf (this.db_id, filter);
424 +        return (SQL_COUNT_WITH_FILTER_TEMPLATE.printf (filter_str));
425 +    }
426 +
427 +    public Album (string         db_id,
428 +                  MediaContainer parent,
429 +                  string         title,
430 +                  LMS.Database   lms_db) {
431 +        base (db_id,
432 +              parent,
433 +              title,
434 +              lms_db,
435 +              get_sql_all (db_id),
436 +              get_sql_find_object (db_id),
437 +              get_sql_count (db_id),
438 +              get_sql_added (db_id),
439 +              get_sql_removed (db_id)
440 +             );
441 +    }
442 +}
443 diff --git a/src/plugins/lms/rygel-lms-albums.vala b/src/plugins/lms/rygel-lms-albums.vala
444 new file mode 100644
445 index 0000000..309a352
446 --- /dev/null
447 +++ b/src/plugins/lms/rygel-lms-albums.vala
448 @@ -0,0 +1,175 @@
449 +/*
450 + * Copyright (C) 2013 Intel Corporation.
451 + *
452 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
453 + *
454 + * This file is part of Rygel.
455 + *
456 + * Rygel is free software; you can redistribute it and/or modify
457 + * it under the terms of the GNU Lesser General Public License as published by
458 + * the Free Software Foundation; either version 2 of the License, or
459 + * (at your option) any later version.
460 + *
461 + * Rygel is distributed in the hope that it will be useful,
462 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
463 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
464 + * GNU Lesser General Public License for more details.
465 + *
466 + * You should have received a copy of the GNU Lesser General Public License
467 + * along with this program; if not, write to the Free Software Foundation,
468 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
469 + */
470 +
471 +using Rygel;
472 +using Sqlite;
473 +
474 +public class Rygel.LMS.Albums : Rygel.LMS.CategoryContainer {
475 +    private static const string SQL_ALL =
476 +        "SELECT audio_albums.id, audio_albums.name as title, " +
477 +               "audio_artists.name as artist " +
478 +        "FROM audio_albums " +
479 +        "LEFT JOIN audio_artists " +
480 +        "ON audio_albums.artist_id = audio_artists.id " +
481 +        "LIMIT ? OFFSET ?;";
482 +
483 +    private static const string SQL_ALL_WITH_FILTER_TEMPLATE =
484 +        "SELECT audio_albums.id, audio_albums.name as title, " +
485 +               "audio_artists.name as artist " +
486 +        "FROM audio_albums " +
487 +        "LEFT JOIN audio_artists " +
488 +        "ON audio_albums.artist_id = audio_artists.id " +
489 +        "WHERE %s " +
490 +        "LIMIT ? OFFSET ?;";
491 +
492 +    private static const string SQL_COUNT =
493 +        "SELECT COUNT(audio_albums.id) " +
494 +        "FROM audio_albums;";
495 +
496 +    private static const string SQL_COUNT_WITH_FILTER_TEMPLATE =
497 +        "SELECT COUNT(audio_albums.id), audio_albums.name as title, " +
498 +               "audio_artists.name as artist " +
499 +        "FROM audio_albums " +
500 +        "LEFT JOIN audio_artists " +
501 +        "ON audio_albums.artist_id = audio_artists.id " +
502 +        "WHERE %s;";
503 +
504 +    /* count songs inside albums */
505 +    private static const string SQL_CHILD_COUNT_WITH_FILTER_TEMPLATE =
506 +        "SELECT COUNT(audios.id), audios.title as title, " +
507 +               "audio_artists.name as artist " +
508 +        "FROM audios, files, audio_albums " +
509 +        "LEFT JOIN audio_artists " +
510 +        "ON audios.artist_id = audio_artists.id " +
511 +        "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = audio_albums.id %s;";
512 +
513 +    /* select songs inside albums */
514 +    private static const string SQL_CHILD_ALL_WITH_FILTER_TEMPLATE =
515 +        "SELECT files.id, files.path, files.size, " +
516 +               "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " +
517 +               "audio_artists.name as artist, " +
518 +               "audio_albums.name, audio_albums.id " +
519 +        "FROM audios, files, audio_albums " +
520 +        "LEFT JOIN audio_artists " +
521 +        "ON audios.artist_id = audio_artists.id " +
522 +        "WHERE dtime = 0 AND audios.id = files.id AND audios.album_id = audio_albums.id %s " +
523 +        "LIMIT ? OFFSET ?;";
524 +
525 +
526 +    private static const string SQL_FIND_OBJECT =
527 +        "SELECT audio_albums.id, audio_albums.name " +
528 +        "FROM audio_albums " +
529 +        "WHERE audio_albums.id = ?;";
530 +
531 +    protected override string get_sql_all_with_filter (string filter) {
532 +        if (filter.length == 0) {
533 +            return Albums.SQL_ALL;
534 +        }
535 +        return (Albums.SQL_ALL_WITH_FILTER_TEMPLATE.printf (filter));
536 +    }
537 +
538 +    protected override string get_sql_count_with_filter (string filter) {
539 +        if (filter.length == 0) {
540 +            return Albums.SQL_COUNT;
541 +        }
542 +        return (Albums.SQL_COUNT_WITH_FILTER_TEMPLATE.printf (filter));
543 +    }
544 +
545 +    protected override uint get_child_count_with_filter (string     where_filter,
546 +                                                        ValueArray args)
547 +    {
548 +
549 +        /* search the children (albums) as usual */
550 +        var count = base.get_child_count_with_filter (where_filter, args);
551 +
552 +        /* now search the album contents */
553 +        var filter = "";
554 +        if (where_filter.length > 0) {
555 +            filter = "AND %s".printf (where_filter);
556 +        }
557 +        var query = Albums.SQL_CHILD_COUNT_WITH_FILTER_TEMPLATE.printf (filter);
558 +        try {
559 +            var stmt = this.lms_db.prepare_and_init (query, args.values);
560 +            if (stmt.step () == Sqlite.ROW) {
561 +                count += stmt.column_int (0);
562 +            }
563 +        } catch (DatabaseError e) {
564 +            warning ("Query failed: %s", e.message);
565 +        }
566 +
567 +        return count;
568 +    }
569 +
570 +    protected override MediaObjects? get_children_with_filter (string     where_filter,
571 +                                                               ValueArray args,
572 +                                                               string     sort_criteria,
573 +                                                               uint       offset,
574 +                                                               uint       max_count) {
575 +        var children = base. get_children_with_filter (where_filter,
576 +                                                       args,
577 +                                                       sort_criteria,
578 +                                                       offset,
579 +                                                       max_count);
580 +        var filter = "";
581 +        if (where_filter.length > 0) {
582 +            filter = "AND %s".printf (where_filter);
583 +        }
584 +        var query = Albums.SQL_CHILD_ALL_WITH_FILTER_TEMPLATE.printf (filter);
585 +        try {
586 +            var stmt = this.lms_db.prepare_and_init (query, args.values);
587 +            while (Database.get_children_step (stmt)) {
588 +                var album_id = stmt.column_text (13);
589 +                var album = new Album (album_id, this, "", this.lms_db);
590 +
591 +                var song = album.object_from_statement (stmt);
592 +                song.parent_ref = song.parent;
593 +                children.add (song);
594 +                
595 +            }
596 +        } catch (DatabaseError e) {
597 +            warning ("Query failed: %s", e.message);
598 +        }
599 +
600 +        return children;
601 +    }
602 +
603 +    protected override MediaObject? object_from_statement (Statement statement) {
604 +        var id = "%d".printf (statement.column_int (0));
605 +        LMS.Album album = new LMS.Album (id,
606 +                                         this,
607 +                                         statement.column_text (1),
608 +                                         this.lms_db);
609 +        return album;
610 +    }
611 +
612 +    public Albums (MediaContainer parent,
613 +                   LMS.Database   lms_db) {
614 +        base ("albums",
615 +              parent,
616 +              _("Albums"),
617 +              lms_db,
618 +              Albums.SQL_ALL,
619 +              Albums.SQL_FIND_OBJECT,
620 +              Albums.SQL_COUNT,
621 +              null, null);
622 +    }
623 +}
624 diff --git a/src/plugins/lms/rygel-lms-all-images.vala b/src/plugins/lms/rygel-lms-all-images.vala
625 new file mode 100644
626 index 0000000..0b54c7f
627 --- /dev/null
628 +++ b/src/plugins/lms/rygel-lms-all-images.vala
629 @@ -0,0 +1,95 @@
630 +/*
631 + * Copyright (C) 2013 Intel Corporation.
632 + *
633 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
634 + *
635 + * This file is part of Rygel.
636 + *
637 + * Rygel is free software; you can redistribute it and/or modify
638 + * it under the terms of the GNU Lesser General Public License as published by
639 + * the Free Software Foundation; either version 2 of the License, or
640 + * (at your option) any later version.
641 + *
642 + * Rygel is distributed in the hope that it will be useful,
643 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
644 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
645 + * GNU Lesser General Public License for more details.
646 + *
647 + * You should have received a copy of the GNU Lesser General Public License
648 + * along with this program; if not, write to the Free Software Foundation,
649 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
650 + */
651 +
652 +using Rygel;
653 +using Sqlite;
654 +
655 +public class Rygel.LMS.AllImages : Rygel.LMS.CategoryContainer {
656 +    private static const string SQL_ALL =
657 +        "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime " +
658 +        "FROM images, files " +
659 +        "WHERE dtime = 0 AND images.id = files.id " +
660 +        "LIMIT ? OFFSET ?;";
661 +
662 +    private static const string SQL_COUNT =
663 +        "SELECT count(images.id) " +
664 +        "FROM images, files " +
665 +        "WHERE dtime = 0 AND images.id = files.id;";
666 +
667 +    private static const string SQL_FIND_OBJECT =
668 +        "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime " +
669 +        "FROM images, files " +
670 +        "WHERE dtime = 0 AND files.id = ? AND images.id = files.id;";
671 +
672 +    private static const string SQL_ADDED =
673 +        "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime " +
674 +        "FROM images, files " +
675 +        "WHERE dtime = 0 AND images.id = files.id " +
676 +        "AND update_id > ? AND update_id <= ?;";
677 +
678 +    private static const string SQL_REMOVED =
679 +        "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime " +
680 +        "FROM images, files " +
681 +        "WHERE dtime <> 0 AND images.id = files.id " +
682 +        "AND update_id > ? AND update_id <= ?;";
683 +
684 +    protected override MediaObject? object_from_statement (Statement statement) {
685 +        var id = statement.column_int(0);
686 +        var path = statement.column_text(6);
687 +        var mime_type = statement.column_text(9);
688 +
689 +        if (mime_type == null || mime_type.length == 0){
690 +            /* TODO is this correct? */
691 +            debug ("Image item %d (%s) has no MIME type",
692 +                   id,
693 +                   path);
694 +        }
695 +
696 +        var title = statement.column_text(1);
697 +        var image = new ImageItem(this.build_child_id (id), this, title);
698 +        image.creator = statement.column_text(2);
699 +        TimeVal tv = { (long) statement.column_int(3), (long) 0 };
700 +        image.date = tv.to_iso8601 ();
701 +        image.width = statement.column_int(4);
702 +        image.height = statement.column_int(5);
703 +        image.size = statement.column_int(7);
704 +        image.mime_type = mime_type;
705 +        image.dlna_profile = statement.column_text(8);
706 +        File file = File.new_for_path(path);
707 +        image.add_uri (file.get_uri ());
708 +
709 +        return image;
710 +    }
711 +
712 +    public AllImages (MediaContainer parent, LMS.Database lms_db) {
713 +        base ("all",
714 +              parent,
715 +              _("All"),
716 +              lms_db,
717 +              AllImages.SQL_ALL,
718 +              AllImages.SQL_FIND_OBJECT,
719 +              AllImages.SQL_COUNT,
720 +              AllImages.SQL_ADDED,
721 +              AllImages.SQL_REMOVED
722 +             );
723 +    }
724 +}
725 diff --git a/src/plugins/lms/rygel-lms-all-music.vala b/src/plugins/lms/rygel-lms-all-music.vala
726 new file mode 100644
727 index 0000000..2a7226f
728 --- /dev/null
729 +++ b/src/plugins/lms/rygel-lms-all-music.vala
730 @@ -0,0 +1,169 @@
731 +/*
732 + * Copyright (C) 2013 Intel Corporation.
733 + *
734 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
735 + *
736 + * This file is part of Rygel.
737 + *
738 + * Rygel is free software; you can redistribute it and/or modify
739 + * it under the terms of the GNU Lesser General Public License as published by
740 + * the Free Software Foundation; either version 2 of the License, or
741 + * (at your option) any later version.
742 + *
743 + * Rygel is distributed in the hope that it will be useful,
744 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
745 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
746 + * GNU Lesser General Public License for more details.
747 + *
748 + * You should have received a copy of the GNU Lesser General Public License
749 + * along with this program; if not, write to the Free Software Foundation,
750 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
751 + */
752 +
753 +using Rygel;
754 +using Sqlite;
755 +
756 +public class Rygel.LMS.AllMusic : Rygel.LMS.CategoryContainer {
757 +    private static const string SQL_ALL_TEMPLATE =
758 +        "SELECT files.id, files.path, files.size, " +
759 +               "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " +
760 +               "audio_artists.name as artist, " +
761 +               "audio_albums.name, " +
762 +               "files.mtime, " +
763 +               "audio_genres.name " +
764 +        "FROM audios, files " +
765 +        "LEFT JOIN audio_artists " +
766 +        "ON audios.artist_id = audio_artists.id " +
767 +        "LEFT JOIN audio_albums " +
768 +        "ON audios.album_id = audio_albums.id " +
769 +        "LEFT JOIN audio_genres " +
770 +        "ON audios.genre_id = audio_genres.id " +
771 +        "WHERE dtime = 0 AND audios.id = files.id %s " +
772 +        "LIMIT ? OFFSET ?;";
773 +
774 +    private static const string SQL_COUNT =
775 +        "SELECT COUNT(audios.id) " +
776 +        "FROM audios, files " +
777 +        "WHERE dtime = 0 AND audios.id = files.id;";
778 +
779 +    private static const string SQL_COUNT_WITH_FILTER_TEMPLATE =
780 +        "SELECT COUNT(audios.id), audios.title as title, " +
781 +               "audio_artists.name as artist " +
782 +        "FROM audios, files " +
783 +        "LEFT JOIN audio_artists " +
784 +        "ON audios.artist_id = audio_artists.id " +
785 +        "WHERE dtime = 0 AND audios.id = files.id %s;";
786 +
787 +    private static const string SQL_FIND_OBJECT =
788 +        "SELECT files.id, files.path, files.size, " +
789 +               "audios.title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " + 
790 +               "audio_artists.name, " +
791 +               "audio_albums.name, " +
792 +               "files.mtime, " +
793 +               "audio_genres.name " +
794 +        "FROM audios, files " +
795 +        "LEFT JOIN audio_artists " +
796 +        "ON audios.artist_id = audio_artists.id " +
797 +        "LEFT JOIN audio_albums " +
798 +        "ON audios.album_id = audio_albums.id " +
799 +        "LEFT JOIN audio_genres " +
800 +        "ON audios.genre_id = audio_genres.id " +
801 +        "WHERE dtime = 0 AND files.id = ? AND audios.id = files.id;";
802 +
803 +    private static const string SQL_ADDED =
804 +        "SELECT files.id, files.path, files.size, " +
805 +               "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " +
806 +               "audio_artists.name as artist, " +
807 +               "audio_albums.name, " +
808 +               "files.mtime, " +
809 +               "audio_genres.name " +
810 +        "FROM audios, files " +
811 +        "LEFT JOIN audio_artists " +
812 +        "ON audios.artist_id = audio_artists.id " +
813 +        "LEFT JOIN audio_albums " +
814 +        "ON audios.album_id = audio_albums.id " +
815 +        "LEFT JOIN audio_genres " +
816 +        "ON audios.genre_id = audio_genres.id " +
817 +        "WHERE dtime = 0 AND audios.id = files.id " +
818 +        "AND update_id > ? AND update_id <= ?;";
819 +
820 +    private static const string SQL_REMOVED =
821 +        "SELECT files.id, files.path, files.size, " +
822 +               "audios.title as title, audios.trackno, audios.length, audios.channels, audios.sampling_rate, audios.bitrate, audios.dlna_profile, audios.dlna_mime, " +
823 +               "audio_artists.name as artist, " +
824 +               "audio_albums.name, " +
825 +               "files.mtime, " +
826 +               "audio_genres.name " +
827 +        "FROM audios, files " +
828 +        "LEFT JOIN audio_artists " +
829 +        "ON audios.artist_id = audio_artists.id " +
830 +        "LEFT JOIN audio_albums " +
831 +        "ON audios.album_id = audio_albums.id " +
832 +        "LEFT JOIN audio_genres " +
833 +        "ON audios.genre_id = audio_genres.id " +
834 +        "WHERE dtime <> 0 AND audios.id = files.id " +
835 +        "AND update_id > ? AND update_id <= ?;";
836 +
837 +    protected override string get_sql_all_with_filter (string filter) {
838 +        if (filter.length == 0) {
839 +            return this.sql_all;
840 +        }
841 +        var filter_str = "AND %s".printf (filter);
842 +        return (AllMusic.SQL_ALL_TEMPLATE.printf (filter_str));
843 +    }
844 +
845 +    protected override string get_sql_count_with_filter (string filter) {
846 +        if (filter.length == 0) {
847 +            return this.sql_count;
848 +        }
849 +        var filter_str = "AND %s".printf (filter);
850 +        return (AllMusic.SQL_COUNT_WITH_FILTER_TEMPLATE.printf (filter_str));
851 +    }
852 +
853 +    protected override MediaObject? object_from_statement (Statement statement) {
854 +        var id = statement.column_int (0);
855 +        var path = statement.column_text (1);
856 +        var mime_type = statement.column_text(10);
857 +
858 +        if (mime_type == null || mime_type.length == 0) {
859 +            /* TODO is this correct? */
860 +            debug ("Music item %d (%s) has no MIME type",
861 +                   id,
862 +                   path);
863 +        }
864 +
865 +        var title = statement.column_text(3);
866 +        var song_id = this.build_child_id (id);
867 +        var song = new MusicItem (song_id, this, title);
868 +        song.size = statement.column_int(2);
869 +        song.track_number = statement.column_int(4);
870 +        song.duration = statement.column_int(5);
871 +        song.channels = statement.column_int(6);
872 +        song.sample_freq = statement.column_int(7); 
873 +        song.bitrate = statement.column_int(8);
874 +        song.dlna_profile = statement.column_text(9);
875 +        song.mime_type = mime_type;
876 +        song.artist = statement.column_text(11);
877 +        song.album = statement.column_text(12);
878 +        TimeVal tv = { (long) statement.column_int(13), (long) 0 };
879 +        song.date = tv.to_iso8601 ();
880 +        song.genre = statement.column_text(14);
881 +        File file = File.new_for_path (path);
882 +        song.add_uri (file.get_uri ());
883 +
884 +        return song;
885 +    }
886 +
887 +    public AllMusic (MediaContainer parent, LMS.Database lms_db) {
888 +        base("all",
889 +             parent,
890 +             _("All"),
891 +             lms_db,
892 +             AllMusic.SQL_ALL_TEMPLATE.printf (""),
893 +             AllMusic.SQL_FIND_OBJECT,
894 +             AllMusic.SQL_COUNT,
895 +             AllMusic.SQL_ADDED,
896 +             AllMusic.SQL_REMOVED
897 +            );
898 +    }
899 +}
900 diff --git a/src/plugins/lms/rygel-lms-all-videos.vala b/src/plugins/lms/rygel-lms-all-videos.vala
901 new file mode 100644
902 index 0000000..dbde0db
903 --- /dev/null
904 +++ b/src/plugins/lms/rygel-lms-all-videos.vala
905 @@ -0,0 +1,123 @@
906 +/*
907 + * Copyright (C) 2013 Intel Corporation.
908 + *
909 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
910 + *
911 + * This file is part of Rygel.
912 + *
913 + * Rygel is free software; you can redistribute it and/or modify
914 + * it under the terms of the GNU Lesser General Public License as published by
915 + * the Free Software Foundation; either version 2 of the License, or
916 + * (at your option) any later version.
917 + *
918 + * Rygel is distributed in the hope that it will be useful,
919 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
920 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
921 + * GNU Lesser General Public License for more details.
922 + *
923 + * You should have received a copy of the GNU Lesser General Public License
924 + * along with this program; if not, write to the Free Software Foundation,
925 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
926 + */
927 +
928 +using Rygel;
929 +using Sqlite;
930 +
931 +public class Rygel.LMS.AllVideos : Rygel.LMS.CategoryContainer {
932 +    private static const string SQL_ALL =
933 +        "SELECT videos.id, title, artist, length, path, mtime, size, dlna_profile, dlna_mime " +
934 +        "FROM videos, files " +
935 +        "WHERE dtime = 0 AND videos.id = files.id " +
936 +        "LIMIT ? OFFSET ?;";
937 +
938 +   private static const string SQL_COUNT =
939 +        "SELECT count(videos.id) " +
940 +        "FROM videos, files " +
941 +        "WHERE dtime = 0 AND videos.id = files.id;";
942 +
943 +    private static const string SQL_FIND_OBJECT =
944 +        "SELECT videos.id, title, artist, length, path, mtime, size, dlna_profile, dlna_mime " +
945 +        "FROM videos, files " +
946 +        "WHERE dtime = 0 AND files.id = ? AND videos.id = files.id;";
947 +
948 +    private static const string SQL_ADDED =
949 +        "SELECT videos.id, title, artist, length, path, mtime, size, dlna_profile, dlna_mime " +
950 +        "FROM videos, files " +
951 +        "WHERE dtime = 0 AND videos.id = files.id " +
952 +        "AND update_id > ? AND update_id <= ?;";
953 +
954 +    private static const string SQL_REMOVED =
955 +        "SELECT videos.id, title, artist, length, path, mtime, size, dlna_profile, dlna_mime " +
956 +        "FROM videos, files " +
957 +        "WHERE dtime <> 0 AND videos.id = files.id " +
958 +        "AND update_id > ? AND update_id <= ?;";
959 +
960 +    protected override MediaObject? object_from_statement (Statement statement) {
961 +        var id = statement.column_int(0);
962 +        var mime_type = statement.column_text(8);
963 +        var path = statement.column_text(4);
964 +        var file = File.new_for_path(path);
965 +
966 +        /* TODO: Temporary code to extract the MIME TYPE.  LMS does not seem
967 +           to compute the mime type of videos.  Don't know why. */
968 +
969 +/*        if (mime_type == null || mime_type.length == 0) {
970 +            try {
971 +                FileInfo info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE,
972 +                                                FileQueryInfoFlags.NONE, null);
973 +                mime_type = info.get_content_type();
974 +            } catch {}
975 +        }
976 +*/
977 +
978 +        if (mime_type == null || mime_type.length == 0) {
979 +            /* TODO is this correct? */
980 +            debug ("Video item %d (%s) has no MIME type",
981 +                   id,
982 +                   path);
983 +            }
984 +
985 +        var title = statement.column_text(1);
986 +        var video = new VideoItem(this.build_child_id (id), this, title);
987 +        video.creator = statement.column_text(2);
988 +        video.duration = statement.column_int(3);
989 +        TimeVal tv = { (long) statement.column_int(5), (long) 0 };
990 +        video.date = tv.to_iso8601 ();
991 +        video.size = statement.column_int(6);
992 +        video.dlna_profile = statement.column_text(7);
993 +        video.mime_type = mime_type;
994 +        video.add_uri (file.get_uri ());
995 +
996 +        // Rygel does not support multiple video and audio tracks in a single file,
997 +        // so we just take the first one
998 +        var video_data = "select videos_videos.bitrate + videos_audios.bitrate, width, height, channels, sampling_rate " +
999 +            "from videos, videos_audios, videos_videos where videos.id = ? " +
1000 +            "and videos.id = videos_audios.video_id and videos.id = videos_videos.video_id;";
1001 +        try {
1002 +            var stmt = this.lms_db.prepare(video_data);
1003 +            Rygel.LMS.Database.find_object("%d".printf(id), stmt);
1004 +            video.bitrate = stmt.column_int(0) / 8; //convert bits per second into bytes per second
1005 +            video.width = stmt.column_int(1);
1006 +            video.height = stmt.column_int(2);
1007 +            video.channels = stmt.column_int(3);
1008 +            video.sample_freq = stmt.column_int(4);
1009 +        } catch (DatabaseError e) {
1010 +            warning ("Query failed: %s", e.message);
1011 +        }
1012 +
1013 +        return video;
1014 +    }
1015 +
1016 +    public AllVideos (string id, MediaContainer parent, string title, LMS.Database lms_db){
1017 +        base (id,
1018 +              parent,
1019 +              title,
1020 +              lms_db,
1021 +              AllVideos.SQL_ALL,
1022 +              AllVideos.SQL_FIND_OBJECT,
1023 +              AllVideos.SQL_COUNT,
1024 +              AllVideos.SQL_ADDED,
1025 +              AllVideos.SQL_REMOVED
1026 +             );
1027 +    }
1028 +}
1029 diff --git a/src/plugins/lms/rygel-lms-artist.vala b/src/plugins/lms/rygel-lms-artist.vala
1030 new file mode 100644
1031 index 0000000..31e9070
1032 --- /dev/null
1033 +++ b/src/plugins/lms/rygel-lms-artist.vala
1034 @@ -0,0 +1,75 @@
1035 +/*
1036 + * Copyright (C) 2013 Intel Corporation.
1037 + *
1038 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
1039 + *
1040 + * This file is part of Rygel.
1041 + *
1042 + * Rygel is free software; you can redistribute it and/or modify
1043 + * it under the terms of the GNU Lesser General Public License as published by
1044 + * the Free Software Foundation; either version 2 of the License, or
1045 + * (at your option) any later version.
1046 + *
1047 + * Rygel is distributed in the hope that it will be useful,
1048 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1049 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1050 + * GNU Lesser General Public License for more details.
1051 + *
1052 + * You should have received a copy of the GNU Lesser General Public License
1053 + * along with this program; if not, write to the Free Software Foundation,
1054 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1055 + */
1056 +
1057 +using Rygel;
1058 +using Sqlite;
1059 +
1060 +public class Rygel.LMS.Artist : Rygel.LMS.CategoryContainer {
1061 +    private static const string SQL_ALL_TEMPLATE =
1062 +        "SELECT audio_albums.id, audio_albums.name " +
1063 +        "FROM audio_albums " +
1064 +        "WHERE audio_albums.artist_id = %s " +
1065 +        "LIMIT ? OFFSET ?;";
1066 +
1067 +    private static const string SQL_COUNT_TEMPLATE =
1068 +        "SELECT COUNT(audio_albums.id) " +
1069 +        "FROM audio_albums " +
1070 +        "WHERE audio_albums.artist_id = %s";
1071 +
1072 +    private static const string SQL_FIND_OBJECT_TEMPLATE =
1073 +        "SELECT audio_albums.id, audio_albums.name " +
1074 +        "FROM audio_albums " +
1075 +        "WHERE audio_albums.id = ? AND audio_albums.artist_id = %s;";
1076 +
1077 +    private static string get_sql_all (string id) {
1078 +        return (SQL_ALL_TEMPLATE.printf (id));
1079 +    }
1080 +    private static string get_sql_find_object (string id) {
1081 +        return (SQL_FIND_OBJECT_TEMPLATE.printf (id));
1082 +    }
1083 +    private static string get_sql_count (string id) {
1084 +        return (SQL_COUNT_TEMPLATE.printf (id));
1085 +    }
1086 +
1087 +    protected override MediaObject? object_from_statement (Statement statement) {
1088 +        var db_id = "%d".printf (statement.column_int (0));
1089 +        var title = statement.column_text (1);
1090 +        return new LMS.Album (db_id, this, title, this.lms_db);
1091 +    }
1092 +
1093 +    public Artist (string         id,
1094 +                   MediaContainer parent,
1095 +                   string         title,
1096 +                   LMS.Database   lms_db) {
1097 +
1098 +        base (id,
1099 +              parent,
1100 +              title,
1101 +              lms_db,
1102 +              get_sql_all (id),
1103 +              get_sql_find_object (id),
1104 +              get_sql_count (id),
1105 +              null, // LMS does not track adding or removing albums
1106 +              null
1107 +             );
1108 +    }
1109 +}
1110 diff --git a/src/plugins/lms/rygel-lms-artists.vala b/src/plugins/lms/rygel-lms-artists.vala
1111 new file mode 100644
1112 index 0000000..a00b2ce
1113 --- /dev/null
1114 +++ b/src/plugins/lms/rygel-lms-artists.vala
1115 @@ -0,0 +1,62 @@
1116 +/*
1117 + * Copyright (C) 2013 Intel Corporation.
1118 + *
1119 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
1120 + *
1121 + * This file is part of Rygel.
1122 + *
1123 + * Rygel is free software; you can redistribute it and/or modify
1124 + * it under the terms of the GNU Lesser General Public License as published by
1125 + * the Free Software Foundation; either version 2 of the License, or
1126 + * (at your option) any later version.
1127 + *
1128 + * Rygel is distributed in the hope that it will be useful,
1129 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1130 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1131 + * GNU Lesser General Public License for more details.
1132 + *
1133 + * You should have received a copy of the GNU Lesser General Public License
1134 + * along with this program; if not, write to the Free Software Foundation,
1135 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1136 + */
1137 +
1138 +using Rygel;
1139 +using Sqlite;
1140 +
1141 +public class Rygel.LMS.Artists : Rygel.LMS.CategoryContainer {
1142 +    private static const string SQL_ALL =
1143 +        "SELECT audio_artists.id, audio_artists.name " +
1144 +        "FROM audio_artists " +
1145 +        "LIMIT ? OFFSET ?;";
1146 +
1147 +    private static const string SQL_COUNT =
1148 +        "SELECT COUNT(audio_artists.id) " +
1149 +        "FROM audio_artists;";
1150 +
1151 +    private static const string SQL_FIND_OBJECT =
1152 +        "SELECT audio_artists.id, audio_artists.name " +
1153 +        "FROM audio_artists " +
1154 +        "WHERE audio_artists.id = ?;";
1155 +
1156 +    protected override MediaObject? object_from_statement (Statement statement) {
1157 +        var db_id = "%d".printf (statement.column_int (0));
1158 +        var title = statement.column_text (1);
1159 +
1160 +        return new LMS.Artist (db_id, this, title, this.lms_db);
1161 +    }
1162 +
1163 +    public Artists (string id,
1164 +                    MediaContainer parent,
1165 +                    string title,
1166 +                    LMS.Database   lms_db) {
1167 +        base (id,
1168 +              parent,
1169 +              title,
1170 +              lms_db,
1171 +              Artists.SQL_ALL,
1172 +              Artists.SQL_FIND_OBJECT,
1173 +              Artists.SQL_COUNT,
1174 +              null, null
1175 +             );
1176 +    }
1177 +}
1178 diff --git a/src/plugins/lms/rygel-lms-category-container.vala b/src/plugins/lms/rygel-lms-category-container.vala
1179 new file mode 100644
1180 index 0000000..e5430d1
1181 --- /dev/null
1182 +++ b/src/plugins/lms/rygel-lms-category-container.vala
1183 @@ -0,0 +1,428 @@
1184 +/*
1185 + * Copyright (C) 2009,2010 Jens Georg <mail@jensge.org>,
1186 + *           (C) 2013 Intel Corporation.
1187 + *
1188 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
1189 + *
1190 + * This file is part of Rygel.
1191 + *
1192 + * Rygel is free software; you can redistribute it and/or modify
1193 + * it under the terms of the GNU Lesser General Public License as published by
1194 + * the Free Software Foundation; either version 2 of the License, or
1195 + * (at your option) any later version.
1196 + *
1197 + * Rygel is distributed in the hope that it will be useful,
1198 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1199 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1200 + * GNU Lesser General Public License for more details.
1201 + *
1202 + * You should have received a copy of the GNU Lesser General Public License
1203 + * along with this program; if not, write to the Free Software Foundation,
1204 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1205 + */
1206 +
1207 +using Rygel;
1208 +using Gee;
1209 +using Sqlite;
1210 +
1211 +public errordomain Rygel.LMS.CategoryContainerError {
1212 +    SQLITE_ERROR,
1213 +    GENERAL_ERROR,
1214 +    INVALID_TYPE,
1215 +    UNSUPPORTED_SEARCH
1216 +}
1217 +
1218 +public abstract class Rygel.LMS.CategoryContainer : Rygel.MediaContainer,
1219 +                                                    Rygel.TrackableContainer,
1220 +                                                    Rygel.SearchableContainer {
1221 +    public ArrayList<string> search_classes { get; set; }
1222 +
1223 +    public unowned LMS.Database lms_db { get; construct; }
1224 +
1225 +    public string db_id { get; construct; }
1226 +
1227 +    public string sql_all { get; construct; }
1228 +    public string sql_find_object { get; construct; }
1229 +    public string sql_count { get; construct; }
1230 +    public string sql_added { get; construct; }
1231 +    public string sql_removed { get; construct; }
1232 +
1233 +    protected Statement stmt_all;
1234 +    protected Statement stmt_find_object;
1235 +    protected Statement stmt_added;
1236 +    protected Statement stmt_removed;
1237 +
1238 +    protected string child_prefix;
1239 +    protected string ref_prefix;
1240 +
1241 +    protected abstract MediaObject? object_from_statement (Statement statement);
1242 +
1243 +    /* TODO these should be abstract */
1244 +    protected virtual string get_sql_all_with_filter (string filter) {
1245 +        return this.sql_all;
1246 +    }
1247 +    protected virtual string get_sql_count_with_filter (string filter) {
1248 +        return this.sql_count;
1249 +    }
1250 +
1251 +    private static string? map_operand_to_column (string     operand,
1252 +                                                  out string? collate = null,
1253 +                                                  bool        for_sort = false)
1254 +                                                  throws Error {
1255 +        string column = null;
1256 +        bool use_collation = false;
1257 +
1258 +        // TODO add all used aliases to sql queries
1259 +        switch (operand) {
1260 +            case "dc:title":
1261 +                column = "title";
1262 +                use_collation = true;
1263 +                break;
1264 +            case "upnp:artist":
1265 +                column = "artist";
1266 +                use_collation = true;
1267 +                break;
1268 +            case "dc:creator":
1269 +                column = "creator";
1270 +                use_collation = true;
1271 +                break;
1272 +            default:
1273 +                var message = "Unsupported column %s".printf (operand);
1274 +
1275 +                throw new CategoryContainerError.UNSUPPORTED_SEARCH (message);
1276 +        }
1277 +
1278 +        if (use_collation) {
1279 +            collate = "COLLATE CASEFOLD";
1280 +        } else {
1281 +            collate = "";
1282 +        }
1283 +
1284 +        return column;
1285 +    }
1286 +
1287 +    private static string? relational_expression_to_sql
1288 +                                        (RelationalExpression exp,
1289 +                                         GLib.ValueArray      args)
1290 +                                         throws Error {
1291 +        GLib.Value? v = null;
1292 +        string collate = null;
1293 +
1294 +        string column = CategoryContainer.map_operand_to_column (exp.operand1,
1295 +                                                                 out collate);
1296 +        SqlOperator operator;
1297 +
1298 +        switch (exp.op) {
1299 +            case GUPnP.SearchCriteriaOp.EXISTS:
1300 +                string sql_function;
1301 +                if (exp.operand2 == "true") {
1302 +                    sql_function = "%s IS NOT NULL AND %s != ''";
1303 +                } else {
1304 +                    sql_function = "%s IS NULL OR %s = ''";
1305 +                }
1306 +
1307 +                return sql_function.printf (column, column);
1308 +            case GUPnP.SearchCriteriaOp.EQ:
1309 +            case GUPnP.SearchCriteriaOp.NEQ:
1310 +            case GUPnP.SearchCriteriaOp.LESS:
1311 +            case GUPnP.SearchCriteriaOp.LEQ:
1312 +            case GUPnP.SearchCriteriaOp.GREATER:
1313 +            case GUPnP.SearchCriteriaOp.GEQ:
1314 +                v = exp.operand2;
1315 +                operator = new SqlOperator.from_search_criteria_op
1316 +                                            (exp.op, column, collate);
1317 +                break;
1318 +            case GUPnP.SearchCriteriaOp.CONTAINS:
1319 +                operator = new SqlFunction ("contains", column);
1320 +                v = exp.operand2;
1321 +                break;
1322 +            case GUPnP.SearchCriteriaOp.DOES_NOT_CONTAIN:
1323 +                operator = new SqlFunction ("NOT contains", column);
1324 +                v = exp.operand2;
1325 +                break;
1326 +            case GUPnP.SearchCriteriaOp.DERIVED_FROM:
1327 +                operator = new SqlOperator ("LIKE", column);
1328 +                v = "%s%%".printf (exp.operand2);
1329 +                break;
1330 +            default:
1331 +                warning ("Unsupported op %d", exp.op);
1332 +                return null;
1333 +        }
1334 +
1335 +        if (v != null) {
1336 +            args.append (v);
1337 +        }
1338 +
1339 +        return operator.to_string ();
1340 +    }
1341 +
1342 +    private static string logical_expression_to_sql
1343 +                                        (LogicalExpression expression,
1344 +                                         GLib.ValueArray   args)
1345 +                                         throws Error {
1346 +        string left_sql_string = CategoryContainer.search_expression_to_sql
1347 +                                        (expression.operand1,
1348 +                                         args);
1349 +        string right_sql_string = CategoryContainer.search_expression_to_sql
1350 +                                        (expression.operand2,
1351 +                                         args);
1352 +        unowned string operator_sql_string = "OR";
1353 +
1354 +        if (expression.op == LogicalOperator.AND) {
1355 +            operator_sql_string = "AND";
1356 +        }
1357 +
1358 +        return "(%s %s %s)".printf (left_sql_string,
1359 +                                    operator_sql_string,
1360 +                                    right_sql_string);
1361 +    }
1362 +
1363 +    private static string? search_expression_to_sql
1364 +                                        (SearchExpression? expression,
1365 +                                         GLib.ValueArray   args)
1366 +                                         throws Error {
1367 +        if (expression == null) {
1368 +            return "";
1369 +        }
1370 +
1371 +        if (expression is LogicalExpression) {
1372 +            return CategoryContainer.logical_expression_to_sql
1373 +                                        (expression as LogicalExpression, args);
1374 +        } else {
1375 +            return CategoryContainer.relational_expression_to_sql
1376 +                                        (expression as RelationalExpression,
1377 +                                         args);
1378 +        }
1379 +    }
1380 +
1381 +    protected virtual uint get_child_count_with_filter (string     where_filter,
1382 +                                                        ValueArray args)
1383 +    {
1384 +        var query = this.get_sql_count_with_filter (where_filter);
1385 +        try {
1386 +            var stmt = this.lms_db.prepare_and_init (query, args.values);
1387 +            if (stmt.step () != Sqlite.ROW) {
1388 +                return 0;
1389 +            }
1390 +            return stmt.column_int (0);
1391 +        } catch (DatabaseError e) {
1392 +            warning ("Query failed: %s", e.message);
1393 +            return 0;
1394 +        }
1395 +    }
1396 +
1397 +    protected virtual MediaObjects? get_children_with_filter (string     where_filter,
1398 +                                                              ValueArray args,
1399 +                                                              string     sort_criteria,
1400 +                                                              uint       offset,
1401 +                                                              uint       max_count) {
1402 +        var children = new MediaObjects ();
1403 +        GLib.Value v = max_count;
1404 +        args.append (v);
1405 +        v = offset;
1406 +        args.append (v);
1407 +
1408 +        var query = this.get_sql_all_with_filter (where_filter);
1409 +        try {
1410 +            var stmt = this.lms_db.prepare_and_init (query, args.values);
1411 +            while (Database.get_children_step (stmt)) {
1412 +                children.add (this.object_from_statement (stmt));
1413 +            }
1414 +        } catch (DatabaseError e) {
1415 +            warning ("Query failed: %s", e.message);
1416 +        }
1417 +
1418 +        return children;
1419 +    }
1420 +
1421 +    public async MediaObjects? search (SearchExpression? expression,
1422 +                                       uint offset,
1423 +                                       uint max_count,
1424 +                                       out uint total_matches,
1425 +                                       string sort_criteria,
1426 +                                       Cancellable? cancellable)
1427 +                                        throws Error {
1428 +        debug ("search()");
1429 +        try {
1430 +            var args = new GLib.ValueArray (0);
1431 +            var filter = CategoryContainer.search_expression_to_sql (expression,
1432 +                                                                     args);
1433 +            total_matches = this.get_child_count_with_filter (filter, args);
1434 +
1435 +            if (expression != null) {
1436 +                debug ("  Original search: %s", expression.to_string ());
1437 +                debug ("  Parsed search expression: %s", filter);
1438 +                debug ("  Filtered cild count is %u", total_matches);
1439 +            }
1440 +
1441 +            if (max_count == 0) {
1442 +                max_count = uint.MAX;
1443 +            }
1444 +            return this.get_children_with_filter (filter,
1445 +                                                  args,
1446 +                                                  sort_criteria,
1447 +                                                  offset,
1448 +                                                  max_count);
1449 +        } catch (Error e) {
1450 +            debug ("  Falling back to simple_search(): %s", e.message);
1451 +            return yield this.simple_search (expression,
1452 +                                             offset,
1453 +                                             max_count,
1454 +                                             out total_matches,
1455 +                                             sort_criteria,
1456 +                                             cancellable);
1457 +        }
1458 +    }
1459 +
1460 +    public async override MediaObjects? get_children (uint offset,
1461 +                                                      uint max_count,
1462 +                                                      string sort_criteria,
1463 +                                                      Cancellable? cancellable)
1464 +                                        throws Error {
1465 +        MediaObjects retval = new MediaObjects ();
1466 +
1467 +        Database.get_children_init (this.stmt_all,
1468 +                                    offset,
1469 +                                    max_count,
1470 +                                    sort_criteria);
1471 +        while (Database.get_children_step (this.stmt_all)) {
1472 +            retval.add (this.object_from_statement (this.stmt_all));
1473 +        }
1474 +
1475 +        return retval;
1476 +    }
1477 +
1478 +    public async override MediaObject? find_object (string id, 
1479 +                                                    Cancellable? cancellable)
1480 +                                        throws Error {
1481 +        if (!id.has_prefix (this.child_prefix)) {
1482 +            /* can't match anything in this container */
1483 +            return null;
1484 +        }
1485 +
1486 +        MediaObject object = null;
1487 +
1488 +        /* remove parent section from id */
1489 +        var real_id = id.substring (this.child_prefix.length);
1490 +        /* remove grandchildren from id */
1491 +        var index = real_id.index_of_char (':');
1492 +        if (index > 0) {
1493 +            real_id = real_id.slice (0, index);
1494 +        }
1495 +
1496 +        try {
1497 +            Database.find_object (real_id, this.stmt_find_object);
1498 +            var child = this.object_from_statement (this.stmt_find_object);
1499 +            if (index < 0) {
1500 +                object = child;
1501 +            } else {
1502 +                /* try grandchildren */
1503 +                var container = child as CategoryContainer; 
1504 +                object = yield container.find_object (id, cancellable);
1505 +
1506 +                /* tell object to keep a reference to the parent --
1507 +                 * otherwise parent is freed before object is serialized */
1508 +                object.parent_ref = object.parent;
1509 +            }
1510 +        } catch (DatabaseError e) {
1511 +            debug ("find_object %s in %s: %s", id, this.id, e.message);
1512 +            /* Happens e.g. if id is not an integer */
1513 +        }
1514 +
1515 +        return object;
1516 +    }
1517 +
1518 +    protected string build_child_id (int db_id) {
1519 +        return "%s%d".printf (this.child_prefix, db_id);
1520 +    }
1521 +
1522 +    protected string build_reference_id (int db_id) {
1523 +        return "%s%d".printf (this.ref_prefix, db_id);
1524 +    }
1525 +
1526 +    protected async void add_child (MediaObject object) {
1527 +    }
1528 +
1529 +    protected async void remove_child (MediaObject object) {
1530 +    }
1531 +
1532 +    private void on_db_updated(uint64 old_id, uint64 new_id) {
1533 +        try {
1534 +            var stmt_count = this.lms_db.prepare (this.sql_count);
1535 +
1536 +            if (stmt_count.step () == Sqlite.ROW) {
1537 +                this.child_count = stmt_count.column_int (0);
1538 +            }
1539 +
1540 +            Database.get_children_with_update_id_init (this.stmt_added,
1541 +                                                       old_id,
1542 +                                                       new_id);
1543 +            while (Database.get_children_step (this.stmt_added)) {
1544 +                this.add_child_tracked.begin(this.object_from_statement (this.stmt_added));
1545 +            }
1546 +
1547 +            Database.get_children_with_update_id_init (this.stmt_removed,
1548 +                                                       old_id,
1549 +                                                       new_id);
1550 +            while (Database.get_children_step (this.stmt_removed)) {
1551 +                this.remove_child_tracked.begin(this.object_from_statement (this.stmt_removed));
1552 +            }
1553 +
1554 +        } catch (DatabaseError e) {
1555 +            warning ("Can't perform container update: %s", e.message);
1556 +        }
1557 +
1558 +    }
1559 +
1560 +    public CategoryContainer (string db_id,
1561 +                              MediaContainer parent,
1562 +                              string title,
1563 +                              LMS.Database lms_db,
1564 +                              string sql_all,
1565 +                              string sql_find_object,
1566 +                              string sql_count,
1567 +                              string? sql_added,
1568 +                              string? sql_removed
1569 +                             ) {
1570 +        Object (id : "%s:%s".printf (parent.id, db_id),
1571 +                db_id : db_id,
1572 +                parent : parent,
1573 +                title : title,
1574 +                lms_db : lms_db,
1575 +                sql_all : sql_all,
1576 +                sql_find_object : sql_find_object,
1577 +                sql_count : sql_count,
1578 +                sql_added : sql_added,
1579 +                sql_removed: sql_removed
1580 +               );
1581 +    }
1582 +
1583 +    construct {
1584 +        this.search_classes = new ArrayList<string> ();
1585 +
1586 +        this.child_prefix = "%s:".printf (this.id);
1587 +
1588 +        var index = this.id.index_of_char (':');
1589 +        this.ref_prefix = this.id.slice (0, index) + ":all:";
1590 +
1591 +        try {
1592 +            this.stmt_all = this.lms_db.prepare (this.sql_all);
1593 +            this.stmt_find_object = this.lms_db.prepare (this.sql_find_object);
1594 +            var stmt_count = this.lms_db.prepare (this.sql_count);
1595 +
1596 +            if (stmt_count.step () == Sqlite.ROW) {
1597 +                this.child_count = stmt_count.column_int (0);
1598 +            }
1599 +            // some container implementations don't have a reasonable way to provide
1600 +            // id-based statements to fetch added or removed items
1601 +            if (this.sql_added != null && this.sql_removed != null) {
1602 +                this.stmt_added = this.lms_db.prepare (this.sql_added);
1603 +                this.stmt_removed = this.lms_db.prepare (this.sql_removed);
1604 +                lms_db.db_updated.connect(this.on_db_updated);
1605 +            }
1606 +        } catch (DatabaseError e) {
1607 +            warning ("Container %s: %s", this.title, e.message);
1608 +        }
1609 +
1610 +    }
1611 +}
1612 diff --git a/src/plugins/lms/rygel-lms-collate.c b/src/plugins/lms/rygel-lms-collate.c
1613 new file mode 100644
1614 index 0000000..8eee80b
1615 --- /dev/null
1616 +++ b/src/plugins/lms/rygel-lms-collate.c
1617 @@ -0,0 +1,49 @@
1618 +/*
1619 + * Copyright (C) 2012 Jens Georg <mail@jensge.org>.
1620 + *
1621 + * Author: Jens Georg <mail@jensge.org>
1622 + *
1623 + * This file is part of Rygel.
1624 + *
1625 + * Rygel is free software; you can redistribute it and/or modify
1626 + * it under the terms of the GNU Lesser General Public License as published by
1627 + * the Free Software Foundation; either version 2 of the License, or
1628 + * (at your option) any later version.
1629 + *
1630 + * Rygel is distributed in the hope that it will be useful,
1631 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1632 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1633 + * GNU Lesser General Public License for more details.
1634 + *
1635 + * You should have received a copy of the GNU Lesser General Public License
1636 + * along with this program; if not, write to the Free Software Foundation,
1637 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1638 + */
1639 +
1640 +#include <glib.h>
1641 +
1642 +#ifdef HAVE_UNISTRING
1643 +#   include <unistr.h>
1644 +#endif
1645 +
1646 +gint rygel_lms_utf8_collate_str (const char *a, gsize alen,
1647 +                                 const char *b, gsize blen)
1648 +{
1649 +    char *a_str, *b_str;
1650 +    gint result;
1651 +
1652 +    /* Make sure the passed strings are null terminated */
1653 +    a_str = g_strndup (a, alen);
1654 +    b_str = g_strndup (b, blen);
1655 +
1656 +#ifdef HAVE_UNISTRING
1657 +    result = u8_strcoll (a_str, b_str);
1658 +#else
1659 +    return g_utf8_collate (a_str, b_str);
1660 +#endif
1661 +
1662 +    g_free (a_str);
1663 +    g_free (b_str);
1664 +
1665 +    return result;
1666 +}
1667 diff --git a/src/plugins/lms/rygel-lms-database.vala b/src/plugins/lms/rygel-lms-database.vala
1668 new file mode 100644
1669 index 0000000..e898d66
1670 --- /dev/null
1671 +++ b/src/plugins/lms/rygel-lms-database.vala
1672 @@ -0,0 +1,294 @@
1673 +/*
1674 + * Copyright (C) 2009,2011 Jens Georg <mail@jensge.org>,
1675 + *           (C) 2013 Intel Corporation.
1676 + *
1677 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
1678 + *
1679 + * This file is part of Rygel.
1680 + *
1681 + * Rygel is free software; you can redistribute it and/or modify
1682 + * it under the terms of the GNU Lesser General Public License as published by
1683 + * the Free Software Foundation; either version 2 of the License, or
1684 + * (at your option) any later version.
1685 + *
1686 + * Rygel is distributed in the hope that it will be useful,
1687 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1688 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1689 + * GNU Lesser General Public License for more details.
1690 + *
1691 + * You should have received a copy of the GNU Lesser General Public License
1692 + * along with this program; if not, write to the Free Software Foundation,
1693 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1694 + */
1695 +
1696 +using Rygel;
1697 +using Gee;
1698 +using Sqlite;
1699 +
1700 +public errordomain Rygel.LMS.DatabaseError {
1701 +    OPEN,
1702 +    PREPARE,
1703 +    BIND,
1704 +    STEP,
1705 +    NOT_FOUND
1706 +}
1707 +
1708 +namespace Rygel.LMS {
1709 +    extern static int utf8_collate_str (uint8[] a, uint8[] b);
1710 +}
1711 +
1712 +public class Rygel.LMS.Database {
1713 +
1714 +    public signal void db_updated(uint64 old_update_id, uint64 new_update_id);
1715 +
1716 +    private Sqlite.Database db;
1717 +    private LMS.DBus lms_proxy;
1718 +    private uint64 update_id;
1719 +
1720 +    /**
1721 +     * Function to implement the custom SQL function 'contains'
1722 +     */
1723 +    private static void utf8_contains (Sqlite.Context context,
1724 +                                       Sqlite.Value[] args)
1725 +                                       requires (args.length == 2) {
1726 +        if (args[0].to_text () == null ||
1727 +            args[1].to_text () == null) {
1728 +           context.result_int (0);
1729 +
1730 +           return;
1731 +        }
1732 +
1733 +        var pattern = Regex.escape_string (args[1].to_text ());
1734 +        if (Regex.match_simple (pattern,
1735 +                                args[0].to_text (),
1736 +                                RegexCompileFlags.CASELESS)) {
1737 +            context.result_int (1);
1738 +        } else {
1739 +            context.result_int (0);
1740 +        }
1741 +    }
1742 +
1743 +    /**
1744 +     * Function to implement the custom SQLite collation 'CASEFOLD'.
1745 +     *
1746 +     * Uses utf8 case-fold to compare the strings.
1747 +     */
1748 +    private static int utf8_collate (int alen, void* a, int blen, void* b) {
1749 +        // unowned to prevent array copy
1750 +        unowned uint8[] _a = (uint8[]) a;
1751 +        _a.length = alen;
1752 +
1753 +        unowned uint8[] _b = (uint8[]) b;
1754 +        _b.length = blen;
1755 +
1756 +        return LMS.utf8_collate_str (_a, _b);
1757 +    }
1758 +
1759 +    public Database () throws DatabaseError {
1760 +        string db_path;
1761 +        try {
1762 +            lms_proxy = Bus.get_proxy_sync (BusType.SESSION,
1763 +                                            "org.lightmediascanner",
1764 +                                            "/org/lightmediascanner/Scanner1");
1765 +            db_path = lms_proxy.data_base_path;
1766 +            debug ("Got db path %s from LMS over dbus", db_path);
1767 +            update_id = lms_proxy.update_id;
1768 +            debug ("Got updated id %lld from LMS over dbus", update_id);
1769 +            lms_proxy.g_properties_changed.connect (this.on_lms_properties_changed);
1770 +
1771 +        } catch (IOError e) {
1772 +            warning("Couldn't get LMS Dbus proxy: %s", e.message);
1773 +            db_path = Environment.get_user_config_dir() +
1774 +                      "/lightmediascannerd/db.sqlite3";
1775 +            debug  ("Using default sqlite database location %s", db_path);
1776 +        }
1777 +
1778 +        Sqlite.Database.open (db_path, out this.db);
1779 +        if (this.db.errcode () != Sqlite.OK) {
1780 +            throw new DatabaseError.OPEN ("Failed to open '%s': %d",
1781 +                                          db_path,
1782 +                                          this.db.errcode () );
1783 +        }
1784 +
1785 +        this.db.create_function ("contains",
1786 +                                 2,
1787 +                                 Sqlite.UTF8,
1788 +                                 null,
1789 +                                 LMS.Database.utf8_contains,
1790 +                                 null,
1791 +                                 null);
1792 +
1793 +        this.db.create_collation ("CASEFOLD",
1794 +                                  Sqlite.UTF8,
1795 +                                  LMS.Database.utf8_collate);
1796 +
1797 +    }
1798 +
1799 +    private void on_lms_properties_changed (DBusProxy lms_proxy,
1800 +                                        Variant   changed,
1801 +                                        string[]  invalidated) {
1802 +        if (!changed.get_type().equal (VariantType.VARDICT)) {
1803 +            return;
1804 +        }
1805 +
1806 +        foreach (var changed_prop in changed) {
1807 +            var key = (string) changed_prop.get_child_value (0);
1808 +            var value = changed_prop.get_child_value (1).get_child_value (0);
1809 +
1810 +            debug ("LMS property %s changed value to %s", key, value.print(true));
1811 +
1812 +            switch (key) {
1813 +                case "UpdateID":
1814 +                    db_updated(update_id, (uint64)value);
1815 +                    update_id = (uint64)value;
1816 +                    break;
1817 +            }
1818 +        }
1819 +    }
1820 +
1821 +
1822 +    public Statement prepare (string query_string) throws DatabaseError {
1823 +        Statement statement;
1824 +
1825 +        var err = this.db.prepare_v2 (query_string, -1, out statement);
1826 +        if (err != Sqlite.OK)
1827 +            throw new DatabaseError.PREPARE ("Unable to create statement '%s': %d",
1828 +                                             query_string,
1829 +                                             err);
1830 +        return statement;
1831 +    }
1832 +
1833 +
1834 +    public Statement prepare_and_init (string   query,
1835 +                                       GLib.Value[]? arguments)
1836 +                                        throws DatabaseError {
1837 +
1838 +        Statement statement;
1839 +
1840 +        var err = this.db.prepare_v2 (query, -1, out statement);
1841 +        if (err != Sqlite.OK)
1842 +            throw new DatabaseError.PREPARE ("Unable to create statement '%s': %d",
1843 +                                             query,
1844 +                                             err);
1845 +
1846 +        for (var i = 1; i <= arguments.length; ++i) {
1847 +            int sqlite_err;
1848 +            unowned GLib.Value current_value = arguments[i - 1];
1849 +
1850 +            if (current_value.holds (typeof (int))) {
1851 +                sqlite_err = statement.bind_int (i, current_value.get_int ());
1852 +                if (sqlite_err != Sqlite.OK)
1853 +                    throw new DatabaseError.BIND("Unable to bind value %d",
1854 +                                                 sqlite_err);
1855 +            } else if (current_value.holds (typeof (int64))) {
1856 +                sqlite_err = statement.bind_int64 (i, current_value.get_int64 ());
1857 +                if (sqlite_err != Sqlite.OK)
1858 +                    throw new DatabaseError.BIND("Unable to bind value %d",
1859 +                                                 sqlite_err);
1860 +            } else if (current_value.holds (typeof (uint64))) {
1861 +                sqlite_err = statement.bind_int64 (i, (int64) current_value.get_uint64 ());
1862 +                if (sqlite_err != Sqlite.OK)
1863 +                    throw new DatabaseError.BIND("Unable to bind value %d",
1864 +                                                 sqlite_err);
1865 +            } else if (current_value.holds (typeof (long))) {
1866 +                sqlite_err = statement.bind_int64 (i, current_value.get_long ());
1867 +                if (sqlite_err != Sqlite.OK)
1868 +                    throw new DatabaseError.BIND("Unable to bind value %d",
1869 +                                                 sqlite_err);
1870 +            } else if (current_value.holds (typeof (uint))) {
1871 +                sqlite_err = statement.bind_int64 (i, current_value.get_uint ());
1872 +                if (sqlite_err != Sqlite.OK)
1873 +                    throw new DatabaseError.BIND("Unable to bind value %d",
1874 +                                                 sqlite_err);
1875 +            } else if (current_value.holds (typeof (string))) {
1876 +                sqlite_err = statement.bind_text (i, current_value.get_string ());
1877 +                if (sqlite_err != Sqlite.OK)
1878 +                    throw new DatabaseError.BIND("Unable to bind value %d",
1879 +                                                 sqlite_err);
1880 +            } else if (current_value.holds (typeof (void *))) {
1881 +                if (current_value.peek_pointer () == null) {
1882 +                    sqlite_err = statement.bind_null (i);
1883 +                    if (sqlite_err != Sqlite.OK)
1884 +                        throw new DatabaseError.BIND("Unable to bind value %d",
1885 +                                                     sqlite_err);
1886 +                } else {
1887 +                    assert_not_reached ();
1888 +                }
1889 +            } else {
1890 +                var type = current_value.type ();
1891 +                warning (_("Unsupported type %s"), type.name ());
1892 +                assert_not_reached ();
1893 +            }
1894 +        }
1895 +
1896 +        return statement;
1897 +    }
1898 +
1899 +    public static void find_object(string id, Statement stmt) throws DatabaseError {
1900 +
1901 +        (void) stmt.reset();
1902 +
1903 +        int integer_id = int.parse(id);
1904 +        int sqlite_err = stmt.bind_int(1, integer_id);
1905 +        if (sqlite_err != Sqlite.OK)
1906 +            throw new DatabaseError.BIND("Unable to bind id %d", sqlite_err);
1907 +
1908 +        sqlite_err = stmt.step();
1909 +        if (sqlite_err != Sqlite.ROW)
1910 +            throw new DatabaseError.STEP("Unable to find id %s", id);
1911 +    }
1912 +
1913 +    public static void get_children_init (Statement stmt,
1914 +        uint offset, uint max_count, string sort_criteria) throws DatabaseError {
1915 +
1916 +        int sqlite_err;
1917 +
1918 +        (void) stmt.reset();
1919 +
1920 +        sqlite_err = stmt.bind_int(1, (int) max_count);
1921 +        if (sqlite_err != Sqlite.OK)
1922 +            throw new DatabaseError.BIND("Unable to bind max_count %d",
1923 +                                         sqlite_err);
1924 +
1925 +        sqlite_err = stmt.bind_int(2, (int) offset);
1926 +        if (sqlite_err != Sqlite.OK)
1927 +            throw new DatabaseError.BIND("Unable to bind offset %d",
1928 +                                         sqlite_err);
1929 +    }
1930 +
1931 +    public static void get_children_with_update_id_init (Statement stmt,
1932 +        uint64 old_id, uint64 new_id) throws DatabaseError {
1933 +
1934 +        int sqlite_err;
1935 +
1936 +        (void) stmt.reset();
1937 +
1938 +        if (new_id < old_id) // id value wrapped over
1939 +            sqlite_err = stmt.bind_int64(1, 0);
1940 +        else
1941 +            sqlite_err = stmt.bind_int64(1, (int64)old_id);
1942 +        if (sqlite_err != Sqlite.OK)
1943 +            throw new DatabaseError.BIND("Unable to bind old_id %d",
1944 +                                         sqlite_err);
1945 +
1946 +        sqlite_err = stmt.bind_int64(2, (int64)new_id);
1947 +        if (sqlite_err != Sqlite.OK)
1948 +            throw new DatabaseError.BIND("Unable to bind new_id %d",
1949 +                                         sqlite_err);
1950 +    }
1951 +
1952 +    public static bool get_children_step(Statement stmt) throws DatabaseError {
1953 +
1954 +        bool retval;
1955 +        int sqlite_err;
1956 +
1957 +        sqlite_err = stmt.step();
1958 +        retval = sqlite_err == Sqlite.ROW;
1959 +
1960 +        if (!retval && (sqlite_err != Sqlite.DONE))
1961 +            throw new DatabaseError.STEP("Error iterating through rows %d",
1962 +                                         sqlite_err);
1963 +
1964 +        return retval;
1965 +    }
1966 +}
1967 diff --git a/src/plugins/lms/rygel-lms-dbus-interfaces.vala b/src/plugins/lms/rygel-lms-dbus-interfaces.vala
1968 new file mode 100644
1969 index 0000000..13f00cb
1970 --- /dev/null
1971 +++ b/src/plugins/lms/rygel-lms-dbus-interfaces.vala
1972 @@ -0,0 +1,30 @@
1973 +/*
1974 + * Copyright (C) 2014 Intel Corporation.
1975 + *
1976 + * Author: Alexander Kanavin <alex.kanavin@gmail.com>
1977 + *
1978 + * This file is part of Rygel.
1979 + *
1980 + * Rygel is free software; you can redistribute it and/or modify
1981 + * it under the terms of the GNU Lesser General Public License as published by
1982 + * the Free Software Foundation; either version 2 of the License, or
1983 + * (at your option) any later version.
1984 + *
1985 + * Rygel is distributed in the hope that it will be useful,
1986 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1987 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
1988 + * GNU Lesser General Public License for more details.
1989 + *
1990 + * You should have received a copy of the GNU Lesser General Public License
1991 + * along with this program; if not, write to the Free Software Foundation,
1992 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1993 + */
1994 +
1995 +[DBus (name = "org.lightmediascanner.Scanner1")]
1996 +interface Rygel.LMS.DBus : DBusProxy {
1997 +    public abstract string data_base_path { owned get; }
1998 +    [DBus (name = "UpdateID")]
1999 +    public abstract uint64 update_id { get; }
2000 +
2001 +    //TODO: add all the other API items which are currently unused
2002 +}
2003 \ No newline at end of file
2004 diff --git a/src/plugins/lms/rygel-lms-image-root.vala b/src/plugins/lms/rygel-lms-image-root.vala
2005 new file mode 100644
2006 index 0000000..466bbe2
2007 --- /dev/null
2008 +++ b/src/plugins/lms/rygel-lms-image-root.vala
2009 @@ -0,0 +1,35 @@
2010 +/*
2011 + * Copyright (C) 2013 Intel Corporation.
2012 + *
2013 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
2014 + *
2015 + * This file is part of Rygel.
2016 + *
2017 + * Rygel is free software; you can redistribute it and/or modify
2018 + * it under the terms of the GNU Lesser General Public License as published by
2019 + * the Free Software Foundation; either version 2 of the License, or
2020 + * (at your option) any later version.
2021 + *
2022 + * Rygel is distributed in the hope that it will be useful,
2023 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2024 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2025 + * GNU Lesser General Public License for more details.
2026 + *
2027 + * You should have received a copy of the GNU Lesser General Public License
2028 + * along with this program; if not, write to the Free Software Foundation,
2029 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2030 + */
2031 +
2032 +using Rygel;
2033 +
2034 +public class Rygel.LMS.ImageRoot : Rygel.SimpleContainer {
2035 +    public ImageRoot (string         id,
2036 +                      MediaContainer parent,
2037 +                      string         title,
2038 +                      LMS.Database   lms_db) {
2039 +        base (id, parent, title);
2040 +
2041 +        this.add_child_container (new AllImages (this, lms_db));
2042 +        this.add_child_container (new ImageYears (this, lms_db));
2043 +    }
2044 +}
2045 diff --git a/src/plugins/lms/rygel-lms-image-year.vala b/src/plugins/lms/rygel-lms-image-year.vala
2046 new file mode 100644
2047 index 0000000..a7768f0
2048 --- /dev/null
2049 +++ b/src/plugins/lms/rygel-lms-image-year.vala
2050 @@ -0,0 +1,114 @@
2051 +/*
2052 + * Copyright (C) 2013 Intel Corporation.
2053 + *
2054 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
2055 + *
2056 + * This file is part of Rygel.
2057 + *
2058 + * Rygel is free software; you can redistribute it and/or modify
2059 + * it under the terms of the GNU Lesser General Public License as published by
2060 + * the Free Software Foundation; either version 2 of the License, or
2061 + * (at your option) any later version.
2062 + *
2063 + * Rygel is distributed in the hope that it will be useful,
2064 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2065 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2066 + * GNU Lesser General Public License for more details.
2067 + *
2068 + * You should have received a copy of the GNU Lesser General Public License
2069 + * along with this program; if not, write to the Free Software Foundation,
2070 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2071 + */
2072 +
2073 +using Rygel;
2074 +using Sqlite;
2075 +
2076 +public class Rygel.LMS.ImageYear : Rygel.LMS.CategoryContainer {
2077 +    private static const string SQL_ALL_TEMPLATE =
2078 +        "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime, strftime('%Y', date, 'unixepoch') as year " +
2079 +        "FROM images, files " +
2080 +        "WHERE dtime = 0 AND images.id = files.id AND year = '%s' " +
2081 +        "LIMIT ? OFFSET ?;";
2082 +
2083 +    private static const string SQL_COUNT_TEMPLATE =
2084 +        "SELECT count(images.id), strftime('%Y', date, 'unixepoch') as year " +
2085 +        "FROM images, files " +
2086 +        "WHERE dtime = 0 AND images.id = files.id AND year = '%s';";
2087 +
2088 +    private static const string SQL_FIND_OBJECT_TEMPLATE =
2089 +        "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime, strftime('%Y', date, 'unixepoch') as year " +
2090 +        "FROM images, files " +
2091 +        "WHERE dtime = 0 AND files.id = ? AND images.id = files.id AND year = '%s';";
2092 +
2093 +    private static const string SQL_ADDED_TEMPLATE =
2094 +        "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime, strftime('%Y', date, 'unixepoch') as year " +
2095 +        "FROM images, files " +
2096 +        "WHERE dtime = 0 AND images.id = files.id AND year = '%s' " +
2097 +        "AND update_id > ? AND update_id <= ?;";
2098 +
2099 +    private static const string SQL_REMOVED_TEMPLATE =
2100 +        "SELECT images.id, title, artist, date, width, height, path, size, dlna_profile, dlna_mime, strftime('%Y', date, 'unixepoch') as year " +
2101 +        "FROM images, files " +
2102 +        "WHERE dtime <> 0 AND images.id = files.id AND year = '%s' " +
2103 +        "AND update_id > ? AND update_id <= ?;";
2104 +
2105 +    protected override MediaObject? object_from_statement (Statement statement) {
2106 +        var id = statement.column_int(0);
2107 +        var path = statement.column_text(6);
2108 +        var mime_type = statement.column_text(9);
2109 +
2110 +        if (mime_type == null || mime_type.length == 0){
2111 +            /* TODO is this correct? */
2112 +            debug ("Image item %d (%s) has no MIME type",
2113 +                   id,
2114 +                   path);
2115 +        }
2116 +
2117 +        var title = statement.column_text(1);
2118 +        var image = new ImageItem(this.build_child_id (id), this, title);
2119 +        image.ref_id = this.build_reference_id (id);
2120 +        image.creator = statement.column_text(2);
2121 +        TimeVal tv = { (long) statement.column_int(3), (long) 0 };
2122 +        image.date = tv.to_iso8601 ();
2123 +        image.width = statement.column_int(4);
2124 +        image.height = statement.column_int(5);
2125 +        image.size = statement.column_int(7);
2126 +        image.mime_type = mime_type;
2127 +        image.dlna_profile = statement.column_text(8);
2128 +        File file = File.new_for_path(path);
2129 +        image.add_uri (file.get_uri ());
2130 +
2131 +        return image;
2132 +    }
2133 +
2134 +    private static string get_sql_all (string year) {
2135 +        return (SQL_ALL_TEMPLATE.printf (year));
2136 +    }
2137 +    private static string get_sql_find_object (string year) {
2138 +        return (SQL_FIND_OBJECT_TEMPLATE.printf (year));
2139 +    }
2140 +    private static string get_sql_count (string year) {
2141 +        return (SQL_COUNT_TEMPLATE.printf (year));
2142 +    }
2143 +    private static string get_sql_added (string year) {
2144 +        return (SQL_ADDED_TEMPLATE.printf (year));
2145 +    }
2146 +    private static string get_sql_removed (string year) {
2147 +        return (SQL_REMOVED_TEMPLATE.printf (year));
2148 +    }
2149 +
2150 +    public ImageYear (MediaContainer parent,
2151 +                      string         year,
2152 +                      LMS.Database   lms_db) {
2153 +        base ("%s".printf (year),
2154 +              parent,
2155 +              year,
2156 +              lms_db,
2157 +              get_sql_all (year),
2158 +              get_sql_find_object (year),
2159 +              get_sql_count (year),
2160 +              get_sql_added (year),
2161 +              get_sql_removed (year)
2162 +             );
2163 +    }
2164 +}
2165 diff --git a/src/plugins/lms/rygel-lms-image-years.vala b/src/plugins/lms/rygel-lms-image-years.vala
2166 new file mode 100644
2167 index 0000000..636f4d1
2168 --- /dev/null
2169 +++ b/src/plugins/lms/rygel-lms-image-years.vala
2170 @@ -0,0 +1,59 @@
2171 +/*
2172 + * Copyright (C) 2013 Intel Corporation.
2173 + *
2174 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
2175 + *
2176 + * This file is part of Rygel.
2177 + *
2178 + * Rygel is free software; you can redistribute it and/or modify
2179 + * it under the terms of the GNU Lesser General Public License as published by
2180 + * the Free Software Foundation; either version 2 of the License, or
2181 + * (at your option) any later version.
2182 + *
2183 + * Rygel is distributed in the hope that it will be useful,
2184 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2185 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2186 + * GNU Lesser General Public License for more details.
2187 + *
2188 + * You should have received a copy of the GNU Lesser General Public License
2189 + * along with this program; if not, write to the Free Software Foundation,
2190 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2191 + */
2192 +
2193 +using Rygel;
2194 +using Sqlite;
2195 +
2196 +public class Rygel.LMS.ImageYears : Rygel.LMS.CategoryContainer {
2197 +    private static const string SQL_ALL =
2198 +        "SELECT DISTINCT(strftime('%Y', images.date, 'unixepoch')) as year " +
2199 +        "FROM images " + 
2200 +        "LIMIT ? OFFSET ?;";
2201 +
2202 +    private static const string SQL_COUNT =
2203 +        "SELECT COUNT(DISTINCT(strftime('%Y', images.date, 'unixepoch'))) " +
2204 +        "FROM images;";
2205 +
2206 +    /* actually returns multiple times the same result (because no DISTINCT) */
2207 +    /* Casting the year is a workaround so we can keep using
2208 +     * Database.find_object() without making the argument a variant or something like it*/
2209 +    private static const string SQL_FIND_OBJECT =
2210 +        "SELECT strftime('%Y', images.date, 'unixepoch') as year " +
2211 +        "FROM images " +
2212 +        "WHERE year = CAST(? AS TEXT)";
2213 +
2214 +    protected override MediaObject? object_from_statement (Statement statement) {
2215 +        return new LMS.ImageYear (this, statement.column_text (0), this.lms_db);
2216 +    }
2217 +
2218 +    public ImageYears (MediaContainer parent, LMS.Database lms_db) {
2219 +        base ("years",
2220 +              parent,
2221 +              _("Years"),
2222 +              lms_db,
2223 +              ImageYears.SQL_ALL,
2224 +              ImageYears.SQL_FIND_OBJECT,
2225 +              ImageYears.SQL_COUNT,
2226 +              null, null
2227 +             );
2228 +    }
2229 +}
2230 diff --git a/src/plugins/lms/rygel-lms-music-root.vala b/src/plugins/lms/rygel-lms-music-root.vala
2231 new file mode 100644
2232 index 0000000..7b1eb0f
2233 --- /dev/null
2234 +++ b/src/plugins/lms/rygel-lms-music-root.vala
2235 @@ -0,0 +1,36 @@
2236 +/*
2237 + * Copyright (C) 2013 Intel Corporation.
2238 + *
2239 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
2240 + *
2241 + * This file is part of Rygel.
2242 + *
2243 + * Rygel is free software; you can redistribute it and/or modify
2244 + * it under the terms of the GNU Lesser General Public License as published by
2245 + * the Free Software Foundation; either version 2 of the License, or
2246 + * (at your option) any later version.
2247 + *
2248 + * Rygel is distributed in the hope that it will be useful,
2249 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2250 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2251 + * GNU Lesser General Public License for more details.
2252 + *
2253 + * You should have received a copy of the GNU Lesser General Public License
2254 + * along with this program; if not, write to the Free Software Foundation,
2255 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2256 + */
2257 +
2258 +using Rygel;
2259 +
2260 +public class Rygel.LMS.MusicRoot : Rygel.SimpleContainer {
2261 +    public MusicRoot (string         id,
2262 +                      MediaContainer parent,
2263 +                      string         title,
2264 +                      LMS.Database   lms_db) {
2265 +        base (id, parent, title);
2266 +
2267 +        this.add_child_container (new AllMusic (this, lms_db));
2268 +        this.add_child_container (new Artists ("artists", this, _("Artists"), lms_db));
2269 +        this.add_child_container (new Albums (this, lms_db));
2270 +    }
2271 +}
2272 diff --git a/src/plugins/lms/rygel-lms-plugin-factory.vala b/src/plugins/lms/rygel-lms-plugin-factory.vala
2273 new file mode 100644
2274 index 0000000..9fa8ccd
2275 --- /dev/null
2276 +++ b/src/plugins/lms/rygel-lms-plugin-factory.vala
2277 @@ -0,0 +1,40 @@
2278 +/*
2279 + * Copyright (C) 2013 Intel Corporation.
2280 + *
2281 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
2282 + *
2283 + * This file is part of Rygel.
2284 + *
2285 + * Rygel is free software; you can redistribute it and/or modify
2286 + * it under the terms of the GNU Lesser General Public License as published by
2287 + * the Free Software Foundation; either version 2 of the License, or
2288 + * (at your option) any later version.
2289 + *
2290 + * Rygel is distributed in the hope that it will be useful,
2291 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2292 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2293 + * GNU Lesser General Public License for more details.
2294 + *
2295 + * You should have received a copy of the GNU Lesser General Public License
2296 + * along with this program; if not, write to the Free Software Foundation,
2297 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2298 + */
2299 +
2300 +using Rygel;
2301 +
2302 +private Rygel.LMS.PluginFactory plugin_factory;
2303 +
2304 +public void module_init(PluginLoader loader) {
2305 +        plugin_factory = new Rygel.LMS.PluginFactory(loader);
2306 +}
2307 +
2308 +public class Rygel.LMS.PluginFactory {
2309 +
2310 +    PluginLoader loader;
2311 +
2312 +    public PluginFactory(PluginLoader loader) {
2313 +        this.loader = loader;
2314 +        this.loader.add_plugin(new LMS.Plugin());
2315 +    }
2316 +
2317 +}
2318 diff --git a/src/plugins/lms/rygel-lms-plugin.vala b/src/plugins/lms/rygel-lms-plugin.vala
2319 new file mode 100644
2320 index 0000000..8bf1284
2321 --- /dev/null
2322 +++ b/src/plugins/lms/rygel-lms-plugin.vala
2323 @@ -0,0 +1,35 @@
2324 +/*
2325 + * Copyright (C) 2013 Intel Corporation.
2326 + *
2327 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
2328 + *
2329 + * This file is part of Rygel.
2330 + *
2331 + * Rygel is free software; you can redistribute it and/or modify
2332 + * it under the terms of the GNU Lesser General Public License as published by
2333 + * the Free Software Foundation; either version 2 of the License, or
2334 + * (at your option) any later version.
2335 + *
2336 + * Rygel is distributed in the hope that it will be useful,
2337 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2338 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2339 + * GNU Lesser General Public License for more details.
2340 + *
2341 + * You should have received a copy of the GNU Lesser General Public License
2342 + * along with this program; if not, write to the Free Software Foundation,
2343 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2344 + */
2345 +
2346 +using Rygel;
2347 +
2348 +public class Rygel.LMS.Plugin : Rygel.MediaServerPlugin {
2349 +    public const string NAME = "LMS";
2350 +
2351 +    private static RootContainer root;
2352 +
2353 +    public Plugin() {
2354 +        if (root == null)
2355 +            root = new RootContainer();
2356 +        base(root, Plugin.NAME, null, PluginCapabilities.TRACK_CHANGES);
2357 +    }
2358 +}
2359 diff --git a/src/plugins/lms/rygel-lms-root-container.vala b/src/plugins/lms/rygel-lms-root-container.vala
2360 new file mode 100644
2361 index 0000000..1623fa3
2362 --- /dev/null
2363 +++ b/src/plugins/lms/rygel-lms-root-container.vala
2364 @@ -0,0 +1,58 @@
2365 +/*
2366 + * Copyright (C) 2013 Intel Corporation.
2367 + *
2368 + * Author: Jussi Kukkonen <jussi.kukkonen@intel.com>
2369 + *
2370 + * This file is part of Rygel.
2371 + *
2372 + * Rygel is free software; you can redistribute it and/or modify
2373 + * it under the terms of the GNU Lesser General Public License as published by
2374 + * the Free Software Foundation; either version 2 of the License, or
2375 + * (at your option) any later version.
2376 + *
2377 + * Rygel is distributed in the hope that it will be useful,
2378 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2379 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2380 + * GNU Lesser General Public License for more details.
2381 + *
2382 + * You should have received a copy of the GNU Lesser General Public License
2383 + * along with this program; if not, write to the Free Software Foundation,
2384 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2385 + */
2386 +
2387 +using Rygel;
2388 +
2389 +using Sqlite;
2390 +
2391 +public class Rygel.LMS.RootContainer : Rygel.SimpleContainer {
2392 +
2393 +    private LMS.Database lms_db = null;
2394 +
2395 +    public RootContainer() {
2396 +        var config = MetaConfig.get_default ();
2397 +
2398 +        var title = _("Shared media");
2399 +        try {
2400 +            title = config.get_string ("LightMediaScanner", "title");
2401 +        } catch (GLib.Error error) {}
2402 +
2403 +        base.root(title);
2404 +
2405 +        try {
2406 +            this.lms_db = new LMS.Database ();
2407 +
2408 +            this.add_child_container (new MusicRoot ("music", this, _("Music"), this.lms_db));
2409 +            this.add_child_container (new AllVideos ("all-videos", this, _("Videos"), this.lms_db));
2410 +            this.add_child_container (new ImageRoot ("images", this, _("Pictures"), this.lms_db));
2411 +
2412 +        } catch (DatabaseError e) {
2413 +            warning ("%s\n", e.message);
2414 +
2415 +            /* TODO if db does not exist we should
2416 +               wait for it to be created and then add folders.  Best to wait for the
2417 +               LMS notification API. */
2418 +        }
2419 +
2420 +    }
2421 +
2422 +}
2423 diff --git a/src/plugins/lms/rygel-lms-sql-function.vala b/src/plugins/lms/rygel-lms-sql-function.vala
2424 new file mode 100644
2425 index 0000000..e8580cc
2426 --- /dev/null
2427 +++ b/src/plugins/lms/rygel-lms-sql-function.vala
2428 @@ -0,0 +1,31 @@
2429 +/*
2430 + * Copyright (C) 2010 Jens Georg <mail@jensge.org>.
2431 + *
2432 + * Author: Jens Georg <mail@jensge.org>
2433 + *
2434 + * This file is part of Rygel.
2435 + *
2436 + * Rygel is free software; you can redistribute it and/or modify
2437 + * it under the terms of the GNU Lesser General Public License as published by
2438 + * the Free Software Foundation; either version 2 of the License, or
2439 + * (at your option) any later version.
2440 + *
2441 + * Rygel is distributed in the hope that it will be useful,
2442 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2443 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2444 + * GNU Lesser General Public License for more details.
2445 + *
2446 + * You should have received a copy of the GNU Lesser General Public License
2447 + * along with this program; if not, write to the Free Software Foundation,
2448 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2449 + */
2450 +
2451 +internal class Rygel.LMS.SqlFunction : SqlOperator {
2452 +    public SqlFunction (string name, string arg) {
2453 +        base (name, arg);
2454 +    }
2455 +
2456 +    public override string to_string () {
2457 +        return "%s(%s,?)".printf (name, arg);
2458 +    }
2459 +}
2460 diff --git a/src/plugins/lms/rygel-lms-sql-operator.vala b/src/plugins/lms/rygel-lms-sql-operator.vala
2461 new file mode 100644
2462 index 0000000..fc4e907
2463 --- /dev/null
2464 +++ b/src/plugins/lms/rygel-lms-sql-operator.vala
2465 @@ -0,0 +1,73 @@
2466 +/*
2467 + * Copyright (C) 2010 Jens Georg <mail@jensge.org>.
2468 + *
2469 + * Author: Jens Georg <mail@jensge.org>
2470 + *
2471 + * This file is part of Rygel.
2472 + *
2473 + * Rygel is free software; you can redistribute it and/or modify
2474 + * it under the terms of the GNU Lesser General Public License as published by
2475 + * the Free Software Foundation; either version 2 of the License, or
2476 + * (at your option) any later version.
2477 + *
2478 + * Rygel is distributed in the hope that it will be useful,
2479 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
2480 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
2481 + * GNU Lesser General Public License for more details.
2482 + *
2483 + * You should have received a copy of the GNU Lesser General Public License
2484 + * along with this program; if not, write to the Free Software Foundation,
2485 + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
2486 + */
2487 +
2488 +using GUPnP;
2489 +
2490 +internal class Rygel.LMS.SqlOperator : GLib.Object {
2491 +    protected string name;
2492 +    protected string arg;
2493 +    protected string collate;
2494 +
2495 +    public SqlOperator (string name,
2496 +                        string arg,
2497 +                        string collate = "") {
2498 +        this.name = name;
2499 +        this.arg = arg;
2500 +        this.collate = collate;
2501 +    }
2502 +
2503 +    public SqlOperator.from_search_criteria_op (SearchCriteriaOp op,
2504 +                                                string           arg,
2505 +                                                string           collate) {
2506 +        string sql = null;
2507 +        switch (op) {
2508 +            case SearchCriteriaOp.EQ:
2509 +                sql = "=";
2510 +                break;
2511 +            case SearchCriteriaOp.NEQ:
2512 +                sql = "!=";
2513 +                break;
2514 +            case SearchCriteriaOp.LESS:
2515 +                sql = "<";
2516 +                break;
2517 +            case SearchCriteriaOp.LEQ:
2518 +                sql = "<=";
2519 +                break;
2520 +            case SearchCriteriaOp.GREATER:
2521 +                sql = ">";
2522 +                break;
2523 +            case SearchCriteriaOp.GEQ:
2524 +                sql = ">=";
2525 +                break;
2526 +            default:
2527 +                assert_not_reached ();
2528 +        }
2529 +
2530 +        this (sql, arg, collate);
2531 +    }
2532 +
2533 +    public virtual string to_string () {
2534 +        return "(%s %s ? %s)".printf (arg, name, collate);
2535 +    }
2536 +}
2537 +
2538 +
2539 -- 
2540 1.7.10.4
2541