udapted vi.po
[rhythmbox.git] / rhythmdb / rhythmdb-tree.c
blob051eb1119cc213a3b456babafc83dbe269ee16fb
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 gboolean rhythmdb_tree_load (RhythmDB *rdb, gboolean *die, GError **error);
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 RhythmDBEntry * rhythmdb_tree_entry_lookup_by_id (RhythmDB *db, gint id);
78 static void rhythmdb_tree_entry_foreach (RhythmDB *adb, GFunc func, gpointer user_data);
79 static gint64 rhythmdb_tree_entry_count (RhythmDB *adb);
80 static void rhythmdb_tree_entry_foreach_by_type (RhythmDB *adb, RhythmDBEntryType type, GFunc func, gpointer user_data);
81 static gint64 rhythmdb_tree_entry_count_by_type (RhythmDB *adb, RhythmDBEntryType type);
82 static void rhythmdb_tree_do_full_query (RhythmDB *db, GPtrArray *query,
83 RhythmDBQueryResults *results,
84 gboolean *cancel);
85 static gboolean rhythmdb_tree_evaluate_query (RhythmDB *adb, GPtrArray *query,
86 RhythmDBEntry *aentry);
87 static void rhythmdb_tree_entry_type_registered (RhythmDB *db,
88 const char *name,
89 RhythmDBEntryType type);
91 typedef void (*RBTreeEntryItFunc)(RhythmDBTree *db,
92 RhythmDBEntry *entry,
93 gpointer data);
95 typedef void (*RBTreePropertyItFunc)(RhythmDBTree *db,
96 RhythmDBTreeProperty *property,
97 gpointer data);
98 static void rhythmdb_hash_tree_foreach (RhythmDB *adb,
99 RhythmDBEntryType type,
100 RBTreeEntryItFunc entry_func,
101 RBTreePropertyItFunc album_func,
102 RBTreePropertyItFunc artist_func,
103 RBTreePropertyItFunc genres_func,
104 gpointer data);
106 #define RHYTHMDB_TREE_XML_VERSION "1.3"
108 static void destroy_tree_property (RhythmDBTreeProperty *prop);
109 static RhythmDBTreeProperty *get_or_create_album (RhythmDBTree *db, RhythmDBTreeProperty *artist,
110 RBRefString *name);
111 static RhythmDBTreeProperty *get_or_create_artist (RhythmDBTree *db, RhythmDBTreeProperty *genre,
112 RBRefString *name);
113 static RhythmDBTreeProperty *get_or_create_genre (RhythmDBTree *db, RhythmDBEntryType type,
114 RBRefString *name);
116 static void remove_entry_from_album (RhythmDBTree *db, RhythmDBEntry *entry);
118 static GList *split_query_by_disjunctions (RhythmDBTree *db, GPtrArray *query);
119 static gboolean evaluate_conjunctive_subquery (RhythmDBTree *db, GPtrArray *query,
120 guint base, guint max, RhythmDBEntry *entry);
122 struct RhythmDBTreePrivate
124 GHashTable *entries;
125 GHashTable *entry_ids;
126 GMutex *entries_lock;
128 GHashTable *genres;
129 GHashTable *unknown_entry_types;
130 GMutex *genres_lock;
131 gboolean finalizing;
133 guint idle_load_id;
136 typedef struct
138 RBRefString *name;
139 RBRefString *value;
140 } RhythmDBUnknownEntryProperty;
142 typedef struct
144 RBRefString *typename;
145 GList *properties;
146 } RhythmDBUnknownEntry;
148 #define RHYTHMDB_TREE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_TREE, RhythmDBTreePrivate))
150 enum
152 PROP_0,
155 const int RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE = 512;
157 GQuark
158 rhythmdb_tree_error_quark (void)
160 static GQuark quark;
161 if (!quark)
162 quark = g_quark_from_static_string ("rhythmdb_tree_error");
164 return quark;
167 static void
168 rhythmdb_tree_class_init (RhythmDBTreeClass *klass)
170 GObjectClass *object_class = G_OBJECT_CLASS (klass);
171 RhythmDBClass *rhythmdb_class = RHYTHMDB_CLASS (klass);
173 object_class->finalize = rhythmdb_tree_finalize;
175 rhythmdb_class->impl_load = rhythmdb_tree_load;
176 rhythmdb_class->impl_save = rhythmdb_tree_save;
177 rhythmdb_class->impl_entry_new = rhythmdb_tree_entry_new;
178 rhythmdb_class->impl_entry_set = rhythmdb_tree_entry_set;
179 rhythmdb_class->impl_entry_delete = rhythmdb_tree_entry_delete;
180 rhythmdb_class->impl_entry_delete_by_type = rhythmdb_tree_entry_delete_by_type;
181 rhythmdb_class->impl_lookup_by_location = rhythmdb_tree_entry_lookup_by_location;
182 rhythmdb_class->impl_lookup_by_id = rhythmdb_tree_entry_lookup_by_id;
183 rhythmdb_class->impl_entry_foreach = rhythmdb_tree_entry_foreach;
184 rhythmdb_class->impl_entry_count = rhythmdb_tree_entry_count;
185 rhythmdb_class->impl_entry_foreach_by_type = rhythmdb_tree_entry_foreach_by_type;
186 rhythmdb_class->impl_entry_count_by_type = rhythmdb_tree_entry_count_by_type;
187 rhythmdb_class->impl_evaluate_query = rhythmdb_tree_evaluate_query;
188 rhythmdb_class->impl_do_full_query = rhythmdb_tree_do_full_query;
189 rhythmdb_class->impl_entry_type_registered = rhythmdb_tree_entry_type_registered;
191 g_type_class_add_private (klass, sizeof (RhythmDBTreePrivate));
194 static void
195 rhythmdb_tree_init (RhythmDBTree *db)
197 db->priv = RHYTHMDB_TREE_GET_PRIVATE (db);
199 db->priv->entries = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
201 db->priv->entry_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
203 db->priv->genres = g_hash_table_new_full (g_direct_hash, g_direct_equal,
204 NULL, (GDestroyNotify)g_hash_table_destroy);
205 db->priv->unknown_entry_types = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
207 db->priv->entries_lock = g_mutex_new();
208 db->priv->genres_lock = g_mutex_new();
211 static void
212 unparent_entries (gpointer key,
213 RhythmDBEntry *entry,
214 RhythmDBTree *db)
216 remove_entry_from_album (db, entry);
219 static void
220 free_unknown_entries (RBRefString *name,
221 GList *entries,
222 gpointer nah)
224 GList *e;
225 for (e = entries; e != NULL; e = e->next) {
226 RhythmDBUnknownEntry *entry;
227 GList *p;
229 entry = (RhythmDBUnknownEntry *)e->data;
230 rb_refstring_unref (entry->typename);
231 for (p = entry->properties; p != NULL; p = p->next) {
232 RhythmDBUnknownEntryProperty *prop;
234 prop = (RhythmDBUnknownEntryProperty *)p->data;
235 rb_refstring_unref (prop->name);
236 rb_refstring_unref (prop->value);
237 g_free (prop);
240 g_list_free (entry->properties);
242 g_list_free (entries);
245 static void
246 rhythmdb_tree_finalize (GObject *object)
248 RhythmDBTree *db;
250 g_return_if_fail (object != NULL);
251 g_return_if_fail (RHYTHMDB_IS_TREE (object));
253 db = RHYTHMDB_TREE (object);
255 g_return_if_fail (db->priv != NULL);
257 db->priv->finalizing = TRUE;
259 g_hash_table_foreach (db->priv->entries, (GHFunc) unparent_entries, db);
260 g_hash_table_destroy (db->priv->entries);
261 g_hash_table_destroy (db->priv->entry_ids);
262 g_mutex_free (db->priv->entries_lock);
264 g_hash_table_destroy (db->priv->genres);
265 g_mutex_free (db->priv->genres_lock);
267 g_hash_table_foreach (db->priv->unknown_entry_types,
268 (GHFunc) free_unknown_entries,
269 NULL);
270 g_hash_table_destroy (db->priv->unknown_entry_types);
272 G_OBJECT_CLASS (rhythmdb_tree_parent_class)->finalize (object);
275 struct RhythmDBTreeLoadContext
277 RhythmDBTree *db;
278 xmlParserCtxtPtr xmlctx;
279 gboolean *die;
280 enum {
281 RHYTHMDB_TREE_PARSER_STATE_START,
282 RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB,
283 RHYTHMDB_TREE_PARSER_STATE_ENTRY,
284 RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY,
285 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY,
286 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY,
287 RHYTHMDB_TREE_PARSER_STATE_END,
288 } state;
289 guint in_unknown_elt;
290 RhythmDBEntry *entry;
291 RhythmDBUnknownEntry *unknown_entry;
292 GString *buf;
293 RhythmDBPropType propid;
294 gint batch_count;
295 GError **error;
297 /* updating */
298 gboolean has_date;
299 gboolean canonicalise_uris;
300 gboolean reload_all_metadata;
303 static void
304 rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext *ctx,
305 const char *name,
306 const char **attrs)
308 if (*ctx->die == TRUE) {
309 xmlStopParser (ctx->xmlctx);
310 return;
313 if (ctx->in_unknown_elt) {
314 ctx->in_unknown_elt++;
315 return;
318 switch (ctx->state)
320 case RHYTHMDB_TREE_PARSER_STATE_START:
322 if (!strcmp (name, "rhythmdb")) {
323 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
324 for (; *attrs; attrs +=2) {
325 if (!strcmp (*attrs, "version")) {
326 const char *version = *(attrs+1);
328 if (!strcmp (version, "1.0") || !strcmp (version, "1.1")) {
329 ctx->canonicalise_uris = TRUE;
330 rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries");
331 } else if (!strcmp (version, "1.2")) {
332 /* current version*/
333 rb_debug ("reloading all file metadata to get MusicBrainz tags");
334 ctx->reload_all_metadata = TRUE;
335 } else if (!strcmp (version, "1.3")) {
336 /* current version*/
337 } else {
338 g_set_error (ctx->error,
339 RHYTHMDB_TREE_ERROR,
340 RHYTHMDB_TREE_ERROR_DATABASE_TOO_NEW,
341 _("The database was created by a later version of rhythmbox."
342 " This version of rhythmbox cannot read the database."));
343 xmlStopParser (ctx->xmlctx);
345 } else {
346 g_assert_not_reached ();
350 } else {
351 ctx->in_unknown_elt++;
354 break;
356 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
358 if (!strcmp (name, "entry")) {
359 RhythmDBEntryType type = RHYTHMDB_ENTRY_TYPE_INVALID;
360 const char *typename = NULL;
361 for (; *attrs; attrs +=2) {
362 if (!strcmp (*attrs, "type")) {
363 typename = *(attrs+1);
364 type = rhythmdb_entry_type_get_by_name (RHYTHMDB (ctx->db), typename);
365 break;
369 g_assert (typename);
370 if (type != RHYTHMDB_ENTRY_TYPE_INVALID) {
371 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
372 ctx->entry = rhythmdb_entry_allocate (RHYTHMDB (ctx->db), type);
373 ctx->entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
374 ctx->has_date = FALSE;
375 } else {
376 rb_debug ("reading unknown entry");
377 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
378 ctx->unknown_entry = g_new0 (RhythmDBUnknownEntry, 1);
379 ctx->unknown_entry->typename = rb_refstring_new (typename);
381 } else {
382 ctx->in_unknown_elt++;
384 break;
386 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
388 int val = rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx->db), BAD_CAST name);
389 if (val < 0) {
390 ctx->in_unknown_elt++;
391 break;
394 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY;
395 ctx->propid = val;
396 g_string_truncate (ctx->buf, 0);
397 break;
399 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
401 RhythmDBUnknownEntryProperty *prop;
403 prop = g_new0 (RhythmDBUnknownEntryProperty, 1);
404 prop->name = rb_refstring_new (name);
406 ctx->unknown_entry->properties = g_list_prepend (ctx->unknown_entry->properties, prop);
407 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY;
408 g_string_truncate (ctx->buf, 0);
409 break;
411 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
412 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
413 case RHYTHMDB_TREE_PARSER_STATE_END:
414 break;
418 static void
419 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext *ctx,
420 const char *name)
422 if (*ctx->die == TRUE) {
423 xmlStopParser (ctx->xmlctx);
424 return;
427 if (ctx->in_unknown_elt) {
428 ctx->in_unknown_elt--;
429 return;
432 switch (ctx->state)
434 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
435 ctx->state = RHYTHMDB_TREE_PARSER_STATE_END;
436 break;
437 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
439 if (!ctx->has_date | ctx->reload_all_metadata) {
440 /* there is no date metadata, so this is from an old version
441 * reset the last-modified timestamp, so that the file is re-read
443 rb_debug ("pre-Date entry found, causing re-read");
444 ctx->entry->mtime = 0;
446 if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
447 RhythmDBPodcastFields *podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (ctx->entry, RhythmDBPodcastFields);
448 /* Handle upgrades from 0.9.2.
449 * Previously, last-seen for podcast feeds was the time of the last post,
450 * and post-time was unused. Now, we want last-seen to be the time we
451 * last updated the feed, and post-time to be the time of the last post.
453 if (podcast->post_time == 0) {
454 podcast->post_time = ctx->entry->last_seen;
458 if (ctx->entry->location != NULL) {
459 RhythmDBEntry *entry;
461 g_mutex_lock (ctx->db->priv->entries_lock);
462 entry = g_hash_table_lookup (ctx->db->priv->entries, ctx->entry->location);
463 if (entry == NULL) {
464 rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx->db), ctx->entry);
465 rhythmdb_entry_insert (RHYTHMDB (ctx->db), ctx->entry);
466 if (++ctx->batch_count == RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
467 rhythmdb_commit (RHYTHMDB (ctx->db));
468 ctx->batch_count = 0;
470 } else {
471 rb_debug ("found entry with duplicate location %s. merging metadata",
472 rb_refstring_get (ctx->entry->location));
473 entry->play_count += ctx->entry->play_count;
475 if (entry->rating < 0.01)
476 entry->rating = ctx->entry->rating;
477 else if (ctx->entry->rating > 0.01)
478 entry->rating = (entry->rating + ctx->entry->rating) / 2;
480 if (ctx->entry->last_played > entry->last_played)
481 entry->last_played = ctx->entry->last_played;
483 if (ctx->entry->first_seen < entry->first_seen)
484 entry->first_seen = ctx->entry->first_seen;
486 if (ctx->entry->last_seen > entry->last_seen)
487 entry->last_seen = ctx->entry->last_seen;
489 rhythmdb_entry_unref (ctx->entry);
491 g_mutex_unlock (ctx->db->priv->entries_lock);
492 } else {
493 rb_debug ("found entry without location");
494 rhythmdb_entry_unref (ctx->entry);
496 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
497 ctx->entry = NULL;
498 break;
500 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
502 GList *entry_list;
504 rb_debug ("finished reading unknown entry");
505 ctx->unknown_entry->properties = g_list_reverse (ctx->unknown_entry->properties);
507 entry_list = g_hash_table_lookup (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename);
508 entry_list = g_list_prepend (entry_list, ctx->unknown_entry);
509 g_hash_table_insert (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename, entry_list);
511 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
512 ctx->unknown_entry = NULL;
513 break;
515 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
517 GValue value = {0,};
518 gboolean set = FALSE;
519 gboolean skip = FALSE;
521 /* special case some properties for upgrade handling etc. */
522 switch (ctx->propid) {
523 case RHYTHMDB_PROP_DATE:
524 ctx->has_date = TRUE;
525 break;
526 case RHYTHMDB_PROP_LOCATION:
527 if (ctx->canonicalise_uris) {
528 char *canon = rb_canonicalise_uri (ctx->buf->str);
530 g_value_init (&value, G_TYPE_STRING);
531 g_value_take_string (&value, canon);
532 set = TRUE;
534 break;
535 case RHYTHMDB_PROP_MOUNTPOINT:
536 /* fix old podcast posts */
537 if (g_str_has_prefix (ctx->buf->str, "http://"))
538 skip = TRUE;
539 break;
540 default:
541 break;
544 if (!skip) {
545 if (!set) {
546 rhythmdb_read_encoded_property (RHYTHMDB (ctx->db), ctx->buf->str, ctx->propid, &value);
549 rhythmdb_entry_set_internal (RHYTHMDB (ctx->db), ctx->entry, FALSE, ctx->propid, &value);
550 g_value_unset (&value);
553 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
554 break;
556 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
558 RhythmDBUnknownEntryProperty *prop;
560 g_assert (ctx->unknown_entry->properties);
561 prop = ctx->unknown_entry->properties->data;
562 g_assert (prop->value == NULL);
563 prop->value = rb_refstring_new (ctx->buf->str);
564 rb_debug ("unknown entry property: %s = %s", rb_refstring_get (prop->name), rb_refstring_get (prop->value));
566 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
567 break;
569 case RHYTHMDB_TREE_PARSER_STATE_START:
570 case RHYTHMDB_TREE_PARSER_STATE_END:
571 break;
575 static void
576 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext *ctx,
577 const char *data,
578 guint len)
580 if (*ctx->die == TRUE) {
581 xmlStopParser (ctx->xmlctx);
582 return;
585 switch (ctx->state)
587 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
588 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
589 g_string_append_len (ctx->buf, data, len);
590 break;
591 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
592 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
593 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
594 case RHYTHMDB_TREE_PARSER_STATE_START:
595 case RHYTHMDB_TREE_PARSER_STATE_END:
596 break;
600 static gboolean
601 rhythmdb_tree_load (RhythmDB *rdb,
602 gboolean *die,
603 GError **error)
605 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
606 xmlParserCtxtPtr ctxt;
607 xmlSAXHandlerPtr sax_handler;
608 struct RhythmDBTreeLoadContext *ctx;
609 char *name;
610 GError *local_error;
611 gboolean ret;
613 local_error = NULL;
615 sax_handler = g_new0 (xmlSAXHandler, 1);
616 ctx = g_new0 (struct RhythmDBTreeLoadContext, 1);
618 sax_handler->startElement = (startElementSAXFunc) rhythmdb_tree_parser_start_element;
619 sax_handler->endElement = (endElementSAXFunc) rhythmdb_tree_parser_end_element;
620 sax_handler->characters = (charactersSAXFunc) rhythmdb_tree_parser_characters;
622 ctx->state = RHYTHMDB_TREE_PARSER_STATE_START;
623 ctx->db = db;
624 ctx->die = die;
625 ctx->buf = g_string_sized_new (RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE);
626 ctx->error = &local_error;
628 g_object_get (G_OBJECT (db), "name", &name, NULL);
630 if (g_file_test (name, G_FILE_TEST_EXISTS)) {
631 ctxt = xmlCreateFileParserCtxt (name);
632 ctx->xmlctx = ctxt;
633 xmlFree (ctxt->sax);
634 ctxt->userData = ctx;
635 ctxt->sax = sax_handler;
636 xmlParseDocument (ctxt);
637 ctxt->sax = NULL;
638 xmlFreeParserCtxt (ctxt);
640 if (ctx->batch_count)
641 rhythmdb_commit (RHYTHMDB (ctx->db));
644 ret = TRUE;
645 if (local_error != NULL) {
646 g_propagate_error (error, local_error);
647 ret = FALSE;
650 g_string_free (ctx->buf, TRUE);
651 g_free (name);
652 g_free (sax_handler);
653 g_free (ctx);
655 return ret;
658 struct RhythmDBTreeSaveContext
660 RhythmDBTree *db;
661 FILE *handle;
662 char *error;
665 #ifdef HAVE_GNU_FWRITE_UNLOCKED
666 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
667 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
668 #else
669 #define RHYTHMDB_FWRITE_REAL fwrite
670 #define RHYTHMDB_FPUTC_REAL fputc
671 #endif
673 #define RHYTHMDB_FWRITE(w,x,len,handle,error) do { \
674 if (error == NULL) { \
675 if (RHYTHMDB_FWRITE_REAL (w,x,len,handle) != len) { \
676 error = g_strdup (g_strerror (errno)); \
679 } while (0)
681 #define RHYTHMDB_FPUTC(x,handle,error) do { \
682 if (error == NULL) { \
683 if (RHYTHMDB_FPUTC_REAL (x,handle) == EOF) { \
684 error = g_strdup (g_strerror (errno)); \
687 } while (0)
689 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
691 static void
692 write_elt_name_open (struct RhythmDBTreeSaveContext *ctx,
693 const xmlChar *elt_name)
695 RHYTHMDB_FWRITE_STATICSTR (" <", ctx->handle, ctx->error);
696 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
697 RHYTHMDB_FPUTC ('>', ctx->handle, ctx->error);
700 static void
701 write_elt_name_close (struct RhythmDBTreeSaveContext *ctx,
702 const xmlChar *elt_name)
704 RHYTHMDB_FWRITE_STATICSTR ("</", ctx->handle, ctx->error);
705 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
706 RHYTHMDB_FWRITE_STATICSTR (">\n", ctx->handle, ctx->error);
709 static void
710 save_entry_string (struct RhythmDBTreeSaveContext *ctx,
711 const xmlChar *elt_name,
712 const char *str)
714 xmlChar *encoded;
716 g_return_if_fail (str != NULL);
717 write_elt_name_open (ctx, elt_name);
718 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST str);
719 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
720 g_free (encoded);
721 write_elt_name_close (ctx, elt_name);
724 static void
725 save_entry_int (struct RhythmDBTreeSaveContext *ctx,
726 const xmlChar *elt_name,
727 int num)
729 char buf[92];
730 if (num == 0)
731 return;
732 write_elt_name_open (ctx, elt_name);
733 g_snprintf (buf, sizeof (buf), "%d", 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_ulong (struct RhythmDBTreeSaveContext *ctx,
740 const xmlChar *elt_name,
741 gulong num,
742 gboolean save_zeroes)
744 char buf[92];
746 if (num == 0 && !save_zeroes)
747 return;
748 write_elt_name_open (ctx, elt_name);
749 g_snprintf (buf, sizeof (buf), "%lu", num);
750 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
751 write_elt_name_close (ctx, elt_name);
754 static void
755 save_entry_boolean (struct RhythmDBTreeSaveContext *ctx,
756 const xmlChar *elt_name,
757 gboolean val)
759 save_entry_ulong (ctx, elt_name, val ? 1 : 0, FALSE);
762 static void
763 save_entry_uint64 (struct RhythmDBTreeSaveContext *ctx,
764 const xmlChar *elt_name,
765 guint64 num)
767 char buf[92];
769 if (num == 0)
770 return;
772 write_elt_name_open (ctx, elt_name);
773 g_snprintf (buf, sizeof (buf), "%" G_GUINT64_FORMAT, num);
774 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
775 write_elt_name_close (ctx, elt_name);
778 static void
779 save_entry_double (struct RhythmDBTreeSaveContext *ctx,
780 const xmlChar *elt_name,
781 double num)
783 char buf[G_ASCII_DTOSTR_BUF_SIZE+1];
785 if (num > -0.001 && num < 0.001)
786 return;
788 write_elt_name_open (ctx, elt_name);
789 g_ascii_dtostr (buf, sizeof (buf), num);
790 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
791 write_elt_name_close (ctx, elt_name);
794 /* This code is intended to be highly optimized. This came at a small
795 * readability cost. Sorry about that.
797 static void
798 save_entry (RhythmDBTree *db,
799 RhythmDBEntry *entry,
800 struct RhythmDBTreeSaveContext *ctx)
802 RhythmDBPropType i;
803 RhythmDBPodcastFields *podcast = NULL;
804 xmlChar *encoded;
806 if (ctx->error)
807 return;
809 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
810 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
811 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
813 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
814 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST entry->type->name);
815 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
816 g_free (encoded);
818 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
820 /* Skip over the first property - the type */
821 for (i = 1; i < RHYTHMDB_NUM_PROPERTIES; i++) {
822 const xmlChar *elt_name;
824 if (ctx->error)
825 return;
827 elt_name = rhythmdb_nice_elt_name_from_propid ((RhythmDB *) ctx->db, i);
829 switch (i) {
830 case RHYTHMDB_PROP_TYPE:
831 break;
832 case RHYTHMDB_PROP_ENTRY_ID:
833 break;
834 case RHYTHMDB_PROP_TITLE:
835 save_entry_string(ctx, elt_name, rb_refstring_get (entry->title));
836 break;
837 case RHYTHMDB_PROP_ALBUM:
838 save_entry_string(ctx, elt_name, rb_refstring_get (entry->album));
839 break;
840 case RHYTHMDB_PROP_ARTIST:
841 save_entry_string(ctx, elt_name, rb_refstring_get (entry->artist));
842 break;
843 case RHYTHMDB_PROP_GENRE:
844 save_entry_string(ctx, elt_name, rb_refstring_get (entry->genre));
845 break;
846 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
847 save_entry_string(ctx, elt_name, rb_refstring_get (entry->musicbrainz_trackid));
848 break;
849 case RHYTHMDB_PROP_TRACK_NUMBER:
850 save_entry_ulong (ctx, elt_name, entry->tracknum, FALSE);
851 break;
852 case RHYTHMDB_PROP_DISC_NUMBER:
853 save_entry_ulong (ctx, elt_name, entry->discnum, FALSE);
854 break;
855 case RHYTHMDB_PROP_DATE:
856 if (g_date_valid (&entry->date))
857 save_entry_ulong (ctx, elt_name, g_date_get_julian (&entry->date), TRUE);
858 else
859 save_entry_ulong (ctx, elt_name, 0, TRUE);
860 break;
861 case RHYTHMDB_PROP_DURATION:
862 save_entry_ulong (ctx, elt_name, entry->duration, FALSE);
863 break;
864 case RHYTHMDB_PROP_BITRATE:
865 save_entry_int(ctx, elt_name, entry->bitrate);
866 break;
867 case RHYTHMDB_PROP_TRACK_GAIN:
868 save_entry_double(ctx, elt_name, entry->track_gain);
869 break;
870 case RHYTHMDB_PROP_TRACK_PEAK:
871 save_entry_double(ctx, elt_name, entry->track_peak);
872 break;
873 case RHYTHMDB_PROP_ALBUM_GAIN:
874 save_entry_double(ctx, elt_name, entry->album_gain);
875 break;
876 case RHYTHMDB_PROP_ALBUM_PEAK:
877 save_entry_double(ctx, elt_name, entry->album_peak);
878 break;
879 case RHYTHMDB_PROP_LOCATION:
880 save_entry_string(ctx, elt_name, rb_refstring_get (entry->location));
881 break;
882 case RHYTHMDB_PROP_MOUNTPOINT:
883 /* Avoid crashes on exit when upgrading from 0.8
884 * and no mountpoint is available from some entries */
885 if (entry->mountpoint) {
886 save_entry_string(ctx, elt_name, rb_refstring_get (entry->mountpoint));
888 break;
889 case RHYTHMDB_PROP_FILE_SIZE:
890 save_entry_uint64(ctx, elt_name, entry->file_size);
891 break;
892 case RHYTHMDB_PROP_MIMETYPE:
893 save_entry_string(ctx, elt_name, rb_refstring_get (entry->mimetype));
894 break;
895 case RHYTHMDB_PROP_MTIME:
896 save_entry_ulong (ctx, elt_name, entry->mtime, FALSE);
897 break;
898 case RHYTHMDB_PROP_FIRST_SEEN:
899 save_entry_ulong (ctx, elt_name, entry->first_seen, FALSE);
900 break;
901 case RHYTHMDB_PROP_LAST_SEEN:
902 save_entry_ulong (ctx, elt_name, entry->last_seen, FALSE);
903 break;
904 case RHYTHMDB_PROP_RATING:
905 save_entry_double(ctx, elt_name, entry->rating);
906 break;
907 case RHYTHMDB_PROP_PLAY_COUNT:
908 save_entry_ulong (ctx, elt_name, entry->play_count, FALSE);
909 break;
910 case RHYTHMDB_PROP_LAST_PLAYED:
911 save_entry_ulong (ctx, elt_name, entry->last_played, FALSE);
912 break;
913 case RHYTHMDB_PROP_HIDDEN:
915 gboolean hidden = ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
916 save_entry_boolean (ctx, elt_name, hidden);
918 break;
919 case RHYTHMDB_PROP_STATUS:
920 if (podcast)
921 save_entry_ulong (ctx, elt_name, podcast->status, FALSE);
922 break;
923 case RHYTHMDB_PROP_DESCRIPTION:
924 if (podcast && podcast->description)
925 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->description));
926 break;
927 case RHYTHMDB_PROP_SUBTITLE:
928 if (podcast && podcast->subtitle)
929 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->subtitle));
930 break;
931 case RHYTHMDB_PROP_SUMMARY:
932 if (podcast && podcast->summary)
933 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->summary));
934 break;
935 case RHYTHMDB_PROP_LANG:
936 if (podcast && podcast->lang)
937 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->lang));
938 break;
939 case RHYTHMDB_PROP_COPYRIGHT:
940 if (podcast && podcast->copyright)
941 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->copyright));
942 break;
943 case RHYTHMDB_PROP_IMAGE:
944 if (podcast && podcast->image)
945 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->image));
946 break;
947 case RHYTHMDB_PROP_POST_TIME:
948 if (podcast)
949 save_entry_ulong (ctx, elt_name, podcast->post_time, FALSE);
950 break;
951 case RHYTHMDB_PROP_TITLE_SORT_KEY:
952 case RHYTHMDB_PROP_GENRE_SORT_KEY:
953 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
954 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
955 case RHYTHMDB_PROP_TITLE_FOLDED:
956 case RHYTHMDB_PROP_GENRE_FOLDED:
957 case RHYTHMDB_PROP_ARTIST_FOLDED:
958 case RHYTHMDB_PROP_ALBUM_FOLDED:
959 case RHYTHMDB_PROP_LAST_PLAYED_STR:
960 case RHYTHMDB_PROP_PLAYBACK_ERROR:
961 case RHYTHMDB_PROP_FIRST_SEEN_STR:
962 case RHYTHMDB_PROP_LAST_SEEN_STR:
963 case RHYTHMDB_PROP_SEARCH_MATCH:
964 case RHYTHMDB_PROP_YEAR:
965 case RHYTHMDB_NUM_PROPERTIES:
966 break;
970 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
973 static void
974 save_entry_type (const char *name,
975 RhythmDBEntryType entry_type,
976 struct RhythmDBTreeSaveContext *ctx)
978 if (entry_type->save_to_disk == FALSE)
979 return;
981 rb_debug ("saving entries of type %s", name);
982 rhythmdb_hash_tree_foreach (RHYTHMDB (ctx->db), entry_type,
983 (RBTreeEntryItFunc) save_entry,
984 NULL, NULL, NULL, ctx);
987 static void
988 save_unknown_entry_type (RBRefString *typename,
989 GList *entries,
990 struct RhythmDBTreeSaveContext *ctx)
992 GList *t;
994 for (t = entries; t != NULL; t = t->next) {
995 RhythmDBUnknownEntry *entry;
996 xmlChar *encoded;
997 GList *p;
999 if (ctx->error)
1000 return;
1002 entry = (RhythmDBUnknownEntry *)t->data;
1004 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
1005 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST rb_refstring_get (entry->typename));
1006 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
1007 g_free (encoded);
1009 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
1011 for (p = entry->properties; p != NULL; p = p->next) {
1012 RhythmDBUnknownEntryProperty *prop;
1013 prop = (RhythmDBUnknownEntryProperty *) p->data;
1014 save_entry_string(ctx, (const xmlChar *)rb_refstring_get (prop->name), rb_refstring_get (prop->value));
1017 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
1021 static void
1022 rhythmdb_tree_save (RhythmDB *rdb)
1024 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1025 char *name;
1026 GString *savepath;
1027 FILE *f;
1028 struct RhythmDBTreeSaveContext ctx;
1030 g_object_get (G_OBJECT (db), "name", &name, NULL);
1032 savepath = g_string_new (name);
1033 g_string_append (savepath, ".tmp");
1035 f = fopen (savepath->str, "w");
1037 if (!f) {
1038 g_warning ("Can't save XML: %s", g_strerror (errno));
1039 goto out;
1042 ctx.db = db;
1043 ctx.handle = f;
1044 ctx.error = NULL;
1045 RHYTHMDB_FWRITE_STATICSTR ("<?xml version=\"1.0\" standalone=\"yes\"?>\n"
1046 "<rhythmdb version=\"" RHYTHMDB_TREE_XML_VERSION "\">\n",
1047 ctx.handle, ctx.error);
1049 rhythmdb_entry_type_foreach (rdb, (GHFunc) save_entry_type, &ctx);
1050 g_hash_table_foreach (db->priv->unknown_entry_types,
1051 (GHFunc) save_unknown_entry_type,
1052 &ctx);
1054 RHYTHMDB_FWRITE_STATICSTR ("</rhythmdb>\n", ctx.handle, ctx.error);
1056 if (fclose (f) < 0) {
1057 g_warning ("Couldn't close %s: %s",
1058 savepath->str,
1059 g_strerror (errno));
1060 unlink (savepath->str);
1061 goto out;
1064 if (ctx.error != NULL) {
1065 g_warning ("Writing to the database failed: %s", ctx.error);
1066 g_free (ctx.error);
1067 unlink (savepath->str);
1068 } else {
1069 if (rename (savepath->str, name) < 0) {
1070 g_warning ("Couldn't rename %s to %s: %s",
1071 name, savepath->str,
1072 g_strerror (errno));
1073 unlink (savepath->str);
1077 out:
1078 g_string_free (savepath, TRUE);
1079 g_free (name);
1080 return;
1083 #undef RHYTHMDB_FWRITE_ENCODED_STR
1084 #undef RHYTHMDB_FWRITE_STATICSTR
1085 #undef RHYTHMDB_FPUTC
1086 #undef RHYTHMDB_FWRITE
1088 RhythmDB *
1089 rhythmdb_tree_new (const char *name)
1091 RhythmDBTree *db = g_object_new (RHYTHMDB_TYPE_TREE, "name", name, NULL);
1093 g_return_val_if_fail (db->priv != NULL, NULL);
1095 return RHYTHMDB (db);
1098 static void
1099 set_entry_album (RhythmDBTree *db,
1100 RhythmDBEntry *entry,
1101 RhythmDBTreeProperty *artist,
1102 RBRefString *name)
1104 struct RhythmDBTreeProperty *prop;
1105 prop = get_or_create_album (db, artist, name);
1106 g_hash_table_insert (prop->children, entry, NULL);
1107 entry->data = prop;
1110 static void
1111 rhythmdb_tree_entry_new (RhythmDB *rdb,
1112 RhythmDBEntry *entry)
1114 g_mutex_lock (RHYTHMDB_TREE(rdb)->priv->entries_lock);
1115 rhythmdb_tree_entry_new_internal (rdb, entry);
1116 g_mutex_unlock (RHYTHMDB_TREE(rdb)->priv->entries_lock);
1119 static void
1120 rhythmdb_tree_entry_new_internal (RhythmDB *rdb, RhythmDBEntry *entry)
1122 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1123 RhythmDBTreeProperty *artist;
1124 RhythmDBTreeProperty *genre;
1126 g_assert (entry != NULL);
1128 g_return_if_fail (entry->location != NULL);
1130 if (entry->title == NULL) {
1131 g_warning ("Entry %s has missing title", rb_refstring_get (entry->location));
1132 entry->title = rb_refstring_new (_("Unknown"));
1134 if (entry->artist == NULL) {
1135 g_warning ("Entry %s has missing artist", rb_refstring_get (entry->location));
1136 entry->artist = rb_refstring_new (_("Unknown"));
1138 if (entry->album == NULL) {
1139 g_warning ("Entry %s has missing album", rb_refstring_get (entry->location));
1140 entry->album = rb_refstring_new (_("Unknown"));
1142 if (entry->genre == NULL) {
1143 g_warning ("Entry %s has missing genre", rb_refstring_get (entry->location));
1144 entry->genre = rb_refstring_new (_("Unknown"));
1146 if (entry->mimetype == NULL) {
1147 g_warning ("Entry %s has missing mimetype", rb_refstring_get (entry->location));
1148 entry->mimetype = rb_refstring_new ("unknown/unknown");
1151 /* Initialize the tree structure. */
1152 genre = get_or_create_genre (db, entry->type, entry->genre);
1153 artist = get_or_create_artist (db, genre, entry->artist);
1154 set_entry_album (db, entry, artist, entry->album);
1156 /* this accounts for the initial reference on the entry */
1157 g_hash_table_insert (db->priv->entries, entry->location, entry);
1158 g_hash_table_insert (db->priv->entry_ids, GINT_TO_POINTER (entry->id), entry);
1160 entry->flags &= ~RHYTHMDB_ENTRY_TREE_LOADING;
1163 static RhythmDBTreeProperty *
1164 rhythmdb_tree_property_new (RhythmDBTree *db)
1166 RhythmDBTreeProperty *ret = g_new0 (RhythmDBTreeProperty, 1);
1167 #ifndef G_DISABLE_ASSERT
1168 ret->magic = 0xf00dbeef;
1169 #endif
1170 return ret;
1173 static GHashTable *
1174 get_genres_hash_for_type (RhythmDBTree *db,
1175 RhythmDBEntryType type)
1177 GHashTable *table;
1179 table = g_hash_table_lookup (db->priv->genres, type);
1180 if (table == NULL) {
1181 table = g_hash_table_new_full (rb_refstring_hash,
1182 rb_refstring_equal,
1183 (GDestroyNotify) rb_refstring_unref,
1184 NULL);
1185 if (table == NULL) {
1186 g_warning ("Out of memory\n");
1187 return NULL;
1189 g_hash_table_insert (db->priv->genres,
1190 type,
1191 table);
1193 return table;
1196 typedef void (*RBHFunc)(RhythmDBTree *db, GHashTable *genres, gpointer data);
1198 typedef struct {
1199 RhythmDBTree *db;
1200 RBHFunc func;
1201 gpointer data;
1202 } GenresIterCtxt;
1204 static void
1205 genres_process_one (gpointer key,
1206 gpointer value,
1207 gpointer user_data)
1209 GenresIterCtxt *ctxt = (GenresIterCtxt *)user_data;
1210 ctxt->func (ctxt->db, (GHashTable *)value, ctxt->data);
1213 static void
1214 genres_hash_foreach (RhythmDBTree *db, RBHFunc func, gpointer data)
1216 GenresIterCtxt ctxt;
1218 ctxt.db = db;
1219 ctxt.func = func;
1220 ctxt.data = data;
1221 g_hash_table_foreach (db->priv->genres, genres_process_one, &ctxt);
1224 static RhythmDBTreeProperty *
1225 get_or_create_genre (RhythmDBTree *db,
1226 RhythmDBEntryType type,
1227 RBRefString *name)
1229 RhythmDBTreeProperty *genre;
1230 GHashTable *table;
1232 g_mutex_lock (db->priv->genres_lock);
1233 table = get_genres_hash_for_type (db, type);
1234 genre = g_hash_table_lookup (table, name);
1236 if (G_UNLIKELY (genre == NULL)) {
1237 genre = rhythmdb_tree_property_new (db);
1238 genre->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1239 (GDestroyNotify) rb_refstring_unref,
1240 NULL);
1241 rb_refstring_ref (name);
1242 g_hash_table_insert (table, name, genre);
1243 genre->parent = NULL;
1245 g_mutex_unlock (db->priv->genres_lock);
1247 return genre;
1250 static RhythmDBTreeProperty *
1251 get_or_create_artist (RhythmDBTree *db,
1252 RhythmDBTreeProperty *genre,
1253 RBRefString *name)
1255 RhythmDBTreeProperty *artist;
1257 artist = g_hash_table_lookup (genre->children, name);
1259 if (G_UNLIKELY (artist == NULL)) {
1260 artist = rhythmdb_tree_property_new (db);
1261 artist->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1262 (GDestroyNotify) rb_refstring_unref,
1263 NULL);
1264 rb_refstring_ref (name);
1265 g_hash_table_insert (genre->children, name, artist);
1266 artist->parent = genre;
1269 return artist;
1272 static RhythmDBTreeProperty *
1273 get_or_create_album (RhythmDBTree *db,
1274 RhythmDBTreeProperty *artist,
1275 RBRefString *name)
1277 RhythmDBTreeProperty *album;
1279 album = g_hash_table_lookup (artist->children, name);
1281 if (G_UNLIKELY (album == NULL)) {
1282 album = rhythmdb_tree_property_new (db);
1283 album->children = g_hash_table_new (g_direct_hash, g_direct_equal);
1284 rb_refstring_ref (name);
1285 g_hash_table_insert (artist->children, name, album);
1286 album->parent = artist;
1289 return album;
1292 static gboolean
1293 remove_child (RhythmDBTreeProperty *parent,
1294 gconstpointer data)
1296 g_assert (g_hash_table_remove (parent->children, data));
1297 if (g_hash_table_size (parent->children) <= 0) {
1298 return TRUE;
1300 return FALSE;
1303 static void
1304 remove_entry_from_album (RhythmDBTree *db,
1305 RhythmDBEntry *entry)
1307 GHashTable *table;
1309 rb_refstring_ref (entry->genre);
1310 rb_refstring_ref (entry->artist);
1311 rb_refstring_ref (entry->album);
1313 g_mutex_lock (db->priv->genres_lock);
1314 table = get_genres_hash_for_type (db, entry->type);
1315 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry), entry)) {
1316 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent,
1317 entry->album)) {
1319 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent,
1320 entry->artist)) {
1321 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent);
1322 g_assert (g_hash_table_remove (table, entry->genre));
1324 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent);
1327 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry));
1329 g_mutex_unlock (db->priv->genres_lock);
1331 rb_refstring_unref (entry->genre);
1332 rb_refstring_unref (entry->artist);
1333 rb_refstring_unref (entry->album);
1336 static gboolean
1337 rhythmdb_tree_entry_set (RhythmDB *adb,
1338 RhythmDBEntry *entry,
1339 guint propid,
1340 const GValue *value)
1342 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1343 RhythmDBEntryType type;
1345 type = entry->type;
1347 /* don't process changes to entries we're loading, we'll get them
1348 * when the entry is complete.
1350 if (entry->flags & RHYTHMDB_ENTRY_TREE_LOADING)
1351 return FALSE;
1353 /* Handle special properties */
1354 switch (propid)
1356 case RHYTHMDB_PROP_LOCATION:
1358 RBRefString *s;
1359 /* We have to use the string in the entry itself as the hash key,
1360 * otherwise either we leak it, or the string vanishes when the
1361 * GValue is freed; this means we have to do the entry modification
1362 * here, rather than letting rhythmdb_entry_set_internal do it.
1364 g_mutex_lock (db->priv->entries_lock);
1365 g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1367 s = rb_refstring_new (g_value_get_string (value));
1368 rb_refstring_unref (entry->location);
1369 entry->location = s;
1370 g_hash_table_insert (db->priv->entries, entry->location, entry);
1371 g_mutex_unlock (db->priv->entries_lock);
1373 return TRUE;
1375 case RHYTHMDB_PROP_ALBUM:
1377 const char *albumname = g_value_get_string (value);
1379 if (strcmp (rb_refstring_get (entry->album), albumname)) {
1380 RhythmDBTreeProperty *artist;
1381 RhythmDBTreeProperty *genre;
1383 rb_refstring_ref (entry->genre);
1384 rb_refstring_ref (entry->artist);
1385 rb_refstring_ref (entry->album);
1387 remove_entry_from_album (db, entry);
1389 genre = get_or_create_genre (db, type, entry->genre);
1390 artist = get_or_create_artist (db, genre, entry->artist);
1391 set_entry_album (db, entry, artist, rb_refstring_new (albumname));
1393 rb_refstring_unref (entry->genre);
1394 rb_refstring_unref (entry->artist);
1395 rb_refstring_unref (entry->album);
1397 break;
1399 case RHYTHMDB_PROP_ARTIST:
1401 const char *artistname = g_value_get_string (value);
1403 if (strcmp (rb_refstring_get (entry->artist), artistname)) {
1404 RhythmDBTreeProperty *new_artist;
1405 RhythmDBTreeProperty *genre;
1407 rb_refstring_ref (entry->genre);
1408 rb_refstring_ref (entry->artist);
1409 rb_refstring_ref (entry->album);
1411 remove_entry_from_album (db, entry);
1413 genre = get_or_create_genre (db, type, entry->genre);
1414 new_artist = get_or_create_artist (db, genre,
1415 rb_refstring_new (artistname));
1416 set_entry_album (db, entry, new_artist, entry->album);
1418 rb_refstring_unref (entry->genre);
1419 rb_refstring_unref (entry->artist);
1420 rb_refstring_unref (entry->album);
1422 break;
1424 case RHYTHMDB_PROP_GENRE:
1426 const char *genrename = g_value_get_string (value);
1428 if (strcmp (rb_refstring_get (entry->genre), genrename)) {
1429 RhythmDBTreeProperty *new_genre;
1430 RhythmDBTreeProperty *new_artist;
1432 rb_refstring_ref (entry->genre);
1433 rb_refstring_ref (entry->artist);
1434 rb_refstring_ref (entry->album);
1436 remove_entry_from_album (db, entry);
1438 new_genre = get_or_create_genre (db, type,
1439 rb_refstring_new (genrename));
1440 new_artist = get_or_create_artist (db, new_genre, entry->artist);
1441 set_entry_album (db, entry, new_artist, entry->album);
1443 rb_refstring_unref (entry->genre);
1444 rb_refstring_unref (entry->artist);
1445 rb_refstring_unref (entry->album);
1447 break;
1449 default:
1450 break;
1453 return FALSE;
1456 static void
1457 rhythmdb_tree_entry_delete (RhythmDB *adb,
1458 RhythmDBEntry *entry)
1460 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1462 remove_entry_from_album (db, entry);
1464 g_mutex_lock (db->priv->entries_lock);
1465 g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1466 g_assert (g_hash_table_remove (db->priv->entry_ids, GINT_TO_POINTER (entry->id)));
1467 rhythmdb_entry_unref (entry);
1468 g_mutex_unlock (db->priv->entries_lock);
1471 typedef struct {
1472 RhythmDB *db;
1473 RhythmDBEntryType type;
1474 } RbEntryRemovalCtxt;
1476 static gboolean
1477 remove_one_song (gpointer key,
1478 RhythmDBEntry *entry,
1479 RbEntryRemovalCtxt *ctxt)
1481 g_return_val_if_fail (entry != NULL, FALSE);
1483 if (entry->type == ctxt->type) {
1484 rhythmdb_emit_entry_deleted (ctxt->db, entry);
1485 remove_entry_from_album (RHYTHMDB_TREE (ctxt->db), entry);
1486 g_hash_table_remove (RHYTHMDB_TREE (ctxt->db)->priv->entry_ids, GINT_TO_POINTER (entry->id));
1487 rhythmdb_entry_unref (entry);
1488 return TRUE;
1490 return FALSE;
1493 static void
1494 rhythmdb_tree_entry_delete_by_type (RhythmDB *adb,
1495 RhythmDBEntryType type)
1497 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1498 RbEntryRemovalCtxt ctxt;
1500 ctxt.db = adb;
1501 ctxt.type = type;
1502 g_mutex_lock (db->priv->entries_lock);
1503 g_hash_table_foreach_remove (db->priv->entries,
1504 (GHRFunc) remove_one_song, &ctxt);
1505 g_mutex_unlock (db->priv->entries_lock);
1508 static void
1509 destroy_tree_property (RhythmDBTreeProperty *prop)
1511 #ifndef G_DISABLE_ASSERT
1512 prop->magic = 0xf33df33d;
1513 #endif
1514 g_hash_table_destroy (prop->children);
1515 g_free (prop);
1518 typedef void (*RhythmDBTreeTraversalFunc) (RhythmDBTree *db, RhythmDBEntry *entry, gpointer data);
1519 typedef void (*RhythmDBTreeAlbumTraversalFunc) (RhythmDBTree *db, RhythmDBTreeProperty *album, gpointer data);
1521 struct RhythmDBTreeTraversalData
1523 RhythmDBTree *db;
1524 GPtrArray *query;
1525 RhythmDBTreeTraversalFunc func;
1526 gpointer data;
1527 gboolean *cancel;
1530 static gboolean
1531 rhythmdb_tree_evaluate_query (RhythmDB *adb,
1532 GPtrArray *query,
1533 RhythmDBEntry *entry)
1535 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1536 guint i;
1537 guint last_disjunction;
1539 for (i = 0, last_disjunction = 0; i < query->len; i++) {
1540 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1542 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1543 if (evaluate_conjunctive_subquery (db, query, last_disjunction, i, entry))
1544 return TRUE;
1546 last_disjunction = i + 1;
1549 if (evaluate_conjunctive_subquery (db, query, last_disjunction, query->len, entry))
1550 return TRUE;
1551 return FALSE;
1554 #define RHYTHMDB_PROPERTY_COMPARE(OP) \
1555 switch (rhythmdb_get_property_type (db, data->propid)) { \
1556 case G_TYPE_STRING: \
1557 if (strcmp (rhythmdb_entry_get_string (entry, data->propid), \
1558 g_value_get_string (data->val)) OP 0) \
1559 return FALSE; \
1560 break; \
1561 case G_TYPE_ULONG: \
1562 if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1563 g_value_get_ulong (data->val)) \
1564 return FALSE; \
1565 break; \
1566 case G_TYPE_BOOLEAN: \
1567 if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1568 g_value_get_boolean (data->val)) \
1569 return FALSE; \
1570 break; \
1571 case G_TYPE_UINT64: \
1572 if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1573 g_value_get_uint64 (data->val)) \
1574 return FALSE; \
1575 break; \
1576 case G_TYPE_DOUBLE: \
1577 if (rhythmdb_entry_get_double (entry, data->propid) OP \
1578 g_value_get_double (data->val)) \
1579 return FALSE; \
1580 break; \
1581 case G_TYPE_POINTER: \
1582 if (rhythmdb_entry_get_pointer (entry, data->propid) OP \
1583 g_value_get_pointer (data->val)) \
1584 return FALSE; \
1585 break; \
1586 default: \
1587 g_warning ("Unexpected type: %s", g_type_name (rhythmdb_get_property_type (db, data->propid))); \
1588 g_assert_not_reached (); \
1591 static gboolean
1592 search_match_properties (RhythmDB *db,
1593 RhythmDBEntry *entry,
1594 gchar **words)
1596 const RhythmDBPropType props[] = {
1597 RHYTHMDB_PROP_TITLE_FOLDED,
1598 RHYTHMDB_PROP_ALBUM_FOLDED,
1599 RHYTHMDB_PROP_ARTIST_FOLDED,
1600 RHYTHMDB_PROP_GENRE_FOLDED
1602 gboolean islike = TRUE;
1603 gchar **current;
1604 int i;
1606 for (current = words; *current != NULL; current++) {
1607 gboolean word_found = FALSE;
1609 for (i = 0; i < G_N_ELEMENTS (props); i++) {
1610 const char *entry_string = rhythmdb_entry_get_string (entry, props[i]);
1611 if (entry_string && (strstr (entry_string, *current) != NULL)) {
1612 /* the word was found, go to the next one */
1613 word_found = TRUE;
1614 break;
1617 if (!word_found) {
1618 /* the word wasn't in any of the properties*/
1619 islike = FALSE;
1620 break;
1624 return islike;
1627 static gboolean
1628 evaluate_conjunctive_subquery (RhythmDBTree *dbtree,
1629 GPtrArray *query,
1630 guint base,
1631 guint max,
1632 RhythmDBEntry *entry)
1635 RhythmDB *db = (RhythmDB *) dbtree;
1636 guint i;
1637 /* Optimization possibility - we may get here without actually having
1638 * anything in the query. It would be faster to instead just merge
1639 * the child hash table into the query result hash.
1641 for (i = base; i < max; i++) {
1642 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1644 switch (data->type) {
1645 case RHYTHMDB_QUERY_SUBQUERY:
1647 gboolean matched = FALSE;
1648 GList *conjunctions = split_query_by_disjunctions (dbtree, data->subquery);
1649 GList *tem;
1651 if (conjunctions == NULL)
1652 matched = TRUE;
1654 for (tem = conjunctions; tem; tem = tem->next) {
1655 GPtrArray *subquery = tem->data;
1656 if (!matched && evaluate_conjunctive_subquery (dbtree, subquery,
1657 0, subquery->len,
1658 entry)) {
1659 matched = TRUE;
1661 g_ptr_array_free (tem->data, TRUE);
1663 g_list_free (conjunctions);
1664 if (!matched)
1665 return FALSE;
1667 break;
1668 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
1669 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
1671 gulong relative_time;
1672 GTimeVal current_time;
1674 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_ULONG);
1676 relative_time = g_value_get_ulong (data->val);
1677 g_get_current_time (&current_time);
1679 if (data->type == RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN) {
1680 if (!(rhythmdb_entry_get_ulong (entry, data->propid) >= (current_time.tv_sec - relative_time)))
1681 return FALSE;
1682 } else {
1683 if (!(rhythmdb_entry_get_ulong (entry, data->propid) < (current_time.tv_sec - relative_time)))
1684 return FALSE;
1686 break;
1688 case RHYTHMDB_QUERY_PROP_PREFIX:
1689 case RHYTHMDB_QUERY_PROP_SUFFIX:
1691 const char *value_s;
1692 const char *entry_s;
1694 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING);
1696 value_s = g_value_get_string (data->val);
1697 entry_s = rhythmdb_entry_get_string (entry, data->propid);
1699 if (data->type == RHYTHMDB_QUERY_PROP_PREFIX && !g_str_has_prefix (entry_s, value_s))
1700 return FALSE;
1701 if (data->type == RHYTHMDB_QUERY_PROP_SUFFIX && !g_str_has_suffix (entry_s, value_s))
1702 return FALSE;
1704 break;
1706 case RHYTHMDB_QUERY_PROP_LIKE:
1707 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
1709 if (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING) {
1710 gboolean islike;
1712 if (data->propid == RHYTHMDB_PROP_SEARCH_MATCH) {
1713 /* this is a special property, that should match several things */
1714 islike = search_match_properties (db, entry, g_value_get_boxed (data->val));
1716 } else {
1717 const gchar *value_string = g_value_get_string (data->val);
1718 const char *entry_string = rhythmdb_entry_get_string (entry, data->propid);
1720 /* check in case the property is NULL, the value should never be NULL */
1721 if (entry_string == NULL)
1722 return FALSE;
1724 islike = (strstr (entry_string, value_string) != NULL);
1727 if ((data->type == RHYTHMDB_QUERY_PROP_LIKE) ^ islike)
1728 return FALSE;
1729 else
1730 continue;
1731 break;
1733 /* Fall through */
1735 case RHYTHMDB_QUERY_PROP_EQUALS:
1736 RHYTHMDB_PROPERTY_COMPARE (!=)
1737 break;
1738 case RHYTHMDB_QUERY_PROP_GREATER:
1739 RHYTHMDB_PROPERTY_COMPARE (<)
1740 break;
1741 case RHYTHMDB_QUERY_PROP_LESS:
1742 RHYTHMDB_PROPERTY_COMPARE (>)
1743 break;
1744 case RHYTHMDB_QUERY_END:
1745 case RHYTHMDB_QUERY_DISJUNCTION:
1746 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
1747 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
1748 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
1749 g_assert_not_reached ();
1750 break;
1753 return TRUE;
1756 static void
1757 do_conjunction (RhythmDBEntry *entry,
1758 gpointer unused,
1759 struct RhythmDBTreeTraversalData *data)
1761 if (G_UNLIKELY (*data->cancel))
1762 return;
1763 /* Finally, we actually evaluate the query! */
1764 if (evaluate_conjunctive_subquery (data->db, data->query, 0, data->query->len,
1765 entry)) {
1766 data->func (data->db, entry, data->data);
1770 static void
1771 conjunctive_query_songs (const char *name,
1772 RhythmDBTreeProperty *album,
1773 struct RhythmDBTreeTraversalData *data)
1775 if (G_UNLIKELY (*data->cancel))
1776 return;
1777 g_hash_table_foreach (album->children, (GHFunc) do_conjunction, data);
1780 static GPtrArray *
1781 clone_remove_ptr_array_index (GPtrArray *arr,
1782 guint index)
1784 GPtrArray *ret = g_ptr_array_new ();
1785 guint i;
1786 for (i = 0; i < arr->len; i++)
1787 if (i != index)
1788 g_ptr_array_add (ret, g_ptr_array_index (arr, i));
1790 return ret;
1793 static void
1794 conjunctive_query_albums (const char *name,
1795 RhythmDBTreeProperty *artist,
1796 struct RhythmDBTreeTraversalData *data)
1798 guint i;
1799 int album_query_idx = -1;
1801 if (G_UNLIKELY (*data->cancel))
1802 return;
1804 for (i = 0; i < data->query->len; i++) {
1805 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1806 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1807 && qdata->propid == RHYTHMDB_PROP_ALBUM) {
1808 if (album_query_idx > 0)
1809 return;
1810 album_query_idx = i;
1815 if (album_query_idx >= 0) {
1816 RhythmDBTreeProperty *album;
1817 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, album_query_idx);
1818 RBRefString *albumname = rb_refstring_new (g_value_get_string (qdata->val));
1819 GPtrArray *oldquery = data->query;
1821 data->query = clone_remove_ptr_array_index (data->query, album_query_idx);
1823 album = g_hash_table_lookup (artist->children, albumname);
1825 if (album != NULL) {
1826 conjunctive_query_songs (rb_refstring_get (albumname), album, data);
1828 g_ptr_array_free (data->query, TRUE);
1829 data->query = oldquery;
1830 return;
1833 g_hash_table_foreach (artist->children, (GHFunc) conjunctive_query_songs, data);
1836 static void
1837 conjunctive_query_artists (const char *name,
1838 RhythmDBTreeProperty *genre,
1839 struct RhythmDBTreeTraversalData *data)
1841 guint i;
1842 int artist_query_idx = -1;
1844 if (G_UNLIKELY (*data->cancel))
1845 return;
1847 for (i = 0; i < data->query->len; i++) {
1848 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1849 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1850 && qdata->propid == RHYTHMDB_PROP_ARTIST) {
1851 if (artist_query_idx > 0)
1852 return;
1853 artist_query_idx = i;
1858 if (artist_query_idx >= 0) {
1859 RhythmDBTreeProperty *artist;
1860 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, artist_query_idx);
1861 RBRefString *artistname = rb_refstring_new (g_value_get_string (qdata->val));
1862 GPtrArray *oldquery = data->query;
1864 data->query = clone_remove_ptr_array_index (data->query, artist_query_idx);
1866 artist = g_hash_table_lookup (genre->children, artistname);
1867 if (artist != NULL) {
1868 conjunctive_query_albums (rb_refstring_get (artistname), artist, data);
1870 g_ptr_array_free (data->query, TRUE);
1871 data->query = oldquery;
1872 return;
1875 g_hash_table_foreach (genre->children, (GHFunc) conjunctive_query_albums, data);
1878 static void
1879 conjunctive_query_genre (RhythmDBTree *db,
1880 GHashTable *genres,
1881 struct RhythmDBTreeTraversalData *data)
1883 int genre_query_idx = -1;
1884 guint i;
1886 if (G_UNLIKELY (*data->cancel))
1887 return;
1889 for (i = 0; i < data->query->len; i++) {
1890 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1891 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1892 && qdata->propid == RHYTHMDB_PROP_GENRE) {
1893 /* A song can't currently have two genres. So
1894 * if we get a conjunctive query for that, we
1895 * know the result must be the empty set. */
1896 if (genre_query_idx > 0)
1897 return;
1898 genre_query_idx = i;
1903 if (genre_query_idx >= 0) {
1904 RhythmDBTreeProperty *genre;
1905 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, genre_query_idx);
1906 RBRefString *genrename = rb_refstring_new (g_value_get_string (qdata->val));
1907 GPtrArray *oldquery = data->query;
1909 data->query = clone_remove_ptr_array_index (data->query, genre_query_idx);
1911 genre = g_hash_table_lookup (genres, genrename);
1912 if (genre != NULL) {
1913 conjunctive_query_artists (rb_refstring_get (genrename), genre, data);
1915 g_ptr_array_free (data->query, TRUE);
1916 data->query = oldquery;
1917 return;
1920 g_hash_table_foreach (genres, (GHFunc) conjunctive_query_artists, data);
1923 static void
1924 conjunctive_query (RhythmDBTree *db,
1925 GPtrArray *query,
1926 RhythmDBTreeTraversalFunc func,
1927 gpointer data,
1928 gboolean *cancel)
1930 int type_query_idx = -1;
1931 guint i;
1932 struct RhythmDBTreeTraversalData *traversal_data;
1934 for (i = 0; i < query->len; i++) {
1935 RhythmDBQueryData *qdata = g_ptr_array_index (query, i);
1936 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1937 && qdata->propid == RHYTHMDB_PROP_TYPE) {
1938 /* A song can't have two types. */
1939 if (type_query_idx > 0)
1940 return;
1941 type_query_idx = i;
1945 traversal_data = g_new (struct RhythmDBTreeTraversalData, 1);
1946 traversal_data->db = db;
1947 traversal_data->query = query;
1948 traversal_data->func = func;
1949 traversal_data->data = data;
1950 traversal_data->cancel = cancel;
1952 g_mutex_lock (db->priv->genres_lock);
1953 if (type_query_idx >= 0) {
1954 GHashTable *genres;
1955 RhythmDBEntryType etype;
1956 RhythmDBQueryData *qdata = g_ptr_array_index (query, type_query_idx);
1958 g_ptr_array_remove_index_fast (query, type_query_idx);
1960 etype = g_value_get_pointer (qdata->val);
1961 genres = get_genres_hash_for_type (db, etype);
1962 if (genres != NULL) {
1963 conjunctive_query_genre (db, genres, traversal_data);
1964 } else {
1965 g_assert_not_reached ();
1967 } else {
1968 /* FIXME */
1969 /* No type was given; punt and query everything */
1970 genres_hash_foreach (db, (RBHFunc)conjunctive_query_genre,
1971 traversal_data);
1973 g_mutex_unlock (db->priv->genres_lock);
1975 g_free (traversal_data);
1978 static GList *
1979 split_query_by_disjunctions (RhythmDBTree *db,
1980 GPtrArray *query)
1982 GList *conjunctions = NULL;
1983 guint i, j;
1984 guint last_disjunction = 0;
1985 GPtrArray *subquery = g_ptr_array_new ();
1987 for (i = 0; i < query->len; i++) {
1988 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1989 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1991 /* Copy the subquery */
1992 for (j = last_disjunction; j < i; j++) {
1993 g_ptr_array_add (subquery, g_ptr_array_index (query, j));
1996 conjunctions = g_list_prepend (conjunctions, subquery);
1997 last_disjunction = i+1;
1998 g_assert (subquery->len > 0);
1999 subquery = g_ptr_array_new ();
2003 /* Copy the last subquery, except for the QUERY_END */
2004 for (i = last_disjunction; i < query->len; i++) {
2005 g_ptr_array_add (subquery, g_ptr_array_index (query, i));
2008 if (subquery->len > 0)
2009 conjunctions = g_list_prepend (conjunctions, subquery);
2010 else
2011 g_ptr_array_free (subquery, TRUE);
2013 return conjunctions;
2016 struct RhythmDBTreeQueryGatheringData
2018 RhythmDBTree *db;
2019 GPtrArray *queue;
2020 GHashTable *entries;
2021 RhythmDBQueryResults *results;
2024 static void
2025 do_query_recurse (RhythmDBTree *db,
2026 GPtrArray *query,
2027 RhythmDBTreeTraversalFunc func,
2028 struct RhythmDBTreeQueryGatheringData *data,
2029 gboolean *cancel)
2031 GList *conjunctions, *tem;
2033 if (query == NULL)
2034 return;
2036 conjunctions = split_query_by_disjunctions (db, query);
2037 rb_debug ("doing recursive query, %d conjunctions", g_list_length (conjunctions));
2039 if (conjunctions == NULL)
2040 return;
2042 /* If there is a disjunction involved, we must uniquify the entry hits. */
2043 if (conjunctions->next != NULL)
2044 data->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
2045 else
2046 data->entries = NULL;
2048 for (tem = conjunctions; tem; tem = tem->next) {
2049 if (G_UNLIKELY (*cancel))
2050 break;
2051 conjunctive_query (db, tem->data, func, data, cancel);
2052 g_ptr_array_free (tem->data, TRUE);
2055 if (data->entries != NULL)
2056 g_hash_table_destroy (data->entries);
2058 g_list_free (conjunctions);
2061 static void
2062 handle_entry_match (RhythmDB *db,
2063 RhythmDBEntry *entry,
2064 struct RhythmDBTreeQueryGatheringData *data)
2067 if (data->entries
2068 && g_hash_table_lookup (data->entries, entry))
2069 return;
2071 g_ptr_array_add (data->queue, entry);
2072 if (data->queue->len > RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
2073 rhythmdb_query_results_add_results (data->results, data->queue);
2074 data->queue = g_ptr_array_new ();
2078 static void
2079 rhythmdb_tree_do_full_query (RhythmDB *adb,
2080 GPtrArray *query,
2081 RhythmDBQueryResults *results,
2082 gboolean *cancel)
2084 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2085 struct RhythmDBTreeQueryGatheringData *data = g_new0 (struct RhythmDBTreeQueryGatheringData, 1);
2087 data->results = results;
2088 data->queue = g_ptr_array_new ();
2090 do_query_recurse (db, query, (RhythmDBTreeTraversalFunc) handle_entry_match, data, cancel);
2092 rhythmdb_query_results_add_results (data->results, data->queue);
2094 g_free (data);
2097 static RhythmDBEntry *
2098 rhythmdb_tree_entry_lookup_by_location (RhythmDB *adb,
2099 RBRefString *uri)
2101 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2102 RhythmDBEntry *entry;
2104 g_mutex_lock (db->priv->entries_lock);
2105 entry = g_hash_table_lookup (db->priv->entries, uri);
2106 g_mutex_unlock (db->priv->entries_lock);
2108 return entry;
2111 static RhythmDBEntry *
2112 rhythmdb_tree_entry_lookup_by_id (RhythmDB *adb,
2113 gint id)
2115 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2116 RhythmDBEntry *entry;
2118 g_mutex_lock (db->priv->entries_lock);
2119 entry = g_hash_table_lookup (db->priv->entry_ids, GINT_TO_POINTER (id));
2120 g_mutex_unlock (db->priv->entries_lock);
2122 return entry;
2125 struct RhythmDBEntryForeachCtxt
2127 RhythmDBTree *db;
2128 GFunc func;
2129 gpointer user_data;
2132 static void
2133 rhythmdb_tree_entry_foreach_func (gpointer key, RhythmDBEntry *val, GPtrArray *list)
2135 rhythmdb_entry_ref (val);
2136 g_ptr_array_add (list, val);
2139 static void
2140 rhythmdb_tree_entry_foreach (RhythmDB *rdb, GFunc foreach_func, gpointer user_data)
2142 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2143 GPtrArray *list;
2144 guint size, i;
2146 g_mutex_lock (db->priv->entries_lock);
2147 size = g_hash_table_size (db->priv->entries);
2148 list = g_ptr_array_sized_new (size);
2149 g_hash_table_foreach (db->priv->entries, (GHFunc)rhythmdb_tree_entry_foreach_func, list);
2150 g_mutex_unlock (db->priv->entries_lock);
2152 for (i = 0; i < size; i++) {
2153 RhythmDBEntry *entry = (RhythmDBEntry*)g_ptr_array_index (list, i);
2154 (*foreach_func) (entry, user_data);
2155 rhythmdb_entry_unref (entry);
2158 g_ptr_array_free (list, TRUE);
2161 static gint64
2162 rhythmdb_tree_entry_count (RhythmDB *rdb)
2164 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2165 return g_hash_table_size (db->priv->entries);
2168 static void
2169 rhythmdb_tree_entry_foreach_by_type (RhythmDB *db,
2170 RhythmDBEntryType type,
2171 GFunc foreach_func,
2172 gpointer data)
2174 rhythmdb_hash_tree_foreach (db, type,
2175 (RBTreeEntryItFunc) foreach_func,
2176 NULL, NULL, NULL, data);
2179 static void
2180 count_entries (RhythmDB *db, RhythmDBTreeProperty *album, gint64 *count)
2182 *count += g_hash_table_size (album->children);
2185 static gint64
2186 rhythmdb_tree_entry_count_by_type (RhythmDB *db,
2187 RhythmDBEntryType type)
2189 gint64 count = 0;
2190 rhythmdb_hash_tree_foreach (db, type,
2191 NULL, (RBTreePropertyItFunc) count_entries, NULL, NULL,
2192 &count);
2193 return count;
2197 struct HashTreeIteratorCtxt {
2198 RhythmDBTree *db;
2199 RBTreeEntryItFunc entry_func;
2200 RBTreePropertyItFunc album_func;
2201 RBTreePropertyItFunc artist_func;
2202 RBTreePropertyItFunc genres_func;
2203 gpointer data;
2206 static void
2207 hash_tree_entries_foreach (gpointer key,
2208 gpointer value,
2209 gpointer data)
2211 RhythmDBEntry *entry = (RhythmDBEntry *) key;
2212 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2214 g_assert (ctxt->entry_func);
2216 ctxt->entry_func (ctxt->db, entry, ctxt->data);
2219 static void
2220 hash_tree_albums_foreach (gpointer key,
2221 gpointer value,
2222 gpointer data)
2224 RhythmDBTreeProperty *album = (RhythmDBTreeProperty *)value;
2225 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2227 if (ctxt->album_func) {
2228 ctxt->album_func (ctxt->db, album, ctxt->data);
2230 if (ctxt->entry_func != NULL) {
2231 g_hash_table_foreach (album->children,
2232 hash_tree_entries_foreach,
2233 ctxt);
2237 static void
2238 hash_tree_artists_foreach (gpointer key,
2239 gpointer value,
2240 gpointer data)
2242 RhythmDBTreeProperty *artist = (RhythmDBTreeProperty *)value;
2243 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2245 if (ctxt->artist_func) {
2246 ctxt->artist_func (ctxt->db, artist, ctxt->data);
2248 if ((ctxt->album_func != NULL) || (ctxt->entry_func != NULL)) {
2249 g_hash_table_foreach (artist->children,
2250 hash_tree_albums_foreach,
2251 ctxt);
2255 static void
2256 hash_tree_genres_foreach (gpointer key,
2257 gpointer value,
2258 gpointer data)
2260 RhythmDBTreeProperty *genre = (RhythmDBTreeProperty *)value;
2261 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2263 if (ctxt->genres_func) {
2264 ctxt->genres_func (ctxt->db, genre, ctxt->data);
2267 if ((ctxt->album_func != NULL)
2268 || (ctxt->artist_func != NULL)
2269 || (ctxt->entry_func != NULL)) {
2270 g_hash_table_foreach (genre->children,
2271 hash_tree_artists_foreach,
2272 ctxt);
2276 static void
2277 rhythmdb_hash_tree_foreach (RhythmDB *adb,
2278 RhythmDBEntryType type,
2279 RBTreeEntryItFunc entry_func,
2280 RBTreePropertyItFunc album_func,
2281 RBTreePropertyItFunc artist_func,
2282 RBTreePropertyItFunc genres_func,
2283 gpointer data)
2285 struct HashTreeIteratorCtxt ctxt;
2286 GHashTable *table;
2288 ctxt.db = RHYTHMDB_TREE (adb);
2289 ctxt.album_func = album_func;
2290 ctxt.artist_func = artist_func;
2291 ctxt.genres_func = genres_func;
2292 ctxt.entry_func = entry_func;
2293 ctxt.data = data;
2295 g_mutex_lock (ctxt.db->priv->genres_lock);
2296 table = get_genres_hash_for_type (RHYTHMDB_TREE (adb), type);
2297 if (table == NULL) {
2298 return;
2300 if ((ctxt.album_func != NULL)
2301 || (ctxt.artist_func != NULL)
2302 || (ctxt.genres_func != NULL)
2303 || (ctxt.entry_func != NULL)) {
2304 g_hash_table_foreach (table, hash_tree_genres_foreach, &ctxt);
2306 g_mutex_unlock (ctxt.db->priv->genres_lock);
2309 static void
2310 rhythmdb_tree_entry_type_registered (RhythmDB *db,
2311 const char *name,
2312 RhythmDBEntryType entry_type)
2314 GList *entries = NULL;
2315 GList *e;
2316 gint count = 0;
2317 RhythmDBTree *rdb;
2318 RBRefString *rs_name;
2320 if (name == NULL)
2321 return;
2323 rdb = RHYTHMDB_TREE (db);
2324 rs_name = rb_refstring_find (name);
2325 if (rs_name)
2326 entries = g_hash_table_lookup (rdb->priv->unknown_entry_types, rs_name);
2327 if (entries == NULL) {
2328 rb_refstring_unref (rs_name);
2329 rb_debug ("no entries of newly registered type %s loaded from db", name);
2330 return;
2333 for (e = entries; e != NULL; e = e->next) {
2334 RhythmDBUnknownEntry *data;
2335 RhythmDBEntry *entry;
2336 GList *p;
2338 data = (RhythmDBUnknownEntry *)e->data;
2339 entry = rhythmdb_entry_allocate (db, entry_type);
2340 entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
2341 for (p = data->properties; p != NULL; p = p->next) {
2342 RhythmDBUnknownEntryProperty *prop;
2343 RhythmDBPropType propid;
2344 GValue value = {0,};
2346 prop = (RhythmDBUnknownEntryProperty *) p->data;
2347 propid = rhythmdb_propid_from_nice_elt_name (db, (const xmlChar *) rb_refstring_get (prop->name));
2349 rhythmdb_read_encoded_property (db, rb_refstring_get (prop->value), propid, &value);
2350 rhythmdb_entry_set_internal (db, entry, FALSE, propid, &value);
2351 g_value_unset (&value);
2353 rhythmdb_tree_entry_new_internal (db, entry);
2354 rhythmdb_entry_insert (db, entry);
2355 count++;
2357 rb_debug ("handled %d entries of newly registered type %s", count, name);
2358 rhythmdb_commit (db);
2360 g_hash_table_remove (rdb->priv->unknown_entry_types, rs_name);
2361 free_unknown_entries (rs_name, entries, NULL);
2362 rb_refstring_unref (rs_name);