Merge lp:~scopes-hackers/unity-lens-music/rb-scope into lp:unity-lens-music

Proposed by David Callé
Status: Merged
Approved by: Michal Hruby
Approved revision: 102
Merged at revision: 73
Proposed branch: lp:~scopes-hackers/unity-lens-music/rb-scope
Merge into: lp:unity-lens-music
Diff against target: 813 lines (+663/-3)
11 files modified
configure.ac (+2/-1)
po/POTFILES.in (+2/-0)
po/POTFILES.skip (+2/-0)
src/Makefile.am (+3/-0)
src/banshee-scope.vala (+1/-0)
src/daemon.vala (+4/-0)
src/genre.vala (+18/-1)
src/rhythmbox-collection.vala (+489/-0)
src/rhythmbox-scope.vala (+116/-0)
src/simple-scope.vala (+19/-0)
src/track.vala (+7/-1)
To merge this branch: bzr merge lp:~scopes-hackers/unity-lens-music/rb-scope
Reviewer Review Type Date Requested Status
Michal Hruby (community) Approve
Mikkel Kamstrup Erlandsen (community) Approve
Neil J. Patel (community) Approve
Review via email: mp+98294@code.launchpad.net

Commit message

Implement a Rhythmbox scope.

Description of the change

Implement a Rhythmbox scope.

To post a comment you must log in.
Revision history for this message
Michal Hruby (mhr3) wrote :

There are still a couple of rough edges (better album handling, monitoring changes to the xml file), but I think it's good to go in for beta2.

review: Approve
Revision history for this message
Alex Launi (alexlauni) wrote :

This looks pretty ok, but there's a commented out FIXME where one would launch an album, so it appears that playing an album hasn't been implemented. Did I miss something?

Revision history for this message
Michal Hruby (mhr3) wrote :

> This looks pretty ok, but there's a commented out FIXME where one would launch
> an album, so it appears that playing an album hasn't been implemented. Did I
> miss something?

Currently albums aren't really handled - they behave same way like the song they are "extracted" from.

Revision history for this message
David Callé (davidc3) wrote :

For the record, Rhythmbox doesn't provide any way to launch albums.
One solution would be, on activation of an album result, to :

rhythmbox-client --play <track1 of album>
rhythmbox-client --enqueue <track2> <track3> <track4> ...

Or, to do what the python rbox scope does :

rhythmbox-client --play <track1 of album>
and "# pray for rhythmbox to be ordered by tracks"

Revision history for this message
Alex Launi (alexlauni) wrote :

the first solution is what's done in banshee, and what i would suggest doing.

Revision history for this message
Alex Launi (alexlauni) wrote :

well, not exactly what's done in banshee but that's what i would do

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

