hythmbox-2.98/metadata/rb-ext-db.c

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 }