1 /*
2 * Copyright (C) 2008, Nokia <ivan.frade@nokia.com>
3 *
4 * This library is free software; you can redistribute it and/or
5 * modify it under the terms of the GNU Lesser General Public
6 * License as published by the Free Software Foundation; either
7 * version 2.1 of the License, or (at your option) any later version.
8 *
9 * This library is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 * Lesser General Public License for more details.
13 *
14 * You should have received a copy of the GNU Lesser General Public
15 * License along with this library; if not, write to the
16 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17 * Boston, MA 02110-1301, USA.
18 */
19
20 #include "config.h"
21
22 #include <string.h>
23 #include <stdlib.h>
24 #include <sys/types.h>
25 #include <sys/stat.h>
26 #include <unistd.h>
27 #include <ctype.h>
28 #include <sys/types.h>
29 #include <utime.h>
30 #include <time.h>
31 #include <errno.h>
32
33 #include <glib.h>
34 #include <glib/gprintf.h>
35 #include <glib/gstdio.h>
36 #include <gio/gio.h>
37
38 #include <libtracker-miner/tracker-miner.h>
39 #include <libtracker-common/tracker-file-utils.h>
40 #include <libtracker-common/tracker-date-time.h>
41 #include <libtracker-common/tracker-media-art.h>
42
43 #include "tracker-media-art.h"
44 #include "tracker-extract.h"
45 #include "tracker-marshal.h"
46 #include "tracker-media-art-generic.h"
47
48 #define ALBUMARTER_SERVICE "com.nokia.albumart"
49 #define ALBUMARTER_PATH "/com/nokia/albumart/Requester"
50 #define ALBUMARTER_INTERFACE "com.nokia.albumart.Requester"
51
52 static const gchar *media_art_type_name[TRACKER_MEDIA_ART_TYPE_COUNT] = {
53 "invalid",
54 "album",
55 "video"
56 };
57
58 typedef struct {
59 TrackerStorage *storage;
60 gchar *art_path;
61 gchar *local_uri;
62 } GetFileInfo;
63
64 typedef struct {
65 gchar *uri;
66 TrackerMediaArtType type;
67 gchar *artist_strdown;
68 gchar *title_strdown;
69 } TrackerMediaArtSearch;
70
71 typedef enum {
72 IMAGE_MATCH_EXACT = 0,
73 IMAGE_MATCH_EXACT_SMALL = 1,
74 IMAGE_MATCH_SAME_DIRECTORY = 2,
75 IMAGE_MATCH_TYPE_COUNT
76 } ImageMatchType;
77
78 static gboolean initialized = FALSE;
79 static gboolean disable_requests;
80 static TrackerStorage *media_art_storage;
81 static GHashTable *media_art_cache;
82 static GDBusConnection *connection;
83
84 static void
85 media_art_queue_cb (GObject *source_object,
86 GAsyncResult *res,
87 gpointer user_data);
88
89
90 static GDir *
91 get_parent_g_dir (const gchar *uri,
92 gchar **dirname,
93 GError **error)
94 {
95 GFile *file, *dirf;
96 GDir *dir;
97
98 g_return_val_if_fail (dirname != NULL, NULL);
99
100 *dirname = NULL;
101
102 file = g_file_new_for_uri (uri);
103 dirf = g_file_get_parent (file);
104 if (dirf) {
105 *dirname = g_file_get_path (dirf);
106 g_object_unref (dirf);
107 }
108 g_object_unref (file);
109
110 if (*dirname == NULL) {
111 *error = g_error_new (G_FILE_ERROR,
112 G_FILE_ERROR_EXIST,
113 "No parent directory found for '%s'",
114 uri);
115 return NULL;
116 }
117
118 dir = g_dir_open (*dirname, 0, error);
119
120 return dir;
121 }
122
123
124 static gchar *
125 checksum_for_data (GChecksumType checksum_type,
126 const guchar *data,
127 gsize length)
128 {
129 GChecksum *checksum;
130 gchar *retval;
131
132 checksum = g_checksum_new (checksum_type);
133 if (!checksum) {
134 return NULL;
135 }
136
137 g_checksum_update (checksum, data, length);
138 retval = g_strdup (g_checksum_get_string (checksum));
139 g_checksum_free (checksum);
140
141 return retval;
142 }
143
144 static gboolean
145 file_get_checksum_if_exists (GChecksumType checksum_type,
146 const gchar *path,
147 gchar **md5,
148 gboolean check_jpeg,
149 gboolean *is_jpeg)
150 {
151 GFile *file = g_file_new_for_path (path);
152 GFileInputStream *stream;
153 GChecksum *checksum;
154 gboolean retval;
155
156 checksum = g_checksum_new (checksum_type);
157
158 if (!checksum) {
159 g_debug ("Can't create checksum engine");
160 g_object_unref (file);
161 return FALSE;
162 }
163
164 stream = g_file_read (file, NULL, NULL);
165
166 if (stream) {
167 gssize rsize;
168 guchar buffer[1024];
169
170 /* File exists & readable always means true retval */
171 retval = TRUE;
172
173 if (check_jpeg) {
174 if (g_input_stream_read_all (G_INPUT_STREAM (stream), buffer, 3, &rsize, NULL, NULL)) {
pointer targets in passing argument 4 of 'g_input_stream_read_all' differ in signedness
(emitted by gcc)
175 if (rsize >= 3 && buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff) {
176 if (is_jpeg) {
177 *is_jpeg = TRUE;
178 }
179 /* Add the read bytes to the checksum */
180 g_checksum_update (checksum, buffer, rsize);
181 } else {
182 /* Larger than 3 bytes but incorrect jpeg header */
183 if (is_jpeg) {
184 *is_jpeg = FALSE;
185 }
186 goto end;
187 }
188 } else {
189 /* Smaller than 3 bytes, not a jpeg */
190 if (is_jpeg) {
191 *is_jpeg = FALSE;
192 }
193 goto end;
194 }
195 }
196
197 while ((rsize = g_input_stream_read (G_INPUT_STREAM (stream), buffer, 1024, NULL, NULL)) > 0) {
198 g_checksum_update (checksum, buffer, rsize);
199 }
200
201 if (md5) {
202 *md5 = g_strdup (g_checksum_get_string (checksum));
203 }
204
205 } else {
206 g_debug ("%s isn't readable while calculating MD5 checksum", path);
207 /* File doesn't exist or isn't readable */
208 retval = FALSE;
209 }
210
211 end:
212
213 if (stream) {
214 g_object_unref (stream);
215 }
216 g_checksum_free (checksum);
217 g_object_unref (file);
218
219 return retval;
220 }
221
222 static gboolean
223 convert_from_other_format (const gchar *found,
224 const gchar *target,
225 const gchar *album_path,
226 const gchar *artist)
227 {
228 gboolean retval;
229 gchar *sum1 = NULL;
230 gchar *target_temp;
231
232 target_temp = g_strdup_printf ("%s-tmp", target);
233
234 retval = tracker_media_art_file_to_jpeg (found, target_temp);
235
236 if (retval && (artist == NULL || g_strcmp0 (artist, " ") == 0)) {
237 if (g_rename (target_temp, album_path) == -1) {
238 g_debug ("rename(%s, %s) error: %s", target_temp, album_path, g_strerror (errno));
239 }
240 } else if (retval && file_get_checksum_if_exists (G_CHECKSUM_MD5, target_temp, &sum1, FALSE, NULL)) {
241 gchar *sum2 = NULL;
242 if (file_get_checksum_if_exists (G_CHECKSUM_MD5, album_path, &sum2, FALSE, NULL)) {
243 if (g_strcmp0 (sum1, sum2) == 0) {
244
245 /* If album-space-md5.jpg is the same as found,
246 * make a symlink */
247
248 if (symlink (album_path, target) != 0) {
249 g_debug ("symlink(%s, %s) error: %s", album_path, target, g_strerror (errno));
250 retval = FALSE;
251 } else {
252 retval = TRUE;
253 }
254
255 g_unlink (target_temp);
256
257 } else {
258
259 /* If album-space-md5.jpg isn't the same as found,
260 * make a new album-md5-md5.jpg (found -> target) */
261
262 if (g_rename (target_temp, album_path) == -1) {
263 g_debug ("rename(%s, %s) error: %s", target_temp, album_path, g_strerror (errno));
264 }
265 }
266 g_free (sum2);
267 } else {
268
269 /* If there's not yet a album-space-md5.jpg, make one,
270 * and symlink album-md5-md5.jpg to it */
271
272 g_rename (target_temp, album_path);
273
274 if (symlink (album_path, target) != 0) {
275 g_debug ("symlink(%s,%s) error: %s", album_path, target, g_strerror (errno));
276 retval = FALSE;
277 } else {
278 retval = TRUE;
279 }
280
281 }
282
283 g_free (sum1);
284 } else if (retval) {
285 g_debug ("Can't read %s while calculating checksum", target_temp);
286 /* Can't read the file that it was converted to, strange ... */
287 g_unlink (target_temp);
288 }
289
290 g_free (target_temp);
291
292 return retval;
293 }
294
295 static TrackerMediaArtSearch *
296 tracker_media_art_search_new (const gchar *uri,
297 TrackerMediaArtType type,
298 const gchar *artist,
299 const gchar *title)
300 {
301 TrackerMediaArtSearch *search;
302 gchar *temp;
303
304 search = g_slice_new0 (TrackerMediaArtSearch);
305 search->uri = g_strdup (uri);
306 search->type = type;
307
308 if (artist) {
309 temp = tracker_media_art_strip_invalid_entities (artist);
310 search->artist_strdown = g_utf8_strdown (temp, -1);
311 g_free (temp);
312 }
313
314 temp = tracker_media_art_strip_invalid_entities (title);
315 search->title_strdown = g_utf8_strdown (temp, -1);
316 g_free (temp);
317
318 return search;
319 }
320
321 static void
322 tracker_media_art_search_free (TrackerMediaArtSearch *search)
323 {
324 g_free (search->uri);
325 g_free (search->artist_strdown);
326 g_free (search->title_strdown);
327
328 g_slice_free (TrackerMediaArtSearch, search);
329 }
330
331 static ImageMatchType
332 classify_image_file (TrackerMediaArtSearch *search,
333 const gchar *file_name_strdown)
334 {
335 if ((search->artist_strdown && search->artist_strdown[0] != '\0' &&
336 strstr (file_name_strdown, search->artist_strdown)) ||
337 (search->title_strdown && search->title_strdown[0] != '\0' &&
338 strstr (file_name_strdown, search->title_strdown))) {
339 return IMAGE_MATCH_EXACT;
340 }
341
342 if (search->type == TRACKER_MEDIA_ART_ALBUM) {
343 /* Accept cover, front, folder, AlbumArt_{GUID}_Large (first choice)
344 * second choice is AlbumArt_{GUID}_Small and AlbumArtSmall. We
345 * don't support just AlbumArt. (it must have a Small or Large) */
346
347 if (strstr (file_name_strdown, "cover") ||
348 strstr (file_name_strdown, "front") ||
349 strstr (file_name_strdown, "folder")) {
350 return IMAGE_MATCH_EXACT;
351 }
352
353 if (strstr (file_name_strdown, "albumart")) {
354 if (strstr (file_name_strdown, "large")) {
355 return IMAGE_MATCH_EXACT;
356 } else if (strstr (file_name_strdown, "small")) {
357 return IMAGE_MATCH_EXACT_SMALL;
358 }
359 }
360 }
361
362 if (search->type == TRACKER_MEDIA_ART_VIDEO) {
363 if (strstr (file_name_strdown, "folder") ||
364 strstr (file_name_strdown, "poster")) {
365 return IMAGE_MATCH_EXACT;
366 }
367 }
368
369 /* Lowest priority for other images, but we still might use it for videos */
370 return IMAGE_MATCH_SAME_DIRECTORY;
371 }
372
373 static gchar *
374 tracker_media_art_find_by_artist_and_title (const gchar *uri,
375 TrackerMediaArtType type,
376 const gchar *artist,
377 const gchar *title)
378 {
379 TrackerMediaArtSearch *search;
380 GDir *dir;
381 GError *error = NULL;
382 gchar *dirname = NULL;
383 const gchar *name;
384 gchar *name_utf8, *name_strdown;
385 guint i;
386 gchar *art_file_name;
387 gchar *art_file_path;
388 gint priority;
389
390 GList *image_list[IMAGE_MATCH_TYPE_COUNT] = { NULL, };
391
392 g_return_val_if_fail (type > TRACKER_MEDIA_ART_NONE && type < TRACKER_MEDIA_ART_TYPE_COUNT, FALSE);
393 g_return_val_if_fail (title != NULL, FALSE);
394
395 dir = get_parent_g_dir (uri, &dirname, &error);
396
397 if (!dir) {
398 g_debug ("Media art directory could not be opened: %s",
399 error ? error->message : "no error given");
400
401 g_clear_error (&error);
402 g_free (dirname);
403
404 return NULL;
405 }
406
407 /* First, classify each file in the directory as either an image, relevant
408 * to the media object in question, or irrelevant. We use this information
409 * to decide if the image is a cover or if the file is in a random directory.
410 */
411
412 search = tracker_media_art_search_new (uri, type, artist, title);
413
414 for (name = g_dir_read_name (dir);
415 name != NULL;
416 name = g_dir_read_name (dir)) {
417
418 name_utf8 = g_filename_to_utf8 (name, -1, NULL, NULL, NULL);
419
420 if (!name_utf8) {
421 g_debug ("Could not convert filename '%s' to UTF-8", name);
422 continue;
423 }
424
425 name_strdown = g_utf8_strdown (name_utf8, -1);
426
427 if (g_str_has_suffix (name_strdown, "jpeg") ||
428 g_str_has_suffix (name_strdown, "jpg") ||
429 g_str_has_suffix (name_strdown, "png")) {
430
431 priority = classify_image_file (search, name_strdown);
432 image_list[priority] = g_list_prepend (image_list[priority], name_strdown);
433 } else {
434 g_free (name_strdown);
435 }
436
437 g_free (name_utf8);
438 }
439
440 /* Use the results to pick a media art image */
441
442 art_file_name = NULL;
443 art_file_path = NULL;
444
445 if (g_list_length (image_list[IMAGE_MATCH_EXACT]) > 0) {
446 art_file_name = g_strdup (image_list[IMAGE_MATCH_EXACT]->data);
447 } else if (g_list_length (image_list[IMAGE_MATCH_EXACT_SMALL]) > 0) {
448 art_file_name = g_strdup (image_list[IMAGE_MATCH_EXACT_SMALL]->data);
449 } else {
450 if (type == TRACKER_MEDIA_ART_VIDEO && g_list_length (image_list[IMAGE_MATCH_SAME_DIRECTORY]) == 1) {
451 art_file_name = g_strdup (image_list[IMAGE_MATCH_SAME_DIRECTORY]->data);
452 }
453 }
454
455 for (i = 0; i < IMAGE_MATCH_TYPE_COUNT; i ++) {
456 g_list_foreach (image_list[i], (GFunc)g_free, NULL);
457 g_list_free (image_list[i]);
458 }
459
460 if (art_file_name) {
461 art_file_path = g_build_filename (dirname, art_file_name, NULL);
462 g_free (art_file_name);
463 } else {
464 g_debug ("Album art NOT found in same directory");
465 art_file_path = NULL;
466 }
467
468 tracker_media_art_search_free (search);
469 g_dir_close (dir);
470 g_free (dirname);
471
472 return art_file_path;
473 }
474
475 static gboolean
476 media_art_heuristic (const gchar *artist,
477 const gchar *title,
478 TrackerMediaArtType type,
479 const gchar *filename_uri,
480 const gchar *local_uri)
481 {
482 gchar *art_file_path = NULL;
483 gchar *album_art_file_path = NULL;
484 gchar *target = NULL;
485 gchar *artist_stripped = NULL;
486 gchar *title_stripped = NULL;
487 gboolean retval = FALSE;
488
489 if (title == NULL || title[0] == '\0') {
490 g_debug ("Unable to fetch media art, no title specified");
491 return FALSE;
492 }
493
494 if (artist) {
495 artist_stripped = tracker_media_art_strip_invalid_entities (artist);
496 }
497 title_stripped = tracker_media_art_strip_invalid_entities (title);
498
499 tracker_media_art_get_path (artist_stripped,
500 title_stripped,
501 media_art_type_name[type],
502 NULL,
503 &target,
504 NULL);
505
506 /* Copy from local album art (.mediaartlocal) to spec */
507 if (local_uri) {
508 GFile *local_file, *file;
509
510 local_file = g_file_new_for_uri (local_uri);
511
512 if (g_file_query_exists (local_file, NULL)) {
513 g_debug ("Album art being copied from local (.mediaartlocal) file:'%s'",
514 local_uri);
515
516 file = g_file_new_for_path (target);
517
518 g_file_copy_async (local_file, file, 0, 0,
519 NULL, NULL, NULL, NULL, NULL);
520
521 g_object_unref (file);
522 g_object_unref (local_file);
523
524 g_free (target);
525 g_free (artist_stripped);
526 g_free (title_stripped);
527
528 return TRUE;
529 }
530
531 g_object_unref (local_file);
532 }
533
534 art_file_path = tracker_media_art_find_by_artist_and_title (filename_uri, type, artist, title);
535
536 if (art_file_path != NULL) {
537 if (g_str_has_suffix (art_file_path, "jpeg") ||
538 g_str_has_suffix (art_file_path, "jpg")) {
539
540 gboolean is_jpeg = FALSE;
541 gchar *sum1 = NULL;
542
543 if (type != TRACKER_MEDIA_ART_ALBUM || (artist == NULL || g_strcmp0 (artist, " ") == 0)) {
544 GFile *art_file;
545 GFile *target_file;
546 GError *err = NULL;
547
548 g_debug ("Album art (JPEG) found in same directory being used:'%s'", art_file_path);
549
550 target_file = g_file_new_for_path (target);
551 art_file = g_file_new_for_path (art_file_path);
552
553 g_file_copy (art_file, target_file, 0, NULL, NULL, NULL, &err);
554 if (err) {
555 g_debug ("%s", err->message);
556 g_clear_error (&err);
557 }
558 g_object_unref (art_file);
559 g_object_unref (target_file);
560 } else if (file_get_checksum_if_exists (G_CHECKSUM_MD5, art_file_path, &sum1, TRUE, &is_jpeg)) {
561 /* Avoid duplicate artwork for each track in an album */
562 tracker_media_art_get_path (NULL,
563 title_stripped,
564 media_art_type_name [type],
565 NULL,
566 &album_art_file_path,
567 NULL);
568
569 if (is_jpeg) {
570 gchar *sum2 = NULL;
571
572 g_debug ("Album art (JPEG) found in same directory being used:'%s'", art_file_path);
573
574 if (file_get_checksum_if_exists (G_CHECKSUM_MD5, album_art_file_path, &sum2, FALSE, NULL)) {
575 if (g_strcmp0 (sum1, sum2) == 0) {
576 /* If album-space-md5.jpg is the same as found,
577 * make a symlink */
578
579 if (symlink (album_art_file_path, target) != 0) {
580 g_debug ("symlink(%s, %s) error: %s", album_art_file_path, target, g_strerror (errno));
581 retval = FALSE;
582 } else {
583 retval = TRUE;
584 }
585 } else {
586 GFile *art_file;
587 GFile *target_file;
588 GError *err = NULL;
589
590 /* If album-space-md5.jpg isn't the same as found,
591 * make a new album-md5-md5.jpg (found -> target) */
592
593 target_file = g_file_new_for_path (target);
594 art_file = g_file_new_for_path (art_file_path);
595 retval = g_file_copy (art_file, target_file, 0, NULL, NULL, NULL, &err);
596 if (err) {
597 g_debug ("%s", err->message);
598 g_clear_error (&err);
599 }
600 g_object_unref (art_file);
601 g_object_unref (target_file);
602 }
603 g_free (sum2);
604 } else {
605 GFile *art_file;
606 GFile *album_art_file;
607 GError *err = NULL;
608
609 /* If there's not yet a album-space-md5.jpg, make one,
610 * and symlink album-md5-md5.jpg to it */
611
612 album_art_file = g_file_new_for_path (album_art_file_path);
613 art_file = g_file_new_for_path (art_file_path);
614 retval = g_file_copy (art_file, album_art_file, 0, NULL, NULL, NULL, &err);
615
616 if (err == NULL) {
617 if (symlink (album_art_file_path, target) != 0) {
618 g_debug ("symlink(%s, %s) error: %s", album_art_file_path, target, g_strerror (errno));
619 retval = FALSE;
620 } else {
621 retval = TRUE;
622 }
623 } else {
624 g_debug ("%s", err->message);
625 g_clear_error (&err);
626 retval = FALSE;
627 }
628
629 g_object_unref (album_art_file);
630 g_object_unref (art_file);
631 }
632 } else {
633 g_debug ("Album art found in same directory but not a real JPEG file (trying to convert): '%s'", art_file_path);
634 retval = convert_from_other_format (art_file_path, target, album_art_file_path, artist);
635 }
636
637 g_free (sum1);
638 } else {
639 /* Can't read contents of the cover.jpg file ... */
640 retval = FALSE;
641 }
642 } else if (g_str_has_suffix (art_file_path, "png")) {
643 if (!album_art_file_path) {
644 tracker_media_art_get_path (NULL,
645 title_stripped,
646 media_art_type_name[type],
647 NULL,
648 &album_art_file_path,
649 NULL);
650 }
651
652 g_debug ("Album art (PNG) found in same directory being used:'%s'", art_file_path);
653 retval = convert_from_other_format (art_file_path, target, album_art_file_path, artist);
654 }
655
656 g_free (art_file_path);
657 g_free (album_art_file_path);
658 }
659
660 g_free (target);
661 g_free (artist_stripped);
662 g_free (title_stripped);
663
664 return retval;
665 }
666
667 static gboolean
668 media_art_set (const unsigned char *buffer,
669 size_t len,
670 const gchar *mime,
671 TrackerMediaArtType type,
672 const gchar *artist,
673 const gchar *title,
674 const gchar *uri)
675 {
676 gchar *local_path;
677 gboolean retval = FALSE;
678
679 g_return_val_if_fail (type > TRACKER_MEDIA_ART_NONE && type < TRACKER_MEDIA_ART_TYPE_COUNT, FALSE);
680
681 if (!artist && !title) {
682 g_warning ("Could not save embedded album art, not enough metadata supplied");
683 return FALSE;
684 }
685
686 tracker_media_art_get_path (artist, title, media_art_type_name[type], NULL, &local_path, NULL);
687
688 if (type != TRACKER_MEDIA_ART_ALBUM || (artist == NULL || g_strcmp0 (artist, " ") == 0)) {
689 retval = tracker_media_art_buffer_to_jpeg (buffer, len, mime, local_path);
690 } else {
691 gchar *album_path;
692
693 tracker_media_art_get_path (NULL, title, media_art_type_name[type], NULL, &album_path, NULL);
694
695 if (!g_file_test (album_path, G_FILE_TEST_EXISTS)) {
696 retval = tracker_media_art_buffer_to_jpeg (buffer, len, mime, album_path);
697
698 /* If album-space-md5.jpg doesn't exist, make one and make a symlink
699 * to album-md5-md5.jpg */
700
701 if (retval && symlink (album_path, local_path) != 0) {
702 g_debug ("symlink(%s, %s) error: %s", album_path, local_path, g_strerror (errno));
703 retval = FALSE;
704 } else {
705 retval = TRUE;
706 }
707 } else {
708 gchar *sum2 = NULL;
709
710 if (file_get_checksum_if_exists (G_CHECKSUM_MD5, album_path, &sum2, FALSE, NULL)) {
711 if ( !(g_strcmp0 (mime, "image/jpeg") == 0 || g_strcmp0 (mime, "JPG") == 0) ||
712 ( !(len > 2 && buffer[0] == 0xff && buffer[1] == 0xd8 && buffer[2] == 0xff) )) {
713 gchar *sum1 = NULL;
714 gchar *temp = g_strdup_printf ("%s-tmp", album_path);
715
716 /* If buffer isn't a JPEG */
717
718 retval = tracker_media_art_buffer_to_jpeg (buffer, len, mime, temp);
719
720 if (retval && file_get_checksum_if_exists (G_CHECKSUM_MD5, temp, &sum1, FALSE, NULL)) {
721 if (g_strcmp0 (sum1, sum2) == 0) {
722
723 /* If album-space-md5.jpg is the same as buffer, make a symlink
724 * to album-md5-md5.jpg */
725
726 g_unlink (temp);
727 if (symlink (album_path, local_path) != 0) {
728 g_debug ("symlink(%s, %s) error: %s", album_path, local_path, g_strerror (errno));
729 retval = FALSE;
730 } else {
731 retval = TRUE;
732 }
733 } else {
734 /* If album-space-md5.jpg isn't the same as buffer, make a
735 * new album-md5-md5.jpg */
736 if (g_rename (temp, local_path) == -1) {
737 g_debug ("rename(%s, %s) error: %s", temp, local_path, g_strerror (errno));
738 }
739 }
740 g_free (sum1);
741 } else {
742 /* Can't read temp file ... */
743 g_unlink (temp);
744 }
745
746 g_free (temp);
747 } else {
748 gchar *sum1 = NULL;
749
750 sum1 = checksum_for_data (G_CHECKSUM_MD5, buffer, len);
751 /* If album-space-md5.jpg is the same as buffer, make a symlink
752 * to album-md5-md5.jpg */
753
754 if (g_strcmp0 (sum1, sum2) == 0) {
755 if (symlink (album_path, local_path) != 0) {
756 g_debug ("symlink(%s, %s) error: %s", album_path, local_path, g_strerror (errno));
757 retval = FALSE;
758 } else {
759 retval = TRUE;
760 }
761 } else {
762 /* If album-space-md5.jpg isn't the same as buffer, make a
763 * new album-md5-md5.jpg */
764 retval = tracker_media_art_buffer_to_jpeg (buffer, len, mime, local_path);
765 }
766 g_free (sum1);
767 }
768 g_free (sum2);
769 }
770 g_free (album_path);
771 }
772 }
773
774 g_free (local_path);
775
776 return retval;
777 }
778
779 static void
780 media_art_request_download (TrackerStorage *storage,
781 TrackerMediaArtType type,
782 const gchar *album,
783 const gchar *artist,
784 const gchar *local_uri,
785 const gchar *art_path)
786 {
787 if (connection) {
788 GetFileInfo *info;
789
790 if (disable_requests) {
791 return;
792 }
793
794 if (type != TRACKER_MEDIA_ART_ALBUM) {
795 return;
796 }
797
798 info = g_slice_new (GetFileInfo);
799
800 info->storage = storage ? g_object_ref (storage) : NULL;
801
802 info->local_uri = g_strdup (local_uri);
803 info->art_path = g_strdup (art_path);
804
805 g_dbus_connection_call (connection,
806 ALBUMARTER_SERVICE,
807 ALBUMARTER_PATH,
808 ALBUMARTER_INTERFACE,
809 "Queue",
810 g_variant_new ("(sssu)",
811 artist ? artist : "",
812 album ? album : "",
813 "album",
814 0),
815 NULL,
816 G_DBUS_CALL_FLAGS_NONE,
817 -1,
818 NULL,
819 media_art_queue_cb,
820 info);
821 }
822 }
823
824 static void
825 media_art_copy_to_local (TrackerStorage *storage,
826 const gchar *filename,
827 const gchar *local_uri)
828 {
829 GSList *roots, *l;
830 gboolean on_removable_device = FALSE;
831 guint flen;
832
833 /* Determining if we are on a removable device */
834 if (!storage) {
835 /* This is usually because we are running on the
836 * command line, so we don't error here with
837 * g_return_if_fail().
838 */
839 return;
840 }
841
842 roots = tracker_storage_get_device_roots (storage, TRACKER_STORAGE_REMOVABLE, FALSE);
843 flen = strlen (filename);
844
845 for (l = roots; l; l = l->next) {
846 guint len;
847
848 len = strlen (l->data);
849
850 if (flen >= len && strncmp (filename, l->data, len)) {
851 on_removable_device = TRUE;
852 break;
853 }
854 }
855
856 g_slist_foreach (roots, (GFunc) g_free, NULL);
857 g_slist_free (roots);
858
859 if (on_removable_device) {
860 GFile *local_file, *from;
861
862 from = g_file_new_for_path (filename);
863 local_file = g_file_new_for_uri (local_uri);
864
865 /* We don't try to overwrite, but we also ignore all errors.
866 * Such an error could be that the removable device is
867 * read-only. Well that's fine then ... ignore */
868
869 if (!g_file_query_exists (local_file, NULL)) {
870 GFile *dirf;
871
872 dirf = g_file_get_parent (local_file);
873 if (dirf) {
874 /* Parent file may not exist, as if the file is in the
875 * root of a gvfs mount. In this case we won't try to
876 * create the parent directory, just try to copy the
877 * file there. */
878 g_file_make_directory_with_parents (dirf, NULL, NULL);
879 g_object_unref (dirf);
880 }
881
882 g_debug ("Copying media art from:'%s' to:'%s'",
883 filename, local_uri);
884
885 g_file_copy_async (from, local_file, 0, 0,
886 NULL, NULL, NULL, NULL, NULL);
887 }
888
889 g_object_unref (local_file);
890 g_object_unref (from);
891 }
892 }
893
894 static void
895 media_art_queue_cb (GObject *source_object,
896 GAsyncResult *res,
897 gpointer user_data)
898 {
899 GError *error = NULL;
900 GetFileInfo *info;
901 GVariant *v;
902
903 info = user_data;
904
905 v = g_dbus_connection_call_finish ((GDBusConnection *) source_object, res, &error);
906
907 if (error) {
908 if (error->code == G_DBUS_ERROR_SERVICE_UNKNOWN) {
909 disable_requests = TRUE;
910 } else {
911 g_warning ("%s", error->message);
912 }
913 g_clear_error (&error);
914 }
915
916 if (v) {
917 g_variant_unref (v);
918 }
919
920 if (info->storage && info->art_path &&
921 g_file_test (info->art_path, G_FILE_TEST_EXISTS)) {
922
923 media_art_copy_to_local (info->storage,
924 info->art_path,
925 info->local_uri);
926 }
927
928 g_free (info->art_path);
929 g_free (info->local_uri);
930
931 if (info->storage) {
932 g_object_unref (info->storage);
933 }
934
935 g_slice_free (GetFileInfo, info);
936 }
937
938 gboolean
939 tracker_media_art_init (void)
940 {
941 GError *error = NULL;
942
943 g_return_val_if_fail (initialized == FALSE, FALSE);
944
945 tracker_media_art_plugin_init ();
946
947 media_art_storage = tracker_storage_new ();
948
949 /* Cache to know if we have already handled uris */
950 media_art_cache = g_hash_table_new_full (g_str_hash,
951 g_str_equal,
952 (GDestroyNotify) g_free,
953 NULL);
954
955 /* Signal handler for new album art from the extractor */
956 connection = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
957
958 if (!connection) {
959 g_critical ("Could not connect to the D-Bus session bus, %s",
960 error ? error->message : "no error given.");
961 g_clear_error (&error);
962 return FALSE;
963 }
964
965 initialized = TRUE;
966
967 return TRUE;
968 }
969
970 void
971 tracker_media_art_shutdown (void)
972 {
973 g_return_if_fail (initialized == TRUE);
974
975 if (connection) {
976 g_object_unref (connection);
977 }
978
979 if (media_art_cache) {
980 g_hash_table_unref (media_art_cache);
981 }
982
983 if (media_art_storage) {
984 g_object_unref (media_art_storage);
985 }
986
987 tracker_media_art_plugin_shutdown ();
988
989 initialized = FALSE;
990 }
991
992 static void
993 set_mtime (const gchar *filename, guint64 mtime)
994 {
995
996 struct utimbuf buf;
997
998 buf.actime = buf.modtime = mtime;
999 utime (filename, &buf);
1000 }
1001
1002 gboolean
1003 tracker_media_art_process (const unsigned char *buffer,
1004 size_t len,
1005 const gchar *mime,
1006 TrackerMediaArtType type,
1007 const gchar *artist,
1008 const gchar *title,
1009 const gchar *uri)
1010 {
1011 gchar *art_path;
1012 gchar *local_art_uri = NULL;
1013 gboolean processed = TRUE, a_exists, created = FALSE;
1014 guint64 mtime, a_mtime = 0;
1015
1016 g_return_val_if_fail (type > TRACKER_MEDIA_ART_NONE && type < TRACKER_MEDIA_ART_TYPE_COUNT, FALSE);
1017
1018 g_debug ("Processing media art: artist:'%s', title:'%s', type:'%s', uri:'%s'. Buffer is %ld bytes, mime:'%s'",
1019 artist ? artist : "",
1020 title ? title : "",
1021 media_art_type_name[type],
1022 uri,
1023 (long int) len,
1024 mime);
1025
1026 /* TODO: We can definitely work with GFiles better here */
1027
1028 mtime = tracker_file_get_mtime_uri (uri);
1029
1030 tracker_media_art_get_path (artist,
1031 title,
1032 media_art_type_name[type],
1033 uri,
1034 &art_path,
1035 &local_art_uri);
1036
1037 if (!art_path) {
1038 g_debug ("Album art path could not be obtained, not processing any further");
1039
1040 g_free (local_art_uri);
1041
1042 return FALSE;
1043 }
1044
1045 a_exists = g_file_test (art_path, G_FILE_TEST_EXISTS);
1046
1047 if (a_exists) {
1048 a_mtime = tracker_file_get_mtime (art_path);
1049 }
1050
1051 if ((buffer && len > 0) && ((!a_exists) || (a_exists && mtime > a_mtime))) {
1052 processed = media_art_set (buffer, len, mime, type, artist, title, uri);
1053 set_mtime (art_path, mtime);
1054 created = TRUE;
1055 }
1056
1057 if ((!created) && ((!a_exists) || (a_exists && mtime > a_mtime))) {
1058 /* If not, we perform a heuristic on the dir */
1059 gchar *key;
1060 gchar *dirname = NULL;
1061 GFile *file, *dirf;
1062
1063 file = g_file_new_for_uri (uri);
1064 dirf = g_file_get_parent (file);
1065 if (dirf) {
1066 dirname = g_file_get_path (dirf);
1067 g_object_unref (dirf);
1068 }
1069 g_object_unref (file);
1070
1071 key = g_strdup_printf ("%i-%s-%s-%s",
1072 type,
1073 artist ? artist : "",
1074 title ? title : "",
1075 dirname ? dirname : "");
1076
1077 g_free (dirname);
1078
1079 if (!g_hash_table_lookup (media_art_cache, key)) {
1080 if (!media_art_heuristic (artist,
1081 title,
1082 type,
1083 uri,
1084 local_art_uri)) {
1085 /* If the heuristic failed, we
1086 * request the download the
1087 * media-art to the media-art
1088 * downloaders
1089 */
1090 media_art_request_download (media_art_storage,
1091 type,
1092 artist,
1093 title,
1094 local_art_uri,
1095 art_path);
1096 }
1097
1098 set_mtime (art_path, mtime);
1099
1100 g_hash_table_insert (media_art_cache,
1101 key,
1102 GINT_TO_POINTER(TRUE));
1103 } else {
1104 g_free (key);
1105 }
1106 } else {
1107 if (!created) {
1108 g_debug ("Album art already exists for uri:'%s' as '%s'",
1109 uri,
1110 art_path);
1111 }
1112 }
1113
1114 if (local_art_uri && !g_file_test (local_art_uri, G_FILE_TEST_EXISTS)) {
1115 /* We can't reuse art_exists here because the
1116 * situation might have changed
1117 */
1118 if (g_file_test (art_path, G_FILE_TEST_EXISTS)) {
1119 media_art_copy_to_local (media_art_storage,
1120 art_path,
1121 local_art_uri);
1122 }
1123 }
1124
1125 g_free (art_path);
1126 g_free (local_art_uri);
1127
1128 return processed;
1129 }