No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2011 Jonathan Matthew <jonathan@d14n.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #include <sys/stat.h>
32 #include <sys/types.h>
33 #include <fcntl.h>
34 #include <string.h>
35 #include <stdlib.h>
36
37 #include <metadata/rb-ext-db.h>
38 #include <lib/rb-file-helpers.h>
39 #include <lib/rb-debug.h>
40 #include <lib/rb-util.h>
41 #include <lib/rb-marshal.h>
42
43 /**
44 * SECTION:rb-ext-db
45 * @short_description: store for external metadata such as album art
46 *
47 * This class simplifies searching for and providing external metadata
48 * such as album art or lyrics. A metadata provider connects to a signal
49 * on the database and in response provides a URI, a buffer containing the
50 * data, or an object representation of the data (such as a GdkPixbuf).
51 * A metadata requestor calls rb_ext_db_request and specifies a callback,
52 * or alternatively connects to a signal to receive all metadata as it is
53 * stored.
54 */
55
56 enum
57 {
58 PROP_0,
59 PROP_NAME,
60 };
61
62 enum
63 {
64 ADDED,
65 REQUEST,
66 STORE,
67 LOAD,
68 LAST_SIGNAL
69 };
70
71 static guint signals[LAST_SIGNAL] = { 0 };
72
73 static GList *instances = NULL;
74
75 static void rb_ext_db_class_init (RBExtDBClass *klass);
76 static void rb_ext_db_init (RBExtDB *store);
77
78 static void maybe_start_store_request (RBExtDB *store);
79
80 struct _RBExtDBPrivate
81 {
82 char *name;
83
84 struct tdb_context *tdb_context;
85
86 GList *requests;
87 GAsyncQueue *store_queue;
88 GSimpleAsyncResult *store_op;
89 };
90
91 typedef struct {
92 RBExtDBKey *key;
93 RBExtDBRequestCallback callback;
94 gpointer user_data;
95 GDestroyNotify destroy_notify;
96
97 char *filename;
98 GValue *data;
99 } RBExtDBRequest;
100
101 typedef struct {
102 RBExtDBKey *key;
103 RBExtDBSourceType source_type;
104 char *uri;
105 GValue *data;
106 GValue *value;
107
108 char *filename;
109 gboolean stored;
110 } RBExtDBStoreRequest;
111
112 G_DEFINE_TYPE (RBExtDB, rb_ext_db, G_TYPE_OBJECT)
113
114 static void
115 free_request (RBExtDBRequest *request)
116 {
117 rb_ext_db_key_free (request->key);
118
119 g_free (request->filename);
120
121 if (request->data) {
122 g_value_unset (request->data);
123 g_free (request->data);
124 }
125
126 if (request->destroy_notify)
127 request->destroy_notify (request->user_data);
128
129 g_slice_free (RBExtDBRequest, request);
130 }
131
132 static void
133 answer_request (RBExtDBRequest *request,
134 const char *filename,
135 GValue *data)
136 {
137 request->callback (request->key, filename, data, request->user_data);
138 free_request (request);
139 }
140
141 static RBExtDBRequest *
142 create_request (RBExtDBKey *key,
143 RBExtDBRequestCallback callback,
144 gpointer user_data,
145 GDestroyNotify destroy_notify)
146 {
147 RBExtDBRequest *req = g_slice_new0 (RBExtDBRequest);
148 req->key = rb_ext_db_key_copy (key);
149 req->callback = callback;
150 req->user_data = user_data;
151 req->destroy_notify = destroy_notify;
152 return req;
153 }
154
155
156 static RBExtDBStoreRequest *
157 create_store_request (RBExtDBKey *key,
158 RBExtDBSourceType source_type,
159 const char *uri,
160 GValue *data,
161 GValue *value)
162 {
163 RBExtDBStoreRequest *sreq = g_slice_new0 (RBExtDBStoreRequest);
164 g_assert (rb_ext_db_key_is_lookup (key) == FALSE);
165 sreq->key = rb_ext_db_key_copy (key);
166 sreq->source_type = source_type;
167 if (uri != NULL) {
168 sreq->uri = g_strdup (uri);
169 }
170 if (data != NULL) {
171 sreq->data = g_new0 (GValue, 1);
172 g_value_init (sreq->data, G_VALUE_TYPE (data));
173 g_value_copy (data, sreq->data);
174 }
175 if (value != NULL) {
176 sreq->value = g_new0 (GValue, 1);
177 g_value_init (sreq->value, G_VALUE_TYPE (value));
178 g_value_copy (value, sreq->value);
179 }
180 return sreq;
181 }
182
183 static void
184 free_store_request (RBExtDBStoreRequest *sreq)
185 {
186 if (sreq->data != NULL) {
187 g_value_unset (sreq->data);
188 g_free (sreq->data);
189 }
190 if (sreq->value != NULL) {
191 g_value_unset (sreq->value);
192 g_free (sreq->value);
193 }
194 g_free (sreq->uri);
195 g_free (sreq->filename);
196 rb_ext_db_key_free (sreq->key);
197 g_slice_free (RBExtDBStoreRequest, sreq);
198 }
199
200
201 static TDB_DATA
202 flatten_data (guint64 search_time, const char *filename, RBExtDBSourceType source_type)
203 {
204 GVariantBuilder vb;
205 GVariant *v;
206 TDB_DATA data;
207 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
208 GVariant *sv;
209 #endif
210
211 g_variant_builder_init (&vb, G_VARIANT_TYPE ("a{sv}"));
212 g_variant_builder_add (&vb, "{sv}", "time", g_variant_new_uint64 (search_time));
213 if (filename != NULL) {
214 g_variant_builder_add (&vb, "{sv}", "file", g_variant_new_string (filename));
215 }
216 if (source_type != RB_EXT_DB_SOURCE_NONE) {
217 g_variant_builder_add (&vb, "{sv}", "srctype", g_variant_new_uint32 (source_type));
218 }
219 v = g_variant_builder_end (&vb);
220
221 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
222 sv = g_variant_byteswap (v);
223 g_variant_unref (v);
224 v = sv;
225 #endif
226 data.dsize = g_variant_get_size (v);
227 data.dptr = g_malloc0 (data.dsize);
228 g_variant_store (v, data.dptr);
229 g_variant_unref (v);
230 return data;
231 }
232
233 static void
234 extract_data (TDB_DATA data, guint64 *search_time, char **filename, RBExtDBSourceType *source_type)
235 {
236 GVariant *v;
237 GVariant *sv;
238 GVariantIter iter;
239 GVariant *value;
240 char *key;
241
242 if (data.dptr == NULL || data.dsize == 0) {
243 return;
244 }
245
246 v = g_variant_new_from_data (G_VARIANT_TYPE ("a{sv}"), data.dptr, data.dsize, FALSE, NULL, NULL);
247 #if G_BYTE_ORDER == G_LITTLE_ENDIAN
248 sv = g_variant_byteswap (v);
249 #else
250 sv = g_variant_get_normal_form (v);
251 #endif
252 g_variant_unref (v);
253
254 g_variant_iter_init (&iter, sv);
255 while (g_variant_iter_loop (&iter, "{sv}", &key, &value)) {
256 if (g_strcmp0 (key, "time") == 0) {
257 if (search_time != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_UINT64)) {
258 *search_time = g_variant_get_uint64 (value);
259 }
260 } else if (g_strcmp0 (key, "file") == 0) {
261 if (filename != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_STRING)) {
262 *filename = g_variant_dup_string (value, NULL);
263 }
264 } else if (g_strcmp0 (key, "srctype") == 0) {
265 if (source_type != NULL && g_variant_is_of_type (value, G_VARIANT_TYPE_UINT32)) {
266 *source_type = g_variant_get_uint32 (value);
267 }
268 } else {
269 rb_debug ("unknown key %s in metametadata", key);
270 }
271 }
272
273 g_variant_unref (sv);
274 }
275
276
277 static GValue *
278 default_load (RBExtDB *store, GValue *data)
279 {
280 GValue *v = g_new0 (GValue, 1);
281 g_value_init (v, G_VALUE_TYPE (data));
282 g_value_copy (data, v);
283 return v;
284 }
285
286 static GValue *
287 default_store (RBExtDB *store, GValue *data)
288 {
289 GValue *v = g_new0 (GValue, 1);
290 g_value_init (v, G_VALUE_TYPE (data));
291 g_value_copy (data, v);
292 return v;
293 }
294
295 static void
296 impl_get_property (GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
297 {
298 RBExtDB *store = RB_EXT_DB (object);
299 switch (prop_id) {
300 case PROP_NAME:
301 g_value_set_string (value, store->priv->name);
302 break;
303 default:
304 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
305 break;
306 }
307 }
308
309 static void
310 impl_set_property (GObject *object, guint prop_id, const GValue *value, GParamSpec *pspec)
311 {
312 RBExtDB *store = RB_EXT_DB (object);
313 switch (prop_id) {
314 case PROP_NAME:
315 store->priv->name = g_value_dup_string (value);
316 break;
317 default:
318 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
319 break;
320 }
321 }
322
323 static GObject *
324 impl_constructor (GType type, guint n_construct_properties, GObjectConstructParam *construct_properties)
325 {
326 GList *l;
327 int i;
328 const char *name;
329 char *storedir;
330 char *tdbfile;
331 RBExtDB *store;
332
333 /* check for an existing instance of this metadata store */
334 name = NULL;
335 for (i = 0; i < n_construct_properties; i++) {
336 if (g_strcmp0 (g_param_spec_get_name (construct_properties[i].pspec), "name") == 0) {
337 name = g_value_get_string (construct_properties[i].value);
338 }
339 }
340 g_assert (name != NULL);
341
342 for (l = instances; l != NULL; l = l->next) {
343 RBExtDB *inst = l->data;
344 if (g_strcmp0 (name, inst->priv->name) == 0) {
345 rb_debug ("found existing metadata store %s", name);
346 return g_object_ref (inst);
347 }
348 }
349
350 rb_debug ("creating new metadata store instance %s", name);
351 /* construct the new instance */
352 store = RB_EXT_DB (G_OBJECT_CLASS (rb_ext_db_parent_class)->constructor (type, n_construct_properties, construct_properties));
353
354 /* open the cache db */
355 storedir = g_build_filename (rb_user_cache_dir (), name, NULL);
356 if (g_mkdir_with_parents (storedir, 0700) != 0) {
357 /* what can we do now? */
358 g_assert_not_reached ();
359 } else {
360 tdbfile = g_build_filename (storedir, "store.tdb", NULL);
361 store->priv->tdb_context = tdb_open (tdbfile, 999, TDB_INCOMPATIBLE_HASH | TDB_SEQNUM, O_RDWR | O_CREAT, 0600);
362 if (store->priv->tdb_context == NULL) {
363 /* umm */
364 g_assert_not_reached ();
365 }
366 g_free (tdbfile);
367 }
368 g_free (storedir);
369
370 /* add to instance list */
371 instances = g_list_append (instances, store);
372
373 return G_OBJECT (store);
374 }
375
376 static void
377 impl_finalize (GObject *object)
378 {
379 RBExtDB *store = RB_EXT_DB (object);
380 RBExtDBStoreRequest *req;
381
382 g_free (store->priv->name);
383
384 g_list_free_full (store->priv->requests, (GDestroyNotify) free_request);
385 while ((req = g_async_queue_try_pop (store->priv->store_queue)) != NULL) {
386 free_store_request (req);
387 }
388 g_async_queue_unref (store->priv->store_queue);
389
390 if (store->priv->tdb_context) {
391 tdb_close (store->priv->tdb_context);
392 }
393
394 instances = g_list_remove (instances, store);
395
396 G_OBJECT_CLASS (rb_ext_db_parent_class)->finalize (object);
397 }
398
399 static void
400 rb_ext_db_init (RBExtDB *store)
401 {
402 store->priv = G_TYPE_INSTANCE_GET_PRIVATE (store, RB_TYPE_EXT_DB, RBExtDBPrivate);
403
404 store->priv->store_queue = g_async_queue_new ();
405 }
406
407 static void
408 rb_ext_db_class_init (RBExtDBClass *klass)
409 {
410 GObjectClass *object_class = G_OBJECT_CLASS (klass);
411
412 object_class->set_property = impl_set_property;
413 object_class->get_property = impl_get_property;
414 object_class->constructor = impl_constructor;
415 object_class->finalize = impl_finalize;
416
417 klass->load = default_load;
418 klass->store = default_store;
419
420 /**
421 * RBExtDB:name:
422 *
423 * Name of the metadata store. Used to locate instances.
424 */
425 g_object_class_install_property (object_class,
426 PROP_NAME,
427 g_param_spec_string ("name",
428 "name",
429 "name",
430 NULL,
431 G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY));
432
433 /**
434 * RBExtDB::added:
435 *
436 * Emitted when metadata is added to the store. Metadata consumers
437 * can use this to process metadata they did not specifically
438 * request, for example to update album art stored on an attached
439 * media player.
440 */
441 signals[ADDED] =
442 g_signal_new ("added",
443 G_OBJECT_CLASS_TYPE (object_class),
444 G_SIGNAL_RUN_LAST,
445 G_STRUCT_OFFSET (RBExtDBClass, added),
446 NULL, NULL, NULL,
447 G_TYPE_NONE,
448 3, RB_TYPE_EXT_DB_KEY, G_TYPE_STRING, G_TYPE_VALUE);
449 /**
450 * RBExtDB::request:
451 *
452 * Emitted when a metadata request cannot be satisfied from the local
453 * store. Metadata providers initiate searches in response to this
454 * signal.
455 */
456 signals[REQUEST] =
457 g_signal_new ("request",
458 G_OBJECT_CLASS_TYPE (object_class),
459 G_SIGNAL_RUN_LAST,
460 G_STRUCT_OFFSET (RBExtDBClass, request),
461 rb_signal_accumulator_boolean_or, NULL, NULL,
462 G_TYPE_BOOLEAN,
463 2, RB_TYPE_EXT_DB_KEY, G_TYPE_ULONG);
464 /**
465 * RBExtDB::store:
466 *
467 * Emitted when a metadata item needs to be written to a local file.
468 * This only needs to be used for metadata that needs to be encoded
469 * or compressed, such as images.
470 */
471 signals[STORE] =
472 g_signal_new ("store",
473 G_OBJECT_CLASS_TYPE (object_class),
474 G_SIGNAL_RUN_LAST,
475 G_STRUCT_OFFSET (RBExtDBClass, store),
476 g_signal_accumulator_first_wins, NULL,
477 rb_marshal_POINTER__BOXED,
478 G_TYPE_POINTER,
479 1, G_TYPE_VALUE);
480 /**
481 * RBExtDB::load:
482 *
483 * Emitted when loading a metadata item from a local file or from a
484 * URI.
485 */
486 signals[LOAD] =
487 g_signal_new ("load",
488 G_OBJECT_CLASS_TYPE (object_class),
489 G_SIGNAL_RUN_LAST,
490 G_STRUCT_OFFSET (RBExtDBClass, load),
491 g_signal_accumulator_first_wins, NULL,
492 rb_marshal_POINTER__BOXED,
493 G_TYPE_POINTER,
494 1, G_TYPE_VALUE);
495
496 g_type_class_add_private (klass, sizeof (RBExtDBPrivate));
497 }
498
499
500 /**
501 * rb_ext_db_new:
502 * @name: name of the metadata store
503 *
504 * Provides access to a metadata store instance.
505 *
506 * Return value: named metadata store instance
507 */
508 RBExtDB *
509 rb_ext_db_new (const char *name)
510 {
511 return RB_EXT_DB (g_object_new (RB_TYPE_EXT_DB, "name", name, NULL));
512 }
513
514 typedef struct {
515 RBExtDB *store;
516 char **filename;
517 guint64 search_time;
518 RBExtDBSourceType source_type;
519 } RBExtDBLookup;
520
521 static gboolean
522 lookup_cb (TDB_DATA data, gpointer user_data)
523 {
524 TDB_DATA tdbvalue;
525 RBExtDBLookup *lookup = user_data;
526 char *fn = NULL;
527 RBExtDBSourceType source_type = RB_EXT_DB_SOURCE_NONE;
528 guint64 search_time = 0;
529
530 tdbvalue = tdb_fetch (lookup->store->priv->tdb_context, data);
531 if (tdbvalue.dptr == NULL) {
532 rb_debug ("lookup failed");
533 return TRUE;
534 }
535
536 extract_data (tdbvalue, &search_time, &fn, &source_type);
537
538 switch (source_type) {
539 case RB_EXT_DB_SOURCE_NONE:
540 if (lookup->search_time == 0)
541 lookup->search_time = search_time;
542 break;
543 default:
544 if (source_type > lookup->source_type && fn != NULL) {
545 g_free (*lookup->filename);
546 *lookup->filename = fn;
547 lookup->source_type = source_type;
548 lookup->search_time = search_time;
549 rb_debug ("found new best match %s, %d", fn, source_type);
550 } else {
551 g_free (fn);
552 rb_debug ("don't care about match %d", source_type);
553 }
554 break;
555 }
556 free (tdbvalue.dptr);
557 return TRUE;
558 }
559
560 /**
561 * rb_ext_db_lookup:
562 * @store: metadata store instance
563 * @key: metadata lookup key
564 *
565 * Looks up a cached metadata item.
566 *
567 * Return value: name of the file storing the cached metadata item
568 */
569 char *
570 rb_ext_db_lookup (RBExtDB *store, RBExtDBKey *key)
571 {
572 char *fn = NULL;
573 RBExtDBLookup lookup;
574 char *path;
575
576 lookup.store = store;
577 lookup.filename = &fn;
578 lookup.source_type = RB_EXT_DB_SOURCE_NONE;
579 lookup.search_time = 0;
580
581 rb_ext_db_key_lookups (key, lookup_cb, &lookup);
582
583 if (fn == NULL) {
584 return NULL;
585 }
586
587 path = g_build_filename (rb_user_cache_dir (), store->priv->name, fn, NULL);
588 g_free (fn);
589 return path;
590 }
591
592 static void
593 load_request_cb (RBExtDB *store, GAsyncResult *result, gpointer data)
594 {
595 RBExtDBRequest *req;
596 req = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
597
598 rb_debug ("finished loading %s", req->filename);
599 req->callback (req->key, req->filename, req->data, req->user_data);
600
601 g_object_unref (result);
602 }
603
604 static void
605 do_load_request (GSimpleAsyncResult *result, GObject *object, GCancellable *cancel)
606 {
607 RBExtDBRequest *req;
608 GFile *f;
609 char *file_data;
610 gsize file_data_size;
611 GError *error = NULL;
612
613 req = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
614
615 rb_debug ("loading data from %s", req->filename);
616 f = g_file_new_for_path (req->filename);
617 g_file_load_contents (f, NULL, &file_data, &file_data_size, NULL, &error);
618 if (error != NULL) {
619 rb_debug ("unable to load item %s: %s", req->filename, error->message);
620 g_clear_error (&error);
621
622 /* probably need to delete the item from the db */
623 } else {
624 GString *s;
625 GValue d = G_VALUE_INIT;
626
627 /* convert the encoded data into a useful object */
628 rb_debug ("converting %" G_GSIZE_FORMAT " bytes of file data", file_data_size);
629 s = g_slice_new0 (GString);
630 s->str = file_data;
631 s->len = file_data_size;
632 s->allocated_len = file_data_size;
633 g_value_init (&d, G_TYPE_GSTRING);
634 g_value_take_boxed (&d, s);
635 req->data = NULL;
636 g_signal_emit (object, signals[LOAD], 0, &d, &req->data);
637 g_value_unset (&d);
638
639 if (req->data) {
640 rb_debug ("converted data into value of type %s",
641 G_VALUE_TYPE_NAME (req->data));
642 } else {
643 rb_debug ("data conversion failed");
644 }
645 }
646
647 g_object_unref (f);
648 }
649
650
651 /**
652 * rb_ext_db_request:
653 * @store: metadata store instance
654 * @key: metadata lookup key
655 * @callback: callback to call with results
656 * @user_data: user data to pass to the callback
657 * @destroy: destroy function for @user_data
658 *
659 * Requests a metadata item. If the item is cached, the callback will be called
660 * synchronously. Otherwise, metadata providers will provide results asynchronously.
661 *
662 * Return value: %TRUE if results may be provided after returning
663 */
664 gboolean
665 rb_ext_db_request (RBExtDB *store,
666 RBExtDBKey *key,
667 RBExtDBRequestCallback callback,
668 gpointer user_data,
669 GDestroyNotify destroy)
670 {
671 RBExtDBRequest *req;
672 gboolean result;
673 guint64 last_time;
674 TDB_DATA tdbvalue;
675 TDB_DATA tdbkey;
676 char *filename;
677 GList *l;
678 gboolean emit_request = TRUE;
679
680 rb_debug ("starting metadata request");
681
682 filename = rb_ext_db_lookup (store, key);
683 if (filename != NULL) {
684 GSimpleAsyncResult *load_op;
685 rb_debug ("found cached match %s", filename);
686 load_op = g_simple_async_result_new (G_OBJECT (store),
687 (GAsyncReadyCallback) load_request_cb,
688 NULL,
689 rb_ext_db_request);
690
691 req = create_request (key, callback, user_data, destroy);
692 req->filename = filename;
693 g_simple_async_result_set_op_res_gpointer (load_op, req, (GDestroyNotify) free_request);
694
695 g_simple_async_result_run_in_thread (load_op,
696 do_load_request,
697 G_PRIORITY_DEFAULT,
698 NULL); /* no cancel? */
699 return FALSE;
700 }
701
702 /* discard duplicate requests, combine equivalent requests */
703 for (l = store->priv->requests; l != NULL; l = l->next) {
704 req = l->data;
705 if (rb_ext_db_key_matches (key, req->key) == FALSE)
706 continue;
707
708 if (req->callback == callback &&
709 req->user_data == user_data &&
710 req->destroy_notify == destroy) {
711 rb_debug ("found matching existing request");
712 if (destroy)
713 destroy (user_data);
714 return TRUE;
715 } else {
716 rb_debug ("found existing equivalent request");
717 emit_request = FALSE;
718 }
719 }
720
721 /* lookup previous request time */
722 tdbkey = rb_ext_db_key_to_store_key (key);
723
724 tdbvalue = tdb_fetch (store->priv->tdb_context, tdbkey);
725 if (tdbvalue.dptr != NULL) {
726 extract_data (tdbvalue, &last_time, NULL, NULL);
727 free (tdbvalue.dptr);
728 } else {
729 last_time = 0;
730 }
731
732 /* add stuff to list of outstanding requests */
733 req = create_request (key, callback, user_data, destroy);
734 store->priv->requests = g_list_append (store->priv->requests, req);
735
736 /* and let metadata providers request it */
737 if (emit_request) {
738 result = FALSE;
739 g_signal_emit (store, signals[REQUEST], 0, req->key, (gulong)last_time, &result);
740 } else {
741 result = TRUE;
742 }
743
744 /* free the request if result == FALSE? */
745 return result;
746 }
747
748
749 static void
750 store_request_cb (RBExtDB *store, GAsyncResult *result, gpointer data)
751 {
752 RBExtDBStoreRequest *sreq;
753 sreq = g_simple_async_result_get_op_res_gpointer (G_SIMPLE_ASYNC_RESULT (result));
754
755 if (sreq == NULL) {
756 /* do nothing */
757 } else if (sreq->stored) {
758 GList *l;
759
760 /* answer any matching queries */
761 l = store->priv->requests;
762 while (l != NULL) {
763 RBExtDBRequest *req = l->data;
764 if (rb_ext_db_key_matches (sreq->key, req->key)) {
765 GList *n = l->next;
766 rb_debug ("answering metadata request %p", req);
767 answer_request (req, sreq->filename, sreq->value);
768 store->priv->requests = g_list_delete_link (store->priv->requests, l);
769 l = n;
770 } else {
771 l = l->next;
772 }
773 }
774
775 /* let passive metadata consumers see it too */
776 rb_debug ("added; filename = %s, value type = %s", sreq->filename, sreq->value ? G_VALUE_TYPE_NAME (sreq->value) : "<none>");
777 g_signal_emit (store, signals[ADDED], 0, sreq->key, sreq->filename, sreq->value);
778 } else {
779 rb_debug ("no metadata was stored");
780 }
781
782 g_object_unref (store->priv->store_op);
783 store->priv->store_op = NULL;
784
785 /* start another store request if we have one */
786 maybe_start_store_request (store);
787 }
788
789 static void
790 do_store_request (GSimpleAsyncResult *result, GObject *object, GCancellable *cancel)
791 {
792 RBExtDB *store = RB_EXT_DB (object);
793 RBExtDBStoreRequest *req;
794 RBExtDBSourceType last_source_type = RB_EXT_DB_SOURCE_NONE;
795 guint64 last_time = 0;
796 const char *file_data;
797 char *filename = NULL;
798 gssize file_data_size;
799 GTimeVal now;
800 TDB_DATA tdbkey;
801 TDB_DATA tdbdata;
802 gboolean ignore;
803
804 req = g_async_queue_try_pop (store->priv->store_queue);
805 if (req == NULL) {
806 rb_debug ("nothing to do");
807 g_simple_async_result_set_op_res_gpointer (result, NULL, NULL);
808 return;
809 }
810 g_simple_async_result_set_op_res_gpointer (result, req, (GDestroyNotify)free_store_request);
811
812 /* convert key to storage blob */
813 tdbkey = rb_ext_db_key_to_store_key (req->key);
814
815 /* fetch current contents, if any */
816 tdbdata = tdb_fetch (store->priv->tdb_context, tdbkey);
817 extract_data (tdbdata, &last_time, &filename, &last_source_type);
818
819 if (req->source_type == last_source_type) {
820 /* ignore new data if it just comes from a search,
821 * but otherwise update.
822 */
823 ignore = (last_source_type == RB_EXT_DB_SOURCE_SEARCH);
824 } else {
825 /* ignore if from a lower priority source */
826 ignore = (req->source_type < last_source_type);
827 }
828
829 if (ignore) {
830 /* don't replace it */
831 rb_debug ("existing result is from a higher or equal priority source");
832 g_free (filename);
833 g_free (tdbkey.dptr);
834 if (tdbdata.dptr != NULL)
835 free (tdbdata.dptr);
836 return;
837 }
838
839 /* if the metadata item is specified by a uri, retrieve the data */
840 if (req->uri != NULL) {
841 GFile *f;
842 GError *error = NULL;
843 char *data;
844 gsize data_size;
845
846 rb_debug ("fetching uri %s", req->uri);
847 f = g_file_new_for_uri (req->uri);
848 g_file_load_contents (f, NULL, &data, &data_size, NULL, &error);
849 if (error != NULL) {
850 /* complain a bit */
851 rb_debug ("unable to read %s: %s", req->uri, error->message);
852 g_clear_error (&error);
853 /* leave req->data alone so we fall into the failure branch? */
854 } else {
855 GString *s;
856 rb_debug ("got %" G_GSIZE_FORMAT " bytes from uri %s", data_size, req->uri);
857 s = g_string_new_len (data, data_size);
858 req->data = g_new0 (GValue, 1);
859 g_value_init (req->data, G_TYPE_GSTRING);
860 g_value_take_boxed (req->data, s);
861 }
862
863 g_object_unref (f);
864 }
865
866 if (req->data != NULL && req->value != NULL) {
867 /* how did this happen? */
868 } else if (req->data != NULL) {
869 /* we got encoded data from somewhere; load it so we can
870 * pass it out to metadata consumers
871 */
872 g_signal_emit (store, signals[LOAD], 0, req->data, &req->value);
873 rb_debug ("converted encoded data into value of type %s", G_VALUE_TYPE_NAME (req->value));
874 } else if (req->value != NULL) {
875 /* we got an object representing the data; store it so we
876 * can write it to a file
877 */
878 g_signal_emit (store, signals[STORE], 0, req->value, &req->data);
879
880 rb_debug ("stored value into encoded data of type %s", G_VALUE_TYPE_NAME (req->data));
881 } else {
882 /* indicates we actually didn't get anything, as opposed to communication errors etc.
883 * providers just shouldn't call rb_ext_db_store_* in that case.
884 */
885 req->source_type = RB_EXT_DB_SOURCE_NONE;
886 }
887
888 /* get data to write to file */
889 file_data = NULL;
890 file_data_size = 0;
891 if (req->data == NULL) {
892 /* do nothing */
893 } else if (G_VALUE_HOLDS_STRING (req->data)) {
894 file_data = g_value_get_string (req->data);
895 file_data_size = strlen (file_data);
896 } else if (G_VALUE_HOLDS (req->data, G_TYPE_BYTE_ARRAY)) {
897 GByteArray *bytes = g_value_get_boxed (req->data);
898 file_data = (const char *)bytes->data;
899 file_data_size = bytes->len;
900 } else if (G_VALUE_HOLDS (req->data, G_TYPE_GSTRING)) {
901 GString *str = g_value_get_boxed (req->data);
902 file_data = (const char *)str->str;
903 file_data_size = str->len;
904 } else {
905 /* warning? */
906 rb_debug ("don't know how to save data of type %s", G_VALUE_TYPE_NAME (req->data));
907 }
908
909 if (file_data != NULL && file_data_size > 0) {
910 GFile *f;
911 GError *error = NULL;
912
913 if (filename == NULL) {
914 filename = g_strdup_printf ("%8.8x", tdb_get_seqnum (store->priv->tdb_context));
915 rb_debug ("generated filename %s", filename);
916 } else {
917 rb_debug ("using existing filename %s", filename);
918 }
919
920 req->filename = g_build_filename (rb_user_cache_dir (),
921 store->priv->name,
922 filename,
923 NULL);
924 f = g_file_new_for_path (req->filename);
925
926 g_file_replace_contents (f,
927 file_data,
928 file_data_size,
929 NULL,
930 FALSE,
931 G_FILE_CREATE_REPLACE_DESTINATION,
932 NULL,
933 NULL,
934 &error);
935 if (error != NULL) {
936 rb_debug ("error saving %s: %s", req->filename, error->message);
937 g_clear_error (&error);
938 } else {
939 req->stored = TRUE;
940 }
941 g_object_unref (f);
942 } else if (req->source_type == RB_EXT_DB_SOURCE_NONE) {
943 req->stored = TRUE;
944 }
945
946 if (req->stored) {
947 TDB_DATA store_data;
948
949 g_get_current_time (&now);
950 rb_debug ("actually storing this in the database");
951 store_data = flatten_data (now.tv_sec, filename, req->source_type);
952 tdb_store (store->priv->tdb_context, tdbkey, store_data, 0);
953 /* XXX warn on error.. */
954 g_free (store_data.dptr);
955 }
956
957 if (tdbdata.dptr) {
958 free (tdbdata.dptr);
959 tdbdata.dptr = NULL;
960 }
961
962 g_free (filename);
963
964 g_free (tdbkey.dptr);
965 }
966
967 static void
968 maybe_start_store_request (RBExtDB *store)
969 {
970 if (store->priv->store_op != NULL) {
971 rb_debug ("already doing something");
972 return;
973 }
974
975 if (g_async_queue_length (store->priv->store_queue) < 1) {
976 rb_debug ("nothing to do");
977 return;
978 }
979
980 store->priv->store_op = g_simple_async_result_new (G_OBJECT (store),
981 (GAsyncReadyCallback) store_request_cb,
982 NULL,
983 maybe_start_store_request);
984 g_simple_async_result_run_in_thread (store->priv->store_op,
985 do_store_request,
986 G_PRIORITY_DEFAULT,
987 NULL); /* no cancel? */
988 }
989
990 static void
991 store_metadata (RBExtDB *store, RBExtDBStoreRequest *req)
992 {
993 g_async_queue_push (store->priv->store_queue, req);
994 rb_debug ("now %d entries in store queue", g_async_queue_length (store->priv->store_queue));
995 maybe_start_store_request (store);
996 }
997
998
999 /**
1000 * rb_ext_db_store_uri:
1001 * @store: metadata store instance
1002 * @key: metadata storage key
1003 * @source_type: metadata source type
1004 * @uri: (allow-none): URI of the item to store
1005 *
1006 * Stores an item identified by @uri in the metadata store so that
1007 * lookups matching @key will return it.
1008 */
1009 void
1010 rb_ext_db_store_uri (RBExtDB *store,
1011 RBExtDBKey *key,
1012 RBExtDBSourceType source_type,
1013 const char *uri)
1014 {
1015 rb_debug ("storing uri %s", uri);
1016 store_metadata (store, create_store_request (key, source_type, uri, NULL, NULL));
1017 }
1018
1019 /**
1020 * rb_ext_db_store:
1021 * @store: metadata store instance
1022 * @key: metadata storage key
1023 * @source_type: metadata source type
1024 * @data: (allow-none): data to store
1025 *
1026 * Stores an item in the metadata store so that lookups matching @key will
1027 * return it. @data should contain an object that must be transformed using
1028 * the RBExtDB::store signal before being stored. For example,
1029 * the album art cache expects #GdkPixbuf objects here, rather than buffers
1030 * containing JPEG encoded files.
1031 */
1032 void
1033 rb_ext_db_store (RBExtDB *store,
1034 RBExtDBKey *key,
1035 RBExtDBSourceType source_type,
1036 GValue *data)
1037 {
1038 rb_debug ("storing value of type %s", data ? G_VALUE_TYPE_NAME (data) : "<none>");
1039 store_metadata (store, create_store_request (key, source_type, NULL, NULL, data));
1040 }
1041
1042 /**
1043 * rb_ext_db_store_raw:
1044 * @store: metadata store instance
1045 * @key: metadata storage key
1046 * @source_type: metadata source type
1047 * @data: (allow-none): data to store
1048 *
1049 * Stores an item in the metadata store so that lookpus matching @key
1050 * will return it. @data should contain the data to be written to the
1051 * store, either as a string or as a #GByteArray.
1052 */
1053 void
1054 rb_ext_db_store_raw (RBExtDB *store,
1055 RBExtDBKey *key,
1056 RBExtDBSourceType source_type,
1057 GValue *data)
1058 {
1059 rb_debug ("storing encoded data of type %s", data ? G_VALUE_TYPE_NAME (data) : "<none>");
1060 store_metadata (store, create_store_request (key, source_type, NULL, data, NULL));
1061 }
1062
1063 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
1064
1065 GType
1066 rb_ext_db_source_type_get_type (void)
1067 {
1068 static GType etype = 0;
1069
1070 if (etype == 0) {
1071 static const GEnumValue values[] = {
1072 ENUM_ENTRY(RB_EXT_DB_SOURCE_NONE, "none"),
1073 ENUM_ENTRY(RB_EXT_DB_SOURCE_SEARCH, "search"),
1074 ENUM_ENTRY(RB_EXT_DB_SOURCE_EMBEDDED, "embedded"),
1075 ENUM_ENTRY(RB_EXT_DB_SOURCE_USER, "user"),
1076 ENUM_ENTRY(RB_EXT_DB_SOURCE_USER_EXPLICIT, "user-explicit"),
1077 { 0, 0, 0 }
1078 };
1079 etype = g_enum_register_static ("RBExtDBSourceType", values);
1080 }
1081
1082 return etype;
1083 }