Merge lp:~mhr3/unity-lens-music/rb-parse-tdb into lp:unity-lens-music

Proposed by Michal Hruby
Status: Merged
Approved by: Michal Hruby
Approved revision: 80
Merged at revision: 80
Proposed branch: lp:~mhr3/unity-lens-music/rb-parse-tdb
Merge into: lp:unity-lens-music
Diff against target: 549 lines (+330/-44)
7 files modified
configure.ac (+2/-1)
src/Makefile.am (+5/-0)
src/daemon.vala (+13/-5)
src/rhythmbox-collection.vala (+160/-37)
src/rhythmbox-scope.vala (+24/-1)
src/tdb.deps (+1/-0)
src/tdb.vapi (+125/-0)
To merge this branch: bzr merge lp:~mhr3/unity-lens-music/rb-parse-tdb
Reviewer Review Type Date Requested Status
Gord Allott (community) Approve
Review via email: mp+101394@code.launchpad.net

Commit message

Added rhythmbox's TDB parsing so we'll have more sources of album arts in the lens

Description of the change

Added rhythmbox's TDB parsing so we'll have more sources of album arts in the lens.

To post a comment you must log in.
Revision history for this message
Gord Allott (gordallott) wrote :

seems good to me, +1

review: Approve
Revision history for this message
Unity Merger (unity-merger) wrote :

No commit message specified.

Revision history for this message
Unity Merger (unity-merger) wrote :

