hythmbox-2.98/rhythmdb/rhythmdb-tree.c

No issues found

   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2003, 2004 Colin Walters <walters@verbum.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 #ifdef HAVE_GNU_FWRITE_UNLOCKED
  32 #define _GNU_SOURCE
  33 #endif
  34 #include <stdio.h>
  35 #ifdef HAVE_GNU_FWRITE_UNLOCKED
  36 #undef _GNU_SOURCE
  37 #endif
  38 #include <unistd.h>
  39 #include <stdlib.h>
  40 #include <errno.h>
  41 #include <string.h>
  42 #include <math.h>
  43 #include <glib/gprintf.h>
  44 #include <glib.h>
  45 #include <glib/gi18n.h>
  46 #include <gtk/gtk.h>
  47 #include <libxml/entities.h>
  48 #include <libxml/SAX.h>
  49 #include <libxml/parserInternals.h>
  50 
  51 #include "rhythmdb-private.h"
  52 #include "rhythmdb-tree.h"
  53 #include "rhythmdb-property-model.h"
  54 #include "rb-debug.h"
  55 #include "rb-util.h"
  56 #include "rb-file-helpers.h"
  57 #include "rb-podcast-entry-types.h"
  58 
  59 typedef struct RhythmDBTreeProperty
  60 {
  61 #ifndef G_DISABLE_ASSERT
  62 	guint magic;
  63 #endif
  64 	struct RhythmDBTreeProperty *parent;
  65 	GHashTable *children;
  66 } RhythmDBTreeProperty;
  67 
  68 #define RHYTHMDB_TREE_PROPERTY_FROM_ENTRY(entry) ((RhythmDBTreeProperty *) entry->data)
  69 
  70 G_DEFINE_TYPE(RhythmDBTree, rhythmdb_tree, RHYTHMDB_TYPE)
  71 
  72 static void rhythmdb_tree_finalize (GObject *object);
  73 
  74 static gboolean rhythmdb_tree_load (RhythmDB *rdb, GCancellable *cancel, GError **error);
  75 static void rhythmdb_tree_save (RhythmDB *rdb);
  76 static void rhythmdb_tree_entry_new (RhythmDB *db, RhythmDBEntry *entry);
  77 static void rhythmdb_tree_entry_new_internal (RhythmDB *db, RhythmDBEntry *entry);
  78 static gboolean rhythmdb_tree_entry_set (RhythmDB *db, RhythmDBEntry *entry,
  79 					 guint propid, const GValue *value);
  80 
  81 static void rhythmdb_tree_entry_delete (RhythmDB *db, RhythmDBEntry *entry);
  82 static void rhythmdb_tree_entry_delete_by_type (RhythmDB *adb, RhythmDBEntryType *type);
  83 
  84 static RhythmDBEntry * rhythmdb_tree_entry_lookup_by_location (RhythmDB *db, RBRefString *uri);
  85 static RhythmDBEntry * rhythmdb_tree_entry_lookup_by_id (RhythmDB *db, gint id);
  86 static void rhythmdb_tree_entry_foreach (RhythmDB *adb, GFunc func, gpointer user_data);
  87 static gint64 rhythmdb_tree_entry_count (RhythmDB *adb);
  88 static void rhythmdb_tree_entry_foreach_by_type (RhythmDB *adb, RhythmDBEntryType *type, GFunc func, gpointer user_data);
  89 static gint64 rhythmdb_tree_entry_count_by_type (RhythmDB *adb, RhythmDBEntryType *type);
  90 static gboolean rhythmdb_tree_entry_keyword_add (RhythmDB *adb, RhythmDBEntry *entry, RBRefString *keyword);
  91 static gboolean rhythmdb_tree_entry_keyword_remove (RhythmDB *adb, RhythmDBEntry *entry, RBRefString *keyword);
  92 static gboolean rhythmdb_tree_entry_keyword_has (RhythmDB *adb, RhythmDBEntry *entry, RBRefString *keyword);
  93 static GList* rhythmdb_tree_entry_keywords_get (RhythmDB *adb, RhythmDBEntry *entry);
  94 static void rhythmdb_tree_do_full_query (RhythmDB *db, GPtrArray *query,
  95 					 RhythmDBQueryResults *results,
  96 					 gboolean *cancel);
  97 static gboolean rhythmdb_tree_evaluate_query (RhythmDB *adb, GPtrArray *query,
  98 				       RhythmDBEntry *aentry);
  99 static void rhythmdb_tree_entry_type_registered (RhythmDB *db,
 100 						 RhythmDBEntryType *type);
 101 
 102 typedef void (*RBTreeEntryItFunc)(RhythmDBTree *db,
 103 				  RhythmDBEntry *entry,
 104 				  gpointer data);
 105 
 106 typedef void (*RBTreePropertyItFunc)(RhythmDBTree *db,
 107 				     RhythmDBTreeProperty *property,
 108 				     gpointer data);
 109 static void rhythmdb_hash_tree_foreach (RhythmDB *adb,
 110 					RhythmDBEntryType *type,
 111 					RBTreeEntryItFunc entry_func,
 112 					RBTreePropertyItFunc album_func,
 113 					RBTreePropertyItFunc artist_func,
 114 					RBTreePropertyItFunc genres_func,
 115 					gpointer data);
 116 
 117 /* Update both of those! */
 118 #define RHYTHMDB_TREE_XML_VERSION "1.8"
 119 #define RHYTHMDB_TREE_XML_VERSION_INT 180
 120 
 121 static void destroy_tree_property (RhythmDBTreeProperty *prop);
 122 static RhythmDBTreeProperty *get_or_create_album (RhythmDBTree *db, RhythmDBTreeProperty *artist,
 123 						  RBRefString *name);
 124 static RhythmDBTreeProperty *get_or_create_artist (RhythmDBTree *db, RhythmDBTreeProperty *genre,
 125 						   RBRefString *name);
 126 static RhythmDBTreeProperty *get_or_create_genre (RhythmDBTree *db, RhythmDBEntryType *type,
 127 							 RBRefString *name);
 128 
 129 static void remove_entry_from_album (RhythmDBTree *db, RhythmDBEntry *entry);
 130 static void remove_entry_from_keywords (RhythmDBTree *db, RhythmDBEntry *entry);
 131 
 132 static GList *split_query_by_disjunctions (RhythmDBTree *db, GPtrArray *query);
 133 static gboolean evaluate_conjunctive_subquery (RhythmDBTree *db, GPtrArray *query,
 134 					       guint base, guint max, RhythmDBEntry *entry);
 135 
 136 struct RhythmDBTreePrivate
 137 {
 138 	GHashTable *entries;
 139 	GHashTable *entry_ids;
 140 	GMutex entries_lock;
 141 
 142 	GHashTable *keywords; /* GHashTable<RBRefString, GHashTable<RhyhmDBEntry, 1>> */
 143 	GMutex keywords_lock;
 144 
 145 	GHashTable *genres;
 146 	GMutex genres_lock; /* must be held while using the tree */
 147 
 148 	GHashTable *unknown_entry_types;
 149 	gboolean finalizing;
 150 
 151 	guint idle_load_id;
 152 };
 153 
 154 typedef struct
 155 {
 156 	RBRefString *name;
 157 	RBRefString *value;
 158 } RhythmDBUnknownEntryProperty;
 159 
 160 typedef struct
 161 {
 162 	RBRefString *typename;
 163 	GList *properties;
 164 } RhythmDBUnknownEntry;
 165 
 166 #define RHYTHMDB_TREE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_TREE, RhythmDBTreePrivate))
 167 
 168 enum
 169 {
 170 	PROP_0,
 171 };
 172 
 173 const int RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE = 512;
 174 
 175 GQuark
 176 rhythmdb_tree_error_quark (void)
 177 {
 178 	static GQuark quark;
 179 	if (!quark)
 180 		quark = g_quark_from_static_string ("rhythmdb_tree_error");
 181 
 182 	return quark;
 183 }
 184 
 185 static void
 186 rhythmdb_tree_class_init (RhythmDBTreeClass *klass)
 187 {
 188 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 189 	RhythmDBClass *rhythmdb_class = RHYTHMDB_CLASS (klass);
 190 
 191 	object_class->finalize = rhythmdb_tree_finalize;
 192 
 193 	rhythmdb_class->impl_load = rhythmdb_tree_load;
 194 	rhythmdb_class->impl_save = rhythmdb_tree_save;
 195 	rhythmdb_class->impl_entry_new = rhythmdb_tree_entry_new;
 196 	rhythmdb_class->impl_entry_set = rhythmdb_tree_entry_set;
 197 	rhythmdb_class->impl_entry_delete = rhythmdb_tree_entry_delete;
 198 	rhythmdb_class->impl_entry_delete_by_type = rhythmdb_tree_entry_delete_by_type;
 199 	rhythmdb_class->impl_lookup_by_location = rhythmdb_tree_entry_lookup_by_location;
 200 	rhythmdb_class->impl_lookup_by_id = rhythmdb_tree_entry_lookup_by_id;
 201 	rhythmdb_class->impl_entry_foreach = rhythmdb_tree_entry_foreach;
 202 	rhythmdb_class->impl_entry_count = rhythmdb_tree_entry_count;
 203 	rhythmdb_class->impl_entry_foreach_by_type = rhythmdb_tree_entry_foreach_by_type;
 204 	rhythmdb_class->impl_entry_count_by_type = rhythmdb_tree_entry_count_by_type;
 205 	rhythmdb_class->impl_entry_keyword_add = rhythmdb_tree_entry_keyword_add;
 206 	rhythmdb_class->impl_entry_keyword_remove = rhythmdb_tree_entry_keyword_remove;
 207 	rhythmdb_class->impl_entry_keyword_has = rhythmdb_tree_entry_keyword_has;
 208 	rhythmdb_class->impl_entry_keywords_get = rhythmdb_tree_entry_keywords_get;
 209 	rhythmdb_class->impl_evaluate_query = rhythmdb_tree_evaluate_query;
 210 	rhythmdb_class->impl_do_full_query = rhythmdb_tree_do_full_query;
 211 	rhythmdb_class->impl_entry_type_registered = rhythmdb_tree_entry_type_registered;
 212 
 213 	g_type_class_add_private (klass, sizeof (RhythmDBTreePrivate));
 214 }
 215 
 216 static void
 217 rhythmdb_tree_init (RhythmDBTree *db)
 218 {
 219 	db->priv = RHYTHMDB_TREE_GET_PRIVATE (db);
 220 
 221 	db->priv->entries = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
 222 	db->priv->entry_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
 223 
 224 	db->priv->keywords = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
 225 						    (GDestroyNotify)rb_refstring_unref, (GDestroyNotify)g_hash_table_destroy);
 226 
 227 	db->priv->genres = g_hash_table_new_full (g_direct_hash, g_direct_equal,
 228 						  NULL, (GDestroyNotify)g_hash_table_destroy);
 229 
 230 	db->priv->unknown_entry_types = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
 231 }
 232 
 233 /* must be called with the genres lock held */
 234 static void
 235 unparent_entries (gpointer key,
 236 		  RhythmDBEntry *entry,
 237 		  RhythmDBTree *db)
 238 {
 239 	rb_assert_locked (&db->priv->genres_lock);
 240 
 241 	remove_entry_from_album (db, entry);
 242 }
 243 
 244 static void
 245 free_unknown_entries (RBRefString *name,
 246 		      GList *entries,
 247 		      gpointer nah)
 248 {
 249 	GList *e;
 250 	for (e = entries; e != NULL; e = e->next) {
 251 		RhythmDBUnknownEntry *entry;
 252 		GList *p;
 253 
 254 		entry = (RhythmDBUnknownEntry *)e->data;
 255 		rb_refstring_unref (entry->typename);
 256 		for (p = entry->properties; p != NULL; p = p->next) {
 257 			RhythmDBUnknownEntryProperty *prop;
 258 
 259 			prop = (RhythmDBUnknownEntryProperty *)p->data;
 260 			rb_refstring_unref (prop->name);
 261 			rb_refstring_unref (prop->value);
 262 			g_free (prop);
 263 		}
 264 
 265 		g_list_free (entry->properties);
 266 	}
 267 	g_list_free (entries);
 268 }
 269 
 270 static void
 271 rhythmdb_tree_finalize (GObject *object)
 272 {
 273 	RhythmDBTree *db;
 274 
 275 	g_return_if_fail (object != NULL);
 276 	g_return_if_fail (RHYTHMDB_IS_TREE (object));
 277 
 278 	db = RHYTHMDB_TREE (object);
 279 
 280 	g_return_if_fail (db->priv != NULL);
 281 
 282 	db->priv->finalizing = TRUE;
 283 
 284 	g_mutex_lock (&db->priv->genres_lock);
 285 	g_hash_table_foreach (db->priv->entries, (GHFunc) unparent_entries, db);
 286 	g_mutex_unlock (&db->priv->genres_lock);
 287 
 288 	g_hash_table_destroy (db->priv->entries);
 289 	g_hash_table_destroy (db->priv->entry_ids);
 290 
 291 	g_hash_table_destroy (db->priv->keywords);
 292 
 293 	g_hash_table_destroy (db->priv->genres);
 294 
 295 	g_hash_table_foreach (db->priv->unknown_entry_types,
 296 			      (GHFunc) free_unknown_entries,
 297 			      NULL);
 298 	g_hash_table_destroy (db->priv->unknown_entry_types);
 299 
 300 	G_OBJECT_CLASS (rhythmdb_tree_parent_class)->finalize (object);
 301 }
 302 
 303 struct RhythmDBTreeLoadContext
 304 {
 305 	RhythmDBTree *db;
 306 	xmlParserCtxtPtr xmlctx;
 307 	GCancellable *cancel;
 308 	enum {
 309 		RHYTHMDB_TREE_PARSER_STATE_START,
 310 		RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB,
 311 		RHYTHMDB_TREE_PARSER_STATE_ENTRY,
 312 		RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY,
 313 		RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD,
 314 		RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY,
 315 		RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY,
 316 		RHYTHMDB_TREE_PARSER_STATE_END,
 317 	} state;
 318 	guint in_unknown_elt;
 319 	RhythmDBEntry *entry;
 320 	RhythmDBUnknownEntry *unknown_entry;
 321 	GString *buf;
 322 	RhythmDBPropType propid;
 323 	gint batch_count;
 324 	GError **error;
 325 
 326 	/* updating */
 327 	guint has_date : 1;
 328 	guint canonicalise_uris : 1;
 329 	guint reload_all_metadata : 1;
 330 	guint update_podcasts : 1;
 331 	guint update_local_mountpoints : 1;
 332 };
 333 
 334 /* Returns the version as an int, multiplied by 100,
 335  * eg. "1.4" becomes 140 */
 336 static int
 337 version_to_int (const char *version)
 338 {
 339 	float ver;
 340 	char *eptr;
 341 
 342 	ver = g_ascii_strtod (version, &eptr);
 343 	if (eptr == version) {
 344 		return (int) (1.0 * 100);
 345 	}
 346 	return (int)roundf(ver * 100);
 347 }
 348 
 349 static void
 350 rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext *ctx,
 351 				    const char *name,
 352 				    const char **attrs)
 353 {
 354 	if (g_cancellable_is_cancelled (ctx->cancel)) {
 355 		xmlStopParser (ctx->xmlctx);
 356 		return;
 357 	}
 358 
 359 	if (ctx->in_unknown_elt) {
 360 		ctx->in_unknown_elt++;
 361 		return;
 362 	}
 363 
 364 	switch (ctx->state)
 365 	{
 366 	case RHYTHMDB_TREE_PARSER_STATE_START:
 367 	{
 368 		if (!strcmp (name, "rhythmdb")) {
 369 			ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
 370 			for (; *attrs; attrs +=2) {
 371 				if (!strcmp (*attrs, "version")) {
 372 					const char *version = *(attrs+1);
 373 
 374 					rb_debug ("loading database version %s (%d)", version, version_to_int (version));
 375 					switch (version_to_int (version)) {
 376 					case 100:
 377 					case 110:
 378 						rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries (DB version 1.0 or 1.1)");
 379 						ctx->canonicalise_uris = TRUE;
 380 					case 120:
 381 						rb_debug ("reloading all file metadata to get MusicBrainz tags (DB version 1.2)");
 382 						ctx->reload_all_metadata = TRUE;
 383 					case 130:
 384 					case 140:
 385 						/* Avoid being warned twice for very old DBs */
 386 						if (ctx->canonicalise_uris == FALSE) {
 387 							rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries (DB version %s)", version);
 388 							ctx->canonicalise_uris = TRUE;
 389 						}
 390 					case 150:
 391 						rb_debug ("Upgrade Podcasts remote vs. local locations");
 392 						ctx->update_podcasts = TRUE;
 393 					case 160:
 394 						rb_debug ("reloading all file metadata to get sortnames, album artist, comments, bpm and updating mountpoints");
 395 						ctx->reload_all_metadata = TRUE;
 396 						ctx->update_local_mountpoints = TRUE;
 397 					case 170:
 398 						rb_debug ("reloading all file metadata to get new media types");
 399 						ctx->reload_all_metadata = TRUE;
 400 					case RHYTHMDB_TREE_XML_VERSION_INT:
 401 						/* current version */
 402 						break;
 403 					default:
 404 						if (version_to_int (version) > RHYTHMDB_TREE_XML_VERSION_INT) {
 405 							g_set_error (ctx->error,
 406 								     RHYTHMDB_TREE_ERROR,
 407 								     RHYTHMDB_TREE_ERROR_DATABASE_TOO_NEW,
 408 								     _("The database was created by a later version of Rhythmbox."
 409 								       "  This version of Rhythmbox cannot read the database."));
 410 							xmlStopParser (ctx->xmlctx);
 411 						}
 412 					}
 413 				} else {
 414 					g_assert_not_reached ();
 415 				}
 416 			}
 417 
 418 		} else {
 419 			ctx->in_unknown_elt++;
 420 		}
 421 
 422 		break;
 423 	}
 424 	case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
 425 	{
 426 		if (!strcmp (name, "entry")) {
 427 			RhythmDBEntryType *type = NULL;
 428 			const char *typename = NULL;
 429 			for (; *attrs; attrs +=2) {
 430 				if (!strcmp (*attrs, "type")) {
 431 					typename = *(attrs+1);
 432 					type = rhythmdb_entry_type_get_by_name (RHYTHMDB (ctx->db), typename);
 433 					break;
 434 				}
 435 			}
 436 
 437 			g_assert (typename);
 438 			if (type != NULL) {
 439 				ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
 440 				ctx->entry = rhythmdb_entry_allocate (RHYTHMDB (ctx->db), type);
 441 				ctx->entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
 442 				ctx->has_date = FALSE;
 443 			} else {
 444 				rb_debug ("reading unknown entry");
 445 				ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
 446 				ctx->unknown_entry = g_new0 (RhythmDBUnknownEntry, 1);
 447 				ctx->unknown_entry->typename = rb_refstring_new (typename);
 448 			}
 449 		} else {
 450 			ctx->in_unknown_elt++;
 451 		}
 452 		break;
 453 	}
 454 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
 455 		if (strcmp (name, "keyword") == 0) {
 456 			ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD;
 457 		} else {
 458 			int val = rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx->db), BAD_CAST name);
 459 			if (val < 0) {
 460 				ctx->in_unknown_elt++;
 461 				break;
 462 			}
 463 
 464 			ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY;
 465 			ctx->propid = val;
 466 		}
 467 		g_string_truncate (ctx->buf, 0);
 468 		break;
 469 	case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
 470 	{
 471 		RhythmDBUnknownEntryProperty *prop;
 472 
 473 		prop = g_new0 (RhythmDBUnknownEntryProperty, 1);
 474 		prop->name = rb_refstring_new (name);
 475 
 476 		ctx->unknown_entry->properties = g_list_prepend (ctx->unknown_entry->properties, prop);
 477 		ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY;
 478 		g_string_truncate (ctx->buf, 0);
 479 		break;
 480 	}
 481 	case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
 482 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
 483 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD:
 484 	case RHYTHMDB_TREE_PARSER_STATE_END:
 485 	break;
 486 	}
 487 }
 488 
 489 static void
 490 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext *ctx,
 491 				  const char *name)
 492 {
 493 	if (g_cancellable_is_cancelled (ctx->cancel)) {
 494 		xmlStopParser (ctx->xmlctx);
 495 		return;
 496 	}
 497 
 498 	if (ctx->in_unknown_elt) {
 499 		ctx->in_unknown_elt--;
 500 		return;
 501 	}
 502 
 503 	switch (ctx->state)
 504 	{
 505 	case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
 506 		ctx->state = RHYTHMDB_TREE_PARSER_STATE_END;
 507 		break;
 508 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
 509 	{
 510 		if (!ctx->has_date || ctx->reload_all_metadata) {
 511 			/* there is no date metadata, so this is from an old version
 512 			 * reset the last-modified timestamp, so that the file is re-read
 513 			 */
 514 			rb_debug ("pre-Date entry found, causing re-read");
 515 			ctx->entry->mtime = 0;
 516 		}
 517 		if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
 518 			RhythmDBPodcastFields *podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (ctx->entry, RhythmDBPodcastFields);
 519 			/* Handle upgrades from 0.9.2.
 520 			 * Previously, last-seen for podcast feeds was the time of the last post,
 521 			 * and post-time was unused.  Now, we want last-seen to be the time we
 522 			 * last updated the feed, and post-time to be the time of the last post.
 523 			 */
 524 			if (podcast->post_time == 0) {
 525 				podcast->post_time = ctx->entry->last_seen;
 526 			}
 527 		}
 528 		if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) {
 529 			/* When upgrading Podcasts from 0.11.6 and prior, we need to
 530 			 * swap mountpoint and location if there is a mountpoint */
 531 			if (ctx->update_podcasts && ctx->entry->mountpoint != NULL) {
 532 				RBRefString *tmp;
 533 
 534 				rb_debug ("pre-Podcast avoidance found, swapping location/mountpoint");
 535 
 536 				tmp = ctx->entry->location;
 537 				ctx->entry->location = ctx->entry->mountpoint;
 538 				ctx->entry->mountpoint = tmp;
 539 			}
 540 		}
 541 		if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
 542 			/* Since we now care about mountpoints for all local entries, not just
 543 			 * those on things that actually get mounted and unmounted, we need to
 544 			 * ensure they're all correct.
 545 			 */
 546 			if (ctx->update_local_mountpoints) {
 547 				const char *loc = rb_refstring_get (ctx->entry->location);
 548 				if (loc == NULL || g_str_has_prefix (loc, "file:///")) {
 549 					char *nmp;
 550 					nmp = rb_uri_get_mount_point (loc);
 551 					if (ctx->entry->mountpoint != NULL) {
 552 						rb_refstring_unref (ctx->entry->mountpoint);
 553 						ctx->entry->mountpoint = NULL;
 554 					}
 555 
 556 					if (nmp != NULL) {
 557 						ctx->entry->mountpoint = rb_refstring_new (nmp);
 558 						g_free (nmp);
 559 					}
 560 				}
 561 			}
 562 		}
 563 
 564 		if (ctx->entry->location != NULL && rb_refstring_get (ctx->entry->location)[0] != '\0') {
 565 			RhythmDBEntry *entry;
 566 
 567 			g_mutex_lock (&ctx->db->priv->entries_lock);
 568 			entry = g_hash_table_lookup (ctx->db->priv->entries, ctx->entry->location);
 569 			if (entry == NULL) {
 570 				rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx->db), ctx->entry);
 571 				rhythmdb_entry_insert (RHYTHMDB (ctx->db), ctx->entry);
 572 				if (++ctx->batch_count == RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
 573 					rhythmdb_commit (RHYTHMDB (ctx->db));
 574 					ctx->batch_count = 0;
 575 				}
 576 			} else if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST &&
 577 				   entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
 578 				rb_debug ("found song entry with duplicate location for Podcast post %s. merging metadata",
 579 					  rb_refstring_get (ctx->entry->location));
 580 
 581 				ctx->entry->play_count += entry->play_count;
 582 				if (ctx->entry->last_played < entry->last_played)
 583 					ctx->entry->last_played = entry->last_played;
 584 
 585 				/* Remove the song entry,
 586 				 * deleting requires relinquishing the locks */
 587 				g_mutex_unlock (&ctx->db->priv->entries_lock);
 588 				rhythmdb_entry_delete (RHYTHMDB(ctx->db), entry);
 589 				g_mutex_lock (&ctx->db->priv->entries_lock);
 590 				rhythmdb_commit (RHYTHMDB (ctx->db));
 591 
 592 				/* And add the Podcast entry to the database */
 593 				rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx->db), ctx->entry);
 594 				rhythmdb_entry_insert (RHYTHMDB (ctx->db), ctx->entry);
 595 				if (++ctx->batch_count == RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
 596 					rhythmdb_commit (RHYTHMDB (ctx->db));
 597 					ctx->batch_count = 0;
 598 				}
 599 			} else {
 600 				rb_debug ("found entry with duplicate location %s. merging metadata",
 601 					  rb_refstring_get (ctx->entry->location));
 602 
 603 				entry->play_count += ctx->entry->play_count;
 604 
 605 				if (entry->rating < 0.01)
 606 					entry->rating = ctx->entry->rating;
 607 				else if (ctx->entry->rating > 0.01)
 608 					entry->rating = (entry->rating + ctx->entry->rating) / 2;
 609 
 610 				if (ctx->entry->last_played > entry->last_played)
 611 					entry->last_played = ctx->entry->last_played;
 612 
 613 				if (ctx->entry->first_seen < entry->first_seen)
 614 					entry->first_seen = ctx->entry->first_seen;
 615 
 616 				if (ctx->entry->last_seen > entry->last_seen)
 617 					entry->last_seen = ctx->entry->last_seen;
 618 
 619 				rhythmdb_entry_unref (ctx->entry);
 620 			}
 621 			g_mutex_unlock (&ctx->db->priv->entries_lock);
 622 		} else {
 623 			rb_debug ("found entry without location");
 624 			rhythmdb_entry_unref (ctx->entry);
 625 		}
 626 		ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
 627 		ctx->entry = NULL;
 628 		break;
 629 	}
 630 	case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
 631 	{
 632 		GList *entry_list;
 633 
 634 		rb_debug ("finished reading unknown entry");
 635 		ctx->unknown_entry->properties = g_list_reverse (ctx->unknown_entry->properties);
 636 
 637 		g_mutex_lock (&ctx->db->priv->entries_lock);
 638 		entry_list = g_hash_table_lookup (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename);
 639 		entry_list = g_list_prepend (entry_list, ctx->unknown_entry);
 640 		g_hash_table_insert (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename, entry_list);
 641 		g_mutex_unlock (&ctx->db->priv->entries_lock);
 642 
 643 		ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
 644 		ctx->unknown_entry = NULL;
 645 		break;
 646 	}
 647 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
 648 	{
 649 		GValue value = {0,};
 650 		gboolean set = FALSE;
 651 		gboolean skip = FALSE;
 652 
 653 		/* special case some properties for upgrade handling etc. */
 654 		switch (ctx->propid) {
 655 		case RHYTHMDB_PROP_DATE:
 656 			ctx->has_date = TRUE;
 657 			break;
 658 		case RHYTHMDB_PROP_LOCATION:
 659 			if (ctx->canonicalise_uris) {
 660 				char *canon = rb_canonicalise_uri (ctx->buf->str);
 661 
 662 				g_value_init (&value, G_TYPE_STRING);
 663 				g_value_take_string (&value, canon);
 664 				set = TRUE;
 665 			}
 666 			break;
 667 			/* drop replaygain properties */
 668 		case RHYTHMDB_PROP_TRACK_GAIN:
 669 		case RHYTHMDB_PROP_TRACK_PEAK:
 670 		case RHYTHMDB_PROP_ALBUM_GAIN:
 671 		case RHYTHMDB_PROP_ALBUM_PEAK:
 672 			skip = TRUE;
 673 			break;
 674 		default:
 675 			break;
 676 		}
 677 
 678 		if (!skip) {
 679 			if (!set) {
 680 				rhythmdb_read_encoded_property (RHYTHMDB (ctx->db), ctx->buf->str, ctx->propid, &value);
 681 			}
 682 
 683 			rhythmdb_entry_set_internal (RHYTHMDB (ctx->db), ctx->entry, FALSE, ctx->propid, &value);
 684 			g_value_unset (&value);
 685 		}
 686 
 687 		ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
 688 		break;
 689 	}
 690 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD:
 691 	{
 692 		RBRefString *keyword;
 693 
 694 		keyword = rb_refstring_new (ctx->buf->str);
 695 		rhythmdb_entry_keyword_add (RHYTHMDB(ctx->db), ctx->entry, keyword);
 696 		rb_refstring_unref (keyword);
 697 
 698 		ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
 699 		break;
 700 	}
 701 	case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
 702 	{
 703 		RhythmDBUnknownEntryProperty *prop;
 704 
 705 		g_assert (ctx->unknown_entry->properties);
 706 		prop = ctx->unknown_entry->properties->data;
 707 		g_assert (prop->value == NULL);
 708 		prop->value = rb_refstring_new (ctx->buf->str);
 709 		rb_debug ("unknown entry property: %s = %s", rb_refstring_get (prop->name), rb_refstring_get (prop->value));
 710 
 711 		ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
 712 		break;
 713 	}
 714 	case RHYTHMDB_TREE_PARSER_STATE_START:
 715 	case RHYTHMDB_TREE_PARSER_STATE_END:
 716 	break;
 717 	}
 718 }
 719 
 720 static void
 721 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext *ctx,
 722 				 const char *data,
 723 				 guint len)
 724 {
 725 	if (g_cancellable_is_cancelled (ctx->cancel)) {
 726 		xmlStopParser (ctx->xmlctx);
 727 		return;
 728 	}
 729 
 730 	switch (ctx->state)
 731 	{
 732 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
 733 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD:
 734 	case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
 735 		g_string_append_len (ctx->buf, data, len);
 736 		break;
 737 	case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
 738 	case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
 739 	case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
 740 	case RHYTHMDB_TREE_PARSER_STATE_START:
 741 	case RHYTHMDB_TREE_PARSER_STATE_END:
 742 	break;
 743 	}
 744 }
 745 
 746 static gboolean
 747 rhythmdb_tree_load (RhythmDB *rdb,
 748 		    GCancellable *cancel,
 749 		    GError **error)
 750 {
 751 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
 752 	xmlParserCtxtPtr ctxt;
 753 	xmlSAXHandlerPtr sax_handler;
 754 	struct RhythmDBTreeLoadContext *ctx;
 755 	char *name;
 756 	GError *local_error;
 757 	gboolean ret;
 758 
 759 	local_error = NULL;
 760 
 761 	sax_handler = g_new0 (xmlSAXHandler, 1);
 762 	ctx = g_new0 (struct RhythmDBTreeLoadContext, 1);
 763 
 764 	sax_handler->startElement = (startElementSAXFunc) rhythmdb_tree_parser_start_element;
 765 	sax_handler->endElement = (endElementSAXFunc) rhythmdb_tree_parser_end_element;
 766 	sax_handler->characters = (charactersSAXFunc) rhythmdb_tree_parser_characters;
 767 
 768 	ctx->state = RHYTHMDB_TREE_PARSER_STATE_START;
 769 	ctx->db = db;
 770 	ctx->cancel = cancel;
 771 	ctx->buf = g_string_sized_new (RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE);
 772 	ctx->error = &local_error;
 773 
 774 	g_object_get (G_OBJECT (db), "name", &name, NULL);
 775 
 776 	if (g_file_test (name, G_FILE_TEST_EXISTS)) {
 777 		ctxt = xmlCreateFileParserCtxt (name);
 778 		ctx->xmlctx = ctxt;
 779 		xmlFree (ctxt->sax);
 780 		ctxt->userData = ctx;
 781 		ctxt->sax = sax_handler;
 782 		xmlParseDocument (ctxt);
 783 		ctxt->sax = NULL;
 784 		xmlFreeParserCtxt (ctxt);
 785 
 786 		if (ctx->batch_count)
 787 			rhythmdb_commit (RHYTHMDB (ctx->db));
 788 	}
 789 
 790 	ret = TRUE;
 791 	if (local_error != NULL) {
 792 		g_propagate_error (error, local_error);
 793 		ret = FALSE;
 794 	}
 795 
 796 	g_string_free (ctx->buf, TRUE);
 797 	g_free (name);
 798 	g_free (sax_handler);
 799 	g_free (ctx);
 800 
 801 	return ret;
 802 }
 803 
 804 struct RhythmDBTreeSaveContext
 805 {
 806 	RhythmDBTree *db;
 807 	FILE *handle;
 808 	char *error;
 809 };
 810 
 811 #ifdef HAVE_GNU_FWRITE_UNLOCKED
 812 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
 813 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
 814 #else
 815 #define RHYTHMDB_FWRITE_REAL fwrite
 816 #define RHYTHMDB_FPUTC_REAL fputc
 817 #endif
 818 
 819 #define RHYTHMDB_FWRITE(w,x,len,handle,error) do {			\
 820 	if (error == NULL) {						\
 821 		if (RHYTHMDB_FWRITE_REAL (w,x,len,handle) != len) {	\
 822 			error = g_strdup (g_strerror (errno));		\
 823 		}							\
 824 	}								\
 825 } while (0)
 826 
 827 #define RHYTHMDB_FPUTC(x,handle,error) do {				\
 828 	if (error == NULL) {						\
 829 		if (RHYTHMDB_FPUTC_REAL (x,handle) == EOF) {		\
 830 			error = g_strdup (g_strerror (errno));		\
 831 		}							\
 832 	}								\
 833 } while (0)
 834 
 835 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
 836 
 837 static void
 838 write_elt_name_open (struct RhythmDBTreeSaveContext *ctx,
 839 		     const xmlChar *elt_name)
 840 {
 841 	RHYTHMDB_FWRITE_STATICSTR ("    <", ctx->handle, ctx->error);
 842 	RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
 843 	RHYTHMDB_FPUTC ('>', ctx->handle, ctx->error);
 844 }
 845 
 846 static void
 847 write_elt_name_close (struct RhythmDBTreeSaveContext *ctx,
 848 		      const xmlChar *elt_name)
 849 {
 850 	RHYTHMDB_FWRITE_STATICSTR ("</", ctx->handle, ctx->error);
 851 	RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
 852 	RHYTHMDB_FWRITE_STATICSTR (">\n", ctx->handle, ctx->error);
 853 }
 854 
 855 static void
 856 save_entry_string (struct RhythmDBTreeSaveContext *ctx,
 857 		   const xmlChar *elt_name,
 858 		   const char *str)
 859 {
 860 	xmlChar *encoded;
 861 
 862 	g_return_if_fail (str != NULL);
 863 	write_elt_name_open (ctx, elt_name);
 864 	encoded	= xmlEncodeEntitiesReentrant (NULL, BAD_CAST str);
 865 	RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
 866 	g_free (encoded);
 867 	write_elt_name_close (ctx, elt_name);
 868 }
 869 
 870 static void
 871 save_entry_string_if_set (struct RhythmDBTreeSaveContext *ctx,
 872 			  const xmlChar *elt_name,
 873 			  const char *str)
 874 {
 875 	if (str == NULL || str[0] == '\0')
 876 		return;
 877 	save_entry_string (ctx, elt_name, str);
 878 }
 879 
 880 static void
 881 save_entry_int (struct RhythmDBTreeSaveContext *ctx,
 882 		const xmlChar *elt_name,
 883 		int num)
 884 {
 885 	char buf[92];
 886 	if (num == 0)
 887 		return;
 888 	write_elt_name_open (ctx, elt_name);
 889 	g_snprintf (buf, sizeof (buf), "%d", num);
 890 	RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
 891 	write_elt_name_close (ctx, elt_name);
 892 }
 893 
 894 static void
 895 save_entry_ulong (struct RhythmDBTreeSaveContext *ctx,
 896 		  const xmlChar *elt_name,
 897 		  gulong num,
 898 		  gboolean save_zeroes)
 899 {
 900 	char buf[92];
 901 
 902 	if (num == 0 && !save_zeroes)
 903 		return;
 904 	write_elt_name_open (ctx, elt_name);
 905 	g_snprintf (buf, sizeof (buf), "%lu", num);
 906 	RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
 907 	write_elt_name_close (ctx, elt_name);
 908 }
 909 
 910 static void
 911 save_entry_boolean (struct RhythmDBTreeSaveContext *ctx,
 912 		    const xmlChar *elt_name,
 913 		    gboolean val)
 914 {
 915 	save_entry_ulong (ctx, elt_name, val ? 1 : 0, FALSE);
 916 }
 917 
 918 static void
 919 save_entry_uint64 (struct RhythmDBTreeSaveContext *ctx,
 920 		   const xmlChar *elt_name,
 921 		   guint64 num)
 922 {
 923 	char buf[92];
 924 
 925 	if (num == 0)
 926 		return;
 927 
 928 	write_elt_name_open (ctx, elt_name);
 929 	g_snprintf (buf, sizeof (buf), "%" G_GUINT64_FORMAT, num);
 930 	RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
 931 	write_elt_name_close (ctx, elt_name);
 932 }
 933 
 934 static void
 935 save_entry_double (struct RhythmDBTreeSaveContext *ctx,
 936 		   const xmlChar *elt_name,
 937 		   double num)
 938 {
 939 	char buf[G_ASCII_DTOSTR_BUF_SIZE+1];
 940 
 941 	if (num > -0.001 && num < 0.001)
 942 		return;
 943 
 944 	write_elt_name_open (ctx, elt_name);
 945 	g_ascii_dtostr (buf, sizeof (buf), num);
 946 	RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
 947 	write_elt_name_close (ctx, elt_name);
 948 }
 949 
 950 /* This code is intended to be highly optimized.  This came at a small
 951  * readability cost.  Sorry about that.
 952  */
 953 static void
 954 save_entry (RhythmDBTree *db,
 955 	    RhythmDBEntry *entry,
 956 	    struct RhythmDBTreeSaveContext *ctx)
 957 {
 958 	RhythmDBPropType i;
 959 	RhythmDBPodcastFields *podcast = NULL;
 960 	xmlChar *encoded;
 961 	GList *keywords, *l;
 962 
 963 	if (ctx->error)
 964 		return;
 965 
 966 	if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
 967 	    entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
 968 		podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
 969 
 970 	RHYTHMDB_FWRITE_STATICSTR ("  <entry type=\"", ctx->handle, ctx->error);
 971 	encoded	= xmlEncodeEntitiesReentrant (NULL, BAD_CAST rhythmdb_entry_type_get_name (entry->type));
 972 	RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
 973 	g_free (encoded);
 974 
 975 	RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
 976 
 977 	/* Skip over the first property - the type */
 978 	for (i = 1; i < RHYTHMDB_NUM_PROPERTIES; i++) {
 979 		const xmlChar *elt_name;
 980 
 981 		if (ctx->error)
 982 			return;
 983 
 984 		elt_name = rhythmdb_nice_elt_name_from_propid ((RhythmDB *) ctx->db, i);
 985 
 986 		switch (i) {
 987 		case RHYTHMDB_PROP_TYPE:
 988 			break;
 989 		case RHYTHMDB_PROP_ENTRY_ID:
 990 			break;
 991 		case RHYTHMDB_PROP_TITLE:
 992 			save_entry_string(ctx, elt_name, rb_refstring_get (entry->title));
 993 			break;
 994 		case RHYTHMDB_PROP_ALBUM:
 995 			save_entry_string(ctx, elt_name, rb_refstring_get (entry->album));
 996 			break;
 997 		case RHYTHMDB_PROP_ARTIST:
 998 			save_entry_string(ctx, elt_name, rb_refstring_get (entry->artist));
 999 			break;
1000 		case RHYTHMDB_PROP_ALBUM_ARTIST:
1001 			save_entry_string_if_set(ctx, elt_name, rb_refstring_get (entry->album_artist));
1002 			break;
1003 		case RHYTHMDB_PROP_GENRE:
1004 			save_entry_string(ctx, elt_name, rb_refstring_get (entry->genre));
1005 			break;
1006 		case RHYTHMDB_PROP_COMMENT:
1007 			save_entry_string_if_set(ctx, elt_name, rb_refstring_get (entry->comment));
1008 			break;
1009 		case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
1010 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->musicbrainz_trackid));
1011 			break;
1012 		case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID:
1013 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->musicbrainz_artistid));
1014 			break;
1015 		case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID:
1016 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->musicbrainz_albumid));
1017 			break;
1018 		case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID:
1019 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->musicbrainz_albumartistid));
1020 			break;
1021 		case RHYTHMDB_PROP_ARTIST_SORTNAME:
1022 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->artist_sortname));
1023 			break;
1024 		case RHYTHMDB_PROP_ALBUM_SORTNAME:
1025 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->album_sortname));
1026 			break;
1027 		case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
1028 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->album_artist_sortname));
1029 			break;
1030 		case RHYTHMDB_PROP_TRACK_NUMBER:
1031 			save_entry_ulong (ctx, elt_name, entry->tracknum, FALSE);
1032 			break;
1033 		case RHYTHMDB_PROP_DISC_NUMBER:
1034 			save_entry_ulong (ctx, elt_name, entry->discnum, FALSE);
1035 			break;
1036 		case RHYTHMDB_PROP_DATE:
1037 			if (g_date_valid (&entry->date))
1038 				save_entry_ulong (ctx, elt_name, g_date_get_julian (&entry->date), TRUE);
1039 			else
1040 				save_entry_ulong (ctx, elt_name, 0, TRUE);
1041 			break;
1042 		case RHYTHMDB_PROP_DURATION:
1043 			save_entry_ulong (ctx, elt_name, entry->duration, FALSE);
1044 			break;
1045 		case RHYTHMDB_PROP_BITRATE:
1046 			save_entry_int(ctx, elt_name, entry->bitrate);
1047 			break;
1048 		case RHYTHMDB_PROP_LOCATION:
1049 			save_entry_string(ctx, elt_name, rb_refstring_get (entry->location));
1050 			break;
1051 		case RHYTHMDB_PROP_BPM:
1052 			save_entry_double(ctx, elt_name, entry->bpm);
1053 			break;
1054 		case RHYTHMDB_PROP_MOUNTPOINT:
1055 			save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->mountpoint));
1056 			break;
1057 		case RHYTHMDB_PROP_FILE_SIZE:
1058 			save_entry_uint64(ctx, elt_name, entry->file_size);
1059 			break;
1060 		case RHYTHMDB_PROP_MEDIA_TYPE:
1061 			save_entry_string(ctx, elt_name, rb_refstring_get (entry->media_type));
1062 			break;
1063 		case RHYTHMDB_PROP_MTIME:
1064 			save_entry_ulong (ctx, elt_name, entry->mtime, FALSE);
1065 			break;
1066 		case RHYTHMDB_PROP_FIRST_SEEN:
1067 			save_entry_ulong (ctx, elt_name, entry->first_seen, FALSE);
1068 			break;
1069 		case RHYTHMDB_PROP_LAST_SEEN:
1070 			save_entry_ulong (ctx, elt_name, entry->last_seen, FALSE);
1071 			break;
1072 		case RHYTHMDB_PROP_RATING:
1073 			save_entry_double(ctx, elt_name, entry->rating);
1074 			break;
1075 		case RHYTHMDB_PROP_PLAY_COUNT:
1076 			save_entry_ulong (ctx, elt_name, entry->play_count, FALSE);
1077 			break;
1078 		case RHYTHMDB_PROP_LAST_PLAYED:
1079 			save_entry_ulong (ctx, elt_name, entry->last_played, FALSE);
1080 			break;
1081 		case RHYTHMDB_PROP_HIDDEN:
1082 			{
1083 				gboolean hidden = ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
1084 				save_entry_boolean (ctx, elt_name, hidden);
1085 			}
1086 			break;
1087 		case RHYTHMDB_PROP_STATUS:
1088 			if (podcast)
1089 				save_entry_ulong (ctx, elt_name, podcast->status, FALSE);
1090 			break;
1091 		case RHYTHMDB_PROP_DESCRIPTION:
1092 			if (podcast && podcast->description)
1093 				save_entry_string(ctx, elt_name, rb_refstring_get (podcast->description));
1094 			break;
1095 		case RHYTHMDB_PROP_SUBTITLE:
1096 			if (podcast && podcast->subtitle)
1097 				save_entry_string(ctx, elt_name, rb_refstring_get (podcast->subtitle));
1098 			break;
1099 		case RHYTHMDB_PROP_SUMMARY:
1100 			if (podcast && podcast->summary)
1101 				save_entry_string(ctx, elt_name, rb_refstring_get (podcast->summary));
1102 			break;
1103 		case RHYTHMDB_PROP_LANG:
1104 			if (podcast && podcast->lang)
1105 				save_entry_string(ctx, elt_name, rb_refstring_get (podcast->lang));
1106 			break;
1107 		case RHYTHMDB_PROP_COPYRIGHT:
1108 			if (podcast && podcast->copyright)
1109 				save_entry_string(ctx, elt_name, rb_refstring_get (podcast->copyright));
1110 			break;
1111 		case RHYTHMDB_PROP_IMAGE:
1112 			if (podcast && podcast->image)
1113 				save_entry_string(ctx, elt_name, rb_refstring_get (podcast->image));
1114 			break;
1115 		case RHYTHMDB_PROP_POST_TIME:
1116 			if (podcast)
1117 				save_entry_ulong (ctx, elt_name, podcast->post_time, FALSE);
1118 			break;
1119 		case RHYTHMDB_PROP_KEYWORD:
1120 			keywords = rhythmdb_entry_keywords_get (RHYTHMDB (db), entry);
1121 
1122 			for (l = keywords; l != NULL; l = g_list_next (l)) {
1123 				RBRefString *keyword = (RBRefString*)l->data;
1124 
1125 				RHYTHMDB_FWRITE_STATICSTR ("    <keyword>", ctx->handle, ctx->error);
1126 				encoded	= xmlEncodeEntitiesReentrant (NULL, BAD_CAST rb_refstring_get (keyword));
1127 				RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
1128 				g_free (encoded);
1129 				RHYTHMDB_FWRITE_STATICSTR ("</keyword>\n", ctx->handle, ctx->error);
1130 
1131 				rb_refstring_unref (keyword);
1132 			}
1133 
1134 			g_list_free (keywords);
1135 			break;
1136 		case RHYTHMDB_PROP_TITLE_SORT_KEY:
1137 		case RHYTHMDB_PROP_GENRE_SORT_KEY:
1138 		case RHYTHMDB_PROP_ARTIST_SORT_KEY:
1139 		case RHYTHMDB_PROP_ALBUM_SORT_KEY:
1140 		case RHYTHMDB_PROP_ALBUM_ARTIST_SORT_KEY:
1141 		case RHYTHMDB_PROP_ARTIST_SORTNAME_SORT_KEY:
1142 		case RHYTHMDB_PROP_ALBUM_SORTNAME_SORT_KEY:
1143 		case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME_SORT_KEY:
1144 		case RHYTHMDB_PROP_TITLE_FOLDED:
1145 		case RHYTHMDB_PROP_GENRE_FOLDED:
1146 		case RHYTHMDB_PROP_ARTIST_FOLDED:
1147 		case RHYTHMDB_PROP_ALBUM_FOLDED:
1148 		case RHYTHMDB_PROP_ALBUM_ARTIST_FOLDED:
1149 		case RHYTHMDB_PROP_ARTIST_SORTNAME_FOLDED:
1150 		case RHYTHMDB_PROP_ALBUM_SORTNAME_FOLDED:
1151 		case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME_FOLDED:
1152 		case RHYTHMDB_PROP_LAST_PLAYED_STR:
1153 		case RHYTHMDB_PROP_PLAYBACK_ERROR:
1154 		case RHYTHMDB_PROP_FIRST_SEEN_STR:
1155 		case RHYTHMDB_PROP_LAST_SEEN_STR:
1156 		case RHYTHMDB_PROP_SEARCH_MATCH:
1157 		case RHYTHMDB_PROP_YEAR:
1158 		case RHYTHMDB_NUM_PROPERTIES:
1159 		/* obsolete replaygain properties */
1160 		case RHYTHMDB_PROP_TRACK_GAIN:
1161 		case RHYTHMDB_PROP_TRACK_PEAK:
1162 		case RHYTHMDB_PROP_ALBUM_GAIN:
1163 		case RHYTHMDB_PROP_ALBUM_PEAK:
1164 			break;
1165 		}
1166 	}
1167 
1168 	RHYTHMDB_FWRITE_STATICSTR ("  </entry>\n", ctx->handle, ctx->error);
1169 }
1170 
1171 static void
1172 save_entry_type (const char *name,
1173 		 RhythmDBEntryType *entry_type,
1174 		 struct RhythmDBTreeSaveContext *ctx)
1175 {
1176 	gboolean save_to_disk = FALSE;
1177 	g_object_get (entry_type, "save-to-disk", &save_to_disk, NULL);
1178 	if (save_to_disk == FALSE)
1179 		return;
1180 
1181 	rb_debug ("saving entries of type %s", name);
1182 	rhythmdb_hash_tree_foreach (RHYTHMDB (ctx->db), entry_type,
1183 				    (RBTreeEntryItFunc) save_entry,
1184 				    NULL, NULL, NULL, ctx);
1185 }
1186 
1187 static void
1188 save_unknown_entry_type (RBRefString *typename,
1189 			 GList *entries,
1190 			 struct RhythmDBTreeSaveContext *ctx)
1191 {
1192 	GList *t;
1193 
1194 	for (t = entries; t != NULL; t = t->next) {
1195 		RhythmDBUnknownEntry *entry;
1196 		xmlChar *encoded;
1197 		GList *p;
1198 
1199 		if (ctx->error)
1200 			return;
1201 
1202 		entry = (RhythmDBUnknownEntry *)t->data;
1203 
1204 		RHYTHMDB_FWRITE_STATICSTR ("  <entry type=\"", ctx->handle, ctx->error);
1205 		encoded	= xmlEncodeEntitiesReentrant (NULL, BAD_CAST rb_refstring_get (entry->typename));
1206 		RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
1207 		g_free (encoded);
1208 
1209 		RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
1210 
1211 		for (p = entry->properties; p != NULL; p = p->next) {
1212 			RhythmDBUnknownEntryProperty *prop;
1213 			prop = (RhythmDBUnknownEntryProperty *) p->data;
1214 			save_entry_string(ctx, (const xmlChar *)rb_refstring_get (prop->name), rb_refstring_get (prop->value));
1215 		}
1216 
1217 		RHYTHMDB_FWRITE_STATICSTR ("  </entry>\n", ctx->handle, ctx->error);
1218 	}
1219 }
1220 
1221 static void
1222 rhythmdb_tree_save (RhythmDB *rdb)
1223 {
1224 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1225 	char *name;
1226 	GString *savepath;
1227 	FILE *f;
1228 	struct RhythmDBTreeSaveContext ctx;
1229 
1230 	g_object_get (G_OBJECT (db), "name", &name, NULL);
1231 
1232 	savepath = g_string_new (name);
1233 	g_string_append (savepath, ".tmp");
1234 
1235 	f = fopen (savepath->str, "w");
1236 
1237 	if (!f) {
1238 		g_warning ("Can't save XML: %s", g_strerror (errno));
1239 		goto out;
1240 	}
1241 
1242 	ctx.db = db;
1243 	ctx.handle = f;
1244 	ctx.error = NULL;
1245 	RHYTHMDB_FWRITE_STATICSTR ("<?xml version=\"1.0\" standalone=\"yes\"?>\n"
1246 				   "<rhythmdb version=\"" RHYTHMDB_TREE_XML_VERSION "\">\n",
1247 				   ctx.handle, ctx.error);
1248 
1249 	rhythmdb_entry_type_foreach (rdb, (GHFunc) save_entry_type, &ctx);
1250 	g_mutex_lock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
1251 	g_hash_table_foreach (db->priv->unknown_entry_types,
1252 			      (GHFunc) save_unknown_entry_type,
1253 			      &ctx);
1254 	g_mutex_unlock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
1255 
1256 	RHYTHMDB_FWRITE_STATICSTR ("</rhythmdb>\n", ctx.handle, ctx.error);
1257 
1258 	if (fclose (f) < 0) {
1259 		g_warning ("Couldn't close %s: %s",
1260 			   savepath->str,
1261 			   g_strerror (errno));
1262 		unlink (savepath->str);
1263 		goto out;
1264 	}
1265 
1266 	if (ctx.error != NULL) {
1267 		g_warning ("Writing to the database failed: %s", ctx.error);
1268 		g_free (ctx.error);
1269 		unlink (savepath->str);
1270 	} else {
1271 		if (rename (savepath->str, name) < 0) {
1272 			g_warning ("Couldn't rename %s to %s: %s",
1273 				   name, savepath->str,
1274 				   g_strerror (errno));
1275 			unlink (savepath->str);
1276 		}
1277 	}
1278 
1279 out:
1280 	g_string_free (savepath, TRUE);
1281 	g_free (name);
1282 	return;
1283 }
1284 
1285 #undef RHYTHMDB_FWRITE_ENCODED_STR
1286 #undef RHYTHMDB_FWRITE_STATICSTR
1287 #undef RHYTHMDB_FPUTC
1288 #undef RHYTHMDB_FWRITE
1289 
1290 RhythmDB *
1291 rhythmdb_tree_new (const char *name)
1292 {
1293 	RhythmDBTree *db = g_object_new (RHYTHMDB_TYPE_TREE, "name", name, NULL);
1294 
1295 	g_return_val_if_fail (db->priv != NULL, NULL);
1296 
1297 	return RHYTHMDB (db);
1298 }
1299 
1300 /* must be called with the genres_lock held */
1301 static void
1302 set_entry_album (RhythmDBTree *db,
1303 		 RhythmDBEntry *entry,
1304 		 RhythmDBTreeProperty *artist,
1305 		 RBRefString *name)
1306 {
1307 	struct RhythmDBTreeProperty *prop;
1308 
1309 	prop = get_or_create_album (db, artist, name);
1310 	g_hash_table_insert (prop->children, entry, NULL);
1311 	entry->data = prop;
1312 }
1313 
1314 static void
1315 rhythmdb_tree_entry_new (RhythmDB *rdb,
1316 			 RhythmDBEntry *entry)
1317 {
1318 	g_mutex_lock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
1319 	rhythmdb_tree_entry_new_internal (rdb, entry);
1320 	g_mutex_unlock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
1321 }
1322 
1323 /* must be called with the entry lock held */
1324 static void
1325 rhythmdb_tree_entry_new_internal (RhythmDB *rdb, RhythmDBEntry *entry)
1326 {
1327 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1328 	RhythmDBTreeProperty *artist;
1329 	RhythmDBTreeProperty *genre;
1330 
1331 	rb_assert_locked (&db->priv->entries_lock);
1332 	g_assert (entry != NULL);
1333 
1334 	g_return_if_fail (entry->location != NULL);
1335 
1336 	if (entry->title == NULL) {
1337 		g_warning ("Entry %s has missing title", rb_refstring_get (entry->location));
1338 		entry->title = rb_refstring_new (_("Unknown"));
1339 	}
1340 	if (entry->artist == NULL) {
1341 		g_warning ("Entry %s has missing artist", rb_refstring_get (entry->location));
1342 		entry->artist = rb_refstring_new (_("Unknown"));
1343 	}
1344 	if (entry->album == NULL) {
1345 		g_warning ("Entry %s has missing album", rb_refstring_get (entry->location));
1346 		entry->album = rb_refstring_new (_("Unknown"));
1347 	}
1348 	if (entry->genre == NULL) {
1349 		g_warning ("Entry %s has missing genre", rb_refstring_get (entry->location));
1350 		entry->genre = rb_refstring_new (_("Unknown"));
1351 	}
1352 	if (entry->media_type == NULL) {
1353 		g_warning ("Entry %s has missing media type", rb_refstring_get (entry->location));
1354 		entry->media_type = rb_refstring_new ("unknown/unknown");
1355 	}
1356 
1357 	/* Initialize the tree structure. */
1358 	g_mutex_lock (&db->priv->genres_lock);
1359 	genre = get_or_create_genre (db, entry->type, entry->genre);
1360 	artist = get_or_create_artist (db, genre, entry->artist);
1361 	set_entry_album (db, entry, artist, entry->album);
1362 	g_mutex_unlock (&db->priv->genres_lock);
1363 
1364 	/* this accounts for the initial reference on the entry */
1365 	g_hash_table_insert (db->priv->entries, entry->location, entry);
1366 	g_hash_table_insert (db->priv->entry_ids, GINT_TO_POINTER (entry->id), entry);
1367 
1368 	entry->flags &= ~RHYTHMDB_ENTRY_TREE_LOADING;
1369 }
1370 
1371 static RhythmDBTreeProperty *
1372 rhythmdb_tree_property_new (RhythmDBTree *db)
1373 {
1374 	RhythmDBTreeProperty *ret = g_new0 (RhythmDBTreeProperty, 1);
1375 #ifndef G_DISABLE_ASSERT
1376 	ret->magic = 0xf00dbeef;
1377 #endif
1378 	return ret;
1379 }
1380 
1381 static GHashTable *
1382 get_genres_hash_for_type (RhythmDBTree *db,
1383 			  RhythmDBEntryType *type)
1384 {
1385 	GHashTable *table;
1386 
1387 	table = g_hash_table_lookup (db->priv->genres, type);
1388 	if (table == NULL) {
1389 		table = g_hash_table_new_full (rb_refstring_hash,
1390 					       rb_refstring_equal,
1391 					       (GDestroyNotify) rb_refstring_unref,
1392 					       NULL);
1393 		if (table == NULL) {
1394 			g_warning ("Out of memory\n");
1395 			return NULL;
1396 		}
1397 		g_hash_table_insert (db->priv->genres,
1398 				     type,
1399 				     table);
1400 	}
1401 	return table;
1402 }
1403 
1404 typedef void (*RBHFunc)(RhythmDBTree *db, GHashTable *genres, gpointer data);
1405 
1406 typedef struct {
1407 	RhythmDBTree *db;
1408 	RBHFunc func;
1409 	gpointer data;
1410 } GenresIterCtxt;
1411 
1412 static void
1413 genres_process_one (gpointer key,
1414 		    gpointer value,
1415 		    gpointer user_data)
1416 {
1417 	GenresIterCtxt *ctxt = (GenresIterCtxt *)user_data;
1418 	ctxt->func (ctxt->db, (GHashTable *)value, ctxt->data);
1419 }
1420 
1421 static void
1422 genres_hash_foreach (RhythmDBTree *db, RBHFunc func, gpointer data)
1423 {
1424 	GenresIterCtxt ctxt;
1425 
1426 	ctxt.db = db;
1427 	ctxt.func = func;
1428 	ctxt.data = data;
1429 	g_hash_table_foreach (db->priv->genres, genres_process_one, &ctxt);
1430 }
1431 
1432 /* must be called with the genres lock held */
1433 static RhythmDBTreeProperty *
1434 get_or_create_genre (RhythmDBTree *db,
1435 		     RhythmDBEntryType *type,
1436 		     RBRefString *name)
1437 {
1438 	RhythmDBTreeProperty *genre;
1439 	GHashTable *table;
1440 
1441 	rb_assert_locked (&db->priv->genres_lock);
1442 
1443 	table = get_genres_hash_for_type (db, type);
1444 	genre = g_hash_table_lookup (table, name);
1445 
1446 	if (G_UNLIKELY (genre == NULL)) {
1447 		genre = rhythmdb_tree_property_new (db);
1448 		genre->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1449 							 (GDestroyNotify) rb_refstring_unref,
1450 							 NULL);
1451 		rb_refstring_ref (name);
1452 		g_hash_table_insert (table, name, genre);
1453 		genre->parent = NULL;
1454 	}
1455 
1456 	return genre;
1457 }
1458 
1459 /* must be called with the genres lock held */
1460 static RhythmDBTreeProperty *
1461 get_or_create_artist (RhythmDBTree *db,
1462 		      RhythmDBTreeProperty *genre,
1463 		      RBRefString *name)
1464 {
1465 	RhythmDBTreeProperty *artist;
1466 
1467 	rb_assert_locked (&db->priv->genres_lock);
1468 
1469 	artist = g_hash_table_lookup (genre->children, name);
1470 
1471 	if (G_UNLIKELY (artist == NULL)) {
1472 		artist = rhythmdb_tree_property_new (db);
1473 		artist->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1474 							  (GDestroyNotify) rb_refstring_unref,
1475 							  NULL);
1476 		rb_refstring_ref (name);
1477 		g_hash_table_insert (genre->children, name, artist);
1478 		artist->parent = genre;
1479 	}
1480 
1481 	return artist;
1482 }
1483 
1484 /* must be called with the genres lock held */
1485 static RhythmDBTreeProperty *
1486 get_or_create_album (RhythmDBTree *db,
1487 		     RhythmDBTreeProperty *artist,
1488 		     RBRefString *name)
1489 {
1490 	RhythmDBTreeProperty *album;
1491 
1492 	rb_assert_locked (&db->priv->genres_lock);
1493 
1494 	album = g_hash_table_lookup (artist->children, name);
1495 
1496 	if (G_UNLIKELY (album == NULL)) {
1497 		album = rhythmdb_tree_property_new (db);
1498 		album->children = g_hash_table_new (g_direct_hash, g_direct_equal);
1499 		rb_refstring_ref (name);
1500 		g_hash_table_insert (artist->children, name, album);
1501 		album->parent = artist;
1502 	}
1503 
1504 	return album;
1505 }
1506 
1507 static gboolean
1508 remove_child (RhythmDBTreeProperty *parent,
1509 	      gconstpointer data)
1510 {
1511 	g_assert (g_hash_table_remove (parent->children, data));
1512 	if (g_hash_table_size (parent->children) <= 0) {
1513 		return TRUE;
1514 	}
1515 	return FALSE;
1516 }
1517 
1518 /* must be called with the genres lock held */
1519 static void
1520 remove_entry_from_album (RhythmDBTree *db,
1521 			 RhythmDBEntry *entry)
1522 {
1523 	GHashTable *table;
1524 
1525 	rb_assert_locked (&db->priv->genres_lock);
1526 
1527 	rb_refstring_ref (entry->genre);
1528 	rb_refstring_ref (entry->artist);
1529 	rb_refstring_ref (entry->album);
1530 
1531 	table = get_genres_hash_for_type (db, entry->type);
1532 	if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry), entry)) {
1533 		if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent,
1534 				  entry->album)) {
1535 
1536 			if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent,
1537 					  entry->artist)) {
1538 				destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent);
1539 				g_assert (g_hash_table_remove (table, entry->genre));
1540 			}
1541 			destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent);
1542 		}
1543 
1544 		destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry));
1545 	}
1546 
1547 	rb_refstring_unref (entry->genre);
1548 	rb_refstring_unref (entry->artist);
1549 	rb_refstring_unref (entry->album);
1550 }
1551 
1552 static gboolean
1553 rhythmdb_tree_entry_set (RhythmDB *adb,
1554 			 RhythmDBEntry *entry,
1555 			 guint propid,
1556 			 const GValue *value)
1557 {
1558 	RhythmDBTree *db = RHYTHMDB_TREE (adb);
1559 	RhythmDBEntryType *type;
1560 
1561 	type = entry->type;
1562 
1563 	/* don't process changes to entries we're loading, we'll get them
1564 	 * when the entry is complete.  don't process changes for entries that
1565 	 * have been removed either.
1566 	 */
1567 	if (entry->flags & (RHYTHMDB_ENTRY_TREE_LOADING | RHYTHMDB_ENTRY_TREE_REMOVED))
1568 		return FALSE;
1569 
1570 	/* Handle special properties */
1571 	switch (propid)
1572 	{
1573 	case RHYTHMDB_PROP_TYPE:
1574 	{
1575 		RhythmDBTreeProperty *artist;
1576 		RhythmDBTreeProperty *genre;
1577 
1578 		g_mutex_lock (&db->priv->genres_lock);
1579 		remove_entry_from_album (db, entry);
1580 
1581 		entry->type = g_value_get_object (value);
1582 
1583 		genre = get_or_create_genre (db, entry->type, entry->genre);
1584 		artist = get_or_create_artist (db, genre, entry->artist);
1585 		set_entry_album (db, entry, artist, entry->album);
1586 		g_mutex_unlock (&db->priv->genres_lock);
1587 
1588 		return TRUE;
1589 	}
1590 	case RHYTHMDB_PROP_LOCATION:
1591 	{
1592 		RBRefString *s;
1593 		/* We have to use the string in the entry itself as the hash key,
1594 		 * otherwise either we leak it, or the string vanishes when the
1595 		 * GValue is freed; this means we have to do the entry modification
1596 		 * here, rather than letting rhythmdb_entry_set_internal do it.
1597 		 */
1598 		g_mutex_lock (&db->priv->entries_lock);
1599 		g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1600 
1601 		s = rb_refstring_new (g_value_get_string (value));
1602 		rb_refstring_unref (entry->location);
1603 		entry->location = s;
1604 		g_hash_table_insert (db->priv->entries, entry->location, entry);
1605 		g_mutex_unlock (&db->priv->entries_lock);
1606 
1607 		return TRUE;
1608 	}
1609 	case RHYTHMDB_PROP_ALBUM:
1610 	{
1611 		const char *albumname = g_value_get_string (value);
1612 
1613 		if (strcmp (rb_refstring_get (entry->album), albumname)) {
1614 			RhythmDBTreeProperty *artist;
1615 			RhythmDBTreeProperty *genre;
1616 
1617 			rb_refstring_ref (entry->genre);
1618 			rb_refstring_ref (entry->artist);
1619 			rb_refstring_ref (entry->album);
1620 
1621 			g_mutex_lock (&db->priv->genres_lock);
1622 			remove_entry_from_album (db, entry);
1623 			genre = get_or_create_genre (db, type, entry->genre);
1624 			artist = get_or_create_artist (db, genre, entry->artist);
1625 			set_entry_album (db, entry, artist, rb_refstring_new (albumname));
1626 			g_mutex_unlock (&db->priv->genres_lock);
1627 
1628 			rb_refstring_unref (entry->genre);
1629 			rb_refstring_unref (entry->artist);
1630 			rb_refstring_unref (entry->album);
1631 		}
1632 		break;
1633 	}
1634 	case RHYTHMDB_PROP_ARTIST:
1635 	{
1636 		const char *artistname = g_value_get_string (value);
1637 
1638 		if (strcmp (rb_refstring_get (entry->artist), artistname)) {
1639 			RhythmDBTreeProperty *new_artist;
1640 			RhythmDBTreeProperty *genre;
1641 
1642 			rb_refstring_ref (entry->genre);
1643 			rb_refstring_ref (entry->artist);
1644 			rb_refstring_ref (entry->album);
1645 
1646 			g_mutex_lock (&db->priv->genres_lock);
1647 			remove_entry_from_album (db, entry);
1648 			genre = get_or_create_genre (db, type, entry->genre);
1649 			new_artist = get_or_create_artist (db, genre,
1650 							   rb_refstring_new (artistname));
1651 			set_entry_album (db, entry, new_artist, entry->album);
1652 			g_mutex_unlock (&db->priv->genres_lock);
1653 
1654 			rb_refstring_unref (entry->genre);
1655 			rb_refstring_unref (entry->artist);
1656 			rb_refstring_unref (entry->album);
1657 		}
1658 		break;
1659 	}
1660 	case RHYTHMDB_PROP_GENRE:
1661 	{
1662 		const char *genrename = g_value_get_string (value);
1663 
1664 		if (strcmp (rb_refstring_get (entry->genre), genrename)) {
1665 			RhythmDBTreeProperty *new_genre;
1666 			RhythmDBTreeProperty *new_artist;
1667 
1668 			rb_refstring_ref (entry->genre);
1669 			rb_refstring_ref (entry->artist);
1670 			rb_refstring_ref (entry->album);
1671 
1672 			g_mutex_lock (&db->priv->genres_lock);
1673 			remove_entry_from_album (db, entry);
1674 			new_genre = get_or_create_genre (db, type,
1675 							 rb_refstring_new (genrename));
1676 			new_artist = get_or_create_artist (db, new_genre, entry->artist);
1677 			set_entry_album (db, entry, new_artist, entry->album);
1678 			g_mutex_unlock (&db->priv->genres_lock);
1679 
1680 			rb_refstring_unref (entry->genre);
1681 			rb_refstring_unref (entry->artist);
1682 			rb_refstring_unref (entry->album);
1683 		}
1684 		break;
1685 	}
1686 	default:
1687 		break;
1688 	}
1689 
1690 	return FALSE;
1691 }
1692 
1693 static void
1694 rhythmdb_tree_entry_delete (RhythmDB *adb,
1695 			    RhythmDBEntry *entry)
1696 {
1697 	RhythmDBTree *db = RHYTHMDB_TREE (adb);
1698 
1699 	g_mutex_lock (&db->priv->genres_lock);
1700 	remove_entry_from_album (db, entry);
1701 	g_mutex_unlock (&db->priv->genres_lock);
1702 
1703 	/* remove all keywords */
1704 	g_mutex_lock (&db->priv->keywords_lock);
1705 	remove_entry_from_keywords (db, entry);
1706 	g_mutex_unlock (&db->priv->keywords_lock);
1707 
1708 	g_mutex_lock (&db->priv->entries_lock);
1709 	g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1710 	g_assert (g_hash_table_remove (db->priv->entry_ids, GINT_TO_POINTER (entry->id)));
1711 
1712 	entry->flags |= RHYTHMDB_ENTRY_TREE_REMOVED;
1713 	rhythmdb_entry_unref (entry);
1714 	g_mutex_unlock (&db->priv->entries_lock);
1715 }
1716 
1717 typedef struct {
1718 	RhythmDB *db;
1719 	RhythmDBEntryType *type;
1720 } RbEntryRemovalCtxt;
1721 
1722 /* must be called with the entries and genres locks held */
1723 static gboolean
1724 remove_one_song (gpointer key,
1725 		 RhythmDBEntry *entry,
1726 		 RbEntryRemovalCtxt *ctxt)
1727 {
1728 	RhythmDBTree *db = RHYTHMDB_TREE(ctxt->db);
1729 
1730 	rb_assert_locked (&db->priv->entries_lock);
1731 	rb_assert_locked (&db->priv->genres_lock);
1732 
1733 	g_return_val_if_fail (entry != NULL, FALSE);
1734 
1735 	if (entry->type == ctxt->type) {
1736 		rhythmdb_emit_entry_deleted (ctxt->db, entry);
1737 		g_mutex_lock (&db->priv->keywords_lock);
1738 		remove_entry_from_keywords (db, entry);
1739 		g_mutex_unlock (&db->priv->keywords_lock);
1740 		remove_entry_from_album (db, entry);
1741 		g_hash_table_remove (db->priv->entry_ids, GINT_TO_POINTER (entry->id));
1742 		entry->flags |= RHYTHMDB_ENTRY_TREE_REMOVED;
1743 		rhythmdb_entry_unref (entry);
1744 		return TRUE;
1745 	}
1746 	return FALSE;
1747 }
1748 
1749 static void
1750 rhythmdb_tree_entry_delete_by_type (RhythmDB *adb,
1751 				    RhythmDBEntryType *type)
1752 {
1753 	RhythmDBTree *db = RHYTHMDB_TREE (adb);
1754 	RbEntryRemovalCtxt ctxt;
1755 
1756 	ctxt.db = adb;
1757 	ctxt.type = type;
1758 	g_mutex_lock (&db->priv->entries_lock);
1759 	g_mutex_lock (&db->priv->genres_lock);
1760 	g_hash_table_foreach_remove (db->priv->entries,
1761 				     (GHRFunc) remove_one_song, &ctxt);
1762 	g_mutex_unlock (&db->priv->genres_lock);
1763 	g_mutex_unlock (&db->priv->entries_lock);
1764 }
1765 
1766 static void
1767 destroy_tree_property (RhythmDBTreeProperty *prop)
1768 {
1769 #ifndef G_DISABLE_ASSERT
1770 	prop->magic = 0xf33df33d;
1771 #endif
1772 	g_hash_table_destroy (prop->children);
1773 	g_free (prop);
1774 }
1775 
1776 typedef void (*RhythmDBTreeTraversalFunc) (RhythmDBTree *db, RhythmDBEntry *entry, gpointer data);
1777 typedef void (*RhythmDBTreeAlbumTraversalFunc) (RhythmDBTree *db, RhythmDBTreeProperty *album, gpointer data);
1778 
1779 struct RhythmDBTreeTraversalData
1780 {
1781 	RhythmDBTree *db;
1782 	GPtrArray *query;
1783 	RhythmDBTreeTraversalFunc func;
1784 	gpointer data;
1785 	gboolean *cancel;
1786 };
1787 
1788 static gboolean
1789 rhythmdb_tree_evaluate_query (RhythmDB *adb,
1790 			      GPtrArray *query,
1791 			      RhythmDBEntry *entry)
1792 {
1793 	RhythmDBTree *db = RHYTHMDB_TREE (adb);
1794 	guint i;
1795 	guint last_disjunction;
1796 
1797 	for (i = 0, last_disjunction = 0; i < query->len; i++) {
1798 		RhythmDBQueryData *data = g_ptr_array_index (query, i);
1799 
1800 		if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1801 			if (evaluate_conjunctive_subquery (db, query, last_disjunction, i, entry))
1802 				return TRUE;
1803 
1804 			last_disjunction = i + 1;
1805 		}
1806 	}
1807 	if (evaluate_conjunctive_subquery (db, query, last_disjunction, query->len, entry))
1808 		return TRUE;
1809 	return FALSE;
1810 }
1811 
1812 #define RHYTHMDB_PROPERTY_COMPARE(OP) \
1813 			switch (rhythmdb_get_property_type (db, data->propid)) { \
1814 			case G_TYPE_STRING: \
1815 				if (g_strcmp0 (rhythmdb_entry_get_string (entry, data->propid), \
1816 					    g_value_get_string (data->val)) OP 0) \
1817 					return FALSE; \
1818 				break; \
1819 			case G_TYPE_ULONG: \
1820 				if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1821 				    g_value_get_ulong (data->val)) \
1822 					return FALSE; \
1823 				break; \
1824 			case G_TYPE_BOOLEAN: \
1825 				if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1826 				    g_value_get_boolean (data->val)) \
1827 					return FALSE; \
1828 				break; \
1829 			case G_TYPE_UINT64: \
1830 				if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1831 				    g_value_get_uint64 (data->val)) \
1832 					return FALSE; \
1833 				break; \
1834 			case G_TYPE_DOUBLE: \
1835 				if (rhythmdb_entry_get_double (entry, data->propid) OP \
1836 				    g_value_get_double (data->val)) \
1837 					return FALSE; \
1838 				break; \
1839                         case G_TYPE_OBJECT: \
1840                                 if ((gpointer)rhythmdb_entry_get_object (entry, data->propid) OP \
1841                                     g_value_get_object (data->val)) \
1842                                         return FALSE; \
1843                                 break; \
1844 			default: \
1845                                 g_warning ("Unexpected type: %s", g_type_name (rhythmdb_get_property_type (db, data->propid))); \
1846 				g_assert_not_reached (); \
1847 			}
1848 
1849 static gboolean
1850 search_match_properties (RhythmDB *db,
1851 			 RhythmDBEntry *entry,
1852 			 gchar **words)
1853 {
1854 	const RhythmDBPropType props[] = {
1855 		RHYTHMDB_PROP_TITLE_FOLDED,
1856 		RHYTHMDB_PROP_ALBUM_FOLDED,
1857 		RHYTHMDB_PROP_ARTIST_FOLDED,
1858 		RHYTHMDB_PROP_GENRE_FOLDED
1859 	};
1860 	gboolean islike = TRUE;
1861 	gchar **current;
1862 	int i;
1863 
1864 	for (current = words; *current != NULL; current++) {
1865 		gboolean word_found = FALSE;
1866 
1867 		for (i = 0; i < G_N_ELEMENTS (props); i++) {
1868 			const char *entry_string = rhythmdb_entry_get_string (entry, props[i]);
1869 			if (entry_string && (strstr (entry_string, *current) != NULL)) {
1870 				/* the word was found, go to the next one */
1871 				word_found = TRUE;
1872 				break;
1873 			}
1874 		}
1875 		if (!word_found) {
1876 			/* the word wasn't in any of the properties*/
1877 			islike = FALSE;
1878 			break;
1879 		}
1880 	}
1881 
1882 	return islike;
1883 }
1884 
1885 static gboolean
1886 evaluate_conjunctive_subquery (RhythmDBTree *dbtree,
1887 			       GPtrArray *query,
1888 			       guint base,
1889 			       guint max,
1890 			       RhythmDBEntry *entry)
1891 
1892 {
1893 	RhythmDB *db = (RhythmDB *) dbtree;
1894 	guint i;
1895 /* Optimization possibility - we may get here without actually having
1896  * anything in the query.  It would be faster to instead just merge
1897  * the child hash table into the query result hash.
1898  */
1899 	for (i = base; i < max; i++) {
1900 		RhythmDBQueryData *data = g_ptr_array_index (query, i);
1901 
1902 		switch (data->type) {
1903 		case RHYTHMDB_QUERY_SUBQUERY:
1904 		{
1905 			gboolean matched = FALSE;
1906 			GList *conjunctions = split_query_by_disjunctions (dbtree, data->subquery);
1907 			GList *tem;
1908 
1909 			if (conjunctions == NULL)
1910 				matched = TRUE;
1911 
1912 			for (tem = conjunctions; tem; tem = tem->next) {
1913 				GPtrArray *subquery = tem->data;
1914 				if (!matched && evaluate_conjunctive_subquery (dbtree, subquery,
1915 									       0, subquery->len,
1916 									       entry)) {
1917 					matched = TRUE;
1918 				}
1919 				g_ptr_array_free (tem->data, TRUE);
1920 			}
1921 			g_list_free (conjunctions);
1922 			if (!matched)
1923 				return FALSE;
1924 		}
1925 		break;
1926 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
1927 		case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
1928 		{
1929 			gulong relative_time;
1930 			GTimeVal current_time;
1931 
1932 			g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_ULONG);
1933 
1934 			relative_time = g_value_get_ulong (data->val);
1935 			g_get_current_time  (&current_time);
1936 
1937 			if (data->type == RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN) {
1938 				if (!(rhythmdb_entry_get_ulong (entry, data->propid) >= (current_time.tv_sec - relative_time)))
1939 					return FALSE;
1940 			} else {
1941 				if (!(rhythmdb_entry_get_ulong (entry, data->propid) < (current_time.tv_sec - relative_time)))
1942 					return FALSE;
1943 			}
1944 			break;
1945 		}
1946 		case RHYTHMDB_QUERY_PROP_PREFIX:
1947 		case RHYTHMDB_QUERY_PROP_SUFFIX:
1948 		{
1949 			const char *value_s;
1950 			const char *entry_s;
1951 
1952 			g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING);
1953 
1954 			value_s = g_value_get_string (data->val);
1955 			entry_s = rhythmdb_entry_get_string (entry, data->propid);
1956 
1957 			if (data->type == RHYTHMDB_QUERY_PROP_PREFIX && !g_str_has_prefix (entry_s, value_s))
1958 				return FALSE;
1959 			if (data->type == RHYTHMDB_QUERY_PROP_SUFFIX && !g_str_has_suffix (entry_s, value_s))
1960 				return FALSE;
1961 
1962 			break;
1963 		}
1964 		case RHYTHMDB_QUERY_PROP_LIKE:
1965 		case RHYTHMDB_QUERY_PROP_NOT_LIKE:
1966 		{
1967 			if (data->propid == RHYTHMDB_PROP_KEYWORD) {
1968 				const char *str;
1969 				RBRefString *keyword;
1970 				gboolean has;
1971 
1972 				str = g_value_get_string (data->val);
1973 				keyword = rb_refstring_find (str);
1974 				if (keyword != NULL) {
1975 					has = rhythmdb_tree_entry_keyword_has (db, entry, keyword);
1976 				} else {
1977 					has = FALSE;
1978 				}
1979 				if ((data->type == RHYTHMDB_QUERY_PROP_LIKE) ^ has)
1980 					return FALSE;
1981 				else
1982 					continue;
1983 				break;
1984 			} else if (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING) {
1985 				gboolean islike;
1986 
1987 				if (data->propid == RHYTHMDB_PROP_SEARCH_MATCH) {
1988 					/* this is a special property, that should match several things */
1989 					islike = search_match_properties (db, entry, g_value_get_boxed (data->val));
1990 
1991 				} else {
1992 					const gchar *value_string = g_value_get_string (data->val);
1993 					const char *entry_string = rhythmdb_entry_get_string (entry, data->propid);
1994 
1995 					/* check in case the property is NULL, the value should never be NULL */
1996 					if (entry_string == NULL)
1997 						return FALSE;
1998 
1999 					islike = (strstr (entry_string, value_string) != NULL);
2000 				}
2001 
2002 				if ((data->type == RHYTHMDB_QUERY_PROP_LIKE) ^ islike)
2003 					return FALSE;
2004 				else
2005 					continue;
2006 				break;
2007 			}
2008 			/* Fall through */
2009 		}
2010 		case RHYTHMDB_QUERY_PROP_EQUALS:
2011 			RHYTHMDB_PROPERTY_COMPARE (!=)
2012 			break;
2013 		case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
2014 			RHYTHMDB_PROPERTY_COMPARE (==)
2015 			break;
2016 		case RHYTHMDB_QUERY_PROP_GREATER:
2017 			RHYTHMDB_PROPERTY_COMPARE (<)
2018 			break;
2019 		case RHYTHMDB_QUERY_PROP_LESS:
2020 			RHYTHMDB_PROPERTY_COMPARE (>)
2021 			break;
2022 		case RHYTHMDB_QUERY_END:
2023 		case RHYTHMDB_QUERY_DISJUNCTION:
2024 		case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
2025 		case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
2026 		case RHYTHMDB_QUERY_PROP_YEAR_LESS:
2027 		case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
2028 			g_assert_not_reached ();
2029 			break;
2030 		}
2031 	}
2032 	return TRUE;
2033 }
2034 
2035 static void
2036 do_conjunction (RhythmDBEntry *entry,
2037 		gpointer unused,
2038 		struct RhythmDBTreeTraversalData *data)
2039 {
2040 	if (G_UNLIKELY (*data->cancel))
2041 		return;
2042 	/* Finally, we actually evaluate the query! */
2043 	if (evaluate_conjunctive_subquery (data->db, data->query, 0, data->query->len,
2044 					   entry)) {
2045 		data->func (data->db, entry, data->data);
2046 	}
2047 }
2048 
2049 static void
2050 conjunctive_query_songs (const char *name,
2051 			 RhythmDBTreeProperty *album,
2052 			 struct RhythmDBTreeTraversalData *data)
2053 {
2054 	if (G_UNLIKELY (*data->cancel))
2055 		return;
2056 	g_hash_table_foreach (album->children, (GHFunc) do_conjunction, data);
2057 }
2058 
2059 static GPtrArray *
2060 clone_remove_ptr_array_index (GPtrArray *arr,
2061 			      guint index)
2062 {
2063 	GPtrArray *ret = g_ptr_array_new ();
2064 	guint i;
2065 	for (i = 0; i < arr->len; i++)
2066 		if (i != index)
2067 			g_ptr_array_add (ret, g_ptr_array_index (arr, i));
2068 
2069 	return ret;
2070 }
2071 
2072 static void
2073 conjunctive_query_albums (const char *name,
2074 			  RhythmDBTreeProperty *artist,
2075 			  struct RhythmDBTreeTraversalData *data)
2076 {
2077 	guint i;
2078 	int album_query_idx = -1;
2079 
2080 	if (G_UNLIKELY (*data->cancel))
2081 		return;
2082 
2083 	for (i = 0; i < data->query->len; i++) {
2084 		RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
2085 		if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
2086 		    && qdata->propid == RHYTHMDB_PROP_ALBUM) {
2087 			if (album_query_idx > 0)
2088 				return;
2089 			album_query_idx = i;
2090 
2091 		}
2092 	}
2093 
2094 	if (album_query_idx >= 0) {
2095 		RhythmDBTreeProperty *album;
2096 		RhythmDBQueryData *qdata = g_ptr_array_index (data->query, album_query_idx);
2097 		RBRefString *albumname = rb_refstring_new (g_value_get_string (qdata->val));
2098 		GPtrArray *oldquery = data->query;
2099 
2100 		data->query = clone_remove_ptr_array_index (data->query, album_query_idx);
2101 
2102 		album = g_hash_table_lookup (artist->children, albumname);
2103 
2104 		if (album != NULL) {
2105 			conjunctive_query_songs (rb_refstring_get (albumname), album, data);
2106 		}
2107 		g_ptr_array_free (data->query, TRUE);
2108 		data->query = oldquery;
2109 		return;
2110 	}
2111 
2112 	g_hash_table_foreach (artist->children, (GHFunc) conjunctive_query_songs, data);
2113 }
2114 
2115 static void
2116 conjunctive_query_artists (const char *name,
2117 			   RhythmDBTreeProperty *genre,
2118 			   struct RhythmDBTreeTraversalData *data)
2119 {
2120 	guint i;
2121 	int artist_query_idx = -1;
2122 
2123 	if (G_UNLIKELY (*data->cancel))
2124 		return;
2125 
2126 	for (i = 0; i < data->query->len; i++) {
2127 		RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
2128 		if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
2129 		    && qdata->propid == RHYTHMDB_PROP_ARTIST) {
2130 			if (artist_query_idx > 0)
2131 				return;
2132 			artist_query_idx = i;
2133 
2134 		}
2135 	}
2136 
2137 	if (artist_query_idx >= 0) {
2138 		RhythmDBTreeProperty *artist;
2139 		RhythmDBQueryData *qdata = g_ptr_array_index (data->query, artist_query_idx);
2140 		RBRefString *artistname = rb_refstring_new (g_value_get_string (qdata->val));
2141 		GPtrArray *oldquery = data->query;
2142 
2143 		data->query = clone_remove_ptr_array_index (data->query, artist_query_idx);
2144 
2145 		artist = g_hash_table_lookup (genre->children, artistname);
2146 		if (artist != NULL) {
2147 			conjunctive_query_albums (rb_refstring_get (artistname), artist, data);
2148 		}
2149 		g_ptr_array_free (data->query, TRUE);
2150 		data->query = oldquery;
2151 		return;
2152 	}
2153 
2154 	g_hash_table_foreach (genre->children, (GHFunc) conjunctive_query_albums, data);
2155 }
2156 
2157 static void
2158 conjunctive_query_genre (RhythmDBTree *db,
2159 			 GHashTable *genres,
2160 			 struct RhythmDBTreeTraversalData *data)
2161 {
2162 	int genre_query_idx = -1;
2163 	guint i;
2164 
2165 	if (G_UNLIKELY (*data->cancel))
2166 		return;
2167 
2168 	for (i = 0; i < data->query->len; i++) {
2169 		RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
2170 		if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
2171 		    && qdata->propid == RHYTHMDB_PROP_GENRE) {
2172 			/* A song can't currently have two genres.  So
2173 			 * if we get a conjunctive query for that, we
2174 			 * know the result must be the empty set. */
2175 			if (genre_query_idx > 0)
2176 				return;
2177 			genre_query_idx = i;
2178 
2179 		}
2180 	}
2181 
2182 	if (genre_query_idx >= 0) {
2183 		RhythmDBTreeProperty *genre;
2184 		RhythmDBQueryData *qdata = g_ptr_array_index (data->query, genre_query_idx);
2185 		RBRefString *genrename = rb_refstring_new (g_value_get_string (qdata->val));
2186 		GPtrArray *oldquery = data->query;
2187 
2188 		data->query = clone_remove_ptr_array_index (data->query, genre_query_idx);
2189 
2190 		genre = g_hash_table_lookup (genres, genrename);
2191 		if (genre != NULL) {
2192 			conjunctive_query_artists (rb_refstring_get (genrename), genre, data);
2193 		}
2194 		g_ptr_array_free (data->query, TRUE);
2195 		data->query = oldquery;
2196 		return;
2197 	}
2198 
2199 	g_hash_table_foreach (genres, (GHFunc) conjunctive_query_artists, data);
2200 }
2201 
2202 static void
2203 conjunctive_query (RhythmDBTree *db,
2204 		   GPtrArray *query,
2205 		   RhythmDBTreeTraversalFunc func,
2206 		   gpointer data,
2207 		   gboolean *cancel)
2208 {
2209 	int type_query_idx = -1;
2210 	guint i;
2211 	struct RhythmDBTreeTraversalData *traversal_data;
2212 
2213 	for (i = 0; i < query->len; i++) {
2214 		RhythmDBQueryData *qdata = g_ptr_array_index (query, i);
2215 		if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
2216 		    && qdata->propid == RHYTHMDB_PROP_TYPE) {
2217 			/* A song can't have two types. */
2218 			if (type_query_idx > 0)
2219 				return;
2220 			type_query_idx = i;
2221 		}
2222 	}
2223 
2224 	traversal_data = g_new (struct RhythmDBTreeTraversalData, 1);
2225 	traversal_data->db = db;
2226 	traversal_data->query = query;
2227 	traversal_data->func = func;
2228 	traversal_data->data = data;
2229 	traversal_data->cancel = cancel;
2230 
2231 	g_mutex_lock (&db->priv->genres_lock);
2232 	if (type_query_idx >= 0) {
2233 		GHashTable *genres;
2234 		RhythmDBEntryType *etype;
2235 		RhythmDBQueryData *qdata = g_ptr_array_index (query, type_query_idx);
2236 
2237 		g_ptr_array_remove_index_fast (query, type_query_idx);
2238 
2239 		etype = g_value_get_object (qdata->val);
2240 		genres = get_genres_hash_for_type (db, etype);
2241 		if (genres != NULL) {
2242 			conjunctive_query_genre (db, genres, traversal_data);
2243 		} else {
2244 			g_assert_not_reached ();
2245 		}
2246 	} else {
2247 		/* FIXME */
2248 		/* No type was given; punt and query everything */
2249 		genres_hash_foreach (db, (RBHFunc)conjunctive_query_genre,
2250 				     traversal_data);
2251 	}
2252 	g_mutex_unlock (&db->priv->genres_lock);
2253 
2254 	g_free (traversal_data);
2255 }
2256 
2257 static GList *
2258 split_query_by_disjunctions (RhythmDBTree *db,
2259 			     GPtrArray *query)
2260 {
2261 	GList *conjunctions = NULL;
2262 	guint i, j;
2263 	guint last_disjunction = 0;
2264 	GPtrArray *subquery = g_ptr_array_new ();
2265 
2266 	for (i = 0; i < query->len; i++) {
2267 		RhythmDBQueryData *data = g_ptr_array_index (query, i);
2268 		if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
2269 
2270 			/* Copy the subquery */
2271 			for (j = last_disjunction; j < i; j++) {
2272 				g_ptr_array_add (subquery, g_ptr_array_index (query, j));
2273 			}
2274 
2275 			conjunctions = g_list_prepend (conjunctions, subquery);
2276 			last_disjunction = i+1;
2277 			g_assert (subquery->len > 0);
2278 			subquery = g_ptr_array_new ();
2279 		}
2280 	}
2281 
2282 	/* Copy the last subquery, except for the QUERY_END */
2283 	for (i = last_disjunction; i < query->len; i++) {
2284 		g_ptr_array_add (subquery, g_ptr_array_index (query, i));
2285 	}
2286 
2287 	if (subquery->len > 0)
2288 		conjunctions = g_list_prepend (conjunctions, subquery);
2289 	else
2290 		g_ptr_array_free (subquery, TRUE);
2291 
2292 	return conjunctions;
2293 }
2294 
2295 struct RhythmDBTreeQueryGatheringData
2296 {
2297 	RhythmDBTree *db;
2298 	GPtrArray *queue;
2299 	GHashTable *entries;
2300 	RhythmDBQueryResults *results;
2301 };
2302 
2303 static void
2304 do_query_recurse (RhythmDBTree *db,
2305 		  GPtrArray *query,
2306 		  RhythmDBTreeTraversalFunc func,
2307 		  struct RhythmDBTreeQueryGatheringData *data,
2308 		  gboolean *cancel)
2309 {
2310 	GList *conjunctions, *tem;
2311 
2312 	if (query == NULL)
2313 		return;
2314 
2315 	conjunctions = split_query_by_disjunctions (db, query);
2316 	rb_debug ("doing recursive query, %d conjunctions", g_list_length (conjunctions));
2317 
2318 	if (conjunctions == NULL)
2319 		return;
2320 
2321 	/* If there is a disjunction involved, we must uniquify the entry hits. */
2322 	if (conjunctions->next != NULL)
2323 		data->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
2324 	else
2325 		data->entries = NULL;
2326 
2327 	for (tem = conjunctions; tem; tem = tem->next) {
2328 		if (G_UNLIKELY (*cancel))
2329 			break;
2330 		conjunctive_query (db, tem->data, func, data, cancel);
2331 		g_ptr_array_free (tem->data, TRUE);
2332 	}
2333 
2334 	if (data->entries != NULL)
2335 		g_hash_table_destroy (data->entries);
2336 
2337 	g_list_free (conjunctions);
2338 }
2339 
2340 static void
2341 handle_entry_match (RhythmDB *db,
2342 		    RhythmDBEntry *entry,
2343 		    struct RhythmDBTreeQueryGatheringData *data)
2344 {
2345 
2346 	if (data->entries
2347 	    && g_hash_table_lookup (data->entries, entry))
2348 		return;
2349 
2350 	g_ptr_array_add (data->queue, entry);
2351 	if (data->queue->len > RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
2352 		rhythmdb_query_results_add_results (data->results, data->queue);
2353 		data->queue = g_ptr_array_new ();
2354 	}
2355 }
2356 
2357 static void
2358 rhythmdb_tree_do_full_query (RhythmDB *adb,
2359 			     GPtrArray *query,
2360 			     RhythmDBQueryResults *results,
2361 			     gboolean *cancel)
2362 {
2363 	RhythmDBTree *db = RHYTHMDB_TREE (adb);
2364 	struct RhythmDBTreeQueryGatheringData *data = g_new0 (struct RhythmDBTreeQueryGatheringData, 1);
2365 
2366 	data->results = results;
2367 	data->queue = g_ptr_array_new ();
2368 
2369 	do_query_recurse (db, query, (RhythmDBTreeTraversalFunc) handle_entry_match, data, cancel);
2370 
2371 	rhythmdb_query_results_add_results (data->results, data->queue);
2372 
2373 	g_free (data);
2374 }
2375 
2376 static RhythmDBEntry *
2377 rhythmdb_tree_entry_lookup_by_location (RhythmDB *adb,
2378 					RBRefString *uri)
2379 {
2380 	RhythmDBTree *db = RHYTHMDB_TREE (adb);
2381 	RhythmDBEntry *entry;
2382 
2383 	g_mutex_lock (&db->priv->entries_lock);
2384 	entry = g_hash_table_lookup (db->priv->entries, uri);
2385 	g_mutex_unlock (&db->priv->entries_lock);
2386 
2387 	return entry;
2388 }
2389 
2390 static RhythmDBEntry *
2391 rhythmdb_tree_entry_lookup_by_id (RhythmDB *adb,
2392 				  gint id)
2393 {
2394 	RhythmDBTree *db = RHYTHMDB_TREE (adb);
2395 	RhythmDBEntry *entry;
2396 
2397 	g_mutex_lock (&db->priv->entries_lock);
2398 	entry = g_hash_table_lookup (db->priv->entry_ids, GINT_TO_POINTER (id));
2399 	g_mutex_unlock (&db->priv->entries_lock);
2400 
2401 	return entry;
2402 }
2403 
2404 struct RhythmDBEntryForeachCtxt
2405 {
2406 	RhythmDBTree *db;
2407 	GFunc func;
2408 	gpointer user_data;
2409 };
2410 
2411 static void
2412 rhythmdb_tree_entry_foreach_func (gpointer key, RhythmDBEntry *val, GPtrArray *list)
2413 {
2414 	rhythmdb_entry_ref (val);
2415 	g_ptr_array_add (list, val);
2416 }
2417 
2418 static void
2419 rhythmdb_tree_entry_foreach (RhythmDB *rdb, GFunc foreach_func, gpointer user_data)
2420 {
2421 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2422 	GPtrArray *list;
2423 	guint size, i;
2424 
2425 	g_mutex_lock (&db->priv->entries_lock);
2426 	size = g_hash_table_size (db->priv->entries);
2427 	list = g_ptr_array_sized_new (size);
2428 	g_hash_table_foreach (db->priv->entries, (GHFunc)rhythmdb_tree_entry_foreach_func, list);
2429 	g_mutex_unlock (&db->priv->entries_lock);
2430 
2431 	for (i = 0; i < size; i++) {
2432 		RhythmDBEntry *entry = (RhythmDBEntry*)g_ptr_array_index (list, i);
2433 		(*foreach_func) (entry, user_data);
2434 		rhythmdb_entry_unref (entry);
2435 	}
2436 
2437 	g_ptr_array_free (list, TRUE);
2438 }
2439 
2440 static gint64
2441 rhythmdb_tree_entry_count (RhythmDB *rdb)
2442 {
2443 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2444 	return g_hash_table_size (db->priv->entries);
2445 }
2446 
2447 typedef struct {
2448 	GFunc foreach_func;
2449 	gpointer data;
2450 } ForeachTypeData;
2451 
2452 static void
2453 _foreach_by_type_cb (RhythmDB *db, RhythmDBEntry *entry, ForeachTypeData *data)
2454 {
2455 	data->foreach_func (entry, data->data);
2456 }
2457 
2458 static void
2459 rhythmdb_tree_entry_foreach_by_type (RhythmDB *db,
2460 				     RhythmDBEntryType *type,
2461 				     GFunc foreach_func,
2462 				     gpointer data)
2463 {
2464 	ForeachTypeData ftdata = {foreach_func, data};
2465 
2466 	rhythmdb_hash_tree_foreach (db, type,
2467 				    (RBTreeEntryItFunc) _foreach_by_type_cb,
2468 				    NULL, NULL, NULL, &ftdata);
2469 }
2470 
2471 static void
2472 count_entries (RhythmDB *db, RhythmDBTreeProperty *album, gint64 *count)
2473 {
2474 	*count += g_hash_table_size (album->children);
2475 }
2476 
2477 static gint64
2478 rhythmdb_tree_entry_count_by_type (RhythmDB *db,
2479 				   RhythmDBEntryType *type)
2480 {
2481 	gint64 count = 0;
2482 	rhythmdb_hash_tree_foreach (db, type,
2483 				    NULL, (RBTreePropertyItFunc) count_entries, NULL, NULL,
2484 				    &count);
2485 	return count;
2486 }
2487 
2488 
2489 /* this is called with keywords_lock held */
2490 static gboolean
2491 remove_entry_from_keyword_table (RBRefString *keyword,
2492 				 GHashTable *table,
2493 				 RhythmDBEntry *entry)
2494 {
2495 	gboolean present;
2496 
2497 	present = g_hash_table_remove (table, entry);
2498 	/* TODO: remove entry hash tables */
2499 	return present;
2500 }
2501 
2502 static gboolean
2503 rhythmdb_tree_entry_keyword_add (RhythmDB *rdb,
2504 				 RhythmDBEntry *entry,
2505 				 RBRefString *keyword)
2506 {
2507 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2508 	GHashTable *keyword_table;
2509 	gboolean present;
2510 
2511 	g_mutex_lock (&db->priv->keywords_lock);
2512 	keyword_table = g_hash_table_lookup (db->priv->keywords, keyword);
2513 	if (keyword_table != NULL) {
2514 		/* it would be nice if _insert told us whether it was replacing a value */
2515 		present = (g_hash_table_lookup (keyword_table, entry) != NULL);
2516 		g_hash_table_insert (keyword_table, entry, GINT_TO_POINTER(1));
2517 	} else {
2518 		/* new keyword */
2519 		present = FALSE;
2520 
2521 		keyword_table = g_hash_table_new (g_direct_hash, g_direct_equal);
2522 		g_hash_table_insert (keyword_table, entry, GINT_TO_POINTER(1));
2523 
2524 		g_hash_table_insert (db->priv->keywords, rb_refstring_ref (keyword), keyword_table);
2525 	}
2526 
2527 	g_mutex_unlock (&db->priv->keywords_lock);
2528 
2529 	return present;
2530 }
2531 
2532 static gboolean
2533 rhythmdb_tree_entry_keyword_remove (RhythmDB *rdb,
2534 				    RhythmDBEntry *entry,
2535 				    RBRefString *keyword)
2536 {
2537 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2538 	GHashTable *keyword_table;
2539 	gboolean ret;
2540 
2541 	g_mutex_lock (&db->priv->keywords_lock);
2542 	keyword_table = g_hash_table_lookup (db->priv->keywords, keyword);
2543 	if (keyword_table != NULL) {
2544 		ret = remove_entry_from_keyword_table (keyword, keyword_table, entry);
2545 	} else {
2546 		ret = FALSE;
2547 	}
2548 	g_mutex_unlock (&db->priv->keywords_lock);
2549 
2550 	return ret;
2551 }
2552 
2553 static gboolean
2554 rhythmdb_tree_entry_keyword_has (RhythmDB *rdb,
2555 				 RhythmDBEntry *entry,
2556 				 RBRefString *keyword)
2557 {
2558 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2559 	GHashTable *keyword_table;
2560 	gboolean ret;
2561 
2562 	g_mutex_lock (&db->priv->keywords_lock);
2563 	keyword_table = g_hash_table_lookup (db->priv->keywords, keyword);
2564 	if (keyword_table != NULL) {
2565 		ret = (g_hash_table_lookup (keyword_table, entry) != NULL);
2566 	} else {
2567 		ret = FALSE;
2568 	}
2569 	g_mutex_unlock (&db->priv->keywords_lock);
2570 
2571 	return ret;
2572 }
2573 
2574 /* this is called with keywords_lock held */
2575 static void
2576 remove_entry_from_keywords (RhythmDBTree *db,
2577 			    RhythmDBEntry *entry)
2578 {
2579 	g_hash_table_foreach (db->priv->keywords, (GHFunc)remove_entry_from_keyword_table, entry);
2580 }
2581 
2582 struct RhythmDBTreeKeywordsGetData {
2583 	RhythmDBTree *db;
2584 	RhythmDBEntry *entry;
2585 	GList *keywords;
2586 };
2587 
2588 static void
2589 check_entry_existance (RBRefString *keyword,
2590 		       GHashTable *keyword_table,
2591 		       struct RhythmDBTreeKeywordsGetData *data)
2592 {
2593 	gboolean present;
2594 
2595 	present = (g_hash_table_lookup (keyword_table, data->entry) != NULL);
2596 	if (present) {
2597 		data->keywords = g_list_prepend (data->keywords, rb_refstring_ref (keyword));
2598 	}
2599 }
2600 
2601 static GList*
2602 rhythmdb_tree_entry_keywords_get (RhythmDB *rdb,
2603 				  RhythmDBEntry *entry)
2604 {
2605 	RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2606 	struct RhythmDBTreeKeywordsGetData data;
2607 
2608 	data.db = db;
2609 	data.entry = entry;
2610 	data.keywords = NULL;
2611 
2612 	g_mutex_lock (&db->priv->keywords_lock);
2613 	g_hash_table_foreach (db->priv->keywords, (GHFunc)check_entry_existance, &data);
2614 	g_mutex_unlock (&db->priv->keywords_lock);
2615 
2616 	return data.keywords;
2617 }
2618 
2619 
2620 struct HashTreeIteratorCtxt {
2621 	RhythmDBTree *db;
2622 	RBTreeEntryItFunc entry_func;
2623 	RBTreePropertyItFunc album_func;
2624 	RBTreePropertyItFunc artist_func;
2625 	RBTreePropertyItFunc genres_func;
2626 	gpointer data;
2627 };
2628 
2629 static void
2630 hash_tree_entries_foreach (gpointer key,
2631 			   gpointer value,
2632 			   gpointer data)
2633 {
2634 	RhythmDBEntry *entry = (RhythmDBEntry *) key;
2635 	struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2636 
2637 	g_assert (ctxt->entry_func);
2638 
2639 	ctxt->entry_func (ctxt->db, entry, ctxt->data);
2640 }
2641 
2642 static void
2643 hash_tree_albums_foreach (gpointer key,
2644 			  gpointer value,
2645 			  gpointer data)
2646 {
2647 	RhythmDBTreeProperty *album = (RhythmDBTreeProperty *)value;
2648 	struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2649 
2650 	if (ctxt->album_func) {
2651 		ctxt->album_func (ctxt->db, album, ctxt->data);
2652 	}
2653 	if (ctxt->entry_func != NULL) {
2654 		g_hash_table_foreach (album->children,
2655 				      hash_tree_entries_foreach,
2656 				      ctxt);
2657 	}
2658 }
2659 
2660 static void
2661 hash_tree_artists_foreach (gpointer key,
2662 			   gpointer value,
2663 			   gpointer data)
2664 {
2665 	RhythmDBTreeProperty *artist = (RhythmDBTreeProperty *)value;
2666 	struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2667 
2668 	if (ctxt->artist_func) {
2669 		ctxt->artist_func (ctxt->db, artist, ctxt->data);
2670 	}
2671 	if ((ctxt->album_func != NULL) || (ctxt->entry_func != NULL)) {
2672 		g_hash_table_foreach (artist->children,
2673 				      hash_tree_albums_foreach,
2674 				      ctxt);
2675 	}
2676 }
2677 
2678 static void
2679 hash_tree_genres_foreach (gpointer key,
2680 			  gpointer value,
2681 			  gpointer data)
2682 {
2683 	RhythmDBTreeProperty *genre = (RhythmDBTreeProperty *)value;
2684 	struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2685 
2686 	if (ctxt->genres_func) {
2687 		ctxt->genres_func (ctxt->db, genre, ctxt->data);
2688 	}
2689 
2690 	if ((ctxt->album_func != NULL)
2691 	    || (ctxt->artist_func != NULL)
2692 	    || (ctxt->entry_func != NULL)) {
2693 		g_hash_table_foreach (genre->children,
2694 				      hash_tree_artists_foreach,
2695 				      ctxt);
2696 	}
2697 }
2698 
2699 static void
2700 rhythmdb_hash_tree_foreach (RhythmDB *adb,
2701 			    RhythmDBEntryType *type,
2702 			    RBTreeEntryItFunc entry_func,
2703 			    RBTreePropertyItFunc album_func,
2704 			    RBTreePropertyItFunc artist_func,
2705 			    RBTreePropertyItFunc genres_func,
2706 			    gpointer data)
2707 {
2708 	struct HashTreeIteratorCtxt ctxt;
2709 	GHashTable *table;
2710 
2711 	ctxt.db = RHYTHMDB_TREE (adb);
2712 	ctxt.album_func = album_func;
2713 	ctxt.artist_func = artist_func;
2714 	ctxt.genres_func = genres_func;
2715 	ctxt.entry_func = entry_func;
2716 	ctxt.data = data;
2717 
2718 	g_mutex_lock (&ctxt.db->priv->genres_lock);
2719 	table = get_genres_hash_for_type (RHYTHMDB_TREE (adb), type);
2720 	if (table == NULL) {
2721 		return;
2722 	}
2723 	if ((ctxt.album_func != NULL)
2724 	    || (ctxt.artist_func != NULL)
2725 	    || (ctxt.genres_func != NULL)
2726 	    || (ctxt.entry_func != NULL)) {
2727 		g_hash_table_foreach (table, hash_tree_genres_foreach, &ctxt);
2728 	}
2729 	g_mutex_unlock (&ctxt.db->priv->genres_lock);
2730 }
2731 
2732 static void
2733 rhythmdb_tree_entry_type_registered (RhythmDB *db,
2734 				     RhythmDBEntryType *entry_type)
2735 {
2736 	GList *entries = NULL;
2737 	GList *e;
2738 	gint count = 0;
2739 	RhythmDBTree *rdb;
2740 	char *name;
2741 	RBRefString *rs_name;
2742 
2743 	rdb = RHYTHMDB_TREE (db);
2744 	g_mutex_lock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
2745 
2746 	/* ugh, this sucks, maybe store the name as a refstring in the object? */
2747 	g_object_get (entry_type, "name", &name, NULL);
2748 	rs_name = rb_refstring_find (name);
2749 
2750 	if (rs_name)
2751 		entries = g_hash_table_lookup (rdb->priv->unknown_entry_types, rs_name);
2752 	if (entries == NULL) {
2753 		g_mutex_unlock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
2754 		rb_refstring_unref (rs_name);
2755 		rb_debug ("no entries of newly registered type %s loaded from db", name);
2756 		g_free (name);
2757 		return;
2758 	}
2759 	g_free (name);
2760 
2761 	for (e = entries; e != NULL; e = e->next) {
2762 		RhythmDBUnknownEntry *data;
2763 		RhythmDBEntry *entry;
2764 		GList *p;
2765 
2766 		data = (RhythmDBUnknownEntry *)e->data;
2767 		entry = rhythmdb_entry_allocate (db, entry_type);
2768 		entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
2769 		for (p = data->properties; p != NULL; p = p->next) {
2770 			RhythmDBUnknownEntryProperty *prop;
2771 			RhythmDBPropType propid;
2772 			GValue value = {0,};
2773 
2774 			prop = (RhythmDBUnknownEntryProperty *) p->data;
2775 			propid = rhythmdb_propid_from_nice_elt_name (db, (const xmlChar *) rb_refstring_get (prop->name));
2776 
2777 			rhythmdb_read_encoded_property (db, rb_refstring_get (prop->value), propid, &value);
2778 			rhythmdb_entry_set_internal (db, entry, FALSE, propid, &value);
2779 			g_value_unset (&value);
2780 		}
2781 		rhythmdb_tree_entry_new_internal (db, entry);
2782 		rhythmdb_entry_insert (db, entry);
2783 		count++;
2784 	}
2785 	rb_debug ("handled %d entries of newly registered type %s", count, name);
2786 	rhythmdb_commit (db);
2787 
2788 	g_hash_table_remove (rdb->priv->unknown_entry_types, rs_name);
2789 	g_mutex_unlock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
2790 	free_unknown_entries (rs_name, entries, NULL);
2791 	rb_refstring_unref (rs_name);
2792 }