Merge lp:~elementary-apps/noise/async-pixbuf-cache into lp:~elementary-apps/noise/trunk

Proposed by Victor Martinez
Status: Merged
Merged at revision: 1058
Proposed branch: lp:~elementary-apps/noise/async-pixbuf-cache
Merge into: lp:~elementary-apps/noise/trunk
Diff against target: 1920 lines (+625/-544)
8 files modified
core/Utils/FileUtils.vala (+79/-36)
plugins/LastFM/Core.vala (+117/-109)
src/GStreamer/GStreamerTagger.vala (+282/-281)
src/LibraryWindow.vala (+17/-8)
src/Objects/CoverartCache.vala (+66/-78)
src/Objects/MediaArtCache.vala (+14/-6)
src/Utils/PixbufCache.vala (+43/-20)
src/Utils/PixbufUtils.vala (+7/-6)
To merge this branch: bzr merge lp:~elementary-apps/noise/async-pixbuf-cache
Reviewer Review Type Date Requested Status
David Gomes (community) Approve
Review via email: mp+128154@code.launchpad.net

Description of the change

This branch should fix bug #1060122: noise crashed while re-scanning Music folder.

Previously, the code used synchronous (i.e. blocking) I/O to load the cached images from disk, and was run from a different thread to avoid freezing the UI. Since we run into many thread-safety issues, and considering there's not enough time to hunt them down one by one, using Idle calls seems to be the most reliable and quick solution to the problem; the code now uses GLib's Idle scheduler to avoid the need of a second thread here.

Also updates:
+ Last.fm plugin (no longer uses threads.)

Opinions and testers are welcome!

To post a comment you must log in.
1055. By Victor Martinez

[CMake] Fix libnotify support

This revision adds back notification support. Recent changes to the CMake files introduced a bug that would override the HAVE_LIBNOTIFY option, since the default options were being set below in the same file. Enjoy the notifications again.

1056. By Victor Martinez

Opps. Search is smart again.

Fix a regression from revision 1049 that would make the searches fail if the search string contained any whitespace character.

1057. By Victor Martinez

Merge translations

1058. By David Gomes

Small code fixes.

1059. By Victor Martinez

CoverartCache.lookup_folder_image_async: rv => image_file

Revision history for this message
David Gomes (davidgomes) wrote :

