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