2006-08-04 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / rhythmdb / rhythmdb-tree.c
blob43261517c2e51a378a0cce770746cce733785b84
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of RhythmDB tree-structured database
5 * Copyright (C) 2003, 2004 Colin Walters <walters@verbum.org>
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "config.h"
25 #ifdef HAVE_GNU_FWRITE_UNLOCKED
26 #define _GNU_SOURCE
27 #endif
28 #include <stdio.h>
29 #ifdef HAVE_GNU_FWRITE_UNLOCKED
30 #undef _GNU_SOURCE
31 #endif
32 #include <unistd.h>
33 #include <stdlib.h>
34 #include <errno.h>
35 #include <string.h>
36 #include <glib/gprintf.h>
37 #include <glib/gatomic.h>
38 #include <glib/gi18n.h>
39 #include <gtk/gtkliststore.h>
40 #include <libxml/entities.h>
41 #include <libxml/SAX.h>
42 #include <libxml/parserInternals.h>
44 #include "rhythmdb-private.h"
45 #include "rhythmdb-tree.h"
46 #include "rhythmdb-property-model.h"
47 #include "rb-debug.h"
48 #include "rb-util.h"
49 #include "rb-file-helpers.h"
51 typedef struct RhythmDBTreeProperty
53 #ifndef G_DISABLE_ASSERT
54 guint magic;
55 #endif
56 struct RhythmDBTreeProperty *parent;
57 GHashTable *children;
58 } RhythmDBTreeProperty;
60 #define RHYTHMDB_TREE_PROPERTY_FROM_ENTRY(entry) ((RhythmDBTreeProperty *) entry->data)
62 G_DEFINE_TYPE(RhythmDBTree, rhythmdb_tree, RHYTHMDB_TYPE)
64 static void rhythmdb_tree_finalize (GObject *object);
66 static void rhythmdb_tree_load (RhythmDB *rdb, gboolean *die);
67 static void rhythmdb_tree_save (RhythmDB *rdb);
68 static void rhythmdb_tree_entry_new (RhythmDB *db, RhythmDBEntry *entry);
69 static void rhythmdb_tree_entry_new_internal (RhythmDB *db, RhythmDBEntry *entry);
70 static gboolean rhythmdb_tree_entry_set (RhythmDB *db, RhythmDBEntry *entry,
71 guint propid, const GValue *value);
73 static void rhythmdb_tree_entry_delete (RhythmDB *db, RhythmDBEntry *entry);
74 static void rhythmdb_tree_entry_delete_by_type (RhythmDB *adb, RhythmDBEntryType type);
76 static RhythmDBEntry * rhythmdb_tree_entry_lookup_by_location (RhythmDB *db, RBRefString *uri);
77 static void rhythmdb_tree_entry_foreach (RhythmDB *adb, GFunc func, gpointer user_data);
78 static void rhythmdb_tree_do_full_query (RhythmDB *db, GPtrArray *query,
79 RhythmDBQueryResults *results,
80 gboolean *cancel);
81 static gboolean rhythmdb_tree_evaluate_query (RhythmDB *adb, GPtrArray *query,
82 RhythmDBEntry *aentry);
83 static void rhythmdb_tree_entry_type_registered (RhythmDB *db,
84 const char *name,
85 RhythmDBEntryType type);
87 typedef void (*RBTreeEntryItFunc)(RhythmDBTree *db,
88 RhythmDBEntry *entry,
89 gpointer data);
91 typedef void (*RBTreePropertyItFunc)(RhythmDBTree *db,
92 RhythmDBTreeProperty *property,
93 gpointer data);
94 static void rhythmdb_hash_tree_foreach (RhythmDB *adb,
95 RhythmDBEntryType type,
96 RBTreeEntryItFunc entry_func,
97 RBTreePropertyItFunc album_func,
98 RBTreePropertyItFunc artist_func,
99 RBTreePropertyItFunc genres_func,
100 gpointer data);
102 #define RHYTHMDB_TREE_XML_VERSION "1.3"
104 static void destroy_tree_property (RhythmDBTreeProperty *prop);
105 static RhythmDBTreeProperty *get_or_create_album (RhythmDBTree *db, RhythmDBTreeProperty *artist,
106 RBRefString *name);
107 static RhythmDBTreeProperty *get_or_create_artist (RhythmDBTree *db, RhythmDBTreeProperty *genre,
108 RBRefString *name);
109 static RhythmDBTreeProperty *get_or_create_genre (RhythmDBTree *db, RhythmDBEntryType type,
110 RBRefString *name);
112 static void remove_entry_from_album (RhythmDBTree *db, RhythmDBEntry *entry);
114 static GList *split_query_by_disjunctions (RhythmDBTree *db, GPtrArray *query);
115 static gboolean evaluate_conjunctive_subquery (RhythmDBTree *db, GPtrArray *query,
116 guint base, guint max, RhythmDBEntry *entry);
118 struct RhythmDBTreePrivate
120 GHashTable *entries;
121 GMutex *entries_lock;
123 GHashTable *genres;
124 GHashTable *unknown_entry_types;
125 GMutex *genres_lock;
126 gboolean finalizing;
128 guint idle_load_id;
131 typedef struct
133 RBRefString *name;
134 RBRefString *value;
135 } RhythmDBUnknownEntryProperty;
137 typedef struct
139 RBRefString *typename;
140 GList *properties;
141 } RhythmDBUnknownEntry;
143 #define RHYTHMDB_TREE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_TREE, RhythmDBTreePrivate))
145 enum
147 PROP_0,
150 const int RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE = 512;
152 static void
153 rhythmdb_tree_class_init (RhythmDBTreeClass *klass)
155 GObjectClass *object_class = G_OBJECT_CLASS (klass);
156 RhythmDBClass *rhythmdb_class = RHYTHMDB_CLASS (klass);
158 object_class->finalize = rhythmdb_tree_finalize;
160 rhythmdb_class->impl_load = rhythmdb_tree_load;
161 rhythmdb_class->impl_save = rhythmdb_tree_save;
162 rhythmdb_class->impl_entry_new = rhythmdb_tree_entry_new;
163 rhythmdb_class->impl_entry_set = rhythmdb_tree_entry_set;
164 rhythmdb_class->impl_entry_delete = rhythmdb_tree_entry_delete;
165 rhythmdb_class->impl_entry_delete_by_type = rhythmdb_tree_entry_delete_by_type;
166 rhythmdb_class->impl_lookup_by_location = rhythmdb_tree_entry_lookup_by_location;
167 rhythmdb_class->impl_entry_foreach = rhythmdb_tree_entry_foreach;
168 rhythmdb_class->impl_evaluate_query = rhythmdb_tree_evaluate_query;
169 rhythmdb_class->impl_do_full_query = rhythmdb_tree_do_full_query;
170 rhythmdb_class->impl_entry_type_registered = rhythmdb_tree_entry_type_registered;
172 g_type_class_add_private (klass, sizeof (RhythmDBTreePrivate));
175 static void
176 rhythmdb_tree_init (RhythmDBTree *db)
178 db->priv = RHYTHMDB_TREE_GET_PRIVATE (db);
180 db->priv->entries = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
182 db->priv->genres = g_hash_table_new_full (g_direct_hash, g_direct_equal,
183 NULL, (GDestroyNotify)g_hash_table_destroy);
184 db->priv->unknown_entry_types = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
186 db->priv->entries_lock = g_mutex_new();
187 db->priv->genres_lock = g_mutex_new();
190 static void
191 unparent_entries (gpointer key,
192 RhythmDBEntry *entry,
193 RhythmDBTree *db)
195 remove_entry_from_album (db, entry);
198 static void
199 free_unknown_entries (RBRefString *name,
200 GList *entries,
201 gpointer nah)
203 GList *e;
204 for (e = entries; e != NULL; e = e->next) {
205 RhythmDBUnknownEntry *entry;
206 GList *p;
208 entry = (RhythmDBUnknownEntry *)e->data;
209 rb_refstring_unref (entry->typename);
210 for (p = entry->properties; p != NULL; p = p->next) {
211 RhythmDBUnknownEntryProperty *prop;
213 prop = (RhythmDBUnknownEntryProperty *)p->data;
214 rb_refstring_unref (prop->name);
215 rb_refstring_unref (prop->value);
216 g_free (prop);
219 g_list_free (entry->properties);
221 g_list_free (entries);
224 static void
225 rhythmdb_tree_finalize (GObject *object)
227 RhythmDBTree *db;
229 g_return_if_fail (object != NULL);
230 g_return_if_fail (RHYTHMDB_IS_TREE (object));
232 db = RHYTHMDB_TREE (object);
234 g_return_if_fail (db->priv != NULL);
236 db->priv->finalizing = TRUE;
238 g_hash_table_foreach (db->priv->entries, (GHFunc) unparent_entries, db);
239 g_hash_table_destroy (db->priv->entries);
240 g_mutex_free (db->priv->entries_lock);
242 g_hash_table_destroy (db->priv->genres);
243 g_mutex_free (db->priv->genres_lock);
245 g_hash_table_foreach (db->priv->unknown_entry_types,
246 (GHFunc) free_unknown_entries,
247 NULL);
248 g_hash_table_destroy (db->priv->unknown_entry_types);
250 G_OBJECT_CLASS (rhythmdb_tree_parent_class)->finalize (object);
253 struct RhythmDBTreeLoadContext
255 RhythmDBTree *db;
256 xmlParserCtxtPtr xmlctx;
257 gboolean *die;
258 enum {
259 RHYTHMDB_TREE_PARSER_STATE_START,
260 RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB,
261 RHYTHMDB_TREE_PARSER_STATE_ENTRY,
262 RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY,
263 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY,
264 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY,
265 RHYTHMDB_TREE_PARSER_STATE_END,
266 } state;
267 guint in_unknown_elt;
268 RhythmDBEntry *entry;
269 RhythmDBUnknownEntry *unknown_entry;
270 GString *buf;
271 RhythmDBPropType propid;
272 gint batch_count;
274 /* updating */
275 gboolean has_date;
276 gboolean canonicalise_uris;
277 gboolean reload_all_metadata;
280 static void
281 rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext *ctx,
282 const char *name,
283 const char **attrs)
285 if (*ctx->die == TRUE) {
286 xmlStopParser (ctx->xmlctx);
287 return;
290 if (ctx->in_unknown_elt) {
291 ctx->in_unknown_elt++;
292 return;
295 switch (ctx->state)
297 case RHYTHMDB_TREE_PARSER_STATE_START:
299 if (!strcmp (name, "rhythmdb")) {
300 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
301 for (; *attrs; attrs +=2) {
302 if (!strcmp (*attrs, "version")) {
303 const char *version = *(attrs+1);
305 if (!strcmp (version, "1.0") || !strcmp (version, "1.1")) {
306 ctx->canonicalise_uris = TRUE;
307 rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries");
308 } else if (!strcmp (version, "1.2")) {
309 /* current version*/
310 rb_debug ("reloading all file metadata to get MusicBrainz tags");
311 ctx->reload_all_metadata = TRUE;
312 } else if (!strcmp (version, "1.3")) {
313 /* current version*/
314 } else {
315 /* too new. FIXME quit gracefully with error */
316 g_assert_not_reached ();
318 } else {
319 g_assert_not_reached ();
323 } else {
324 ctx->in_unknown_elt++;
327 break;
329 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
331 if (!strcmp (name, "entry")) {
332 RhythmDBEntryType type = RHYTHMDB_ENTRY_TYPE_INVALID;
333 const char *typename = NULL;
334 for (; *attrs; attrs +=2) {
335 if (!strcmp (*attrs, "type")) {
336 typename = *(attrs+1);
337 type = rhythmdb_entry_type_get_by_name (RHYTHMDB (ctx->db), typename);
338 break;
342 g_assert (typename);
343 if (type != RHYTHMDB_ENTRY_TYPE_INVALID) {
344 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
345 ctx->entry = rhythmdb_entry_allocate (RHYTHMDB (ctx->db), type);
346 ctx->entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
347 ctx->has_date = FALSE;
348 } else {
349 rb_debug ("reading unknown entry");
350 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
351 ctx->unknown_entry = g_new0 (RhythmDBUnknownEntry, 1);
352 ctx->unknown_entry->typename = rb_refstring_new (typename);
354 } else {
355 ctx->in_unknown_elt++;
357 break;
359 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
361 int val = rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx->db), BAD_CAST name);
362 if (val < 0) {
363 ctx->in_unknown_elt++;
364 break;
367 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY;
368 ctx->propid = val;
369 g_string_truncate (ctx->buf, 0);
370 break;
372 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
374 RhythmDBUnknownEntryProperty *prop;
376 prop = g_new0 (RhythmDBUnknownEntryProperty, 1);
377 prop->name = rb_refstring_new (name);
379 ctx->unknown_entry->properties = g_list_prepend (ctx->unknown_entry->properties, prop);
380 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY;
381 g_string_truncate (ctx->buf, 0);
382 break;
384 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
385 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
386 case RHYTHMDB_TREE_PARSER_STATE_END:
387 break;
391 static void
392 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext *ctx,
393 const char *name)
395 if (*ctx->die == TRUE) {
396 xmlStopParser (ctx->xmlctx);
397 return;
400 if (ctx->in_unknown_elt) {
401 ctx->in_unknown_elt--;
402 return;
405 switch (ctx->state)
407 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
408 ctx->state = RHYTHMDB_TREE_PARSER_STATE_END;
409 break;
410 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
412 if (!ctx->has_date | ctx->reload_all_metadata) {
413 /* there is no date metadata, so this is from an old version
414 * reset the last-modified timestamp, so that the file is re-read
416 rb_debug ("pre-Date entry found, causing re-read");
417 ctx->entry->mtime = 0;
419 if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
420 RhythmDBPodcastFields *podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (ctx->entry, RhythmDBPodcastFields);
421 /* Handle upgrades from 0.9.2.
422 * Previously, last-seen for podcast feeds was the time of the last post,
423 * and post-time was unused. Now, we want last-seen to be the time we
424 * last updated the feed, and post-time to be the time of the last post.
426 if (podcast->post_time == 0) {
427 podcast->post_time = ctx->entry->last_seen;
431 if (ctx->entry->location != NULL) {
432 RhythmDBEntry *entry;
434 g_mutex_lock (ctx->db->priv->entries_lock);
435 entry = g_hash_table_lookup (ctx->db->priv->entries, ctx->entry->location);
436 if (entry == NULL) {
437 rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx->db), ctx->entry);
438 rhythmdb_entry_insert (RHYTHMDB (ctx->db), ctx->entry);
439 if (++ctx->batch_count == RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
440 rhythmdb_commit (RHYTHMDB (ctx->db));
441 ctx->batch_count = 0;
443 } else {
444 rb_debug ("found entry with duplicate location %s. merging metadata",
445 rb_refstring_get (ctx->entry->location));
446 entry->play_count += ctx->entry->play_count;
448 if (entry->rating < 0.01)
449 entry->rating = ctx->entry->rating;
450 else if (ctx->entry->rating > 0.01)
451 entry->rating = (entry->rating + ctx->entry->rating) / 2;
453 if (ctx->entry->last_played > entry->last_played)
454 entry->last_played = ctx->entry->last_played;
456 if (ctx->entry->first_seen < entry->first_seen)
457 entry->first_seen = ctx->entry->first_seen;
459 if (ctx->entry->last_seen > entry->last_seen)
460 entry->last_seen = ctx->entry->last_seen;
462 rhythmdb_entry_unref (ctx->entry);
464 g_mutex_unlock (ctx->db->priv->entries_lock);
465 } else {
466 rb_debug ("found entry without location");
467 rhythmdb_entry_unref (ctx->entry);
469 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
470 ctx->entry = NULL;
471 break;
473 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
475 GList *entry_list;
477 rb_debug ("finished reading unknown entry");
478 ctx->unknown_entry->properties = g_list_reverse (ctx->unknown_entry->properties);
480 entry_list = g_hash_table_lookup (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename);
481 entry_list = g_list_prepend (entry_list, ctx->unknown_entry);
482 g_hash_table_insert (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename, entry_list);
484 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
485 ctx->unknown_entry = NULL;
486 break;
488 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
490 GValue value = {0,};
491 gboolean set = FALSE;
492 gboolean skip = FALSE;
494 /* special case some properties for upgrade handling etc. */
495 switch (ctx->propid) {
496 case RHYTHMDB_PROP_DATE:
497 ctx->has_date = TRUE;
498 break;
499 case RHYTHMDB_PROP_LOCATION:
500 if (ctx->canonicalise_uris) {
501 char *canon = rb_canonicalise_uri (ctx->buf->str);
503 g_value_init (&value, G_TYPE_STRING);
504 g_value_take_string (&value, canon);
505 set = TRUE;
507 break;
508 case RHYTHMDB_PROP_MOUNTPOINT:
509 /* fix old podcast posts */
510 if (g_str_has_prefix (ctx->buf->str, "http://"))
511 skip = TRUE;
512 break;
513 default:
514 break;
517 if (!skip) {
518 if (!set) {
519 rhythmdb_read_encoded_property (RHYTHMDB (ctx->db), ctx->buf->str, ctx->propid, &value);
522 rhythmdb_entry_set_internal (RHYTHMDB (ctx->db), ctx->entry, FALSE, ctx->propid, &value);
523 g_value_unset (&value);
526 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
527 break;
529 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
531 RhythmDBUnknownEntryProperty *prop;
533 g_assert (ctx->unknown_entry->properties);
534 prop = ctx->unknown_entry->properties->data;
535 g_assert (prop->value == NULL);
536 prop->value = rb_refstring_new (ctx->buf->str);
537 rb_debug ("unknown entry property: %s = %s", rb_refstring_get (prop->name), rb_refstring_get (prop->value));
539 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
540 break;
542 case RHYTHMDB_TREE_PARSER_STATE_START:
543 case RHYTHMDB_TREE_PARSER_STATE_END:
544 break;
548 static void
549 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext *ctx,
550 const char *data,
551 guint len)
553 if (*ctx->die == TRUE) {
554 xmlStopParser (ctx->xmlctx);
555 return;
558 switch (ctx->state)
560 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
561 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
562 g_string_append_len (ctx->buf, data, len);
563 break;
564 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
565 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
566 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
567 case RHYTHMDB_TREE_PARSER_STATE_START:
568 case RHYTHMDB_TREE_PARSER_STATE_END:
569 break;
573 static void
574 rhythmdb_tree_load (RhythmDB *rdb,
575 gboolean *die)
577 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
578 xmlParserCtxtPtr ctxt;
579 xmlSAXHandlerPtr sax_handler;
580 struct RhythmDBTreeLoadContext *ctx;
581 char *name;
583 sax_handler = g_new0 (xmlSAXHandler, 1);
584 ctx = g_new0 (struct RhythmDBTreeLoadContext, 1);
586 sax_handler->startElement = (startElementSAXFunc) rhythmdb_tree_parser_start_element;
587 sax_handler->endElement = (endElementSAXFunc) rhythmdb_tree_parser_end_element;
588 sax_handler->characters = (charactersSAXFunc) rhythmdb_tree_parser_characters;
590 ctx->state = RHYTHMDB_TREE_PARSER_STATE_START;
591 ctx->db = db;
592 ctx->die = die;
593 ctx->buf = g_string_sized_new (RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE);
595 g_object_get (G_OBJECT (db), "name", &name, NULL);
597 if (g_file_test (name, G_FILE_TEST_EXISTS)) {
598 ctxt = xmlCreateFileParserCtxt (name);
599 ctx->xmlctx = ctxt;
600 xmlFree (ctxt->sax);
601 ctxt->userData = ctx;
602 ctxt->sax = sax_handler;
603 xmlParseDocument (ctxt);
604 ctxt->sax = NULL;
605 xmlFreeParserCtxt (ctxt);
607 if (ctx->batch_count)
608 rhythmdb_commit (RHYTHMDB (ctx->db));
612 g_string_free (ctx->buf, TRUE);
613 g_free (name);
614 g_free (sax_handler);
615 g_free (ctx);
618 struct RhythmDBTreeSaveContext
620 RhythmDBTree *db;
621 FILE *handle;
622 char *error;
625 #ifdef HAVE_GNU_FWRITE_UNLOCKED
626 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
627 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
628 #else
629 #define RHYTHMDB_FWRITE_REAL fwrite
630 #define RHYTHMDB_FPUTC_REAL fputc
631 #endif
633 #define RHYTHMDB_FWRITE(w,x,len,handle,error) do { \
634 if (error == NULL) { \
635 if (RHYTHMDB_FWRITE_REAL (w,x,len,handle) != len) { \
636 error = g_strdup (g_strerror (errno)); \
639 } while (0)
641 #define RHYTHMDB_FPUTC(x,handle,error) do { \
642 if (error == NULL) { \
643 if (RHYTHMDB_FPUTC_REAL (x,handle) == EOF) { \
644 error = g_strdup (g_strerror (errno)); \
647 } while (0)
649 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
651 static void
652 write_elt_name_open (struct RhythmDBTreeSaveContext *ctx,
653 const xmlChar *elt_name)
655 RHYTHMDB_FWRITE_STATICSTR (" <", ctx->handle, ctx->error);
656 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
657 RHYTHMDB_FPUTC ('>', ctx->handle, ctx->error);
660 static void
661 write_elt_name_close (struct RhythmDBTreeSaveContext *ctx,
662 const xmlChar *elt_name)
664 RHYTHMDB_FWRITE_STATICSTR ("</", ctx->handle, ctx->error);
665 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
666 RHYTHMDB_FWRITE_STATICSTR (">\n", ctx->handle, ctx->error);
669 static void
670 save_entry_string (struct RhythmDBTreeSaveContext *ctx,
671 const xmlChar *elt_name,
672 const char *str)
674 xmlChar *encoded;
676 g_return_if_fail (str != NULL);
677 write_elt_name_open (ctx, elt_name);
678 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST str);
679 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
680 g_free (encoded);
681 write_elt_name_close (ctx, elt_name);
684 static void
685 save_entry_int (struct RhythmDBTreeSaveContext *ctx,
686 const xmlChar *elt_name,
687 int num)
689 char buf[92];
690 if (num == 0)
691 return;
692 write_elt_name_open (ctx, elt_name);
693 g_snprintf (buf, sizeof (buf), "%d", num);
694 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
695 write_elt_name_close (ctx, elt_name);
698 static void
699 save_entry_ulong (struct RhythmDBTreeSaveContext *ctx,
700 const xmlChar *elt_name,
701 gulong num,
702 gboolean save_zeroes)
704 char buf[92];
706 if (num == 0 && !save_zeroes)
707 return;
708 write_elt_name_open (ctx, elt_name);
709 g_snprintf (buf, sizeof (buf), "%lu", num);
710 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
711 write_elt_name_close (ctx, elt_name);
714 static void
715 save_entry_boolean (struct RhythmDBTreeSaveContext *ctx,
716 const xmlChar *elt_name,
717 gboolean val)
719 save_entry_ulong (ctx, elt_name, val ? 1 : 0, FALSE);
722 static void
723 save_entry_uint64 (struct RhythmDBTreeSaveContext *ctx,
724 const xmlChar *elt_name,
725 guint64 num)
727 char buf[92];
729 if (num == 0)
730 return;
732 write_elt_name_open (ctx, elt_name);
733 g_snprintf (buf, sizeof (buf), "%" G_GUINT64_FORMAT, num);
734 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
735 write_elt_name_close (ctx, elt_name);
738 static void
739 save_entry_double (struct RhythmDBTreeSaveContext *ctx,
740 const xmlChar *elt_name,
741 double num)
743 char buf[92];
745 if (num > -0.001 && num < 0.001)
746 return;
748 write_elt_name_open (ctx, elt_name);
749 g_snprintf (buf, sizeof (buf), "%f", num);
750 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
751 write_elt_name_close (ctx, elt_name);
754 /* This code is intended to be highly optimized. This came at a small
755 * readability cost. Sorry about that.
757 static void
758 save_entry (RhythmDBTree *db,
759 RhythmDBEntry *entry,
760 struct RhythmDBTreeSaveContext *ctx)
762 RhythmDBPropType i;
763 RhythmDBPodcastFields *podcast = NULL;
764 xmlChar *encoded;
766 if (ctx->error)
767 return;
769 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
770 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
771 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
773 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
774 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST entry->type->name);
775 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
776 g_free (encoded);
778 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
780 /* Skip over the first property - the type */
781 for (i = 1; i < RHYTHMDB_NUM_PROPERTIES; i++) {
782 const xmlChar *elt_name;
784 if (ctx->error)
785 return;
787 elt_name = rhythmdb_nice_elt_name_from_propid ((RhythmDB *) ctx->db, i);
789 switch (i) {
790 case RHYTHMDB_PROP_TYPE:
791 break;
792 case RHYTHMDB_PROP_TITLE:
793 save_entry_string(ctx, elt_name, rb_refstring_get (entry->title));
794 break;
795 case RHYTHMDB_PROP_ALBUM:
796 save_entry_string(ctx, elt_name, rb_refstring_get (entry->album));
797 break;
798 case RHYTHMDB_PROP_ARTIST:
799 save_entry_string(ctx, elt_name, rb_refstring_get (entry->artist));
800 break;
801 case RHYTHMDB_PROP_GENRE:
802 save_entry_string(ctx, elt_name, rb_refstring_get (entry->genre));
803 break;
804 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
805 save_entry_string(ctx, elt_name, rb_refstring_get (entry->musicbrainz_trackid));
806 break;
807 case RHYTHMDB_PROP_TRACK_NUMBER:
808 save_entry_ulong (ctx, elt_name, entry->tracknum, FALSE);
809 break;
810 case RHYTHMDB_PROP_DISC_NUMBER:
811 save_entry_ulong (ctx, elt_name, entry->discnum, FALSE);
812 break;
813 case RHYTHMDB_PROP_DATE:
814 if (g_date_valid (&entry->date))
815 save_entry_ulong (ctx, elt_name, g_date_get_julian (&entry->date), TRUE);
816 else
817 save_entry_ulong (ctx, elt_name, 0, TRUE);
818 break;
819 case RHYTHMDB_PROP_DURATION:
820 save_entry_ulong (ctx, elt_name, entry->duration, FALSE);
821 break;
822 case RHYTHMDB_PROP_BITRATE:
823 save_entry_int(ctx, elt_name, entry->bitrate);
824 break;
825 case RHYTHMDB_PROP_TRACK_GAIN:
826 save_entry_double(ctx, elt_name, entry->track_gain);
827 break;
828 case RHYTHMDB_PROP_TRACK_PEAK:
829 save_entry_double(ctx, elt_name, entry->track_peak);
830 break;
831 case RHYTHMDB_PROP_ALBUM_GAIN:
832 save_entry_double(ctx, elt_name, entry->album_gain);
833 break;
834 case RHYTHMDB_PROP_ALBUM_PEAK:
835 save_entry_double(ctx, elt_name, entry->album_peak);
836 break;
837 case RHYTHMDB_PROP_LOCATION:
838 save_entry_string(ctx, elt_name, rb_refstring_get (entry->location));
839 break;
840 case RHYTHMDB_PROP_MOUNTPOINT:
841 /* Avoid crashes on exit when upgrading from 0.8
842 * and no mountpoint is available from some entries */
843 if (entry->mountpoint) {
844 save_entry_string(ctx, elt_name, rb_refstring_get (entry->mountpoint));
846 break;
847 case RHYTHMDB_PROP_FILE_SIZE:
848 save_entry_uint64(ctx, elt_name, entry->file_size);
849 break;
850 case RHYTHMDB_PROP_MIMETYPE:
851 save_entry_string(ctx, elt_name, rb_refstring_get (entry->mimetype));
852 break;
853 case RHYTHMDB_PROP_MTIME:
854 save_entry_ulong (ctx, elt_name, entry->mtime, FALSE);
855 break;
856 case RHYTHMDB_PROP_FIRST_SEEN:
857 save_entry_ulong (ctx, elt_name, entry->first_seen, FALSE);
858 break;
859 case RHYTHMDB_PROP_LAST_SEEN:
860 save_entry_ulong (ctx, elt_name, entry->last_seen, FALSE);
861 break;
862 case RHYTHMDB_PROP_RATING:
863 save_entry_double(ctx, elt_name, entry->rating);
864 break;
865 case RHYTHMDB_PROP_PLAY_COUNT:
866 save_entry_ulong (ctx, elt_name, entry->play_count, FALSE);
867 break;
868 case RHYTHMDB_PROP_LAST_PLAYED:
869 save_entry_ulong (ctx, elt_name, entry->last_played, FALSE);
870 break;
871 case RHYTHMDB_PROP_HIDDEN:
873 gboolean hidden = ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
874 save_entry_boolean (ctx, elt_name, hidden);
876 break;
877 case RHYTHMDB_PROP_STATUS:
878 if (podcast)
879 save_entry_ulong (ctx, elt_name, podcast->status, FALSE);
880 break;
881 case RHYTHMDB_PROP_DESCRIPTION:
882 if (podcast && podcast->description)
883 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->description));
884 break;
885 case RHYTHMDB_PROP_SUBTITLE:
886 if (podcast && podcast->subtitle)
887 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->subtitle));
888 break;
889 case RHYTHMDB_PROP_SUMMARY:
890 if (podcast && podcast->summary)
891 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->summary));
892 break;
893 case RHYTHMDB_PROP_LANG:
894 if (podcast && podcast->lang)
895 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->lang));
896 break;
897 case RHYTHMDB_PROP_COPYRIGHT:
898 if (podcast && podcast->copyright)
899 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->copyright));
900 break;
901 case RHYTHMDB_PROP_IMAGE:
902 if (podcast && podcast->image)
903 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->image));
904 break;
905 case RHYTHMDB_PROP_POST_TIME:
906 if (podcast)
907 save_entry_ulong (ctx, elt_name, podcast->post_time, FALSE);
908 break;
909 case RHYTHMDB_PROP_TITLE_SORT_KEY:
910 case RHYTHMDB_PROP_GENRE_SORT_KEY:
911 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
912 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
913 case RHYTHMDB_PROP_TITLE_FOLDED:
914 case RHYTHMDB_PROP_GENRE_FOLDED:
915 case RHYTHMDB_PROP_ARTIST_FOLDED:
916 case RHYTHMDB_PROP_ALBUM_FOLDED:
917 case RHYTHMDB_PROP_LAST_PLAYED_STR:
918 case RHYTHMDB_PROP_PLAYBACK_ERROR:
919 case RHYTHMDB_PROP_FIRST_SEEN_STR:
920 case RHYTHMDB_PROP_LAST_SEEN_STR:
921 case RHYTHMDB_PROP_SEARCH_MATCH:
922 case RHYTHMDB_PROP_YEAR:
923 case RHYTHMDB_NUM_PROPERTIES:
924 break;
928 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
931 static void
932 save_entry_type (const char *name,
933 RhythmDBEntryType entry_type,
934 struct RhythmDBTreeSaveContext *ctx)
936 if (entry_type->save_to_disk == FALSE)
937 return;
939 rb_debug ("saving entries of type %s", name);
940 rhythmdb_hash_tree_foreach (RHYTHMDB (ctx->db), entry_type,
941 (RBTreeEntryItFunc) save_entry,
942 NULL, NULL, NULL, ctx);
945 static void
946 save_unknown_entry_type (RBRefString *typename,
947 GList *entries,
948 struct RhythmDBTreeSaveContext *ctx)
950 GList *t;
952 for (t = entries; t != NULL; t = t->next) {
953 RhythmDBUnknownEntry *entry;
954 xmlChar *encoded;
955 GList *p;
957 if (ctx->error)
958 return;
960 entry = (RhythmDBUnknownEntry *)t->data;
962 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
963 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST rb_refstring_get (entry->typename));
964 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
965 g_free (encoded);
967 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
969 for (p = entry->properties; p != NULL; p = p->next) {
970 RhythmDBUnknownEntryProperty *prop;
971 prop = (RhythmDBUnknownEntryProperty *) p->data;
972 save_entry_string(ctx, (const xmlChar *)rb_refstring_get (prop->name), rb_refstring_get (prop->value));
975 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
979 static void
980 rhythmdb_tree_save (RhythmDB *rdb)
982 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
983 char *name;
984 GString *savepath;
985 FILE *f;
986 struct RhythmDBTreeSaveContext ctx;
988 g_object_get (G_OBJECT (db), "name", &name, NULL);
990 savepath = g_string_new (name);
991 g_string_append (savepath, ".tmp");
993 f = fopen (savepath->str, "w");
995 if (!f) {
996 g_warning ("Can't save XML: %s", g_strerror (errno));
997 goto out;
1000 ctx.db = db;
1001 ctx.handle = f;
1002 ctx.error = NULL;
1003 RHYTHMDB_FWRITE_STATICSTR ("<?xml version=\"1.0\" standalone=\"yes\"?>\n"
1004 "<rhythmdb version=\"" RHYTHMDB_TREE_XML_VERSION "\">\n",
1005 ctx.handle, ctx.error);
1007 rhythmdb_entry_type_foreach (rdb, (GHFunc) save_entry_type, &ctx);
1008 g_hash_table_foreach (db->priv->unknown_entry_types,
1009 (GHFunc) save_unknown_entry_type,
1010 &ctx);
1012 RHYTHMDB_FWRITE_STATICSTR ("</rhythmdb>\n", ctx.handle, ctx.error);
1014 if (fclose (f) < 0) {
1015 g_warning ("Couldn't close %s: %s",
1016 savepath->str,
1017 g_strerror (errno));
1018 unlink (savepath->str);
1019 goto out;
1022 if (ctx.error != NULL) {
1023 g_warning ("Writing to the database failed: %s", ctx.error);
1024 g_free (ctx.error);
1025 unlink (savepath->str);
1026 } else {
1027 if (rename (savepath->str, name) < 0) {
1028 g_warning ("Couldn't rename %s to %s: %s",
1029 name, savepath->str,
1030 g_strerror (errno));
1031 unlink (savepath->str);
1035 out:
1036 g_string_free (savepath, TRUE);
1037 g_free (name);
1038 return;
1041 #undef RHYTHMDB_FWRITE_ENCODED_STR
1042 #undef RHYTHMDB_FWRITE_STATICSTR
1043 #undef RHYTHMDB_FPUTC
1044 #undef RHYTHMDB_FWRITE
1046 RhythmDB *
1047 rhythmdb_tree_new (const char *name)
1049 RhythmDBTree *db = g_object_new (RHYTHMDB_TYPE_TREE, "name", name, NULL);
1051 g_return_val_if_fail (db->priv != NULL, NULL);
1053 return RHYTHMDB (db);
1056 static void
1057 set_entry_album (RhythmDBTree *db,
1058 RhythmDBEntry *entry,
1059 RhythmDBTreeProperty *artist,
1060 RBRefString *name)
1062 struct RhythmDBTreeProperty *prop;
1063 prop = get_or_create_album (db, artist, name);
1064 g_hash_table_insert (prop->children, entry, NULL);
1065 entry->data = prop;
1068 static void
1069 rhythmdb_tree_entry_new (RhythmDB *rdb,
1070 RhythmDBEntry *entry)
1072 g_mutex_lock (RHYTHMDB_TREE(rdb)->priv->entries_lock);
1073 rhythmdb_tree_entry_new_internal (rdb, entry);
1074 g_mutex_unlock (RHYTHMDB_TREE(rdb)->priv->entries_lock);
1077 static void
1078 rhythmdb_tree_entry_new_internal (RhythmDB *rdb, RhythmDBEntry *entry)
1080 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1081 RhythmDBTreeProperty *artist;
1082 RhythmDBTreeProperty *genre;
1084 g_assert (entry != NULL);
1086 g_return_if_fail (entry->location != NULL);
1088 if (entry->title == NULL) {
1089 g_warning ("Entry %s has missing title", rb_refstring_get (entry->location));
1090 entry->title = rb_refstring_new (_("Unknown"));
1092 if (entry->artist == NULL) {
1093 g_warning ("Entry %s has missing artist", rb_refstring_get (entry->location));
1094 entry->artist = rb_refstring_new (_("Unknown"));
1096 if (entry->album == NULL) {
1097 g_warning ("Entry %s has missing album", rb_refstring_get (entry->location));
1098 entry->album = rb_refstring_new (_("Unknown"));
1100 if (entry->genre == NULL) {
1101 g_warning ("Entry %s has missing genre", rb_refstring_get (entry->location));
1102 entry->genre = rb_refstring_new (_("Unknown"));
1104 if (entry->mimetype == NULL) {
1105 g_warning ("Entry %s has missing mimetype", rb_refstring_get (entry->location));
1106 entry->mimetype = rb_refstring_new ("unknown/unknown");
1109 /* Initialize the tree structure. */
1110 genre = get_or_create_genre (db, entry->type, entry->genre);
1111 artist = get_or_create_artist (db, genre, entry->artist);
1112 set_entry_album (db, entry, artist, entry->album);
1114 /* this accounts for the initial reference on the entry */
1115 g_hash_table_insert (db->priv->entries, entry->location, entry);
1117 entry->flags &= ~RHYTHMDB_ENTRY_TREE_LOADING;
1120 static RhythmDBTreeProperty *
1121 rhythmdb_tree_property_new (RhythmDBTree *db)
1123 RhythmDBTreeProperty *ret = g_new0 (RhythmDBTreeProperty, 1);
1124 #ifndef G_DISABLE_ASSERT
1125 ret->magic = 0xf00dbeef;
1126 #endif
1127 return ret;
1130 static GHashTable *
1131 get_genres_hash_for_type (RhythmDBTree *db,
1132 RhythmDBEntryType type)
1134 GHashTable *table;
1136 table = g_hash_table_lookup (db->priv->genres, type);
1137 if (table == NULL) {
1138 table = g_hash_table_new_full (rb_refstring_hash,
1139 rb_refstring_equal,
1140 (GDestroyNotify) rb_refstring_unref,
1141 NULL);
1142 if (table == NULL) {
1143 g_warning ("Out of memory\n");
1144 return NULL;
1146 g_hash_table_insert (db->priv->genres,
1147 type,
1148 table);
1150 return table;
1153 typedef void (*RBHFunc)(RhythmDBTree *db, GHashTable *genres, gpointer data);
1155 typedef struct {
1156 RhythmDBTree *db;
1157 RBHFunc func;
1158 gpointer data;
1159 } GenresIterCtxt;
1161 static void
1162 genres_process_one (gpointer key,
1163 gpointer value,
1164 gpointer user_data)
1166 GenresIterCtxt *ctxt = (GenresIterCtxt *)user_data;
1167 ctxt->func (ctxt->db, (GHashTable *)value, ctxt->data);
1170 static void
1171 genres_hash_foreach (RhythmDBTree *db, RBHFunc func, gpointer data)
1173 GenresIterCtxt ctxt;
1175 ctxt.db = db;
1176 ctxt.func = func;
1177 ctxt.data = data;
1178 g_hash_table_foreach (db->priv->genres, genres_process_one, &ctxt);
1181 static RhythmDBTreeProperty *
1182 get_or_create_genre (RhythmDBTree *db,
1183 RhythmDBEntryType type,
1184 RBRefString *name)
1186 RhythmDBTreeProperty *genre;
1187 GHashTable *table;
1189 g_mutex_lock (db->priv->genres_lock);
1190 table = get_genres_hash_for_type (db, type);
1191 genre = g_hash_table_lookup (table, name);
1193 if (G_UNLIKELY (genre == NULL)) {
1194 genre = rhythmdb_tree_property_new (db);
1195 genre->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1196 (GDestroyNotify) rb_refstring_unref,
1197 NULL);
1198 rb_refstring_ref (name);
1199 g_hash_table_insert (table, name, genre);
1200 genre->parent = NULL;
1202 g_mutex_unlock (db->priv->genres_lock);
1204 return genre;
1207 static RhythmDBTreeProperty *
1208 get_or_create_artist (RhythmDBTree *db,
1209 RhythmDBTreeProperty *genre,
1210 RBRefString *name)
1212 RhythmDBTreeProperty *artist;
1214 artist = g_hash_table_lookup (genre->children, name);
1216 if (G_UNLIKELY (artist == NULL)) {
1217 artist = rhythmdb_tree_property_new (db);
1218 artist->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1219 (GDestroyNotify) rb_refstring_unref,
1220 NULL);
1221 rb_refstring_ref (name);
1222 g_hash_table_insert (genre->children, name, artist);
1223 artist->parent = genre;
1226 return artist;
1229 static RhythmDBTreeProperty *
1230 get_or_create_album (RhythmDBTree *db,
1231 RhythmDBTreeProperty *artist,
1232 RBRefString *name)
1234 RhythmDBTreeProperty *album;
1236 album = g_hash_table_lookup (artist->children, name);
1238 if (G_UNLIKELY (album == NULL)) {
1239 album = rhythmdb_tree_property_new (db);
1240 album->children = g_hash_table_new (g_direct_hash, g_direct_equal);
1241 rb_refstring_ref (name);
1242 g_hash_table_insert (artist->children, name, album);
1243 album->parent = artist;
1246 return album;
1249 static gboolean
1250 remove_child (RhythmDBTreeProperty *parent,
1251 gconstpointer data)
1253 g_assert (g_hash_table_remove (parent->children, data));
1254 if (g_hash_table_size (parent->children) <= 0) {
1255 return TRUE;
1257 return FALSE;
1260 static void
1261 remove_entry_from_album (RhythmDBTree *db,
1262 RhythmDBEntry *entry)
1264 GHashTable *table;
1266 rb_refstring_ref (entry->genre);
1267 rb_refstring_ref (entry->artist);
1268 rb_refstring_ref (entry->album);
1270 g_mutex_lock (db->priv->genres_lock);
1271 table = get_genres_hash_for_type (db, entry->type);
1272 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry), entry)) {
1273 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent,
1274 entry->album)) {
1276 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent,
1277 entry->artist)) {
1278 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent);
1279 g_assert (g_hash_table_remove (table, entry->genre));
1281 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent);
1284 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry));
1286 g_mutex_unlock (db->priv->genres_lock);
1288 rb_refstring_unref (entry->genre);
1289 rb_refstring_unref (entry->artist);
1290 rb_refstring_unref (entry->album);
1293 static gboolean
1294 rhythmdb_tree_entry_set (RhythmDB *adb,
1295 RhythmDBEntry *entry,
1296 guint propid,
1297 const GValue *value)
1299 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1300 RhythmDBEntryType type;
1302 type = entry->type;
1304 /* don't process changes to entries we're loading, we'll get them
1305 * when the entry is complete.
1307 if (entry->flags & RHYTHMDB_ENTRY_TREE_LOADING)
1308 return FALSE;
1310 /* Handle special properties */
1311 switch (propid)
1313 case RHYTHMDB_PROP_LOCATION:
1315 RBRefString *s;
1316 /* We have to use the string in the entry itself as the hash key,
1317 * otherwise either we leak it, or the string vanishes when the
1318 * GValue is freed; this means we have to do the entry modification
1319 * here, rather than letting rhythmdb_entry_set_internal do it.
1321 g_mutex_lock (db->priv->entries_lock);
1322 g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1324 s = rb_refstring_new (g_value_get_string (value));
1325 rb_refstring_unref (entry->location);
1326 entry->location = s;
1327 g_hash_table_insert (db->priv->entries, entry->location, entry);
1328 g_mutex_unlock (db->priv->entries_lock);
1330 return TRUE;
1332 case RHYTHMDB_PROP_ALBUM:
1334 const char *albumname = g_value_get_string (value);
1336 if (strcmp (rb_refstring_get (entry->album), albumname)) {
1337 RhythmDBTreeProperty *artist;
1338 RhythmDBTreeProperty *genre;
1340 rb_refstring_ref (entry->genre);
1341 rb_refstring_ref (entry->artist);
1342 rb_refstring_ref (entry->album);
1344 remove_entry_from_album (db, entry);
1346 genre = get_or_create_genre (db, type, entry->genre);
1347 artist = get_or_create_artist (db, genre, entry->artist);
1348 set_entry_album (db, entry, artist, rb_refstring_new (albumname));
1350 rb_refstring_unref (entry->genre);
1351 rb_refstring_unref (entry->artist);
1352 rb_refstring_unref (entry->album);
1354 break;
1356 case RHYTHMDB_PROP_ARTIST:
1358 const char *artistname = g_value_get_string (value);
1360 if (strcmp (rb_refstring_get (entry->artist), artistname)) {
1361 RhythmDBTreeProperty *new_artist;
1362 RhythmDBTreeProperty *genre;
1364 rb_refstring_ref (entry->genre);
1365 rb_refstring_ref (entry->artist);
1366 rb_refstring_ref (entry->album);
1368 remove_entry_from_album (db, entry);
1370 genre = get_or_create_genre (db, type, entry->genre);
1371 new_artist = get_or_create_artist (db, genre,
1372 rb_refstring_new (artistname));
1373 set_entry_album (db, entry, new_artist, entry->album);
1375 rb_refstring_unref (entry->genre);
1376 rb_refstring_unref (entry->artist);
1377 rb_refstring_unref (entry->album);
1379 break;
1381 case RHYTHMDB_PROP_GENRE:
1383 const char *genrename = g_value_get_string (value);
1385 if (strcmp (rb_refstring_get (entry->genre), genrename)) {
1386 RhythmDBTreeProperty *new_genre;
1387 RhythmDBTreeProperty *new_artist;
1389 rb_refstring_ref (entry->genre);
1390 rb_refstring_ref (entry->artist);
1391 rb_refstring_ref (entry->album);
1393 remove_entry_from_album (db, entry);
1395 new_genre = get_or_create_genre (db, type,
1396 rb_refstring_new (genrename));
1397 new_artist = get_or_create_artist (db, new_genre, entry->artist);
1398 set_entry_album (db, entry, new_artist, entry->album);
1400 rb_refstring_unref (entry->genre);
1401 rb_refstring_unref (entry->artist);
1402 rb_refstring_unref (entry->album);
1404 break;
1406 default:
1407 break;
1410 return FALSE;
1413 static void
1414 rhythmdb_tree_entry_delete (RhythmDB *adb,
1415 RhythmDBEntry *entry)
1417 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1419 remove_entry_from_album (db, entry);
1421 g_mutex_lock (db->priv->entries_lock);
1422 g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1423 rhythmdb_entry_unref (entry);
1424 g_mutex_unlock (db->priv->entries_lock);
1427 typedef struct {
1428 RhythmDB *db;
1429 RhythmDBEntryType type;
1430 } RbEntryRemovalCtxt;
1432 static gboolean
1433 remove_one_song (gpointer key,
1434 RhythmDBEntry *entry,
1435 RbEntryRemovalCtxt *ctxt)
1437 g_return_val_if_fail (entry != NULL, FALSE);
1439 if (entry->type == ctxt->type) {
1440 rhythmdb_emit_entry_deleted (ctxt->db, entry);
1441 remove_entry_from_album (RHYTHMDB_TREE (ctxt->db), entry);
1442 rhythmdb_entry_unref (entry);
1443 return TRUE;
1445 return FALSE;
1448 static void
1449 rhythmdb_tree_entry_delete_by_type (RhythmDB *adb,
1450 RhythmDBEntryType type)
1452 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1453 RbEntryRemovalCtxt ctxt;
1455 ctxt.db = adb;
1456 ctxt.type = type;
1457 g_mutex_lock (db->priv->entries_lock);
1458 g_hash_table_foreach_remove (db->priv->entries,
1459 (GHRFunc) remove_one_song, &ctxt);
1460 g_mutex_unlock (db->priv->entries_lock);
1463 static void
1464 destroy_tree_property (RhythmDBTreeProperty *prop)
1466 #ifndef G_DISABLE_ASSERT
1467 prop->magic = 0xf33df33d;
1468 #endif
1469 g_hash_table_destroy (prop->children);
1470 g_free (prop);
1473 typedef void (*RhythmDBTreeTraversalFunc) (RhythmDBTree *db, RhythmDBEntry *entry, gpointer data);
1474 typedef void (*RhythmDBTreeAlbumTraversalFunc) (RhythmDBTree *db, RhythmDBTreeProperty *album, gpointer data);
1476 struct RhythmDBTreeTraversalData
1478 RhythmDBTree *db;
1479 GPtrArray *query;
1480 RhythmDBTreeTraversalFunc func;
1481 gpointer data;
1482 gboolean *cancel;
1485 static gboolean
1486 rhythmdb_tree_evaluate_query (RhythmDB *adb,
1487 GPtrArray *query,
1488 RhythmDBEntry *entry)
1490 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1491 guint i;
1492 guint last_disjunction;
1494 for (i = 0, last_disjunction = 0; i < query->len; i++) {
1495 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1497 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1498 if (evaluate_conjunctive_subquery (db, query, last_disjunction, i, entry))
1499 return TRUE;
1501 last_disjunction = i + 1;
1504 if (evaluate_conjunctive_subquery (db, query, last_disjunction, query->len, entry))
1505 return TRUE;
1506 return FALSE;
1509 #define RHYTHMDB_PROPERTY_COMPARE(OP) \
1510 switch (rhythmdb_get_property_type (db, data->propid)) { \
1511 case G_TYPE_STRING: \
1512 if (strcmp (rhythmdb_entry_get_string (entry, data->propid), \
1513 g_value_get_string (data->val)) OP 0) \
1514 return FALSE; \
1515 break; \
1516 case G_TYPE_ULONG: \
1517 if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1518 g_value_get_ulong (data->val)) \
1519 return FALSE; \
1520 break; \
1521 case G_TYPE_BOOLEAN: \
1522 if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1523 g_value_get_boolean (data->val)) \
1524 return FALSE; \
1525 break; \
1526 case G_TYPE_UINT64: \
1527 if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1528 g_value_get_uint64 (data->val)) \
1529 return FALSE; \
1530 break; \
1531 case G_TYPE_DOUBLE: \
1532 if (rhythmdb_entry_get_double (entry, data->propid) OP \
1533 g_value_get_double (data->val)) \
1534 return FALSE; \
1535 break; \
1536 case G_TYPE_POINTER: \
1537 if (rhythmdb_entry_get_pointer (entry, data->propid) OP \
1538 g_value_get_pointer (data->val)) \
1539 return FALSE; \
1540 break; \
1541 default: \
1542 g_warning ("Unexpected type: %s", g_type_name (rhythmdb_get_property_type (db, data->propid))); \
1543 g_assert_not_reached (); \
1546 static gboolean
1547 search_match_properties (RhythmDB *db,
1548 RhythmDBEntry *entry,
1549 gchar **words)
1551 const RhythmDBPropType props[] = {
1552 RHYTHMDB_PROP_TITLE_FOLDED,
1553 RHYTHMDB_PROP_ALBUM_FOLDED,
1554 RHYTHMDB_PROP_ARTIST_FOLDED,
1555 RHYTHMDB_PROP_GENRE_FOLDED
1557 gboolean islike = TRUE;
1558 gchar **current;
1559 int i;
1561 for (current = words; *current != NULL; current++) {
1562 gboolean word_found = FALSE;
1564 for (i = 0; i < G_N_ELEMENTS (props); i++) {
1565 const char *entry_string = rhythmdb_entry_get_string (entry, props[i]);
1566 if (entry_string && (strstr (entry_string, *current) != NULL)) {
1567 /* the word was found, go to the next one */
1568 word_found = TRUE;
1569 break;
1572 if (!word_found) {
1573 /* the word wasn't in any of the properties*/
1574 islike = FALSE;
1575 break;
1579 return islike;
1582 static gboolean
1583 evaluate_conjunctive_subquery (RhythmDBTree *dbtree,
1584 GPtrArray *query,
1585 guint base,
1586 guint max,
1587 RhythmDBEntry *entry)
1590 RhythmDB *db = (RhythmDB *) dbtree;
1591 guint i;
1592 /* Optimization possibility - we may get here without actually having
1593 * anything in the query. It would be faster to instead just merge
1594 * the child hash table into the query result hash.
1596 for (i = base; i < max; i++) {
1597 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1599 switch (data->type) {
1600 case RHYTHMDB_QUERY_SUBQUERY:
1602 gboolean matched = FALSE;
1603 GList *conjunctions = split_query_by_disjunctions (dbtree, data->subquery);
1604 GList *tem;
1606 if (conjunctions == NULL)
1607 matched = TRUE;
1609 for (tem = conjunctions; tem; tem = tem->next) {
1610 GPtrArray *subquery = tem->data;
1611 if (!matched && evaluate_conjunctive_subquery (dbtree, subquery,
1612 0, subquery->len,
1613 entry)) {
1614 matched = TRUE;
1616 g_ptr_array_free (tem->data, TRUE);
1618 g_list_free (conjunctions);
1619 if (!matched)
1620 return FALSE;
1622 break;
1623 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
1624 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
1626 gulong relative_time;
1627 GTimeVal current_time;
1629 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_ULONG);
1631 relative_time = g_value_get_ulong (data->val);
1632 g_get_current_time (&current_time);
1634 if (data->type == RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN) {
1635 if (!(rhythmdb_entry_get_ulong (entry, data->propid) >= (current_time.tv_sec - relative_time)))
1636 return FALSE;
1637 } else {
1638 if (!(rhythmdb_entry_get_ulong (entry, data->propid) < (current_time.tv_sec - relative_time)))
1639 return FALSE;
1641 break;
1643 case RHYTHMDB_QUERY_PROP_PREFIX:
1644 case RHYTHMDB_QUERY_PROP_SUFFIX:
1646 const char *value_s;
1647 const char *entry_s;
1649 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING);
1651 value_s = g_value_get_string (data->val);
1652 entry_s = rhythmdb_entry_get_string (entry, data->propid);
1654 if (data->type == RHYTHMDB_QUERY_PROP_PREFIX && !g_str_has_prefix (entry_s, value_s))
1655 return FALSE;
1656 if (data->type == RHYTHMDB_QUERY_PROP_SUFFIX && !g_str_has_suffix (entry_s, value_s))
1657 return FALSE;
1659 break;
1661 case RHYTHMDB_QUERY_PROP_LIKE:
1662 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
1664 if (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING) {
1665 gboolean islike;
1667 if (data->propid == RHYTHMDB_PROP_SEARCH_MATCH) {
1668 /* this is a special property, that should match several things */
1669 islike = search_match_properties (db, entry, g_value_get_boxed (data->val));
1671 } else {
1672 const gchar *value_string = g_value_get_string (data->val);
1673 const char *entry_string = rhythmdb_entry_get_string (entry, data->propid);
1675 /* check in case the property is NULL, the value should never be NULL */
1676 if (entry_string == NULL)
1677 return FALSE;
1679 islike = (strstr (entry_string, value_string) != NULL);
1682 if ((data->type == RHYTHMDB_QUERY_PROP_LIKE) ^ islike)
1683 return FALSE;
1684 else
1685 continue;
1686 break;
1688 /* Fall through */
1690 case RHYTHMDB_QUERY_PROP_EQUALS:
1691 RHYTHMDB_PROPERTY_COMPARE (!=)
1692 break;
1693 case RHYTHMDB_QUERY_PROP_GREATER:
1694 RHYTHMDB_PROPERTY_COMPARE (<)
1695 break;
1696 case RHYTHMDB_QUERY_PROP_LESS:
1697 RHYTHMDB_PROPERTY_COMPARE (>)
1698 break;
1699 case RHYTHMDB_QUERY_END:
1700 case RHYTHMDB_QUERY_DISJUNCTION:
1701 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
1702 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
1703 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
1704 g_assert_not_reached ();
1705 break;
1708 return TRUE;
1711 static void
1712 do_conjunction (RhythmDBEntry *entry,
1713 gpointer unused,
1714 struct RhythmDBTreeTraversalData *data)
1716 if (G_UNLIKELY (*data->cancel))
1717 return;
1718 /* Finally, we actually evaluate the query! */
1719 if (evaluate_conjunctive_subquery (data->db, data->query, 0, data->query->len,
1720 entry)) {
1721 data->func (data->db, entry, data->data);
1725 static void
1726 conjunctive_query_songs (const char *name,
1727 RhythmDBTreeProperty *album,
1728 struct RhythmDBTreeTraversalData *data)
1730 if (G_UNLIKELY (*data->cancel))
1731 return;
1732 g_hash_table_foreach (album->children, (GHFunc) do_conjunction, data);
1735 static GPtrArray *
1736 clone_remove_ptr_array_index (GPtrArray *arr,
1737 guint index)
1739 GPtrArray *ret = g_ptr_array_new ();
1740 guint i;
1741 for (i = 0; i < arr->len; i++)
1742 if (i != index)
1743 g_ptr_array_add (ret, g_ptr_array_index (arr, i));
1745 return ret;
1748 static void
1749 conjunctive_query_albums (const char *name,
1750 RhythmDBTreeProperty *artist,
1751 struct RhythmDBTreeTraversalData *data)
1753 guint i;
1754 int album_query_idx = -1;
1756 if (G_UNLIKELY (*data->cancel))
1757 return;
1759 for (i = 0; i < data->query->len; i++) {
1760 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1761 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1762 && qdata->propid == RHYTHMDB_PROP_ALBUM) {
1763 if (album_query_idx > 0)
1764 return;
1765 album_query_idx = i;
1770 if (album_query_idx >= 0) {
1771 RhythmDBTreeProperty *album;
1772 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, album_query_idx);
1773 RBRefString *albumname = rb_refstring_new (g_value_get_string (qdata->val));
1774 GPtrArray *oldquery = data->query;
1776 data->query = clone_remove_ptr_array_index (data->query, album_query_idx);
1778 album = g_hash_table_lookup (artist->children, albumname);
1780 if (album != NULL) {
1781 conjunctive_query_songs (rb_refstring_get (albumname), album, data);
1783 g_ptr_array_free (data->query, TRUE);
1784 data->query = oldquery;
1785 return;
1788 g_hash_table_foreach (artist->children, (GHFunc) conjunctive_query_songs, data);
1791 static void
1792 conjunctive_query_artists (const char *name,
1793 RhythmDBTreeProperty *genre,
1794 struct RhythmDBTreeTraversalData *data)
1796 guint i;
1797 int artist_query_idx = -1;
1799 if (G_UNLIKELY (*data->cancel))
1800 return;
1802 for (i = 0; i < data->query->len; i++) {
1803 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1804 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1805 && qdata->propid == RHYTHMDB_PROP_ARTIST) {
1806 if (artist_query_idx > 0)
1807 return;
1808 artist_query_idx = i;
1813 if (artist_query_idx >= 0) {
1814 RhythmDBTreeProperty *artist;
1815 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, artist_query_idx);
1816 RBRefString *artistname = rb_refstring_new (g_value_get_string (qdata->val));
1817 GPtrArray *oldquery = data->query;
1819 data->query = clone_remove_ptr_array_index (data->query, artist_query_idx);
1821 artist = g_hash_table_lookup (genre->children, artistname);
1822 if (artist != NULL) {
1823 conjunctive_query_albums (rb_refstring_get (artistname), artist, data);
1825 g_ptr_array_free (data->query, TRUE);
1826 data->query = oldquery;
1827 return;
1830 g_hash_table_foreach (genre->children, (GHFunc) conjunctive_query_albums, data);
1833 static void
1834 conjunctive_query_genre (RhythmDBTree *db,
1835 GHashTable *genres,
1836 struct RhythmDBTreeTraversalData *data)
1838 int genre_query_idx = -1;
1839 guint i;
1841 if (G_UNLIKELY (*data->cancel))
1842 return;
1844 for (i = 0; i < data->query->len; i++) {
1845 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1846 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1847 && qdata->propid == RHYTHMDB_PROP_GENRE) {
1848 /* A song can't currently have two genres. So
1849 * if we get a conjunctive query for that, we
1850 * know the result must be the empty set. */
1851 if (genre_query_idx > 0)
1852 return;
1853 genre_query_idx = i;
1858 if (genre_query_idx >= 0) {
1859 RhythmDBTreeProperty *genre;
1860 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, genre_query_idx);
1861 RBRefString *genrename = rb_refstring_new (g_value_get_string (qdata->val));
1862 GPtrArray *oldquery = data->query;
1864 data->query = clone_remove_ptr_array_index (data->query, genre_query_idx);
1866 genre = g_hash_table_lookup (genres, genrename);
1867 if (genre != NULL) {
1868 conjunctive_query_artists (rb_refstring_get (genrename), genre, data);
1870 g_ptr_array_free (data->query, TRUE);
1871 data->query = oldquery;
1872 return;
1875 g_hash_table_foreach (genres, (GHFunc) conjunctive_query_artists, data);
1878 static void
1879 conjunctive_query (RhythmDBTree *db,
1880 GPtrArray *query,
1881 RhythmDBTreeTraversalFunc func,
1882 gpointer data,
1883 gboolean *cancel)
1885 int type_query_idx = -1;
1886 guint i;
1887 struct RhythmDBTreeTraversalData *traversal_data;
1889 for (i = 0; i < query->len; i++) {
1890 RhythmDBQueryData *qdata = g_ptr_array_index (query, i);
1891 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1892 && qdata->propid == RHYTHMDB_PROP_TYPE) {
1893 /* A song can't have two types. */
1894 if (type_query_idx > 0)
1895 return;
1896 type_query_idx = i;
1900 traversal_data = g_new (struct RhythmDBTreeTraversalData, 1);
1901 traversal_data->db = db;
1902 traversal_data->query = query;
1903 traversal_data->func = func;
1904 traversal_data->data = data;
1905 traversal_data->cancel = cancel;
1907 g_mutex_lock (db->priv->genres_lock);
1908 if (type_query_idx >= 0) {
1909 GHashTable *genres;
1910 RhythmDBEntryType etype;
1911 RhythmDBQueryData *qdata = g_ptr_array_index (query, type_query_idx);
1913 g_ptr_array_remove_index_fast (query, type_query_idx);
1915 etype = g_value_get_pointer (qdata->val);
1916 genres = get_genres_hash_for_type (db, etype);
1917 if (genres != NULL) {
1918 conjunctive_query_genre (db, genres, traversal_data);
1919 } else {
1920 g_assert_not_reached ();
1922 } else {
1923 /* FIXME */
1924 /* No type was given; punt and query everything */
1925 genres_hash_foreach (db, (RBHFunc)conjunctive_query_genre,
1926 traversal_data);
1928 g_mutex_unlock (db->priv->genres_lock);
1930 g_free (traversal_data);
1933 static GList *
1934 split_query_by_disjunctions (RhythmDBTree *db,
1935 GPtrArray *query)
1937 GList *conjunctions = NULL;
1938 guint i, j;
1939 guint last_disjunction = 0;
1940 GPtrArray *subquery = g_ptr_array_new ();
1942 for (i = 0; i < query->len; i++) {
1943 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1944 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1946 /* Copy the subquery */
1947 for (j = last_disjunction; j < i; j++) {
1948 g_ptr_array_add (subquery, g_ptr_array_index (query, j));
1951 conjunctions = g_list_prepend (conjunctions, subquery);
1952 last_disjunction = i+1;
1953 g_assert (subquery->len > 0);
1954 subquery = g_ptr_array_new ();
1958 /* Copy the last subquery, except for the QUERY_END */
1959 for (i = last_disjunction; i < query->len; i++) {
1960 g_ptr_array_add (subquery, g_ptr_array_index (query, i));
1963 if (subquery->len > 0)
1964 conjunctions = g_list_prepend (conjunctions, subquery);
1965 else
1966 g_ptr_array_free (subquery, TRUE);
1968 return conjunctions;
1971 struct RhythmDBTreeQueryGatheringData
1973 RhythmDBTree *db;
1974 GPtrArray *queue;
1975 GHashTable *entries;
1976 RhythmDBQueryResults *results;
1979 static void
1980 do_query_recurse (RhythmDBTree *db,
1981 GPtrArray *query,
1982 RhythmDBTreeTraversalFunc func,
1983 struct RhythmDBTreeQueryGatheringData *data,
1984 gboolean *cancel)
1986 GList *conjunctions, *tem;
1988 if (query == NULL)
1989 return;
1991 conjunctions = split_query_by_disjunctions (db, query);
1992 rb_debug ("doing recursive query, %d conjunctions", g_list_length (conjunctions));
1994 if (conjunctions == NULL)
1995 return;
1997 /* If there is a disjunction involved, we must uniquify the entry hits. */
1998 if (conjunctions->next != NULL)
1999 data->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
2000 else
2001 data->entries = NULL;
2003 for (tem = conjunctions; tem; tem = tem->next) {
2004 if (G_UNLIKELY (*cancel))
2005 break;
2006 conjunctive_query (db, tem->data, func, data, cancel);
2007 g_ptr_array_free (tem->data, TRUE);
2010 if (data->entries != NULL)
2011 g_hash_table_destroy (data->entries);
2013 g_list_free (conjunctions);
2016 static void
2017 handle_entry_match (RhythmDB *db,
2018 RhythmDBEntry *entry,
2019 struct RhythmDBTreeQueryGatheringData *data)
2022 if (data->entries
2023 && g_hash_table_lookup (data->entries, entry))
2024 return;
2026 g_ptr_array_add (data->queue, entry);
2027 if (data->queue->len > RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
2028 rhythmdb_query_results_add_results (data->results, data->queue);
2029 data->queue = g_ptr_array_new ();
2033 static void
2034 rhythmdb_tree_do_full_query (RhythmDB *adb,
2035 GPtrArray *query,
2036 RhythmDBQueryResults *results,
2037 gboolean *cancel)
2039 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2040 struct RhythmDBTreeQueryGatheringData *data = g_new0 (struct RhythmDBTreeQueryGatheringData, 1);
2042 data->results = results;
2043 data->queue = g_ptr_array_new ();
2045 do_query_recurse (db, query, (RhythmDBTreeTraversalFunc) handle_entry_match, data, cancel);
2047 rhythmdb_query_results_add_results (data->results, data->queue);
2049 g_free (data);
2052 static RhythmDBEntry *
2053 rhythmdb_tree_entry_lookup_by_location (RhythmDB *adb,
2054 RBRefString *uri)
2056 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2057 RhythmDBEntry *entry;
2059 g_mutex_lock (db->priv->entries_lock);
2060 entry = g_hash_table_lookup (db->priv->entries, uri);
2061 g_mutex_unlock (db->priv->entries_lock);
2063 return entry;
2066 struct RhythmDBEntryForeachCtxt
2068 RhythmDBTree *db;
2069 GFunc func;
2070 gpointer user_data;
2073 static void
2074 rhythmdb_tree_entry_foreach_func (gpointer key, RhythmDBEntry *val, GPtrArray *list)
2076 rhythmdb_entry_ref (val);
2077 g_ptr_array_add (list, val);
2080 static void
2081 rhythmdb_tree_entry_foreach (RhythmDB *rdb, GFunc foreach_func, gpointer user_data)
2083 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2084 GPtrArray *list;
2085 guint size, i;
2087 g_mutex_lock (db->priv->entries_lock);
2088 size = g_hash_table_size (db->priv->entries);
2089 list = g_ptr_array_sized_new (size);
2090 g_hash_table_foreach (db->priv->entries, (GHFunc)rhythmdb_tree_entry_foreach_func, list);
2091 g_mutex_unlock (db->priv->entries_lock);
2093 for (i = 0; i < size; i++) {
2094 RhythmDBEntry *entry = (RhythmDBEntry*)g_ptr_array_index (list, i);
2095 (*foreach_func) (entry, user_data);
2096 rhythmdb_entry_unref (entry);
2099 g_ptr_array_free (list, TRUE);
2103 struct HashTreeIteratorCtxt {
2104 RhythmDBTree *db;
2105 RBTreeEntryItFunc entry_func;
2106 RBTreePropertyItFunc album_func;
2107 RBTreePropertyItFunc artist_func;
2108 RBTreePropertyItFunc genres_func;
2109 gpointer data;
2112 static void
2113 hash_tree_entries_foreach (gpointer key,
2114 gpointer value,
2115 gpointer data)
2117 RhythmDBEntry *entry = (RhythmDBEntry *) key;
2118 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2120 g_assert (ctxt->entry_func);
2122 ctxt->entry_func (ctxt->db, entry, ctxt->data);
2125 static void
2126 hash_tree_albums_foreach (gpointer key,
2127 gpointer value,
2128 gpointer data)
2130 RhythmDBTreeProperty *album = (RhythmDBTreeProperty *)value;
2131 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2133 if (ctxt->album_func) {
2134 ctxt->album_func (ctxt->db, album, ctxt->data);
2136 if (ctxt->entry_func != NULL) {
2137 g_hash_table_foreach (album->children,
2138 hash_tree_entries_foreach,
2139 ctxt);
2143 static void
2144 hash_tree_artists_foreach (gpointer key,
2145 gpointer value,
2146 gpointer data)
2148 RhythmDBTreeProperty *artist = (RhythmDBTreeProperty *)value;
2149 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2151 if (ctxt->artist_func) {
2152 ctxt->artist_func (ctxt->db, artist, ctxt->data);
2154 if ((ctxt->album_func != NULL) || (ctxt->entry_func != NULL)) {
2155 g_hash_table_foreach (artist->children,
2156 hash_tree_albums_foreach,
2157 ctxt);
2161 static void
2162 hash_tree_genres_foreach (gpointer key,
2163 gpointer value,
2164 gpointer data)
2166 RhythmDBTreeProperty *genre = (RhythmDBTreeProperty *)value;
2167 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2169 if (ctxt->genres_func) {
2170 ctxt->genres_func (ctxt->db, genre, ctxt->data);
2173 if ((ctxt->album_func != NULL)
2174 || (ctxt->artist_func != NULL)
2175 || (ctxt->entry_func != NULL)) {
2176 g_hash_table_foreach (genre->children,
2177 hash_tree_artists_foreach,
2178 ctxt);
2182 static void
2183 rhythmdb_hash_tree_foreach (RhythmDB *adb,
2184 RhythmDBEntryType type,
2185 RBTreeEntryItFunc entry_func,
2186 RBTreePropertyItFunc album_func,
2187 RBTreePropertyItFunc artist_func,
2188 RBTreePropertyItFunc genres_func,
2189 gpointer data)
2191 struct HashTreeIteratorCtxt ctxt;
2192 GHashTable *table;
2194 ctxt.db = RHYTHMDB_TREE (adb);
2195 ctxt.album_func = album_func;
2196 ctxt.artist_func = artist_func;
2197 ctxt.genres_func = genres_func;
2198 ctxt.entry_func = entry_func;
2199 ctxt.data = data;
2201 g_mutex_lock (ctxt.db->priv->genres_lock);
2202 table = get_genres_hash_for_type (RHYTHMDB_TREE (adb), type);
2203 if (table == NULL) {
2204 return;
2206 if ((ctxt.album_func != NULL)
2207 || (ctxt.artist_func != NULL)
2208 || (ctxt.genres_func != NULL)
2209 || (ctxt.entry_func != NULL)) {
2210 g_hash_table_foreach (table, hash_tree_genres_foreach, &ctxt);
2212 g_mutex_unlock (ctxt.db->priv->genres_lock);
2215 static void
2216 rhythmdb_tree_entry_type_registered (RhythmDB *db,
2217 const char *name,
2218 RhythmDBEntryType entry_type)
2220 GList *entries = NULL;
2221 GList *e;
2222 gint count = 0;
2223 RhythmDBTree *rdb;
2224 RBRefString *rs_name;
2226 if (name == NULL)
2227 return;
2229 rdb = RHYTHMDB_TREE (db);
2230 rs_name = rb_refstring_find (name);
2231 if (rs_name)
2232 entries = g_hash_table_lookup (rdb->priv->unknown_entry_types, rs_name);
2233 if (entries == NULL) {
2234 rb_refstring_unref (rs_name);
2235 rb_debug ("no entries of newly registered type %s loaded from db", name);
2236 return;
2239 for (e = entries; e != NULL; e = e->next) {
2240 RhythmDBUnknownEntry *data;
2241 RhythmDBEntry *entry;
2242 GList *p;
2244 data = (RhythmDBUnknownEntry *)e->data;
2245 entry = rhythmdb_entry_allocate (db, entry_type);
2246 entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
2247 for (p = data->properties; p != NULL; p = p->next) {
2248 RhythmDBUnknownEntryProperty *prop;
2249 RhythmDBPropType propid;
2250 GValue value = {0,};
2252 prop = (RhythmDBUnknownEntryProperty *) p->data;
2253 propid = rhythmdb_propid_from_nice_elt_name (db, (const xmlChar *) rb_refstring_get (prop->name));
2255 rhythmdb_read_encoded_property (db, rb_refstring_get (prop->value), propid, &value);
2256 rhythmdb_entry_set_internal (db, entry, FALSE, propid, &value);
2257 g_value_unset (&value);
2259 rhythmdb_tree_entry_new_internal (db, entry);
2260 rhythmdb_entry_insert (db, entry);
2261 count++;
2263 rb_debug ("handled %d entries of newly registered type %s", count, name);
2264 rhythmdb_commit (db);
2266 g_hash_table_remove (rdb->priv->unknown_entry_types, rs_name);
2267 free_unknown_entries (rs_name, entries, NULL);
2268 rb_refstring_unref (rs_name);