Good branch, GStreamerTagger.vala needs some work, but I can do it tomorrow.

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'core/Utils/FileUtils.vala'
2--- core/Utils/FileUtils.vala 2012-09-17 22:14:19 +0000
3+++ core/Utils/FileUtils.vala 2012-10-06 03:24:21 +0000
4@@ -46,6 +46,26 @@
5 }
6
7 /**
8+ * Asynchronously checks whether a file exists or not.
9+ * It follows symbolic links.
10+ */
11+ public async bool query_exists_async (File file_or_dir, Cancellable? cancellable = null) {
12+ FileInfo? info = null;
13+
14+ try {
15+ info = yield file_or_dir.query_info_async (FileAttribute.STANDARD_NAME,
16+ FileQueryInfoFlags.NONE,
17+ Priority.DEFAULT,
18+ cancellable);
19+ } catch (Error err) {
20+ if (err is IOError.NOT_FOUND)
21+ return false;
22+ }
23+
24+ return info != null;
25+ }
26+
27+ /**
28 * Convenience method to get the size of a file or directory (recursively)
29 *
30 * @param file a {@link GLib.File} representing the file or directory to be queried
31@@ -53,12 +73,12 @@
32 * @return size in bytes of file. It is recommended to use GLib.format_size() in case
33 * you want to convert it to a string representation.
34 */
35- public uint64 get_size (File file_or_dir, Cancellable? cancellable = null) {
36+ public async uint64 get_size_async (File file_or_dir, Cancellable? cancellable = null) {
37 uint64 size = 0;
38 Gee.Collection<File> files;
39
40- if (is_directory (file_or_dir, cancellable)) {
41- enumerate_files (file_or_dir, null, true, out files, cancellable);
42+ if (yield is_directory_async (file_or_dir, cancellable)) {
43+ yield enumerate_files_async (file_or_dir, null, true, out files, cancellable);
44 } else {
45 files = new Gee.LinkedList<File> ();
46 files.add (file_or_dir);
47@@ -69,8 +89,10 @@
48 break;
49
50 try {
51- var info = file.query_info (FileAttribute.STANDARD_SIZE,
52- FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
53+ var info = yield file.query_info_async (FileAttribute.STANDARD_SIZE,
54+ FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
55+ Priority.DEFAULT,
56+ cancellable);
57 size += info.get_attribute_uint64 (FileAttribute.STANDARD_SIZE);
58 } catch (Error err) {
59 warning ("Could not get size of '%s': %s", file.get_uri (), err.message);
60@@ -80,9 +102,23 @@
61 return size;
62 }
63
64- public bool is_directory (File file, Cancellable? cancellable = null) {
65- var type = file.query_file_type (FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
66- return type == FileType.DIRECTORY;
67+ /**
68+ * Checks whether //dir// is a directory.
69+ * Does not follow symbolic links.
70+ */
71+ public async bool is_directory_async (File dir, Cancellable? cancellable = null) {
72+ FileInfo? info = null;
73+
74+ try {
75+ info = yield dir.query_info_async (FileAttribute.STANDARD_TYPE,
76+ FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
77+ Priority.DEFAULT,
78+ cancellable);
79+ } catch (Error err) {
80+ warning (err.message);
81+ }
82+
83+ return info != null && info.get_file_type () == FileType.DIRECTORY;
84 }
85
86 /**
87@@ -90,24 +126,25 @@
88 *
89 * @param folder a {@link GLib.File} representing the folder you wish to query
90 * @param types a string array containing the formats you want to limit the search to, or null
91- * to allow any file type. e.g. [[[string[] types = {"mp3", "jpg"}]]] [allow-none]
92+ * to allow any file type. e.g. string[] types = {"mp3", "jpg"} [allow-none]
93 * @param recursive whether to query the whole directory tree or only immediate children. [allow-none]
94 * @param files the data container for the files found. This only includes files, not directories [allow-none]
95- * @param cancellable a cancellable object for cancelling the operation. [allow-none]
96+ * @param cancellable a cancellable object for canceling the operation. [allow-none]
97 *
98 * @return total number of files found (should be the same as files.size)
99 */
100- public uint enumerate_files (File folder, string[]? types = null,
101- bool recursive = true,
102- out Gee.Collection<File>? files = null,
103- Cancellable? cancellable = null) {
104- return_val_if_fail (is_directory (folder), 0);
105+ public async uint enumerate_files_async (File folder, string[]? types = null,
106+ bool recursive = true,
107+ out Gee.Collection<File>? files = null,
108+ Cancellable? cancellable = null)
109+ {
110+ return_val_if_fail (yield is_directory_async (folder), 0);
111 var counter = new FileEnumerator ();
112- return counter.enumerate_files (folder, types, out files, recursive, cancellable);
113+ return yield counter.enumerate_files_async (folder, types, out files, recursive, cancellable);
114 }
115
116 /**
117- * Comprobates whether a filename matches a given extension.
118+ * Queries whether a filename matches a given extension.
119 *
120 * @param name path, URI or name of the file to verify
121 * @param types a string array containing the expected file extensions (without dot).
122@@ -143,17 +180,18 @@
123 * the directories descendant from folder. In case you only want the first-level
124 * descendants, set recursive to false.
125 */
126- public uint enumerate_files (File folder, string[]? types,
127- out Gee.Collection<File>? files,
128- bool recursive = true,
129- Cancellable? cancellable = null) {
130+ public async uint enumerate_files_async (File folder, string[]? types,
131+ out Gee.Collection<File>? files,
132+ bool recursive = true,
133+ Cancellable? cancellable = null)
134+ {
135 assert (file_count == 0);
136
137 this.types = types;
138 this.cancellable = cancellable;
139
140 files = new Gee.LinkedList<File> ();
141- enumerate_files_internal (folder, ref files, recursive);
142+ yield enumerate_files_internal_async (folder, files, recursive);
143 return file_count;
144 }
145
146@@ -161,19 +199,25 @@
147 return Utils.is_cancelled (cancellable);
148 }
149
150- private void enumerate_files_internal (File folder, ref Gee.Collection<File>? files,
151- bool recursive) {
152-
153+ private async void enumerate_files_internal_async (File folder, Gee.Collection<File>? files,
154+ bool recursive)
155+ {
156 if (is_cancelled ())
157 return;
158
159 try {
160- var enumerator = folder.enumerate_children (ATTRIBUTES,
161- FileQueryInfoFlags.NOFOLLOW_SYMLINKS, cancellable);
162-
163- FileInfo? file_info = null;
164-
165- while ((file_info = enumerator.next_file ()) != null && !is_cancelled ()) {
166+ var enumerator = yield folder.enumerate_children_async (ATTRIBUTES,
167+ FileQueryInfoFlags.NOFOLLOW_SYMLINKS,
168+ Priority.DEFAULT,
169+ cancellable);
170+
171+ while (!is_cancelled ()) {
172+ var enum_files = yield enumerator.next_files_async (1, Priority.DEFAULT, cancellable);
173+ FileInfo? file_info = enum_files.nth_data (0);
174+
175+ if (file_info == null)
176+ break;
177+
178 var file_name = file_info.get_name ();
179 var file_type = file_info.get_file_type ();
180 var file = folder.get_child (file_name);
181@@ -183,15 +227,14 @@
182 continue;
183
184 file_count++;
185+
186 if (files != null)
187 files.add (file);
188- }
189- else if (recursive && file_type == FileType.DIRECTORY) {
190- enumerate_files_internal (file, ref files, true);
191+ } else if (recursive && file_type == FileType.DIRECTORY) {
192+ yield enumerate_files_internal_async (file, files, true);
193 }
194 }
195- }
196- catch (Error err) {
197+ } catch (Error err) {
198 warning ("Could not scan folder: %s", err.message);
199 }
200 }
201
202=== modified file 'plugins/LastFM/Core.vala'
203--- plugins/LastFM/Core.vala 2012-09-18 10:14:54 +0000
204+++ plugins/LastFM/Core.vala 2012-10-06 03:24:21 +0000
205@@ -40,26 +40,26 @@
206 public Settings lastfm_settings { get; private set; }
207
208 public string token;
209-
210+
211 public signal void logged_in();
212 public signal void similar_retrieved(LinkedList<int> similarIDs, LinkedList<Noise.Media> similarDont);
213-
214+
215 LastFM.SimilarMedias similarMedias;
216-
217+
218 Mutex _artists_lock;
219 Mutex _albums_lock;
220 Mutex _tracks_lock;
221 HashMap<string, LastFM.ArtistInfo> _artists;//key:artist
222 HashMap<string, LastFM.AlbumInfo> _albums;//key:artist<sep>album
223 HashMap<string, LastFM.TrackInfo> _tracks;//key:artist<sep>album<sep>track
224-
225+
226 public Core(Noise.LibraryManager lmm) {
227 lm = lmm;
228-
229+
230 lastfm_settings = new LastFM.Settings ();
231-
232+
233 similarMedias = new LastFM.SimilarMedias(lm);
234-
235+
236 _artists = new HashMap<string, LastFM.ArtistInfo>();
237 _albums = new HashMap<string, LastFM.AlbumInfo>();
238 _tracks = new HashMap<string, LastFM.TrackInfo>();
239@@ -69,13 +69,13 @@
240 _artists.set(a.name, (LastFM.ArtistInfo)a);
241 }
242 _artists_lock.unlock();
243-
244+
245 _albums_lock.lock();
246 foreach(Noise.AlbumInfo a in lm.dbm.load_albums()) {
247 _albums.set(a.name + " by " + a.artist, (LastFM.AlbumInfo)a);
248 }
249 _albums_lock.unlock();
250-
251+
252 _tracks_lock.lock();
253 foreach(Noise.TrackInfo t in lm.dbm.load_tracks()) {
254 _tracks.set(t.name + " by " + t.artist, (LastFM.TrackInfo)t);
255@@ -84,98 +84,98 @@
256
257 similarMedias.similar_retrieved.connect(similar_retrieved_signal);
258 }
259-
260+
261 /************* Last FM Artist Stuff ************/
262 public GLib.List<LastFM.ArtistInfo> artists() {
263 var rv = new GLib.List<LastFM.ArtistInfo>();
264 foreach(var artist in _artists.values)
265 rv.append(artist);
266-
267+
268 return rv;
269 }
270-
271+
272 public void save_artist(LastFM.ArtistInfo artist) {
273 _artists_lock.lock();
274 _artists.set(artist.name.down(), artist);
275 _artists_lock.unlock();
276 }
277-
278+
279 public bool artist_info_exists(string artist_key) {
280 return _artists.get(artist_key.down()) != null;
281 }
282-
283+
284 public LastFM.ArtistInfo? get_artist(string artist_key) {
285 LastFM.ArtistInfo? rv = null;
286-
287+
288 _artists_lock.lock();
289- if(artist_info_exists(artist_key.down()))
290+ if(artist_info_exists(artist_key.down()))
291 rv = _artists.get(artist_key.down());
292 _artists_lock.unlock();
293-
294+
295 return rv;
296 }
297-
298+
299 /************** LastFM Album stuff **************/
300 public GLib.List<LastFM.AlbumInfo> albums() {
301 var rv = new GLib.List<LastFM.AlbumInfo>();
302 foreach(var album in _albums.values)
303 rv.append(album);
304-
305+
306 return rv;
307 }
308-
309+
310 public void save_album(LastFM.AlbumInfo album) {
311 _albums_lock.lock();
312 _albums.set(album.name.down() + " by " + album.artist.down(), album);
313 _albums_lock.unlock();
314 }
315-
316+
317 public bool album_info_exists(string album_key) {
318 return _albums.get(album_key) != null;
319 }
320-
321+
322 public LastFM.AlbumInfo? get_album(string album_key) {
323 LastFM.AlbumInfo? rv = null;
324-
325+
326 _albums_lock.lock();
327- if(album_info_exists(album_key.down()))
328+ if(album_info_exists(album_key.down()))
329 rv = _albums.get(album_key.down());
330 _albums_lock.unlock();
331-
332+
333 return rv;
334 }
335-
336+
337 /************** Last FM Track Stuff ***************/
338 public GLib.List<LastFM.TrackInfo> tracks() {
339 var rv = new GLib.List<LastFM.TrackInfo>();
340 foreach(var track in _tracks.values)
341 rv.append(track);
342-
343+
344 return rv;
345 }
346-
347+
348 public void save_track(LastFM.TrackInfo track) {
349 _tracks_lock.lock();
350 if (track != null && track.name != null && track.artist != null)
351 _tracks.set(track.name.down() + " by " + track.artist.down(), track);
352 _tracks_lock.unlock();
353 }
354-
355+
356 public bool track_info_exists(string track_key) {
357 return _tracks.get(track_key.down()) != null;
358 }
359-
360+
361 public LastFM.TrackInfo? get_track(string track_key) {
362 LastFM.TrackInfo? rv = null;
363-
364+
365 _tracks_lock.lock();
366 if(track_info_exists(track_key.down()))
367 rv = _tracks.get(track_key.down());
368 _tracks_lock.unlock();
369-
370+
371 return rv;
372 }
373-
374+
375 /** Last.FM Api functions **/
376 public static string fix_for_url(string fix) {
377 var fix1 = fix.replace(" ", "%20");
378@@ -190,40 +190,40 @@
379 var fix0 = fix9.replace("*", "%2A");
380 return fix0;
381 }
382-
383+
384 public string generate_md5(string text) {
385 return GLib.Checksum.compute_for_string(ChecksumType.MD5, text, text.length);
386 }
387-
388+
389 public string generate_getsession_signature(string token) {
390 return generate_md5("api_key" + API + "methodauth.getSessiontoken" + token + SECRET);
391 }
392-
393+
394 public string generate_tracklove_signature(string artist, string track) {
395 return generate_md5("api_key" + API + "artist" + artist + "methodtrack.lovesk" + lastfm_settings.session_key + "track" + track + SECRET);
396 }
397-
398+
399 public string generate_trackban_signature(string artist, string track) {
400 return generate_md5("api_key" + API + "artist" + artist + "methodtrack.bansk" + lastfm_settings.session_key + "track" + track + SECRET);
401 }
402-
403+
404 public string generate_trackscrobble_signature(string artist, string track, int timestamp) {
405 return generate_md5("api_key" + API + "artist" + artist + "methodtrack.scrobblesk" + lastfm_settings.session_key + "timestamp" + timestamp.to_string() + "track" + track + SECRET);
406 }
407-
408+
409 public string generate_trackupdatenowplaying_signature(string artist, string track) {
410 return generate_md5("api_key" + API + "artist" + artist + "methodtrack.updateNowPlayingsk" + lastfm_settings.session_key + "track" + track + SECRET);
411 }
412-
413+
414 public string? getToken() {
415 var url = "http://ws.audioscrobbler.com/2.0/?method=auth.gettoken&api_key=" + API;
416-
417+
418 Xml.Doc* doc = Parser.parse_file (url);
419 if(doc == null) return null;
420-
421+
422 Xml.Node* root = doc->get_root_element();
423 if(root == null) return null;
424-
425+
426 for (Xml.Node* iter = root->children; iter != null; iter = iter->next) {
427 if(iter->name == "token") {
428 return iter->get_content();
429@@ -231,19 +231,19 @@
430 }
431 return null;
432 }
433-
434+
435 public string? getSessionKey(string token) {
436 var sig = generate_getsession_signature(token);
437 var url = "http://ws.audioscrobbler.com/2.0/?method=auth.getSession&api_key=" + API + "&api_sig=" + sig + "&token=" + token;
438-
439+
440 message ("url: %s\n", url);
441-
442+
443 Xml.Doc* doc = Parser.parse_file (url);
444 if(doc == null) return null;
445-
446+
447 Xml.Node* root = doc->get_root_element();
448 if(root == null) return null;
449-
450+
451 for (Xml.Node* iter = root->children; iter != null; iter = iter->next) {
452 if(iter->name == "session") {
453 for(Xml.Node* n = iter->children; n != null; n = n->next) {
454@@ -255,18 +255,18 @@
455 }
456 return null;
457 }
458-
459+
460 public bool loveTrack(string title, string artist) {
461 if(lastfm_settings.session_key == null || lastfm_settings.session_key == "") {
462 warning ("User tried to ban a track, but is not logged into Last FM\n");
463 return false;
464 }
465-
466+
467 var uri = "http://ws.audioscrobbler.com/2.0/?api_key=" + API + "&api_sig=" + generate_tracklove_signature(artist, title) + "&artist=" + fix_for_url(artist) + "&method=track.love&sk=" + lastfm_settings.session_key + "&track=" + fix_for_url(title);
468-
469+
470 Soup.SessionSync session = new Soup.SessionSync();
471 Soup.Message message = new Soup.Message ("POST", uri);
472-
473+
474 var headers = new Soup.MessageHeaders(MessageHeadersType.REQUEST);
475 headers.append("api_key", API);
476 headers.append("api_sig", generate_tracklove_signature(artist, title));
477@@ -274,29 +274,29 @@
478 headers.append("method", "track.love");
479 headers.append("sk", lastfm_settings.session_key);
480 headers.append("track", title);
481-
482+
483 message.request_headers = headers;
484-
485+
486 /* send the HTTP request */
487 session.send_message(message);
488-
489+
490 if(message.response_body.length == 0)
491 return false;
492-
493+
494 return true;
495 }
496-
497+
498 public bool banTrack(string title, string artist) {
499 if(lastfm_settings.session_key == null || lastfm_settings.session_key == "") {
500 warning ("User tried to ban a track, but is not logged into Last FM\n");
501 return false;
502 }
503-
504+
505 var uri = "http://ws.audioscrobbler.com/2.0/?api_key=" + API + "&api_sig=" + generate_trackban_signature(artist, title) + "&artist=" + fix_for_url(artist) + "&method=track.ban&sk=" + lastfm_settings.session_key + "&track=" + fix_for_url(title);
506-
507+
508 Soup.SessionSync session = new Soup.SessionSync();
509 Soup.Message message = new Soup.Message ("POST", uri);
510-
511+
512 var headers = new Soup.MessageHeaders(MessageHeadersType.REQUEST);
513 headers.append("api_key", API);
514 headers.append("api_sig", generate_trackban_signature(artist, title));
515@@ -304,31 +304,31 @@
516 headers.append("method", "track.ban");
517 headers.append("sk", lastfm_settings.session_key);
518 headers.append("track", title);
519-
520+
521 message.request_headers = headers;
522-
523+
524 /* send the HTTP request */
525 session.send_message(message);
526-
527+
528 if(message.response_body.length == 0)
529 return false;
530-
531+
532 return true;
533 }
534-
535+
536 /** Fetches the current track's info from last.fm
537 */
538
539 Mutex fetch_info_guard;
540
541 public void fetchCurrentTrackInfo() {
542- Noise.Threads.add (track_thread_function);
543+ Idle.add (track_thread_function);
544 }
545-
546- void track_thread_function () {
547+
548+ private bool track_thread_function () {
549 var current_media = Noise.App.player.media_info.media;
550 if (current_media == null)
551- return;
552+ return false;
553
554 string album_artist_s = current_media.album_artist;
555 string track_s = current_media.title;
556@@ -350,17 +350,19 @@
557 Noise.App.player.media_info.track = track;
558 fetch_info_guard.unlock ();
559 }
560+
561+ return false;
562 }
563-
564+
565 public void fetchCurrentAlbumInfo() {
566- Noise.Threads.add (album_thread_function);
567+ Idle.add (album_thread_function);
568 }
569
570- void album_thread_function () {
571+ private bool album_thread_function () {
572
573 var current_media = Noise.App.player.media_info.media;
574 if (current_media == null)
575- return;
576+ return false;
577
578 string album_artist_s = current_media.album_artist;
579 string album_s = current_media.album;
580@@ -373,10 +375,10 @@
581 // This does the fetching to internet. may take a few seconds
582 var album = new LastFM.AlbumInfo.with_info (album_artist_s, album_s);
583
584- if (album != null)
585+ if (album != null)
586 save_album (album);
587 else
588- return;
589+ return false;
590
591 /* If on same song, update Noise.App.player.media_info.album */
592 fetch_info_guard.lock ();
593@@ -391,33 +393,33 @@
594 var coverart_cache = Noise.CoverartCache.instance;
595
596 if (coverart_cache.has_image (current_media))
597- return;
598+ return false;
599
600 if (album.image_uri != "") {
601 message ("Caching last.fm image from URL: %s", album.image_uri);
602
603- fetch_info_guard.lock ();
604 var image_file = File.new_for_uri (album.image_uri);
605- coverart_cache.cache_image_from_file (current_media, image_file);
606- fetch_info_guard.unlock ();
607+ coverart_cache.cache_image_from_file_async.begin (current_media, image_file);
608 }
609
610 }
611 else {
612 message ("Not fetching album info or art");
613 }
614+
615+ return false;
616 }
617-
618+
619 /** Fetches artist info for currently playing song's artist
620 */
621 public void fetchCurrentArtistInfo() {
622- Noise.Threads.add (artist_thread_function);
623+ Idle.add (artist_thread_function);
624 }
625-
626- void artist_thread_function () {
627+
628+ private bool artist_thread_function () {
629 var current_media = Noise.App.player.media_info.media;
630 if (current_media == null)
631- return;
632+ return false;
633
634 string album_artist_s = current_media.album_artist;
635 if (album_artist_s == "")
636@@ -442,30 +444,32 @@
637
638 fetch_info_guard.unlock ();
639 }
640+
641+ return false;
642 }
643-
644+
645 /** Update's the user's currently playing track on last.fm
646- *
647+ *
648 */
649 public void postNowPlaying() {
650- Noise.Threads.add (update_nowplaying_thread_function);
651+ Idle.add (update_nowplaying_thread_function);
652 }
653-
654- void update_nowplaying_thread_function() {
655+
656+ private bool update_nowplaying_thread_function() {
657 if(lastfm_settings.session_key == null || lastfm_settings.session_key == "") {
658 message ("Last.FM user not logged in\n");
659- return;
660+ return false;
661 }
662 if(!Noise.App.player.media_active)
663- return;
664-
665+ return false;
666+
667 var artist = Noise.App.player.media_info.media.artist;
668 var title = Noise.App.player.media_info.media.title;
669 var uri = "http://ws.audioscrobbler.com/2.0/?api_key=" + API + "&api_sig=" + generate_trackupdatenowplaying_signature(artist, title) + "&artist=" + fix_for_url(artist) + "&method=track.updateNowPlaying&sk=" + lastfm_settings.session_key + "&track=" + fix_for_url(title);
670-
671+
672 Soup.SessionSync session = new Soup.SessionSync();
673 Soup.Message message = new Soup.Message ("POST", uri);
674-
675+
676 var headers = new Soup.MessageHeaders(MessageHeadersType.REQUEST);
677 headers.append("api_key", API);
678 headers.append("api_sig", generate_trackupdatenowplaying_signature(artist, title));
679@@ -473,38 +477,40 @@
680 headers.append("method", "track.updateNowPlaying");
681 headers.append("sk", lastfm_settings.session_key);
682 headers.append("track", title);
683-
684+
685 message.request_headers = headers;
686-
687+
688 /* send the HTTP request */
689 session.send_message(message);
690+
691+ return false;
692 }
693-
694+
695 /**
696 * Scrobbles the currently playing track to last.fm
697 */
698 public void postScrobbleTrack() {
699- Noise.Threads.add (scrobble_thread_function);
700+ Idle.add (scrobble_thread_function);
701 }
702-
703- void scrobble_thread_function () {
704+
705+ private bool scrobble_thread_function () {
706 if(lastfm_settings.session_key == null || lastfm_settings.session_key == "") {
707 message ("Last.FM user not logged in\n");
708- return;
709+ return false;
710 }
711 if(!Noise.App.player.media_active)
712- return;
713+ return false;
714
715 var current_media = Noise.App.player.media_info.media;
716-
717+
718 var timestamp = (int)time_t();
719 var artist = current_media.artist;
720 var title = current_media.title;
721 var uri = "http://ws.audioscrobbler.com/2.0/?api_key=" + API + "&api_sig=" + generate_trackscrobble_signature(artist, title, timestamp) + "&artist=" + fix_for_url(artist) + "&method=track.scrobble&sk=" + lastfm_settings.session_key + "&timestamp=" + timestamp.to_string() + "&track=" + fix_for_url(title);
722-
723+
724 Soup.SessionSync session = new Soup.SessionSync();
725 Soup.Message message = new Soup.Message ("POST", uri);
726-
727+
728 var headers = new Soup.MessageHeaders(MessageHeadersType.REQUEST);
729 headers.append("api_key", API);
730 headers.append("api_sig", generate_trackscrobble_signature(artist, title, timestamp));
731@@ -513,17 +519,19 @@
732 headers.append("sk", lastfm_settings.session_key);
733 headers.append("timestamp", timestamp.to_string());
734 headers.append("track", title);
735-
736+
737 message.request_headers = headers;
738-
739+
740 /* send the HTTP request */
741 session.send_message(message);
742+
743+ return false;
744 }
745-
746+
747 public void fetchCurrentSimilarSongs() {
748 similarMedias.queryForSimilar(Noise.App.player.media_info.media);
749 }
750-
751+
752 void similar_retrieved_signal(LinkedList<int> similarIDs, LinkedList<Noise.Media> similarDont) {
753 similar_retrieved(similarIDs, similarDont);
754 }
755
756=== modified file 'src/GStreamer/GStreamerTagger.vala'
757--- src/GStreamer/GStreamerTagger.vala 2012-08-06 17:12:33 +0000
758+++ src/GStreamer/GStreamerTagger.vala 2012-10-06 03:24:21 +0000
759@@ -24,146 +24,147 @@
760 using Gee;
761
762 public class Noise.GStreamerTagger : GLib.Object {
763- LibraryManager lm;
764- static int DISCOVER_SET_SIZE = 50;
765- Gst.Discoverer d;
766- HashMap<string, int> uri_to_id;
767- LinkedList<string> path_queue;
768-
769- public signal void media_imported(Media m);
770- public signal void import_error(string file);
771- public signal void queue_finished();
772-
773- bool cancelled;
774-
775- public GStreamerTagger(LibraryManager lm) {
776- this.lm = lm;
777+
778+ LibraryManager lm;
779+ static int DISCOVER_SET_SIZE = 50;
780+ Gst.Discoverer d;
781+ HashMap<string, int> uri_to_id;
782+ LinkedList<string> path_queue;
783+
784+ public signal void media_imported(Media m);
785+ public signal void import_error(string file);
786+ public signal void queue_finished();
787+
788+ bool cancelled;
789+
790+ public GStreamerTagger(LibraryManager lm) {
791+ this.lm = lm;
792
793 d = create_discoverer ();
794
795-
796- uri_to_id = new HashMap<string, int>();
797- path_queue = new LinkedList<string>();
798- }
799+
800+ uri_to_id = new HashMap<string, int>();
801+ path_queue = new LinkedList<string>();
802+ }
803
804 private Gst.Discoverer? create_discoverer () {
805 Gst.Discoverer? discoverer = null;
806
807- try {
808- discoverer = new Gst.Discoverer ((ClockTime)(30*Gst.SECOND));
809- }
810- catch (Error err) {
811- critical ("Metadata reader could not create discoverer object: %s\n", err.message);
812- }
813+ try {
814+ discoverer = new Gst.Discoverer ((ClockTime)(30*Gst.SECOND));
815+ }
816+ catch (Error err) {
817+ critical ("Metadata reader could not create discoverer object: %s\n", err.message);
818+ }
819
820- discoverer.discovered.connect (import_media);
821- discoverer.finished.connect (finished);
822+ discoverer.discovered.connect (import_media);
823+ discoverer.finished.connect (finished);
824
825 return discoverer;
826 }
827
828- void finished() {
829- if(!cancelled && path_queue.size > 0) {
830- d = create_discoverer ();
831-
832- d.start();
833- for(int i = 0; i < DISCOVER_SET_SIZE && i < path_queue.size; ++i) {
834- d.discover_uri_async("file://" + path_queue.get(i));
835- }
836- }
837- else {
838- debug("queue finished\n");
839- queue_finished();
840- }
841- }
842-
843- public void cancel_operations() {
844- cancelled = true;
845- }
846-
847- public void discoverer_import_media (LinkedList<string> files) {
848- int size = 0;
849- cancelled = false;
850- path_queue.clear();
851-
852- foreach(string s in files) {
853- path_queue.add(s);
854-
855- d.start();
856- if(size < DISCOVER_SET_SIZE) {
857- ++size;
858- d.discover_uri_async("file://" + s);
859- }
860- }
861- }
862-
863- void import_media(DiscovererInfo info, Error err) {
864- path_queue.remove(info.get_uri().replace("file://",""));
865+ void finished() {
866+ if(!cancelled && path_queue.size > 0) {
867+ d = create_discoverer ();
868+
869+ d.start();
870+ for(int i = 0; i < DISCOVER_SET_SIZE && i < path_queue.size; ++i) {
871+ d.discover_uri_async("file://" + path_queue.get(i));
872+ }
873+ }
874+ else {
875+ debug("queue finished\n");
876+ queue_finished();
877+ }
878+ }
879+
880+ public void cancel_operations() {
881+ cancelled = true;
882+ }
883+
884+ public void discoverer_import_media (LinkedList<string> files) {
885+ int size = 0;
886+ cancelled = false;
887+ path_queue.clear();
888+
889+ foreach(string s in files) {
890+ path_queue.add(s);
891+
892+ d.start();
893+ if(size < DISCOVER_SET_SIZE) {
894+ ++size;
895+ d.discover_uri_async("file://" + s);
896+ }
897+ }
898+ }
899+
900+ private async void import_media(DiscovererInfo info, Error err) {
901+ path_queue.remove(info.get_uri().replace("file://",""));
902
903 Media? s = null;
904
905- if(info != null && info.get_tags() != null) {
906- s = new Media(info.get_uri());
907-
908- string title = "";
909- string artist, composer, album_artist, album, grouping, genre, comment, lyrics;
910- uint track, track_count, album_number, album_count, bitrate, rating;
911- double bpm;
912- GLib.Date? date = GLib.Date();
913-
914- // get title, artist, album artist, album, genre, comment, lyrics strings
915- if(info.get_tags().get_string(TAG_TITLE, out title))
916- s.title = title;
917- if(info.get_tags().get_string(TAG_ARTIST, out artist))
918- s.artist = artist;
919- if(info.get_tags().get_string(TAG_COMPOSER, out composer))
920- s.composer = composer;
921-
922- if(info.get_tags().get_string(TAG_ALBUM_ARTIST, out album_artist))
923- s.album_artist = album_artist;
924- else
925- s.album_artist = s.artist;
926-
927- if(info.get_tags().get_string(TAG_ALBUM, out album))
928- s.album = album;
929- if(info.get_tags().get_string(TAG_GROUPING, out grouping))
930- s.grouping = grouping;
931- if(info.get_tags().get_string(TAG_GENRE, out genre))
932- s.genre = genre;
933- if(info.get_tags().get_string(TAG_COMMENT, out comment))
934- s.comment = comment;
935- if(info.get_tags().get_string(TAG_LYRICS, out lyrics))
936- s.lyrics = lyrics;
937-
938- // get the year
939- if(info.get_tags().get_date(TAG_DATE, out date)) {
940- if(date != null)
941- s.year = (int)date.get_year();
942- }
943- // get track/album number/count, bitrating, rating, bpm
944- if(info.get_tags().get_uint(TAG_TRACK_NUMBER, out track))
945- s.track = (int)track;
946- if(info.get_tags().get_uint(TAG_TRACK_COUNT, out track_count))
947- s.track_count = track_count;
948-
949- if(info.get_tags().get_uint(TAG_ALBUM_VOLUME_NUMBER, out album_number))
950- s.album_number = album_number;
951- if(info.get_tags().get_uint(TAG_ALBUM_VOLUME_COUNT, out album_count))
952- s.album_count = album_count;
953-
954- if(info.get_tags().get_uint(TAG_BITRATE, out bitrate))
955- s.bitrate = (int)(bitrate/1000);
956- if(info.get_tags().get_uint(TAG_USER_RATING, out rating))
957- s.rating = (int)((rating > 0 && rating <= 5) ? rating : 0);
958- if(info.get_tags().get_double(TAG_BEATS_PER_MINUTE, out bpm))
959- s.bpm = (int)bpm;
960-
961-
962- // get length
963+ if(info != null && info.get_tags() != null) {
964+ s = new Media(info.get_uri());
965+
966+ string title = "";
967+ string artist, composer, album_artist, album, grouping, genre, comment, lyrics;
968+ uint track, track_count, album_number, album_count, bitrate, rating;
969+ double bpm;
970+ GLib.Date? date = GLib.Date();
971+
972+ // get title, artist, album artist, album, genre, comment, lyrics strings
973+ if(info.get_tags().get_string(TAG_TITLE, out title))
974+ s.title = title;
975+ if(info.get_tags().get_string(TAG_ARTIST, out artist))
976+ s.artist = artist;
977+ if(info.get_tags().get_string(TAG_COMPOSER, out composer))
978+ s.composer = composer;
979+
980+ if(info.get_tags().get_string(TAG_ALBUM_ARTIST, out album_artist))
981+ s.album_artist = album_artist;
982+ else
983+ s.album_artist = s.artist;
984+
985+ if(info.get_tags().get_string(TAG_ALBUM, out album))
986+ s.album = album;
987+ if(info.get_tags().get_string(TAG_GROUPING, out grouping))
988+ s.grouping = grouping;
989+ if(info.get_tags().get_string(TAG_GENRE, out genre))
990+ s.genre = genre;
991+ if(info.get_tags().get_string(TAG_COMMENT, out comment))
992+ s.comment = comment;
993+ if(info.get_tags().get_string(TAG_LYRICS, out lyrics))
994+ s.lyrics = lyrics;
995+
996+ // get the year
997+ if(info.get_tags().get_date(TAG_DATE, out date)) {
998+ if(date != null)
999+ s.year = (int)date.get_year();
1000+ }
1001+ // get track/album number/count, bitrating, rating, bpm
1002+ if(info.get_tags().get_uint(TAG_TRACK_NUMBER, out track))
1003+ s.track = (int)track;
1004+ if(info.get_tags().get_uint(TAG_TRACK_COUNT, out track_count))
1005+ s.track_count = track_count;
1006+
1007+ if(info.get_tags().get_uint(TAG_ALBUM_VOLUME_NUMBER, out album_number))
1008+ s.album_number = album_number;
1009+ if(info.get_tags().get_uint(TAG_ALBUM_VOLUME_COUNT, out album_count))
1010+ s.album_count = album_count;
1011+
1012+ if(info.get_tags().get_uint(TAG_BITRATE, out bitrate))
1013+ s.bitrate = (int)(bitrate/1000);
1014+ if(info.get_tags().get_uint(TAG_USER_RATING, out rating))
1015+ s.rating = (int)((rating > 0 && rating <= 5) ? rating : 0);
1016+ if(info.get_tags().get_double(TAG_BEATS_PER_MINUTE, out bpm))
1017+ s.bpm = (int)bpm;
1018+
1019+
1020+ // get length
1021 uint64 duration = info.get_duration ();
1022
1023 if (duration == 0)
1024- info.get_tags ().get_uint64 (TAG_DURATION, out duration);
1025+ info.get_tags ().get_uint64 (TAG_DURATION, out duration);
1026
1027 // we convert from nanoseconds (10E-9) to miliseconds (10E-3);
1028 s.length = (uint)((duration * Numeric.MILI_INV) / Numeric.NANO_INV);
1029@@ -188,63 +189,63 @@
1030 }
1031
1032 // Get cover art
1033- import_art (s, info);
1034- }
1035- else {
1036- s = taglib_import_media(info.get_uri());
1037+ import_art_async.begin (s, info);
1038+ }
1039+ else {
1040+ s = taglib_import_media(info.get_uri());
1041
1042- if (s == null) {
1043- import_error (info.get_uri().replace("file://", ""));
1044- return;
1045+ if (s == null) {
1046+ import_error (info.get_uri().replace("file://", ""));
1047+ return;
1048 }
1049- }
1050-
1051- // get the size
1052- s.file_size = FileUtils.get_size (s.file);
1053- s.date_added = (int)time_t();
1054-
1055- media_imported(s);
1056- }
1057-
1058- public Media? taglib_import_media(string uri) {
1059- Media s = new Media(uri);
1060- TagLib.File tag_file;
1061-
1062- tag_file = new TagLib.File(uri.replace("file://",""));
1063-
1064- if(tag_file != null && tag_file.tag != null && tag_file.audioproperties != null) {
1065- try {
1066- s.title = tag_file.tag.title;
1067- s.artist = tag_file.tag.artist;
1068- s.album = tag_file.tag.album;
1069- s.genre = tag_file.tag.genre;
1070- s.comment = tag_file.tag.comment;
1071- s.year = (int)tag_file.tag.year;
1072- s.track = (int)tag_file.tag.track;
1073- s.bitrate = tag_file.audioproperties.bitrate;
1074-
1075- s.length = (uint)(tag_file.audioproperties.length * Numeric.MILI_INV);
1076- s.samplerate = tag_file.audioproperties.samplerate;
1077- }
1078- finally {
1079- if(s.title == null || s.title == "") {
1080- string[] paths = uri.split("/", 0);
1081- s.title = paths[paths.length - 1];
1082- }
1083- if(s.artist == null || s.artist == "") s.artist = Media.UNKNOWN_ARTIST;
1084-
1085- s.album_artist = s.artist;
1086- s.album_number = 1;
1087- }
1088- }
1089- else {
1090- return null;
1091- }
1092-
1093- return s;
1094- }
1095-
1096- void import_art (Media m, DiscovererInfo info) {
1097+ }
1098+
1099+ // get the size
1100+ s.file_size = yield FileUtils.get_size_async (s.file);
1101+ s.date_added = (int)time_t();
1102+
1103+ media_imported(s);
1104+ }
1105+
1106+ public Media? taglib_import_media(string uri) {
1107+ Media s = new Media(uri);
1108+ TagLib.File tag_file;
1109+
1110+ tag_file = new TagLib.File(uri.replace("file://",""));
1111+
1112+ if(tag_file != null && tag_file.tag != null && tag_file.audioproperties != null) {
1113+ try {
1114+ s.title = tag_file.tag.title;
1115+ s.artist = tag_file.tag.artist;
1116+ s.album = tag_file.tag.album;
1117+ s.genre = tag_file.tag.genre;
1118+ s.comment = tag_file.tag.comment;
1119+ s.year = (int)tag_file.tag.year;
1120+ s.track = (int)tag_file.tag.track;
1121+ s.bitrate = tag_file.audioproperties.bitrate;
1122+
1123+ s.length = (uint)(tag_file.audioproperties.length * Numeric.MILI_INV);
1124+ s.samplerate = tag_file.audioproperties.samplerate;
1125+ }
1126+ finally {
1127+ if(s.title == null || s.title == "") {
1128+ string[] paths = uri.split("/", 0);
1129+ s.title = paths[paths.length - 1];
1130+ }
1131+ if(s.artist == null || s.artist == "") s.artist = Media.UNKNOWN_ARTIST;
1132+
1133+ s.album_artist = s.artist;
1134+ s.album_number = 1;
1135+ }
1136+ }
1137+ else {
1138+ return null;
1139+ }
1140+
1141+ return s;
1142+ }
1143+
1144+ private async void import_art_async (Media m, DiscovererInfo info) {
1145 var cache = CoverartCache.instance;
1146
1147 if (cache.has_image (m))
1148@@ -253,10 +254,10 @@
1149 var pix = get_image (info.get_tags ());
1150
1151 if (pix != null)
1152- cache.cache_image (m, pix);
1153+ yield cache.cache_image_async (m, pix);
1154 else
1155 warning ("import_art: null pixbuf");
1156- }
1157+ }
1158
1159 private static Gdk.Pixbuf? get_image (Gst.TagList tag) {
1160 Gst.Buffer? buffer = null;
1161@@ -274,14 +275,14 @@
1162 continue;
1163
1164 int image_type;
1165- structure.get_enum ("image-type", typeof (Gst.TagImageType), out image_type);
1166+ structure.get_enum ("image-type", typeof (Gst.TagImageType), out image_type);
1167
1168- if (image_type == Gst.TagImageType.FRONT_COVER) {
1169- buffer = loop_buffer;
1170- break;
1171- } else if (image_type == Gst.TagImageType.UNDEFINED || buffer == null) {
1172+ if (image_type == Gst.TagImageType.FRONT_COVER) {
1173+ buffer = loop_buffer;
1174+ break;
1175+ } else if (image_type == Gst.TagImageType.UNDEFINED || buffer == null) {
1176 buffer = loop_buffer;
1177- }
1178+ }
1179 }
1180
1181 if (buffer == null) {
1182@@ -306,100 +307,100 @@
1183
1184 return pix;
1185 }
1186-
1187- public bool save_media(Media s) {
1188- return false;
1189-
1190- /*Gst.Pipeline pipe = new Pipeline("pipe");
1191- Element src = Element.make_from_uri(URIType.SRC, "file://" + s.file, null);
1192- Element decoder = ElementFactory.make("decodebin", "decoder");
1193-
1194- GLib.Signal.connect(decoder, "new-decoded-pad", (GLib.Callback)newDecodedPad, this);
1195-
1196- if(!((Gst.Bin)pipe).add_many(src, decoder)) {
1197- message ("Could not add src and decoder to pipeline to save metadata\n");
1198- return false;
1199- }
1200-
1201- if(!src.link_many(decoder)) {
1202- message ("Could not link src to decoder to save metadata\n");
1203- return false;
1204- }
1205-
1206-
1207- Gst.Element queue = ElementFactory.make("queue", "queue");
1208- Gst.Element queue2 = ElementFactory.make("queue", "queue2");
1209-
1210- if(queue == null || queue2 == null) {
1211- message ("could not add create queues to save metadata\n");
1212- return false;
1213- }
1214-
1215- if(!((Gst.Bin)pipe).add_many(queue, queue2)) {
1216- warning ("Could not add queue's to save metadata\n");
1217- return false;
1218- }
1219-
1220- queue.set("max-size-time", 120 * Gst.SECOND);
1221-
1222-
1223-
1224-
1225- //Element encoder = new_element_from_uri(URIType.SINK, "file://" + s.file, null);
1226-
1227- Gst.TagList tags;
1228- bool rv = true;
1229- //long day;
1230-
1231- tags = new TagList();
1232- tags.add(TagMergeMode.REPLACE, TAG_TITLE, s.title,
1233- TAG_ARTIST, s.artist,
1234- TAG_COMPOSER, s.composer,
1235- TAG_ALBUM_ARTIST, s.album_artist,
1236- TAG_ALBUM, s.album,
1237- TAG_GROUPING, s.grouping,
1238- TAG_GENRE, s.genre,
1239- TAG_COMMENT, s.comment,
1240- TAG_LYRICS, s.lyrics,
1241- TAG_TRACK_NUMBER, s.track,
1242- TAG_TRACK_COUNT, s.track_count,
1243- TAG_ALBUM_VOLUME_NUMBER, s.album_number,
1244- TAG_ALBUM_VOLUME_COUNT, s.album_count,
1245- TAG_USER_RATING, s.rating);
1246-
1247- /* fetch date, set new year to s.year, set date */
1248-
1249- // now find a tag setter interface and use it
1250- /*Gst.Iterator iter;
1251- bool done;
1252-
1253- iter = ((Gst.Bin)pipeline).iterate_all_by_interface(typeof(Gst.TagSetter));
1254- done = false;
1255- while (!done) {
1256- Gst.TagSetter tagger = null;
1257-
1258- switch (iter.next(out tagger) {
1259- case GST_ITERATOR_OK:
1260- tagger.merge_tags (tags, GST_TAG_MERGE_REPLACE_ALL);
1261- break;
1262- case GST_ITERATOR_RESYNC:
1263- iter.resync();
1264- break;
1265- case GST_ITERATOR_ERROR:
1266- warning("Could not update metadata on media\n");
1267- rv = false;
1268- done = true;
1269- break;
1270- case GST_ITERATOR_DONE:
1271- done = true;
1272- break;
1273- }
1274- }
1275-
1276- return rv; */
1277- }
1278-
1279- public bool save_embeddeart_d(Gdk.Pixbuf pix) {
1280- return false;
1281- }
1282+
1283+ public bool save_media(Media s) {
1284+ return false;
1285+
1286+ /*Gst.Pipeline pipe = new Pipeline("pipe");
1287+ Element src = Element.make_from_uri(URIType.SRC, "file://" + s.file, null);
1288+ Element decoder = ElementFactory.make("decodebin", "decoder");
1289+
1290+ GLib.Signal.connect(decoder, "new-decoded-pad", (GLib.Callback)newDecodedPad, this);
1291+
1292+ if(!((Gst.Bin)pipe).add_many(src, decoder)) {
1293+ message ("Could not add src and decoder to pipeline to save metadata\n");
1294+ return false;
1295+ }
1296+
1297+ if(!src.link_many(decoder)) {
1298+ message ("Could not link src to decoder to save metadata\n");
1299+ return false;
1300+ }
1301+
1302+
1303+ Gst.Element queue = ElementFactory.make("queue", "queue");
1304+ Gst.Element queue2 = ElementFactory.make("queue", "queue2");
1305+
1306+ if(queue == null || queue2 == null) {
1307+ message ("could not add create queues to save metadata\n");
1308+ return false;
1309+ }
1310+
1311+ if(!((Gst.Bin)pipe).add_many(queue, queue2)) {
1312+ warning ("Could not add queue's to save metadata\n");
1313+ return false;
1314+ }
1315+
1316+ queue.set("max-size-time", 120 * Gst.SECOND);
1317+
1318+
1319+
1320+
1321+ //Element encoder = new_element_from_uri(URIType.SINK, "file://" + s.file, null);
1322+
1323+ Gst.TagList tags;
1324+ bool rv = true;
1325+ //long day;
1326+
1327+ tags = new TagList();
1328+ tags.add(TagMergeMode.REPLACE, TAG_TITLE, s.title,
1329+ TAG_ARTIST, s.artist,
1330+ TAG_COMPOSER, s.composer,
1331+ TAG_ALBUM_ARTIST, s.album_artist,
1332+ TAG_ALBUM, s.album,
1333+ TAG_GROUPING, s.grouping,
1334+ TAG_GENRE, s.genre,
1335+ TAG_COMMENT, s.comment,
1336+ TAG_LYRICS, s.lyrics,
1337+ TAG_TRACK_NUMBER, s.track,
1338+ TAG_TRACK_COUNT, s.track_count,
1339+ TAG_ALBUM_VOLUME_NUMBER, s.album_number,
1340+ TAG_ALBUM_VOLUME_COUNT, s.album_count,
1341+ TAG_USER_RATING, s.rating);
1342+
1343+ /* fetch date, set new year to s.year, set date */
1344+
1345+ // now find a tag setter interface and use it
1346+ /*Gst.Iterator iter;
1347+ bool done;
1348+
1349+ iter = ((Gst.Bin)pipeline).iterate_all_by_interface(typeof(Gst.TagSetter));
1350+ done = false;
1351+ while (!done) {
1352+ Gst.TagSetter tagger = null;
1353+
1354+ switch (iter.next(out tagger) {
1355+ case GST_ITERATOR_OK:
1356+ tagger.merge_tags (tags, GST_TAG_MERGE_REPLACE_ALL);
1357+ break;
1358+ case GST_ITERATOR_RESYNC:
1359+ iter.resync();
1360+ break;
1361+ case GST_ITERATOR_ERROR:
1362+ warning("Could not update metadata on media\n");
1363+ rv = false;
1364+ done = true;
1365+ break;
1366+ case GST_ITERATOR_DONE:
1367+ done = true;
1368+ break;
1369+ }
1370+ }
1371+
1372+ return rv; */
1373+ }
1374+
1375+ public bool save_embeddeart_d(Gdk.Pixbuf pix) {
1376+ return false;
1377+ }
1378 }
1379
1380=== modified file 'src/LibraryWindow.vala'
1381--- src/LibraryWindow.vala 2012-09-25 20:11:04 +0000
1382+++ src/LibraryWindow.vala 2012-10-06 03:24:21 +0000
1383@@ -362,7 +362,7 @@
1384 #endif
1385 }
1386
1387- public void show_notification_from_media (Media media) {
1388+ public async void show_notification_from_media_async (Media media) {
1389 if (media == null)
1390 return;
1391
1392@@ -372,8 +372,8 @@
1393 Gdk.Pixbuf? pixbuf = null;
1394
1395 try {
1396- string path = CoverartCache.instance.get_cached_image_path_for_media (media);
1397- pixbuf = new Gdk.Pixbuf.from_file_at_size (path, 64, 64);
1398+ var file = File.new_for_path (CoverartCache.instance.get_cached_image_path_for_media (media));
1399+ pixbuf = yield PixbufUtils.get_pixbuf_from_file_at_scale_async (file, 64, 64, false);
1400 } catch (Error err) {
1401 // Media often doesn't have an associated album art,
1402 // so we shouldn't treat this as an unexpected error.
1403@@ -383,9 +383,9 @@
1404 show_notification (primary_text, secondary_text, pixbuf);
1405 }
1406
1407- private void notify_current_media () {
1408+ private async void notify_current_media_async () {
1409 if (App.player.media_info != null && App.player.media_info.media != null)
1410- show_notification_from_media (App.player.media_info.media);
1411+ yield show_notification_from_media_async (App.player.media_info.media);
1412 }
1413
1414 /**
1415@@ -539,11 +539,17 @@
1416 }
1417
1418
1419+ private bool update_sensitivities_pending = false;
1420
1421 /**
1422 * This is handled more carefully inside each ViewWrapper object.
1423 */
1424 public async void update_sensitivities () {
1425+ if (update_sensitivities_pending)
1426+ return;
1427+
1428+ update_sensitivities_pending = true;
1429+
1430 Idle.add_full (Priority.HIGH_IDLE, update_sensitivities.callback);
1431 yield;
1432
1433@@ -590,7 +596,10 @@
1434
1435 if(!App.player.media_active || have_media && !App.player.playing) {
1436 playButton.set_stock_id(Gtk.Stock.MEDIA_PLAY);
1437+
1438 }
1439+
1440+ update_sensitivities_pending = false;
1441 }
1442
1443 public virtual void progressNotification(string? message, double progress) {
1444@@ -742,7 +751,7 @@
1445 App.player.player.play();
1446
1447 if (!inhibit_notifications)
1448- notify_current_media ();
1449+ notify_current_media_async.begin ();
1450 }
1451 else {
1452 if(App.player.playing) {
1453@@ -788,7 +797,7 @@
1454 }
1455
1456 if (!inhibit_notifications)
1457- notify_current_media ();
1458+ notify_current_media_async.begin ();
1459 }
1460
1461 public virtual void play_previous_media (bool inhibit_notifications = false) {
1462@@ -804,7 +813,7 @@
1463 return;
1464 }
1465 else if (play && !inhibit_notifications) {
1466- notify_current_media ();
1467+ notify_current_media_async.begin ();
1468 }
1469 }
1470 else
1471
1472=== modified file 'src/Objects/CoverartCache.vala'
1473--- src/Objects/CoverartCache.vala 2012-10-04 22:32:58 +0000
1474+++ src/Objects/CoverartCache.vala 2012-10-06 03:24:21 +0000
1475@@ -44,7 +44,6 @@
1476
1477 private Gdk.Pixbuf default_image;
1478
1479-
1480 public CoverartCache () {
1481 assert (_instance == null);
1482
1483@@ -54,13 +53,13 @@
1484 default_image = filter_func (default_pix);
1485 }
1486
1487-
1488- // add a shadow to every image
1489+ /**
1490+ * Adds a shadow to every image.
1491+ */
1492 protected override Gdk.Pixbuf? filter_func (Gdk.Pixbuf pix) {
1493 return PixbufUtils.get_pixbuf_shadow (pix, Icons.ALBUM_VIEW_IMAGE_SIZE);
1494 }
1495
1496-
1497 protected override string get_key (Media m) {
1498 string album_name = m.album;
1499 string artist_name = m.album_artist;
1500@@ -71,137 +70,126 @@
1501 return @"$artist_name-$album_name";
1502 }
1503
1504-
1505 public Gdk.Pixbuf get_cover (Media m) {
1506- var image = get_image (m, false);
1507- return image ?? default_image;
1508+ return get_image (m) ?? default_image;
1509 }
1510
1511-
1512 public async void fetch_all_cover_art_async (Gee.Collection<Media> media) {
1513 yield fetch_folder_images_async (media);
1514 yield load_for_media_async (media);
1515 }
1516
1517-
1518+ /**
1519+ * Retrieves images from the cache for the specified media.
1520+ */
1521 public async void load_for_media_async (Gee.Collection<Media> media) {
1522- SourceFunc callback = load_for_media_async.callback;
1523-
1524- Threads.add ( () => {
1525- load_for_media (media);
1526- Idle.add ((owned)callback);
1527- });
1528-
1529- yield;
1530- }
1531-
1532-
1533- public void load_for_media (Gee.Collection<Media> media) {
1534- debug ("READING CACHED COVERART");
1535-
1536+ // get_key() can yield a similar key for different media files, so we keep
1537+ // track of all the keys we've explored in order to lookup images only once
1538+ // for every equivalent media.
1539 var used_keys_set = new Gee.HashSet<string> ();
1540
1541 foreach (var m in media) {
1542 string key = get_key (m);
1543-
1544 if (!used_keys_set.contains (key) && !has_image (m)) {
1545- debug ("Getting [%s]", key);
1546-
1547- // Pass true to lookup_file in order to fetch the images for the first time
1548- get_image (m, true);
1549-
1550+ yield get_image_async (m, true);
1551 used_keys_set.add (key);
1552 }
1553 }
1554
1555- debug ("FINISHED LOADING CACHED COVERART");
1556-
1557 queue_notify ();
1558 }
1559
1560-
1561+ /**
1562+ * Looks up for image types in the media's directory.
1563+ */
1564 public async void fetch_folder_images_async (Gee.Collection<Media> media) {
1565- SourceFunc callback = fetch_folder_images_async.callback;
1566-
1567- Threads.add ( () => {
1568- fetch_folder_images (media);
1569-
1570- Idle.add ((owned) callback);
1571- });
1572-
1573- yield;
1574- }
1575-
1576-
1577- /**
1578- * Looks up for image types in the media's directory. We look for image files
1579- * that follow certain name patterns, like "album.png", "folder.jpg", etc.
1580- */
1581- public void fetch_folder_images (Gee.Collection<Media> media) {
1582+ // get_key() can yield a similar key for different media files, so we keep
1583+ // track of all the keys we've explored in order to lookup images only once
1584+ // for every equivalent media.
1585+ var used_keys_set = new Gee.HashSet<string> ();
1586+
1587 foreach (var m in media) {
1588- if (!has_image (m)) {
1589- var art_file = lookup_folder_image_file (m);
1590+ string key = get_key (m);
1591+ if (!used_keys_set.contains (key) && !has_image (m)) {
1592+ var art_file = yield lookup_folder_image_file_async (m);
1593 if (art_file != null)
1594- cache_image_from_file (m, art_file);
1595+ yield cache_image_from_file_async (m, art_file);
1596+
1597+ used_keys_set.add (key);
1598 }
1599 }
1600-
1601- queue_notify ();
1602 }
1603
1604
1605- // Awesome method taken from BeatBox's FileOperator.vala (adapted to use Noise's internal API)
1606- private static File? lookup_folder_image_file (Media m) {
1607- File? rv = null, media_file = m.file;
1608+ /**
1609+ * Looks up a valid album image in a media's directory.
1610+ *
1611+ * It tries to find image files that follow certain name patterns, like "album.png",
1612+ * "folder.jpg", the album name, etc. If no image matching the pattern is found, null
1613+ * is returned.
1614+ */
1615+ private static async File? lookup_folder_image_file_async (Media m) {
1616+ File? media_file = m.file;
1617+ return_val_if_fail (media_file != null, null);
1618
1619- if (!media_file.query_exists ())
1620- return rv;
1621+ // Check file existence
1622+ return_val_if_fail (yield FileUtils.query_exists_async (media_file), null);
1623
1624 var album_folder = media_file.get_parent ();
1625-
1626- if (album_folder == null)
1627- return rv;
1628+ return_val_if_fail (album_folder != null, null);
1629
1630 // Don't consider generic image names if the album folder doesn't contain the name of
1631 // the media's album. This is probably the simpler way to prevent considering images
1632 // from folders that contain multiple unrelated tracks.
1633 bool generic_folder = !album_folder.get_path ().contains (m.album);
1634
1635- string[] image_types = { "jpg", "jpeg", "png" };
1636+ string[] image_types = { "jpg", "jpeg", "png", "tiff" };
1637 Gee.Collection<File> image_files;
1638- FileUtils.enumerate_files (album_folder, image_types, false, out image_files);
1639+ yield FileUtils.enumerate_files_async (album_folder, image_types, false, out image_files);
1640
1641+ File? image_file = null;
1642+ bool good_image_found = false;
1643 // Choose an image based on priorities.
1644+
1645 foreach (var file in image_files) {
1646- string file_path = file.get_path ().down ();
1647+ // We don't want to be fooled by strange characters or whitespace
1648+ string file_path = String.canonicalize_for_search (file.get_path ().down ());
1649+ string album_name = String.canonicalize_for_search ((m.album ?? "").down ());
1650
1651 if (generic_folder) {
1652- if (m.album in file_path) {
1653- rv = file;
1654+ if (!String.is_white_space (album_name) && album_name in file_path) {
1655+ image_file = file;
1656 break;
1657 }
1658
1659 continue;
1660 }
1661
1662-
1663 if ("folder" in file_path) {
1664- rv = file;
1665+ image_file = file;
1666+ good_image_found = true;
1667 break;
1668 }
1669
1670 if ("cover" in file_path) {
1671- rv = file;
1672- } else if (rv != null) {
1673- if (!("cover" in rv.get_path ()) && "album" in file_path)
1674- rv = file;
1675- else if (!("album" in rv.get_path ()) && "front" in file_path)
1676- rv = file;
1677- else if (!("front" in rv.get_path ()) && m.album in file_path)
1678- rv = file;
1679+ good_image_found = true;
1680+ image_file = file;
1681+ continue;
1682+ }
1683+
1684+ // Let's use whatever we found
1685+ if (image_file == null)
1686+ image_file = file;
1687+
1688+ if (!("cover" in image_file.get_path ()) && "album" in file_path) {
1689+ good_image_found = true;
1690+ image_file = file;
1691+ } else if (!("album" in image_file.get_path ()) && "front" in file_path) {
1692+ good_image_found = true;
1693+ image_file = file;
1694 }
1695 }
1696
1697- return rv;
1698+ return good_image_found ? image_file : null;
1699 }
1700 }
1701
1702=== modified file 'src/Objects/MediaArtCache.vala'
1703--- src/Objects/MediaArtCache.vala 2012-09-24 19:58:02 +0000
1704+++ src/Objects/MediaArtCache.vala 2012-10-06 03:24:21 +0000
1705@@ -103,8 +103,8 @@
1706 * (e.g. due to metadata changes, etc.), since the old pixbuf and cached image
1707 * are overwritten.
1708 */
1709- public void cache_image (Media m, Gdk.Pixbuf image) {
1710- pixbuf_cache.cache_image (get_key (m), image);
1711+ public async void cache_image_async (Media m, Gdk.Pixbuf image) {
1712+ yield pixbuf_cache.cache_image_async (get_key (m), image);
1713 queue_notify ();
1714 }
1715
1716@@ -112,8 +112,8 @@
1717 * This method does the same as cache_image(), with the only difference that it
1718 * first fetches the image from the given file.
1719 */
1720- public void cache_image_from_file (Media m, File image_file, Cancellable? c = null) {
1721- pixbuf_cache.cache_image_from_file (get_key (m), image_file, c);
1722+ public async void cache_image_from_file_async (Media m, File image_file, Cancellable? c = null) {
1723+ yield pixbuf_cache.cache_image_from_file_async (get_key (m), image_file, c);
1724 queue_notify ();
1725 }
1726
1727@@ -131,8 +131,16 @@
1728 * @return null if the media's corresponding image was not found; otherwise
1729 * a valid {@link Gdk.Pixbuf}
1730 */
1731- protected Gdk.Pixbuf? get_image (Media m, bool lookup_file) {
1732- return pixbuf_cache.get_image (get_key (m), lookup_file);
1733+ protected Gdk.Pixbuf? get_image (Media m) {
1734+ return pixbuf_cache.get_image (get_key (m));
1735+ }
1736+
1737+ /**
1738+ * @return null if the media's corresponding image was not found; otherwise
1739+ * a valid {@link Gdk.Pixbuf}
1740+ */
1741+ protected async Gdk.Pixbuf? get_image_async (Media m, bool lookup_file) {
1742+ return yield pixbuf_cache.get_image_async (get_key (m), lookup_file);
1743 }
1744
1745 protected void queue_notify () {
1746
1747=== modified file 'src/Utils/PixbufCache.vala'
1748--- src/Utils/PixbufCache.vala 2012-10-04 22:32:58 +0000
1749+++ src/Utils/PixbufCache.vala 2012-10-06 03:24:21 +0000
1750@@ -50,12 +50,13 @@
1751 * to be generic). It should be easy to re-use it on another application.
1752 */
1753 public class Noise.PixbufCache {
1754+ private const string DEFAULT_FORMAT_NAME = "jpeg";
1755
1756 public Gee.Map<string, Gdk.Pixbuf> images {
1757 owned get { return image_map.read_only_view; }
1758 }
1759
1760- public string image_format { get; private set; default = "jpeg"; }
1761+ public Gdk.PixbufFormat image_format { get; private set; }
1762
1763 private File image_dir;
1764 private Gee.HashMap<string, Gdk.Pixbuf> image_map;
1765@@ -68,11 +69,20 @@
1766 * @param image_format a string specifying the image format, or null to use the default
1767 * format (JPEG). Valid image formats are those supported by {@link Gdk.Pixbuf.save}.
1768 */
1769- public PixbufCache (File image_dir, string? image_format = null) {
1770- image_map = new Gee.HashMap<string, Gdk.Pixbuf> ();
1771-
1772- if (image_format != null)
1773+ public PixbufCache (File image_dir, Gdk.PixbufFormat? image_format = null) {
1774+ if (image_format == null) {
1775+ foreach (var format in Gdk.Pixbuf.get_formats ()) {
1776+ if (format.get_name () == DEFAULT_FORMAT_NAME) {
1777+ this.image_format = format;
1778+ break;
1779+ }
1780+ }
1781+ } else {
1782 this.image_format = image_format;
1783+ }
1784+
1785+ // We need to be able to write images to disk for permanent storage
1786+ assert (this.image_format != null && this.image_format.is_writable ());
1787
1788 this.image_dir = image_dir;
1789
1790@@ -82,6 +92,8 @@
1791 if (!(err is IOError.EXISTS))
1792 warning ("Could not create image cache directory: %s", err.message);
1793 }
1794+
1795+ image_map = new Gee.HashMap<string, Gdk.Pixbuf> ();
1796 }
1797
1798 /**
1799@@ -113,7 +125,7 @@
1800 * recommended to use {@link Noise.PixbufCache.has_image} to check for that.
1801 */
1802 public string get_cached_image_path (string key) {
1803- string filename = Checksum.compute_for_string (ChecksumType.MD5, key + image_format);
1804+ string filename = Checksum.compute_for_string (ChecksumType.MD5, key + image_format.get_name ());
1805 return image_dir.get_child (filename).get_path ();
1806 }
1807
1808@@ -141,19 +153,30 @@
1809 * This method can also be used to update image buffers when they have changed,
1810 * since the old image is overwritten (in both primary memory and disk.)
1811 */
1812- public void cache_image (string key, Gdk.Pixbuf image) {
1813- cache_image_internal (key, image, true);
1814+ public async void cache_image_async (string key, Gdk.Pixbuf image) {
1815+ yield cache_image_internal_async (key, image, true);
1816 }
1817
1818 /**
1819 * This method does the same as {@link Noise.PixbufCache.cache_image}, with the only
1820 * difference that it first fetches the image from the given file.
1821 */
1822- public void cache_image_from_file (string key, File image_file, Cancellable? c = null) {
1823- var image = load_image_from_file (image_file, c);
1824+ public async void cache_image_from_file_async (string key, File image_file, Cancellable? c = null) {
1825+ var image = yield load_image_from_file_async (image_file, c);
1826 if (image != null)
1827- cache_image (key, image);
1828- }
1829+ yield cache_image_async (key, image);
1830+ }
1831+
1832+
1833+ /**
1834+ * Retrieves the image for the given key from the cache.
1835+ *
1836+ * @return A valid {@link Gdk.Pixbuf}, or null if the image is not found.
1837+ */
1838+ public Gdk.Pixbuf? get_image (string key) {
1839+ return image_map.get (key);
1840+ }
1841+
1842
1843 /**
1844 * Retrieves the image for the given key from the cache. If lookup_file
1845@@ -165,12 +188,12 @@
1846 * @return null if the key's corresponding image was not found; Otherwise
1847 * a valid {@link Gdk.Pixbuf}
1848 */
1849- public Gdk.Pixbuf? get_image (string key, bool lookup_file = true) {
1850- if (lookup_file && !image_map.has_key (key)) {
1851+ public async Gdk.Pixbuf? get_image_async (string key, bool lookup_file = true) {
1852+ if (lookup_file && !has_image (key)) {
1853 var image_file = File.new_for_path (get_cached_image_path (key));
1854- var image = load_image_from_file (image_file, null);
1855+ var image = yield load_image_from_file_async (image_file, null);
1856 if (image != null)
1857- cache_image_internal (key, image, false);
1858+ yield cache_image_internal_async (key, image, false);
1859 }
1860
1861 return image_map.get (key);
1862@@ -179,7 +202,7 @@
1863 /**
1864 * Adds an image to the hash map and also writes the image to disk if save_to_disk is true.
1865 */
1866- private void cache_image_internal (string key, Gdk.Pixbuf image, bool save_to_disk) {
1867+ private async void cache_image_internal_async (string key, Gdk.Pixbuf image, bool save_to_disk) {
1868 Gdk.Pixbuf? modified_image = (filter_func != null) ? filter_func (key, image) : image;
1869
1870 if (modified_image != null) {
1871@@ -198,11 +221,11 @@
1872 * Central place for retrieving images from permanent-storage locations. This is not
1873 * limited to this cache's local directory.
1874 */
1875- private Gdk.Pixbuf? load_image_from_file (File image_file, Cancellable? cancellable) {
1876+ private async Gdk.Pixbuf? load_image_from_file_async (File image_file, Cancellable? cancellable) {
1877 Gdk.Pixbuf? image = null;
1878
1879 try {
1880- image = PixbufUtils.get_pixbuf_from_file (image_file, cancellable);
1881+ image = yield PixbufUtils.get_pixbuf_from_file_async (image_file, cancellable);
1882 } catch (Error err) {
1883 warning ("Could not get image from file [%s]: %s", image_file.get_uri (), err.message);
1884 }
1885@@ -219,7 +242,7 @@
1886 try {
1887 string path = get_cached_image_path (key);
1888 if (delete_file (path))
1889- to_save.save (path, image_format);
1890+ to_save.save (path, image_format.get_name ());
1891 } catch (Error err) {
1892 warning ("Could not save pixbuf: %s", err.message);
1893 }
1894
1895=== modified file 'src/Utils/PixbufUtils.vala'
1896--- src/Utils/PixbufUtils.vala 2012-09-15 07:04:34 +0000
1897+++ src/Utils/PixbufUtils.vala 2012-10-06 03:24:21 +0000
1898@@ -93,15 +93,16 @@
1899 return buffer_surface.load_to_pixbuf();
1900 }
1901
1902- public Gdk.Pixbuf? get_pixbuf_from_file (File file, Cancellable? c) throws Error {
1903- return get_pixbuf_from_file_at_scale (file, -1, -1, false, c);
1904+ public async Gdk.Pixbuf? get_pixbuf_from_file_async (File file, Cancellable? c = null) throws Error {
1905+ return yield get_pixbuf_from_file_at_scale_async (file, -1, -1, false, c);
1906 }
1907
1908- public Gdk.Pixbuf? get_pixbuf_from_file_at_scale (File file, int width, int height,
1909- bool preserve_aspect_ratio,
1910- Cancellable? c) throws Error {
1911+ public async Gdk.Pixbuf? get_pixbuf_from_file_at_scale_async (File file, int width, int height,
1912+ bool preserve_aspect_ratio,
1913+ Cancellable? c = null) throws Error
1914+ {
1915 Gdk.Pixbuf? image = null;
1916- var filestream = file.read (c);
1917+ var filestream = yield file.read_async (Priority.DEFAULT, c);
1918
1919 if (filestream != null);
1920 image = new Gdk.Pixbuf.from_stream_at_scale (filestream, width, height,

Subscribers

People subscribed via source and target branches