The Jenkins job https://jenkins.qa.ubuntu.com/job/automerge-unity-lens-music/16/console reported an error when processing this lp:~mhr3/unity-lens-music/rb-parse-tdb branch.
Not merging it.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'configure.ac'
2--- configure.ac 2012-04-12 07:57:42 +0000
3+++ configure.ac 2012-04-23 12:39:24 +0000
4@@ -64,7 +64,8 @@
5 sqlite3 >= 3.7.7
6 gee-1.0
7 json-glib-1.0
8- unity >= 4.99.0)
9+ unity >= 4.99.0
10+ tdb >= 1.2.6)
11
12 AC_SUBST(LENS_DAEMON_CFLAGS)
13 AC_SUBST(LENS_DAEMON_LIBS)
14
15=== modified file 'src/Makefile.am'
16--- src/Makefile.am 2012-03-27 21:51:45 +0000
17+++ src/Makefile.am 2012-04-23 12:39:24 +0000
18@@ -28,6 +28,9 @@
19 --pkg gio-2.0 \
20 --pkg gio-unix-2.0 \
21 --pkg glib-2.0 \
22+ --vapidir $(srcdir) \
23+ --pkg tdb \
24+ --target-glib=2.26 \
25 $(MAINTAINER_VALAFLAGS)
26
27 unity_music_daemon_LDADD = \
28@@ -98,6 +101,8 @@
29 unity_musicstore_daemon.vala.stamp \
30 $(unity_music_daemon_VALASOURCES) \
31 $(unity_musicstore_daemon_VALASOURCES) \
32+ tdb.vapi \
33+ tdb.deps \
34 $(NULL)
35
36 unity_music_daemon.vala.stamp: $(unity_music_daemon_VALASOURCES)
37
38=== modified file 'src/daemon.vala'
39--- src/daemon.vala 2012-03-20 14:59:27 +0000
40+++ src/daemon.vala 2012-04-23 12:39:24 +0000
41@@ -32,9 +32,6 @@
42
43 construct
44 {
45- banshee = new BansheeScopeProxy ();
46- rb = new RhythmboxScope ();
47-
48 lens = new Unity.Lens("/com/canonical/unity/lens/music", "music");
49 lens.search_in_global = true;
50 lens.search_hint = _("Search Music Collection");
51@@ -44,8 +41,19 @@
52 populate_categories ();
53 populate_filters();
54
55- lens.add_local_scope (banshee.scope);
56- lens.add_local_scope (rb.scope);
57+ var app_check = new DesktopAppInfo ("banshee.desktop");
58+ if (app_check != null)
59+ {
60+ banshee = new BansheeScopeProxy ();
61+ lens.add_local_scope (banshee.scope);
62+ }
63+
64+ app_check = new DesktopAppInfo ("rhythmbox.desktop");
65+ if (app_check != null)
66+ {
67+ rb = new RhythmboxScope ();
68+ lens.add_local_scope (rb.scope);
69+ }
70
71 try {
72 lens.export ();
73
74=== modified file 'src/rhythmbox-collection.vala'
75--- src/rhythmbox-collection.vala 2012-03-27 21:51:45 +0000
76+++ src/rhythmbox-collection.vala 2012-04-23 12:39:24 +0000
77@@ -45,14 +45,19 @@
78 {
79
80 SequenceModel all_tracks;
81+ ModelTag<int> album_art_tag;
82 FilterModel tracks_by_play_count;
83
84+ TDB.Database album_art_tdb;
85+ FileMonitor tdb_monitor;
86+ int current_album_art_tag;
87+
88 HashTable<unowned string, Variant> variant_store;
89 HashTable<int, Variant> int_variant_store;
90 Variant row_buffer[11];
91
92 Analyzer analyzer;
93- Index index;
94+ Index? index;
95 ICUTermFilter ascii_filter;
96
97 string media_art_dir;
98@@ -203,9 +208,10 @@
99 direct_equal);
100 all_tracks = new SequenceModel ();
101 // the columns correspond to the Columns enum
102- all_tracks.set_schema ("s", "s", "s", "s", "s", "s", "s", "s", "i", "i", "i");
103+ all_tracks.set_schema ("s", "s", "s", "s", "s", "s",
104+ "s", "s", "i", "i", "i");
105 assert (all_tracks.get_schema ().length == Columns.N_COLUMNS);
106-
107+ album_art_tag = new ModelTag<int> (all_tracks);
108
109 var filter = Dee.Filter.new_sort ((row1, row2) =>
110 {
111@@ -227,6 +233,11 @@
112 if (folded != term) terms_out.add_term (folded);
113 }
114 });
115+ initialize_index ();
116+ }
117+
118+ private void initialize_index ()
119+ {
120 var reader = ModelReader.new ((model, iter) =>
121 {
122 var s ="%s\n%s\n%s".printf (model.get_string (iter, Columns.TITLE),
123@@ -237,21 +248,63 @@
124
125 index = new TreeIndex (all_tracks, analyzer, reader);
126 }
127+
128+ private string? check_album_art_tdb (string artist, string album)
129+ {
130+ if (album_art_tdb == null) return null;
131+
132+ uint8 null_helper[1] = { 0 };
133+ ByteArray byte_arr = new ByteArray ();
134+ byte_arr.append ("album".data);
135+ byte_arr.append (null_helper);
136+ byte_arr.append (album.data);
137+ byte_arr.append (null_helper);
138+ byte_arr.append ("artist".data);
139+ byte_arr.append (null_helper);
140+ byte_arr.append (artist.data);
141+ byte_arr.append (null_helper);
142+
143+ TDB.Data key = TDB.NULL_DATA;
144+ key.data = byte_arr.data;
145+ var val = album_art_tdb.fetch (key);
146+
147+ if (val.data != null)
148+ {
149+ Variant v = Variant.new_from_data<int> (new VariantType ("a{sv}"), val.data, false);
150+ var file_variant = v.lookup_value ("file", VariantType.STRING);
151+ if (file_variant != null)
152+ {
153+ return file_variant.get_string ();
154+ }
155+ }
156+
157+ return null;
158+ }
159
160 private string? get_albumart (Track track)
161 {
162+ string filename;
163 var artist = track.album_artist ?? track.artist;
164 var album = track.album;
165
166 var artist_norm = artist.normalize (-1, NormalizeMode.NFKD);
167 var album_norm = album.normalize (-1, NormalizeMode.NFKD);
168
169+ filename = check_album_art_tdb (artist, album);
170+ if (filename != null)
171+ {
172+ filename = Path.build_filename (Environment.get_user_cache_dir (),
173+ "rhythmbox", "album-art",
174+ filename);
175+
176+ if (FileUtils.test (filename, FileTest.EXISTS)) return filename;
177+ }
178+
179 var artist_md5 = Checksum.compute_for_string (ChecksumType.MD5,
180 artist_norm);
181 var album_md5 = Checksum.compute_for_string (ChecksumType.MD5,
182 album_norm);
183
184- string filename;
185 filename = Path.build_filename (media_art_dir,
186 "album-%s-%s".printf (artist_md5, album_md5));
187 if (FileUtils.test (filename, FileTest.EXISTS)) return filename;
188@@ -331,8 +384,59 @@
189 row_buffer[10] = play_count;
190 }
191
192+ public void parse_metadata_file (string path)
193+ {
194+ if (album_art_tdb != null) return;
195+
196+ if (tdb_monitor == null)
197+ {
198+ var tdb_file = File.new_for_path (path);
199+ try
200+ {
201+ tdb_monitor = tdb_file.monitor (FileMonitorFlags.NONE);
202+ tdb_monitor.changed.connect (() =>
203+ {
204+ if (album_art_tdb == null) parse_metadata_file (path);
205+ else current_album_art_tag++;
206+ });
207+ }
208+ catch (Error err)
209+ {
210+ warning ("%s", err.message);
211+ }
212+ }
213+
214+ var flags = TDB.OpenFlags.INCOMPATIBLE_HASH | TDB.OpenFlags.SEQNUM | TDB.OpenFlags.NOLOCK;
215+ album_art_tdb = new TDB.Database (path, 999, flags,
216+ Posix.O_RDONLY, 0600);
217+ if (album_art_tdb == null)
218+ {
219+ warning ("Unable to open album-art DB!");
220+ return;
221+ }
222+
223+ /*
224+ album_art_tdb.traverse ((db, key, val) =>
225+ {
226+ var byte_arr = new ByteArray.sized ((uint) val.data_size);
227+ byte_arr.append (val.data);
228+ Variant v = Variant.new_from_data<ByteArray> (new VariantType ("a{sv}"), byte_arr.data, false, byte_arr);
229+ message ("value: %s", v.print (true));
230+
231+ return 0;
232+ });
233+ */
234+ }
235+
236 public void parse_file (string path)
237 {
238+ // this could be really expensive if the index was already built, so
239+ // we'll destroy it first
240+ index = null;
241+ all_tracks.clear ();
242+ initialize_index ();
243+ current_album_art_tag = 0;
244+
245 var parser = new XmlParser ();
246 parser.track_info_ready.connect ((track) =>
247 {
248@@ -366,6 +470,48 @@
249 }
250 }
251
252+ private void add_result (Model results_model, Model model,
253+ ModelIter iter, Columns title_col,
254+ uint category_id)
255+ {
256+ // check for updated album art
257+ var tag = album_art_tag[model, iter];
258+ if (tag < current_album_art_tag)
259+ {
260+ unowned string album = model.get_string (iter, Columns.ALBUM);
261+ unowned string artist = model.get_string (iter,
262+ Columns.ALBUM_ARTIST);
263+ if (artist == "")
264+ artist = model.get_string (iter, Columns.ARTIST);
265+
266+ var album_art_string = check_album_art_tdb (artist, album);
267+ if (album_art_string != null)
268+ {
269+ string filename;
270+ filename = Path.build_filename (Environment.get_user_cache_dir (),
271+ "rhythmbox", "album-art",
272+ album_art_string);
273+ album_art_string = FileUtils.test (filename, FileTest.EXISTS) ?
274+ filename : "audio-x-generic";
275+
276+ if (album_art_string != model.get_string (iter, Columns.ARTWORK))
277+ {
278+ model.set_value (iter, Columns.ARTWORK,
279+ cached_variant_for_string (album_art_string));
280+ }
281+ }
282+ album_art_tag[model, iter] = current_album_art_tag;
283+ }
284+
285+ results_model.append (model.get_string (iter, Columns.URI),
286+ model.get_string (iter, Columns.ARTWORK),
287+ category_id,
288+ model.get_string (iter, Columns.MIMETYPE),
289+ model.get_string (iter, title_col),
290+ model.get_string (iter, Columns.ARTIST),
291+ model.get_string (iter, Columns.URI));
292+ }
293+
294 public void search (LensSearch search,
295 SearchType search_type,
296 GLib.List<FilterParser>? filters = null,
297@@ -420,28 +566,16 @@
298 {
299 category_id = category_override >= 0 ?
300 category_override : Category.ALBUMS;
301-
302- search.results_model.append (
303- model.get_string (iter, Columns.URI),
304- model.get_string (iter, Columns.ARTWORK),
305- category_id,
306- model.get_string (iter, Columns.MIMETYPE),
307- model.get_string (iter, Columns.ALBUM),
308- model.get_string (iter, Columns.ARTIST),
309- model.get_string (iter, Columns.URI));
310+
311+ add_result (search.results_model, model, iter,
312+ Columns.ALBUM, category_id);
313 }
314
315 category_id = category_override >= 0 ?
316 category_override : Category.SONGS;
317
318- search.results_model.append (
319- model.get_string (iter, Columns.URI),
320- model.get_string (iter, Columns.ARTWORK),
321- category_id,
322- model.get_string (iter, Columns.MIMETYPE),
323- model.get_string (iter, Columns.TITLE),
324- model.get_string (iter, Columns.ARTIST),
325- model.get_string (iter, Columns.URI));
326+ add_result (search.results_model, model, iter,
327+ Columns.TITLE, category_id);
328
329 num_results++;
330 if (max_results >= 0 && num_results >= max_results) break;
331@@ -515,6 +649,7 @@
332
333 unowned string album = model.get_string (model_iter,
334 Columns.ALBUM);
335+
336 // it's not first as in track #1, but first found from album
337 bool first_track_from_album = !(album in albums_list);
338 albums_list.add (album);
339@@ -524,27 +659,15 @@
340 category_id = category_override >= 0 ?
341 category_override : Category.ALBUMS;
342
343- search.results_model.append (
344- model.get_string (model_iter, Columns.URI),
345- model.get_string (model_iter, Columns.ARTWORK),
346- category_id,
347- model.get_string (model_iter, Columns.MIMETYPE),
348- model.get_string (model_iter, Columns.ALBUM),
349- model.get_string (model_iter, Columns.ARTIST),
350- model.get_string (model_iter, Columns.URI));
351+ add_result (search.results_model, model, model_iter,
352+ Columns.ALBUM, category_id);
353 }
354
355 category_id = category_override >= 0 ?
356 category_override : Category.SONGS;
357
358- search.results_model.append (
359- model.get_string (model_iter, Columns.URI),
360- model.get_string (model_iter, Columns.ARTWORK),
361- category_id,
362- model.get_string (model_iter, Columns.MIMETYPE),
363- model.get_string (model_iter, Columns.TITLE),
364- model.get_string (model_iter, Columns.ARTIST),
365- model.get_string (model_iter, Columns.URI));
366+ add_result (search.results_model, model, model_iter,
367+ Columns.TITLE, category_id);
368
369 num_results++;
370 if (max_results >= 0 && num_results >= max_results) break;
371
372=== modified file 'src/rhythmbox-scope.vala'
373--- src/rhythmbox-scope.vala 2012-03-20 14:59:27 +0000
374+++ src/rhythmbox-scope.vala 2012-04-23 12:39:24 +0000
375@@ -25,6 +25,7 @@
376 {
377 private RhythmboxCollection collection;
378 private bool db_ready;
379+ private FileMonitor rb_xml_monitor;
380
381 public RhythmboxScope ()
382 {
383@@ -105,7 +106,29 @@
384 if (!db_ready)
385 {
386 // parse the DB lazily
387- collection.parse_file ("%s/.local/share/rhythmbox/rhythmdb.xml".printf (Environment.get_home_dir ()));
388+ var tdb_path = Path.build_filename (Environment.get_user_cache_dir (),
389+ "rhythmbox", "album-art",
390+ "store.tdb");
391+ collection.parse_metadata_file (tdb_path);
392+
393+ var xml_path = Path.build_filename (Environment.get_user_data_dir (),
394+ "rhythmbox", "rhythmdb.xml");
395+ collection.parse_file (xml_path);
396+ if (rb_xml_monitor == null)
397+ {
398+ // re-parse the file if it changes
399+ File xml_file = File.new_for_path (xml_path);
400+ try
401+ {
402+ rb_xml_monitor = xml_file.monitor (FileMonitorFlags.NONE);
403+ rb_xml_monitor.changed.connect (() => { db_ready = false; });
404+ }
405+ catch (Error err)
406+ {
407+ warning ("%s", err.message);
408+ }
409+ }
410+
411 db_ready = true;
412 }
413
414
415=== added file 'src/tdb.deps'
416--- src/tdb.deps 1970-01-01 00:00:00 +0000
417+++ src/tdb.deps 2012-04-23 12:39:24 +0000
418@@ -0,0 +1,1 @@
419+posix
420
421=== added file 'src/tdb.vapi'
422--- src/tdb.vapi 1970-01-01 00:00:00 +0000
423+++ src/tdb.vapi 2012-04-23 12:39:24 +0000
424@@ -0,0 +1,125 @@
425+/* tdb.vapi
426+ *
427+ * Copyright (C) 2012 Canonical Ltd.
428+ *
429+ * This library is free software; you can redistribute it and/or
430+ * modify it under the terms of the GNU Lesser General Public
431+ * License as published by the Free Software Foundation; either
432+ * version 2.1 of the License, or (at your option) any later version.
433+
434+ * This library is distributed in the hope that it will be useful,
435+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
436+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
437+ * Lesser General Public License for more details.
438+
439+ * You should have received a copy of the GNU Lesser General Public
440+ * License along with this library; if not, write to the Free Software
441+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
442+ *
443+ * Author:
444+ * Michal Hruby <michal.hruby@canonical.com>
445+ */
446+
447+[CCode (lower_case_cprefix = "tdb_", cheader_filename = "tdb.h")]
448+namespace TDB {
449+ /* Database Connection Handle */
450+ [Compact]
451+ [CCode (free_function = "tdb_close", cname = "TDB_CONTEXT", cprefix = "tdb_")]
452+ public class Database {
453+ [CCode (cname = "tdb_open")]
454+ public Database (string name, int hash_size, TDB.OpenFlags tdb_flags, int open_flags, Posix.mode_t mode);
455+ [CCode (cname = "tdb_open_ex")]
456+ public Database.open_ex (string name, int hash_size, int tdb_flags, int open_flags, Posix.mode_t mode, TDB.LogFunc log_fn);
457+
458+ public int reopen ();
459+ public static int reopen_all ();
460+
461+ public TDB.Error error ();
462+ public unowned string errorstr ();
463+
464+ public TDB.Data fetch (TDB.Data key);
465+ public int @delete (TDB.Data key);
466+ public int store (TDB.Data key, TDB.Data dbuf, TDB.StoreType type_flag);
467+ public TDB.Data firstkey ();
468+ public TDB.Data nextkey (TDB.Data key);
469+ public int traverse (TDB.TraverseFunc traverse_func);
470+ public int exists (TDB.Data key);
471+ public int lockkeys (TDB.Data[] keys);
472+ public void unlockkeys ();
473+ public int lockall ();
474+ public void unlockall ();
475+
476+ public int chainlock (TDB.Data key);
477+ public void chainunlock (TDB.Data key);
478+ }
479+
480+ [CCode (cname = "TDB_DATA")]
481+ [SimpleType]
482+ public struct Data {
483+ [CCode (array_length_cname = "dsize", array_length_type = "size_t", cname = "dptr")]
484+ public unowned uint8[] data;
485+ [CCode (cname = "dsize")]
486+ public size_t data_size;
487+ }
488+
489+ [CCode (cname = "tdb_null")]
490+ public const TDB.Data NULL_DATA;
491+
492+ [CCode (has_target = false)]
493+ public delegate void LogFunc (TDB.Database db, TDB.DebugLevel debug_level, string format, ...);
494+
495+ public delegate int TraverseFunc (TDB.Database db, TDB.Data key, TDB.Data @value);
496+
497+ [CCode (cname = "SQLITE_ANY")]
498+ public const int ANY;
499+
500+ [CCode (cname = "enum tdb_debug_level", cprefix = "TDB_DEBUG_")]
501+ public enum DebugLevel {
502+ FATAL,
503+ ERROR,
504+ WARNING,
505+ TRACE
506+ }
507+
508+ [CCode (cname = "int", cprefix = "TDB_")]
509+ public enum StoreType {
510+ REPLACE,
511+ INSERT,
512+ MODIFY
513+ }
514+
515+ [CCode (cname = "int", cprefix = "TDB_")]
516+ public enum OpenFlags {
517+ DEFAULT,
518+ CLEAR_IF_FIRST,
519+ INTERNAL,
520+ NOLOCK,
521+ NOMMAP,
522+ CONVERT,
523+ BIGENDIAN,
524+ NOSYNC,
525+ SEQNUM,
526+ VOLATILE,
527+ ALLOW_NESTING,
528+ DISALLOW_NESTING,
529+ INCOMPATIBLE_HASH
530+ }
531+
532+ [CCode (cname = "enum TDB_ERROR", cprefix = "TDB_ERR_")]
533+ public enum Error {
534+ [CCode (cname = "TDB_SUCCESS")]
535+ SUCCESS,
536+ CORRUPT,
537+ IO,
538+ LOCK,
539+ OOM,
540+ EXISTS,
541+ NOLOCK,
542+ LOCK_TIMEOUT,
543+ NOEXISTS,
544+ EINVAL,
545+ RDONLY,
546+ NESTING
547+ }
548+}
549+

Subscribers

People subscribed via source and target branches

to all changes: