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