I took the lens for a test spin but I am not getting any results from it. Looking at the code I think everything looks alright. I am not sure what gives (and I don't get any errors on stdout).

Nice to see that all the bells and whistles of Dee is being put to use! :-D

review: Needs Fixing
Revision history for this message
Michal Hruby (mhr3) wrote :

Mikkel, are you running fixed Dee (the non-C-locale issue)? If not run the lens with LC_ALL=C ./...

Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

Thanks Michal, that makes it work. I think that we can bump the build dep, now that Dee has been post-release-version-bumped.

Search works nicely, but it seems that the Genre filter doesn't work?

review: Needs Fixing
Revision history for this message
Neil J. Patel (njpatel) wrote :

Works well for me, genres included.

review: Approve
Revision history for this message
Mikkel Kamstrup Erlandsen (kamstrup) wrote :

Ok, on a clean jhbuild wiht everything up-to-date this is working fine. Running well here.

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

There are additional revisions which have not been approved in review. Please seek review and approval of these new revisions.

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

The Jenkins job https://jenkins.qa.ubuntu.com/job/automerge-unity-lens-music/10/console reported an error when processing this lp:~scopes-hackers/unity-lens-music/rb-scope branch.
Not merging it.

Revision history for this message
Didier Roche-Tolomelli (didrocks) wrote :

New build-dep added.

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

The Jenkins job https://jenkins.qa.ubuntu.com/job/automerge-unity-lens-music/11/console reported an error when processing this lp:~scopes-hackers/unity-lens-music/rb-scope branch.
Not merging it.

102. By Michal Hruby

Update POTFILES

Revision history for this message
Michal Hruby (mhr3) :
review: Approve

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-02-17 10:45:31 +0000
3+++ configure.ac 2012-03-21 12:24:18 +0000
4@@ -60,9 +60,10 @@
5 gobject-2.0 >= $GLIB_REQUIRED
6 gio-2.0 >= $GLIB_REQUIRED
7 gio-unix-2.0 >= $GLIB_REQUIRED
8- dee-1.0 >= 0.5.16
9+ dee-1.0 >= 1.0.7
10 sqlite3 >= 3.7.7
11 gee-1.0
12+ libxml-2.0
13 json-glib-1.0
14 unity >= 4.99.0)
15
16
17=== modified file 'po/POTFILES.in'
18--- po/POTFILES.in 2012-01-23 13:57:10 +0000
19+++ po/POTFILES.in 2012-03-21 12:24:18 +0000
20@@ -1,6 +1,8 @@
21 [encoding: UTF-8]
22 src/daemon.vala
23 src/main.vala
24+src/banshee-scope.vala
25+src/rhythmbox-scope.vala
26 src/simple-scope.vala
27 [type: gettext/ini]music.lens.in.in
28
29
30=== modified file 'po/POTFILES.skip'
31--- po/POTFILES.skip 2012-01-23 13:57:10 +0000
32+++ po/POTFILES.skip 2012-03-21 12:24:18 +0000
33@@ -2,3 +2,5 @@
34 src/daemon.c
35 src/utils.c
36 src/simple-scope.c
37+src/banshee-scope.c
38+src/rhythmbox-scope.c
39
40=== modified file 'src/Makefile.am'
41--- src/Makefile.am 2012-01-20 11:48:10 +0000
42+++ src/Makefile.am 2012-03-21 12:24:18 +0000
43@@ -28,6 +28,7 @@
44 --pkg gio-2.0 \
45 --pkg gio-unix-2.0 \
46 --pkg glib-2.0 \
47+ --pkg libxml-2.0 \
48 $(MAINTAINER_VALAFLAGS)
49
50 unity_music_daemon_LDADD = \
51@@ -49,6 +50,8 @@
52 filter-parser-genre.vala \
53 genre.vala \
54 main.vala \
55+ rhythmbox-scope.vala \
56+ rhythmbox-collection.vala \
57 simple-scope.vala \
58 track.vala \
59 $(NULL)
60
61=== modified file 'src/banshee-scope.vala'
62--- src/banshee-scope.vala 2012-02-07 08:59:17 +0000
63+++ src/banshee-scope.vala 2012-03-21 12:24:18 +0000
64@@ -32,6 +32,7 @@
65 scope = new Unity.Scope ("/com/canonical/unity/scope/banshee");
66 scope.search_in_global = true;
67 scope.activate_uri.connect (activate);
68+ scope.sources.add_option ("banshee", _("Banshee"), null);
69
70 base.initialize ();
71
72
73=== modified file 'src/daemon.vala'
74--- src/daemon.vala 2012-01-20 11:48:10 +0000
75+++ src/daemon.vala 2012-03-21 12:24:18 +0000
76@@ -28,20 +28,24 @@
77 {
78 private Unity.Lens lens;
79 private BansheeScopeProxy banshee;
80+ private RhythmboxScope rb;
81
82 construct
83 {
84 banshee = new BansheeScopeProxy ();
85+ rb = new RhythmboxScope ();
86
87 lens = new Unity.Lens("/com/canonical/unity/lens/music", "music");
88 lens.search_in_global = true;
89 lens.search_hint = _("Search Music Collection");
90+ lens.sources_display_name = _("Sources");
91 lens.visible = true;
92
93 populate_categories ();
94 populate_filters();
95
96 lens.add_local_scope (banshee.scope);
97+ lens.add_local_scope (rb.scope);
98
99 try {
100 lens.export ();
101
102=== modified file 'src/genre.vala'
103--- src/genre.vala 2011-09-28 18:44:11 +0000
104+++ src/genre.vala 2012-03-21 12:24:18 +0000
105@@ -43,6 +43,7 @@
106 public static const string OTHER_ID = "other";
107
108 private static TreeMultiMap<string, string> map;
109+ private static Map<string, string> inverted_map;
110
111 static construct
112 {
113@@ -123,6 +124,16 @@
114 map.set (OTHER_ID, "ambient");
115 map.set (OTHER_ID, "asian");
116 map.set (OTHER_ID, "brazilian");
117+
118+ inverted_map = new HashMap<string, string> ();
119+ foreach (var key in map.get_keys ())
120+ {
121+ var values_collection = map[key];
122+ foreach (var val in values_collection)
123+ {
124+ inverted_map[val] = key;
125+ }
126+ }
127 }
128
129 public Collection<string> get_genre_synonyms (string genre_id)
130@@ -132,5 +143,11 @@
131
132 return new LinkedList<string> ();
133 }
134+
135+ public string get_id_for_genre (string genre)
136+ {
137+ return inverted_map[genre] ?? OTHER_ID;
138+ }
139 }
140-}
141\ No newline at end of file
142+}
143+
144
145=== added file 'src/rhythmbox-collection.vala'
146--- src/rhythmbox-collection.vala 1970-01-01 00:00:00 +0000
147+++ src/rhythmbox-collection.vala 2012-03-21 12:24:18 +0000
148@@ -0,0 +1,489 @@
149+/*
150+ * Copyright (C) 2012 Canonical Ltd
151+ *
152+ * This program is free software: you can redistribute it and/or modify
153+ * it under the terms of the GNU General Public License version 3 as
154+ * published by the Free Software Foundation.
155+ *
156+ * This program is distributed in the hope that it will be useful,
157+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
158+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
159+ * GNU General Public License for more details.
160+ *
161+ * You should have received a copy of the GNU General Public License
162+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
163+ *
164+ * Authored by David Calle <davidc@framli.eu>
165+ * Michal Hruby <michal.hruby@canonical.com>
166+ *
167+ */
168+
169+using Dee;
170+using Gee;
171+
172+namespace Unity.MusicLens
173+{
174+
175+ private enum Columns
176+ {
177+ URI,
178+ TITLE,
179+ ARTIST,
180+ ALBUM,
181+ ARTWORK,
182+ MIMETYPE,
183+ GENRE,
184+ TRACK_NUMBER,
185+ ALBUM_ARTIST,
186+ YEAR,
187+ PLAY_COUNT
188+ }
189+
190+ class RhythmboxCollection : Object
191+ {
192+
193+ GenericArray<Track> tracks = new GenericArray<Track> ();
194+ SequenceModel all_tracks;
195+ FilterModel tracks_by_play_count;
196+
197+ Analyzer analyzer;
198+ Index index;
199+ ICUTermFilter ascii_filter;
200+
201+ string media_art_dir;
202+
203+ // contains genre maps
204+ Genre genre = new Genre ();
205+
206+ construct
207+ {
208+ media_art_dir = Path.build_filename (
209+ Environment.get_user_cache_dir (), "media-art");
210+
211+ all_tracks = new SequenceModel ();
212+ // the columns correspond to the Columns enum
213+ all_tracks.set_schema ("s", "s", "s", "s", "s", "s", "s", "s", "s", "i", "i");
214+
215+ var filter = Dee.Filter.new_sort ((row1_in, row2_in) =>
216+ {
217+ // magic typecasting because dee has wrong vapi (1.0.6)
218+ void* row1_ptr = row1_in;
219+ void* row2_ptr = row2_in;
220+ unowned Variant[] row1 = (Variant[]) (row1_ptr);
221+ unowned Variant[] row2 = (Variant[]) (row2_ptr);
222+
223+ int a = row1[Columns.PLAY_COUNT].get_int32 ();
224+ int b = row2[Columns.PLAY_COUNT].get_int32 ();
225+
226+ return b - a; // higher play count first
227+ });
228+ tracks_by_play_count = new FilterModel (all_tracks, filter);
229+
230+ ascii_filter = new ICUTermFilter.ascii_folder ();
231+ analyzer = new TextAnalyzer ();
232+ analyzer.add_term_filter ((terms_in, terms_out) =>
233+ {
234+ foreach (unowned string term in terms_in)
235+ {
236+ var folded = ascii_filter.apply (term);
237+ terms_out.add_term (term);
238+ if (folded != term) terms_out.add_term (folded);
239+ }
240+ });
241+ var reader = ModelReader.new ((model, iter) =>
242+ {
243+ var s ="%s\n%s\n%s".printf (model.get_string (iter, Columns.TITLE),
244+ model.get_string (iter, Columns.ARTIST),
245+ model.get_string (iter, Columns.ALBUM));
246+ return s;
247+ });
248+
249+ index = new TreeIndex (all_tracks, analyzer, reader);
250+ }
251+
252+ private string? get_albumart (Track track)
253+ {
254+ var artist = track.album_artist ?? track.artist;
255+ var album = track.album;
256+
257+ var artist_norm = artist.normalize (-1, NormalizeMode.NFKD);
258+ var album_norm = album.normalize (-1, NormalizeMode.NFKD);
259+
260+ var artist_md5 = Checksum.compute_for_string (ChecksumType.MD5,
261+ artist_norm);
262+ var album_md5 = Checksum.compute_for_string (ChecksumType.MD5,
263+ album_norm);
264+
265+ string filename;
266+ filename = Path.build_filename (media_art_dir,
267+ "album-%s-%s".printf (artist_md5, album_md5));
268+ if (FileUtils.test (filename, FileTest.EXISTS)) return filename;
269+
270+ var combined = "%s\t%s".printf (artist, album).normalize (-1, NormalizeMode.NFKD);
271+ filename = Path.build_filename (media_art_dir,
272+ "album-%s.jpg".printf (Checksum.compute_for_string (
273+ ChecksumType.MD5, combined)));
274+ if (FileUtils.test (filename, FileTest.EXISTS)) return filename;
275+
276+ // Try Nautilus thumbnails
277+ try
278+ {
279+ File artwork_file = File.new_for_uri (track.uri);
280+ var info = artwork_file.query_info (FILE_ATTRIBUTE_THUMBNAIL_PATH, 0, null);
281+ var thumbnail_path = info.get_attribute_string (FILE_ATTRIBUTE_THUMBNAIL_PATH);
282+ if (thumbnail_path != null) return thumbnail_path;
283+ } catch {}
284+
285+ // Try covers folder
286+ string artwork = Path.build_filename (
287+ Environment.get_user_cache_dir (), "rhythmbox", "covers",
288+ "%s - %s.jpg".printf (track.artist, track.album));
289+ if (FileUtils.test (artwork, FileTest.EXISTS)) return artwork;
290+
291+ return null;
292+ }
293+
294+ public void parse_file (string path)
295+ {
296+ Xml.Parser.init ();
297+
298+ Xml.Doc* doc = Xml.Parser.parse_file (path);
299+ if (doc == null) {
300+ return;
301+ }
302+
303+ Xml.Node* root = doc->get_root_element ();
304+ if (root == null) {
305+ delete doc;
306+ return;
307+ }
308+ parse_node (root);
309+ delete doc;
310+
311+ Xml.Parser.cleanup ();
312+ }
313+
314+ private void parse_node (Xml.Node* node)
315+ {
316+ for (Xml.Node* iter = node->children; iter != null; iter = iter->next) {
317+ if (iter->type != Xml.ElementType.ELEMENT_NODE) {
318+ continue;
319+ }
320+ for (Xml.Attr* prop = iter->properties; prop != null; prop = prop->next) {
321+ string attr_content = prop->children->content;
322+ if (attr_content == "song") {
323+ Track track = new Track ();
324+
325+ for (Xml.Node* iter_track = iter->children; iter_track != null;
326+ iter_track = iter_track->next) {
327+ if (iter_track->type != Xml.ElementType.ELEMENT_NODE) {
328+ continue;
329+ }
330+ switch (iter_track->name) {
331+ case "title":
332+ string node_content = iter_track->get_content ();
333+ track.title = node_content;
334+ break;
335+ case "location":
336+ string node_content = iter_track->get_content ();
337+ track.uri = node_content;
338+ break;
339+ case "artist":
340+ string node_content = iter_track->get_content ();
341+ track.artist = node_content;
342+ break;
343+ case "media-type":
344+ string node_content = iter_track->get_content ();
345+ track.mime_type = node_content;
346+ break;
347+ case "album":
348+ string node_content = iter_track->get_content ();
349+ track.album = node_content;
350+ break;
351+ case "genre":
352+ string node_content = iter_track->get_content ();
353+ track.genre = node_content;
354+ break;
355+ case "track-number":
356+ string node_content = iter_track->get_content ();
357+ track.track_number = node_content;
358+ break;
359+ case "album-artist":
360+ string node_content = iter_track->get_content ();
361+ track.album_artist = node_content;
362+ break;
363+ case "date":
364+ string node_content = iter_track->get_content ();
365+ track.year = int.parse (node_content);
366+ break;
367+ case "play-count":
368+ string node_content = iter_track->get_content ();
369+ track.play_count = int.parse (node_content);
370+ break;
371+ }
372+ }
373+ // append to tracks array
374+ tracks.add (track);
375+
376+ // Get cover art
377+ string albumart = get_albumart (track);
378+ if (albumart != null)
379+ track.artwork_path = albumart;
380+ else
381+ track.artwork_path = "audio-x-generic";
382+
383+ // Get genre filter id
384+ track.genre = genre.get_id_for_genre(track.genre.down ());
385+
386+ if (track.year > 0) {
387+ track.year /= 365;
388+ }
389+
390+ all_tracks.append (track.uri, track.title,
391+ track.artist, track.album,
392+ track.artwork_path,
393+ track.mime_type,
394+ track.genre,
395+ track.track_number,
396+ track.album_artist,
397+ track.year,
398+ track.play_count);
399+ }
400+ // Next track
401+ parse_node (iter);
402+ }
403+ }
404+ }
405+
406+ public void search (LensSearch search,
407+ SearchType search_type,
408+ GLib.List<FilterParser>? filters = null,
409+ int max_results = -1,
410+ int category_override = -1)
411+ {
412+ int num_results = 0;
413+ var empty_search = search.search_string.strip () == "";
414+ int min_year;
415+ int max_year;
416+ int category_id;
417+
418+ Model model = all_tracks;
419+ get_decade_filter (filters, out min_year, out max_year);
420+ var active_genres = get_genre_filter (filters);
421+
422+ if (empty_search)
423+ {
424+ // display a couple of most played songs
425+ model = tracks_by_play_count;
426+ var iter = model.get_first_iter ();
427+ var end_iter = model.get_last_iter ();
428+ var albums_list_nosearch = new HashSet<string> ();
429+
430+ while (iter != end_iter)
431+ {
432+ int year = model.get_int32 (iter, Columns.YEAR);
433+ unowned string genre = model.get_string (iter, Columns.GENRE);
434+
435+ // check filters
436+ if (year < min_year || year > max_year)
437+ {
438+ iter = model.next (iter);
439+ continue;
440+ }
441+
442+ // check filters
443+ if (active_genres != null) {
444+ if (!(genre in active_genres)) {
445+ iter = model.next (iter);
446+ continue;
447+ }
448+ }
449+
450+ unowned string album = model.get_string (iter,
451+ Columns.ALBUM);
452+ // it's not first as in track #1, but first found from album
453+ bool first_track_from_album = !(album in albums_list_nosearch);
454+ albums_list_nosearch.add (album);
455+
456+ if (first_track_from_album)
457+ {
458+ category_id = category_override >= 0 ?
459+ category_override : Category.ALBUMS;
460+
461+ search.results_model.append (
462+ model.get_string (iter, Columns.URI),
463+ model.get_string (iter, Columns.ARTWORK),
464+ category_id,
465+ model.get_string (iter, Columns.MIMETYPE),
466+ model.get_string (iter, Columns.ALBUM),
467+ model.get_string (iter, Columns.ARTIST),
468+ model.get_string (iter, Columns.URI));
469+ }
470+
471+ category_id = category_override >= 0 ?
472+ category_override : Category.SONGS;
473+
474+ search.results_model.append (
475+ model.get_string (iter, Columns.URI),
476+ model.get_string (iter, Columns.ARTWORK),
477+ category_id,
478+ model.get_string (iter, Columns.MIMETYPE),
479+ model.get_string (iter, Columns.TITLE),
480+ model.get_string (iter, Columns.ARTIST),
481+ model.get_string (iter, Columns.URI));
482+
483+ num_results++;
484+ if (max_results >= 0 && num_results >= max_results) break;
485+
486+ iter = model.next (iter);
487+ }
488+ return;
489+ }
490+
491+
492+ var term_list = Object.new (typeof (Dee.TermList)) as Dee.TermList;
493+ // search only the folded terms, FIXME: is that a good idea?
494+ analyzer.tokenize (ascii_filter.apply (search.search_string),
495+ term_list);
496+
497+ var matches = new Sequence<Dee.ModelIter> ();
498+ bool first_pass = true;
499+ foreach (unowned string term in term_list)
500+ {
501+ // FIXME: use PREFIX search only for the last term?
502+ var result_set = index.lookup (term, TermMatchFlag.PREFIX);
503+
504+ CompareDataFunc<Dee.ModelIter> cmp_func = (a, b) =>
505+ {
506+ return a == b ? 0 : ((void*) a > (void*) b ? 1 : -1);
507+ };
508+
509+ // intersect the results (cause we want to AND the terms)
510+ var remaining = new Sequence<Dee.ModelIter> ();
511+ foreach (var item in result_set)
512+ {
513+ if (first_pass)
514+ matches.insert_sorted (item, cmp_func);
515+ else if (matches.lookup (item, cmp_func) != null)
516+ remaining.insert_sorted (item, cmp_func);
517+ }
518+ if (!first_pass) matches = (owned) remaining;
519+ // final result set empty already?
520+ if (matches.get_begin_iter () == matches.get_end_iter ()) break;
521+
522+ first_pass = false;
523+ }
524+
525+ // matches now contain iterators into the all_tracks model which
526+ // match the search string
527+ var seq_iter = matches.get_begin_iter ();
528+ var seq_end_iter = matches.get_end_iter ();
529+
530+ var albums_list = new HashSet<string> ();
531+ while (seq_iter != seq_end_iter)
532+ {
533+ var model_iter = seq_iter.get ();
534+ int year = model.get_int32 (model_iter, Columns.YEAR);
535+ string genre = model.get_string (model_iter, Columns.GENRE);
536+
537+ // check filters
538+ if (year < min_year || year > max_year)
539+ {
540+ seq_iter = seq_iter.next ();
541+ continue;
542+ }
543+
544+ // check filters
545+ if (active_genres != null) {
546+ bool genre_match = (genre in active_genres);
547+ if (!genre_match) {
548+ seq_iter = seq_iter.next ();
549+ continue;
550+ }
551+ }
552+
553+ unowned string album = model.get_string (model_iter,
554+ Columns.ALBUM);
555+ // it's not first as in track #1, but first found from album
556+ bool first_track_from_album = !(album in albums_list);
557+ albums_list.add (album);
558+
559+ if (first_track_from_album)
560+ {
561+ category_id = category_override >= 0 ?
562+ category_override : Category.ALBUMS;
563+
564+ search.results_model.append (
565+ model.get_string (model_iter, Columns.URI),
566+ model.get_string (model_iter, Columns.ARTWORK),
567+ category_id,
568+ model.get_string (model_iter, Columns.MIMETYPE),
569+ model.get_string (model_iter, Columns.ALBUM),
570+ model.get_string (model_iter, Columns.ARTIST),
571+ model.get_string (model_iter, Columns.URI));
572+ }
573+
574+ category_id = category_override >= 0 ?
575+ category_override : Category.SONGS;
576+
577+ search.results_model.append (
578+ model.get_string (model_iter, Columns.URI),
579+ model.get_string (model_iter, Columns.ARTWORK),
580+ category_id,
581+ model.get_string (model_iter, Columns.MIMETYPE),
582+ model.get_string (model_iter, Columns.TITLE),
583+ model.get_string (model_iter, Columns.ARTIST),
584+ model.get_string (model_iter, Columns.URI));
585+
586+ num_results++;
587+ if (max_results >= 0 && num_results >= max_results) break;
588+
589+ seq_iter = seq_iter.next ();
590+ }
591+ }
592+
593+ private void get_decade_filter (GLib.List<FilterParser> filters,
594+ out int min_year, out int max_year)
595+ {
596+ Filter? filter = null;
597+ foreach (var parser in filters)
598+ {
599+ if (parser is DecadeFilterParser) filter = parser.filter;
600+ }
601+
602+ if (filter == null || !filter.filtering)
603+ {
604+ min_year = 0;
605+ max_year = int.MAX;
606+ return;
607+ }
608+
609+ var mrf = filter as MultiRangeFilter;
610+ min_year = int.parse (mrf.get_first_active ().id);
611+ max_year = int.parse (mrf.get_last_active ().id);
612+ }
613+
614+ private Set<string>? get_genre_filter (GLib.List<FilterParser> filters)
615+ {
616+ Filter? filter = null;
617+ foreach (var parser in filters)
618+ {
619+ if (parser is GenreFilterParser) filter = parser.filter;
620+ }
621+ if (filter == null || !filter.filtering)
622+ {
623+ return null;
624+ }
625+
626+ var active_genres = new HashSet<string> ();
627+ var all_genres = filter as CheckOptionFilterCompact;
628+ foreach (FilterOption option in all_genres.options)
629+ {
630+ if (option.id == null || !option.active) continue;
631+ active_genres.add (option.id);
632+ }
633+
634+ return active_genres;
635+ }
636+ }
637+}
638
639=== added file 'src/rhythmbox-scope.vala'
640--- src/rhythmbox-scope.vala 1970-01-01 00:00:00 +0000
641+++ src/rhythmbox-scope.vala 2012-03-21 12:24:18 +0000
642@@ -0,0 +1,116 @@
643+/*
644+ * Copyright (C) 2011 Canonical Ltd
645+ *
646+ * This program is free software: you can redistribute it and/or modify
647+ * it under the terms of the GNU General Public License version 3 as
648+ * published by the Free Software Foundation.
649+ *
650+ * This program is distributed in the hope that it will be useful,
651+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
652+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
653+ * GNU General Public License for more details.
654+ *
655+ * You should have received a copy of the GNU General Public License
656+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
657+ *
658+ * Authored by Alex Launi <alex.launi@canonical.com>
659+ *
660+ */
661+
662+using GLib;
663+
664+namespace Unity.MusicLens {
665+
666+ public class RhythmboxScope : SimpleScope
667+ {
668+ private RhythmboxCollection collection;
669+ private bool db_ready;
670+
671+ public RhythmboxScope ()
672+ {
673+ base ();
674+
675+ scope = new Unity.Scope ("/com/canonical/unity/scope/rhythmbox");
676+ scope.search_in_global = true;
677+ scope.activate_uri.connect (activate);
678+ scope.sources.add_option ("rhythmbox", _("Rhythmbox"), null);
679+
680+ base.initialize ();
681+ collection = new RhythmboxCollection ();
682+ db_ready = false;
683+ }
684+
685+ protected override int num_results_without_search { get {return 100; } }
686+ protected override int num_results_global_search { get { return 20; } }
687+ protected override int num_results_lens_search { get { return 50; } }
688+
689+ /**
690+ * Tells banshee to play the selected uri(s)
691+ */
692+ public Unity.ActivationResponse activate (string uri)
693+ {
694+ string[] exec = {"rhythmbox-client", "--play-uri"};
695+
696+ try {
697+ if (Uri.parse_scheme (uri) == "album")
698+ {
699+ debug (@"searching for tracks for $uri");
700+ string[] split = uri.split ("/");
701+ string artist = split[2];
702+ string title = split[3];
703+
704+ Album album = new Album ();
705+ album.artist = artist;
706+ album.title = title;
707+ // FIXME there must be a better way..
708+// foreach (string track in collection.get_track_uris (album))
709+// exec += track;
710+ }
711+ else
712+ {
713+ exec += uri;
714+ }
715+
716+ exec += null;
717+
718+ debug ("Spawning rb '%s'", string.joinv (" ", exec));
719+ Process.spawn_async (null,
720+ exec,
721+ null,
722+ SpawnFlags.SEARCH_PATH,
723+ null,
724+ null);
725+
726+ return new Unity.ActivationResponse (Unity.HandledType.HIDE_DASH);
727+ } catch (SpawnError error) {
728+ warning ("Failed to launch URI %s", uri);
729+ return new Unity.ActivationResponse (Unity.HandledType.NOT_HANDLED);
730+ }
731+ }
732+
733+ public override async void perform_search (LensSearch search,
734+ SearchType search_type,
735+ owned List<FilterParser> filters,
736+ int max_results = -1,
737+ Cancellable? cancellable = null)
738+ {
739+ int category_override = -1;
740+ if (search_type == SearchType.GLOBAL)
741+ {
742+ category_override = Category.MUSIC;
743+ // the lens shouldn't display anything for empty search
744+ if (is_search_empty (search)) return;
745+ }
746+
747+ if (!db_ready)
748+ {
749+ // parse the DB lazily
750+ collection.parse_file ("%s/.local/share/rhythmbox/rhythmdb.xml".printf (Environment.get_home_dir ()));
751+ db_ready = true;
752+ }
753+
754+ collection.search (search, search_type, filters,
755+ max_results, category_override);
756+ }
757+ }
758+}
759
760=== modified file 'src/simple-scope.vala'
761--- src/simple-scope.vala 2012-02-07 08:59:17 +0000
762+++ src/simple-scope.vala 2012-03-21 12:24:18 +0000
763@@ -46,6 +46,10 @@
764 scope.queue_search_changed (SearchType.DEFAULT);
765 });
766
767+ scope.active_sources_changed.connect (() => {
768+ scope.queue_search_changed (SearchType.DEFAULT);
769+ });
770+
771 /* No need to search if only the whitespace changes */
772 scope.generate_search_key.connect ((lens_search) => {
773 return lens_search.search_string.strip ();
774@@ -98,6 +102,21 @@
775
776 results_model.clear ();
777
778+ // don't perform search is all sources are inactive
779+ if (scope.sources.options.length () > 0 && scope.sources.filtering)
780+ {
781+ bool any_active = false;
782+ foreach (var source in scope.sources.options)
783+ {
784+ if (source.active) any_active = true;
785+ }
786+ if (!any_active)
787+ {
788+ search.finished ();
789+ return;
790+ }
791+ }
792+
793 yield perform_search (search, search_type, (owned) filters, max_results, cancellable);
794
795 if (results_model.get_n_rows () == 0)
796
797=== modified file 'src/track.vala'
798--- src/track.vala 2011-09-14 18:33:52 +0000
799+++ src/track.vala 2012-03-21 12:24:18 +0000
800@@ -26,5 +26,11 @@
801 public string artist { get; set; }
802 public string mime_type { get; set; }
803 public string artwork_path { get; set; }
804+ public string album { get; set; }
805+ public string album_artist { get; set; }
806+ public string genre { get; set; }
807+ public string track_number { get; set; }
808+ public int year { get; set; }
809+ public int play_count { get; set; }
810 }
811-}
812\ No newline at end of file
813+}

Subscribers

People subscribed via source and target branches

to all changes: