Updated Finnish translation
[rhythmbox.git] / rhythmdb / rhythmdb-tree.c
blobd15587affbedd131fd94459cee296d663fab2d80
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 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 GQuark
153 rhythmdb_tree_error_quark (void)
155 static GQuark quark;
156 if (!quark)
157 quark = g_quark_from_static_string ("rhythmdb_tree_error");
159 return quark;
162 static void
163 rhythmdb_tree_class_init (RhythmDBTreeClass *klass)
165 GObjectClass *object_class = G_OBJECT_CLASS (klass);
166 RhythmDBClass *rhythmdb_class = RHYTHMDB_CLASS (klass);
168 object_class->finalize = rhythmdb_tree_finalize;
170 rhythmdb_class->impl_load = rhythmdb_tree_load;
171 rhythmdb_class->impl_save = rhythmdb_tree_save;
172 rhythmdb_class->impl_entry_new = rhythmdb_tree_entry_new;
173 rhythmdb_class->impl_entry_set = rhythmdb_tree_entry_set;
174 rhythmdb_class->impl_entry_delete = rhythmdb_tree_entry_delete;
175 rhythmdb_class->impl_entry_delete_by_type = rhythmdb_tree_entry_delete_by_type;
176 rhythmdb_class->impl_lookup_by_location = rhythmdb_tree_entry_lookup_by_location;
177 rhythmdb_class->impl_entry_foreach = rhythmdb_tree_entry_foreach;
178 rhythmdb_class->impl_evaluate_query = rhythmdb_tree_evaluate_query;
179 rhythmdb_class->impl_do_full_query = rhythmdb_tree_do_full_query;
180 rhythmdb_class->impl_entry_type_registered = rhythmdb_tree_entry_type_registered;
182 g_type_class_add_private (klass, sizeof (RhythmDBTreePrivate));
185 static void
186 rhythmdb_tree_init (RhythmDBTree *db)
188 db->priv = RHYTHMDB_TREE_GET_PRIVATE (db);
190 db->priv->entries = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
192 db->priv->genres = g_hash_table_new_full (g_direct_hash, g_direct_equal,
193 NULL, (GDestroyNotify)g_hash_table_destroy);
194 db->priv->unknown_entry_types = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
196 db->priv->entries_lock = g_mutex_new();
197 db->priv->genres_lock = g_mutex_new();
200 static void
201 unparent_entries (gpointer key,
202 RhythmDBEntry *entry,
203 RhythmDBTree *db)
205 remove_entry_from_album (db, entry);
208 static void
209 free_unknown_entries (RBRefString *name,
210 GList *entries,
211 gpointer nah)
213 GList *e;
214 for (e = entries; e != NULL; e = e->next) {
215 RhythmDBUnknownEntry *entry;
216 GList *p;
218 entry = (RhythmDBUnknownEntry *)e->data;
219 rb_refstring_unref (entry->typename);
220 for (p = entry->properties; p != NULL; p = p->next) {
221 RhythmDBUnknownEntryProperty *prop;
223 prop = (RhythmDBUnknownEntryProperty *)p->data;
224 rb_refstring_unref (prop->name);
225 rb_refstring_unref (prop->value);
226 g_free (prop);
229 g_list_free (entry->properties);
231 g_list_free (entries);
234 static void
235 rhythmdb_tree_finalize (GObject *object)
237 RhythmDBTree *db;
239 g_return_if_fail (object != NULL);
240 g_return_if_fail (RHYTHMDB_IS_TREE (object));
242 db = RHYTHMDB_TREE (object);
244 g_return_if_fail (db->priv != NULL);
246 db->priv->finalizing = TRUE;
248 g_hash_table_foreach (db->priv->entries, (GHFunc) unparent_entries, db);
249 g_hash_table_destroy (db->priv->entries);
250 g_mutex_free (db->priv->entries_lock);
252 g_hash_table_destroy (db->priv->genres);
253 g_mutex_free (db->priv->genres_lock);
255 g_hash_table_foreach (db->priv->unknown_entry_types,
256 (GHFunc) free_unknown_entries,
257 NULL);
258 g_hash_table_destroy (db->priv->unknown_entry_types);
260 G_OBJECT_CLASS (rhythmdb_tree_parent_class)->finalize (object);
263 struct RhythmDBTreeLoadContext
265 RhythmDBTree *db;
266 xmlParserCtxtPtr xmlctx;
267 gboolean *die;
268 enum {
269 RHYTHMDB_TREE_PARSER_STATE_START,
270 RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB,
271 RHYTHMDB_TREE_PARSER_STATE_ENTRY,
272 RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY,
273 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY,
274 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY,
275 RHYTHMDB_TREE_PARSER_STATE_END,
276 } state;
277 guint in_unknown_elt;
278 RhythmDBEntry *entry;
279 RhythmDBUnknownEntry *unknown_entry;
280 GString *buf;
281 RhythmDBPropType propid;
282 gint batch_count;
283 GError **error;
285 /* updating */
286 gboolean has_date;
287 gboolean canonicalise_uris;
288 gboolean reload_all_metadata;
291 static void
292 rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext *ctx,
293 const char *name,
294 const char **attrs)
296 if (*ctx->die == TRUE) {
297 xmlStopParser (ctx->xmlctx);
298 return;
301 if (ctx->in_unknown_elt) {
302 ctx->in_unknown_elt++;
303 return;
306 switch (ctx->state)
308 case RHYTHMDB_TREE_PARSER_STATE_START:
310 if (!strcmp (name, "rhythmdb")) {
311 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
312 for (; *attrs; attrs +=2) {
313 if (!strcmp (*attrs, "version")) {
314 const char *version = *(attrs+1);
316 if (!strcmp (version, "1.0") || !strcmp (version, "1.1")) {
317 ctx->canonicalise_uris = TRUE;
318 rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries");
319 } else if (!strcmp (version, "1.2")) {
320 /* current version*/
321 rb_debug ("reloading all file metadata to get MusicBrainz tags");
322 ctx->reload_all_metadata = TRUE;
323 } else if (!strcmp (version, "1.3")) {
324 /* current version*/
325 } else {
326 g_set_error (ctx->error,
327 RHYTHMDB_TREE_ERROR,
328 RHYTHMDB_TREE_ERROR_DATABASE_TOO_NEW,
329 _("The database was created by a later version of rhythmbox."
330 " This version of rhythmbox cannot read the database."));
331 xmlStopParser (ctx->xmlctx);
333 } else {
334 g_assert_not_reached ();
338 } else {
339 ctx->in_unknown_elt++;
342 break;
344 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
346 if (!strcmp (name, "entry")) {
347 RhythmDBEntryType type = RHYTHMDB_ENTRY_TYPE_INVALID;
348 const char *typename = NULL;
349 for (; *attrs; attrs +=2) {
350 if (!strcmp (*attrs, "type")) {
351 typename = *(attrs+1);
352 type = rhythmdb_entry_type_get_by_name (RHYTHMDB (ctx->db), typename);
353 break;
357 g_assert (typename);
358 if (type != RHYTHMDB_ENTRY_TYPE_INVALID) {
359 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
360 ctx->entry = rhythmdb_entry_allocate (RHYTHMDB (ctx->db), type);
361 ctx->entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
362 ctx->has_date = FALSE;
363 } else {
364 rb_debug ("reading unknown entry");
365 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
366 ctx->unknown_entry = g_new0 (RhythmDBUnknownEntry, 1);
367 ctx->unknown_entry->typename = rb_refstring_new (typename);
369 } else {
370 ctx->in_unknown_elt++;
372 break;
374 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
376 int val = rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx->db), BAD_CAST name);
377 if (val < 0) {
378 ctx->in_unknown_elt++;
379 break;
382 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY;
383 ctx->propid = val;
384 g_string_truncate (ctx->buf, 0);
385 break;
387 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
389 RhythmDBUnknownEntryProperty *prop;
391 prop = g_new0 (RhythmDBUnknownEntryProperty, 1);
392 prop->name = rb_refstring_new (name);
394 ctx->unknown_entry->properties = g_list_prepend (ctx->unknown_entry->properties, prop);
395 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY;
396 g_string_truncate (ctx->buf, 0);
397 break;
399 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
400 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
401 case RHYTHMDB_TREE_PARSER_STATE_END:
402 break;
406 static void
407 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext *ctx,
408 const char *name)
410 if (*ctx->die == TRUE) {
411 xmlStopParser (ctx->xmlctx);
412 return;
415 if (ctx->in_unknown_elt) {
416 ctx->in_unknown_elt--;
417 return;
420 switch (ctx->state)
422 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
423 ctx->state = RHYTHMDB_TREE_PARSER_STATE_END;
424 break;
425 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
427 if (!ctx->has_date | ctx->reload_all_metadata) {
428 /* there is no date metadata, so this is from an old version
429 * reset the last-modified timestamp, so that the file is re-read
431 rb_debug ("pre-Date entry found, causing re-read");
432 ctx->entry->mtime = 0;
434 if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
435 RhythmDBPodcastFields *podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (ctx->entry, RhythmDBPodcastFields);
436 /* Handle upgrades from 0.9.2.
437 * Previously, last-seen for podcast feeds was the time of the last post,
438 * and post-time was unused. Now, we want last-seen to be the time we
439 * last updated the feed, and post-time to be the time of the last post.
441 if (podcast->post_time == 0) {
442 podcast->post_time = ctx->entry->last_seen;
446 if (ctx->entry->location != NULL) {
447 RhythmDBEntry *entry;
449 g_mutex_lock (ctx->db->priv->entries_lock);
450 entry = g_hash_table_lookup (ctx->db->priv->entries, ctx->entry->location);
451 if (entry == NULL) {
452 rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx->db), ctx->entry);
453 rhythmdb_entry_insert (RHYTHMDB (ctx->db), ctx->entry);
454 if (++ctx->batch_count == RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
455 rhythmdb_commit (RHYTHMDB (ctx->db));
456 ctx->batch_count = 0;
458 } else {
459 rb_debug ("found entry with duplicate location %s. merging metadata",
460 rb_refstring_get (ctx->entry->location));
461 entry->play_count += ctx->entry->play_count;
463 if (entry->rating < 0.01)
464 entry->rating = ctx->entry->rating;
465 else if (ctx->entry->rating > 0.01)
466 entry->rating = (entry->rating + ctx->entry->rating) / 2;
468 if (ctx->entry->last_played > entry->last_played)
469 entry->last_played = ctx->entry->last_played;
471 if (ctx->entry->first_seen < entry->first_seen)
472 entry->first_seen = ctx->entry->first_seen;
474 if (ctx->entry->last_seen > entry->last_seen)
475 entry->last_seen = ctx->entry->last_seen;
477 rhythmdb_entry_unref (ctx->entry);
479 g_mutex_unlock (ctx->db->priv->entries_lock);
480 } else {
481 rb_debug ("found entry without location");
482 rhythmdb_entry_unref (ctx->entry);
484 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
485 ctx->entry = NULL;
486 break;
488 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
490 GList *entry_list;
492 rb_debug ("finished reading unknown entry");
493 ctx->unknown_entry->properties = g_list_reverse (ctx->unknown_entry->properties);
495 entry_list = g_hash_table_lookup (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename);
496 entry_list = g_list_prepend (entry_list, ctx->unknown_entry);
497 g_hash_table_insert (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename, entry_list);
499 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
500 ctx->unknown_entry = NULL;
501 break;
503 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
505 GValue value = {0,};
506 gboolean set = FALSE;
507 gboolean skip = FALSE;
509 /* special case some properties for upgrade handling etc. */
510 switch (ctx->propid) {
511 case RHYTHMDB_PROP_DATE:
512 ctx->has_date = TRUE;
513 break;
514 case RHYTHMDB_PROP_LOCATION:
515 if (ctx->canonicalise_uris) {
516 char *canon = rb_canonicalise_uri (ctx->buf->str);
518 g_value_init (&value, G_TYPE_STRING);
519 g_value_take_string (&value, canon);
520 set = TRUE;
522 break;
523 case RHYTHMDB_PROP_MOUNTPOINT:
524 /* fix old podcast posts */
525 if (g_str_has_prefix (ctx->buf->str, "http://"))
526 skip = TRUE;
527 break;
528 default:
529 break;
532 if (!skip) {
533 if (!set) {
534 rhythmdb_read_encoded_property (RHYTHMDB (ctx->db), ctx->buf->str, ctx->propid, &value);
537 rhythmdb_entry_set_internal (RHYTHMDB (ctx->db), ctx->entry, FALSE, ctx->propid, &value);
538 g_value_unset (&value);
541 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
542 break;
544 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
546 RhythmDBUnknownEntryProperty *prop;
548 g_assert (ctx->unknown_entry->properties);
549 prop = ctx->unknown_entry->properties->data;
550 g_assert (prop->value == NULL);
551 prop->value = rb_refstring_new (ctx->buf->str);
552 rb_debug ("unknown entry property: %s = %s", rb_refstring_get (prop->name), rb_refstring_get (prop->value));
554 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
555 break;
557 case RHYTHMDB_TREE_PARSER_STATE_START:
558 case RHYTHMDB_TREE_PARSER_STATE_END:
559 break;
563 static void
564 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext *ctx,
565 const char *data,
566 guint len)
568 if (*ctx->die == TRUE) {
569 xmlStopParser (ctx->xmlctx);
570 return;
573 switch (ctx->state)
575 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
576 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
577 g_string_append_len (ctx->buf, data, len);
578 break;
579 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
580 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
581 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
582 case RHYTHMDB_TREE_PARSER_STATE_START:
583 case RHYTHMDB_TREE_PARSER_STATE_END:
584 break;
588 static gboolean
589 rhythmdb_tree_load (RhythmDB *rdb,
590 gboolean *die,
591 GError **error)
593 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
594 xmlParserCtxtPtr ctxt;
595 xmlSAXHandlerPtr sax_handler;
596 struct RhythmDBTreeLoadContext *ctx;
597 char *name;
598 GError *local_error;
599 gboolean ret;
601 local_error = NULL;
603 sax_handler = g_new0 (xmlSAXHandler, 1);
604 ctx = g_new0 (struct RhythmDBTreeLoadContext, 1);
606 sax_handler->startElement = (startElementSAXFunc) rhythmdb_tree_parser_start_element;
607 sax_handler->endElement = (endElementSAXFunc) rhythmdb_tree_parser_end_element;
608 sax_handler->characters = (charactersSAXFunc) rhythmdb_tree_parser_characters;
610 ctx->state = RHYTHMDB_TREE_PARSER_STATE_START;
611 ctx->db = db;
612 ctx->die = die;
613 ctx->buf = g_string_sized_new (RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE);
614 ctx->error = &local_error;
616 g_object_get (G_OBJECT (db), "name", &name, NULL);
618 if (g_file_test (name, G_FILE_TEST_EXISTS)) {
619 ctxt = xmlCreateFileParserCtxt (name);
620 ctx->xmlctx = ctxt;
621 xmlFree (ctxt->sax);
622 ctxt->userData = ctx;
623 ctxt->sax = sax_handler;
624 xmlParseDocument (ctxt);
625 ctxt->sax = NULL;
626 xmlFreeParserCtxt (ctxt);
628 if (ctx->batch_count)
629 rhythmdb_commit (RHYTHMDB (ctx->db));
632 ret = TRUE;
633 if (local_error != NULL) {
634 g_propagate_error (error, local_error);
635 ret = FALSE;
638 g_string_free (ctx->buf, TRUE);
639 g_free (name);
640 g_free (sax_handler);
641 g_free (ctx);
643 return ret;
646 struct RhythmDBTreeSaveContext
648 RhythmDBTree *db;
649 FILE *handle;
650 char *error;
653 #ifdef HAVE_GNU_FWRITE_UNLOCKED
654 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
655 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
656 #else
657 #define RHYTHMDB_FWRITE_REAL fwrite
658 #define RHYTHMDB_FPUTC_REAL fputc
659 #endif
661 #define RHYTHMDB_FWRITE(w,x,len,handle,error) do { \
662 if (error == NULL) { \
663 if (RHYTHMDB_FWRITE_REAL (w,x,len,handle) != len) { \
664 error = g_strdup (g_strerror (errno)); \
667 } while (0)
669 #define RHYTHMDB_FPUTC(x,handle,error) do { \
670 if (error == NULL) { \
671 if (RHYTHMDB_FPUTC_REAL (x,handle) == EOF) { \
672 error = g_strdup (g_strerror (errno)); \
675 } while (0)
677 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
679 static void
680 write_elt_name_open (struct RhythmDBTreeSaveContext *ctx,
681 const xmlChar *elt_name)
683 RHYTHMDB_FWRITE_STATICSTR (" <", ctx->handle, ctx->error);
684 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
685 RHYTHMDB_FPUTC ('>', ctx->handle, ctx->error);
688 static void
689 write_elt_name_close (struct RhythmDBTreeSaveContext *ctx,
690 const xmlChar *elt_name)
692 RHYTHMDB_FWRITE_STATICSTR ("</", ctx->handle, ctx->error);
693 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
694 RHYTHMDB_FWRITE_STATICSTR (">\n", ctx->handle, ctx->error);
697 static void
698 save_entry_string (struct RhythmDBTreeSaveContext *ctx,
699 const xmlChar *elt_name,
700 const char *str)
702 xmlChar *encoded;
704 g_return_if_fail (str != NULL);
705 write_elt_name_open (ctx, elt_name);
706 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST str);
707 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
708 g_free (encoded);
709 write_elt_name_close (ctx, elt_name);
712 static void
713 save_entry_int (struct RhythmDBTreeSaveContext *ctx,
714 const xmlChar *elt_name,
715 int num)
717 char buf[92];
718 if (num == 0)
719 return;
720 write_elt_name_open (ctx, elt_name);
721 g_snprintf (buf, sizeof (buf), "%d", num);
722 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
723 write_elt_name_close (ctx, elt_name);
726 static void
727 save_entry_ulong (struct RhythmDBTreeSaveContext *ctx,
728 const xmlChar *elt_name,
729 gulong num,
730 gboolean save_zeroes)
732 char buf[92];
734 if (num == 0 && !save_zeroes)
735 return;
736 write_elt_name_open (ctx, elt_name);
737 g_snprintf (buf, sizeof (buf), "%lu", num);
738 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
739 write_elt_name_close (ctx, elt_name);
742 static void
743 save_entry_boolean (struct RhythmDBTreeSaveContext *ctx,
744 const xmlChar *elt_name,
745 gboolean val)
747 save_entry_ulong (ctx, elt_name, val ? 1 : 0, FALSE);
750 static void
751 save_entry_uint64 (struct RhythmDBTreeSaveContext *ctx,
752 const xmlChar *elt_name,
753 guint64 num)
755 char buf[92];
757 if (num == 0)
758 return;
760 write_elt_name_open (ctx, elt_name);
761 g_snprintf (buf, sizeof (buf), "%" G_GUINT64_FORMAT, num);
762 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
763 write_elt_name_close (ctx, elt_name);
766 static void
767 save_entry_double (struct RhythmDBTreeSaveContext *ctx,
768 const xmlChar *elt_name,
769 double num)
771 char buf[92];
773 if (num > -0.001 && num < 0.001)
774 return;
776 write_elt_name_open (ctx, elt_name);
777 g_snprintf (buf, sizeof (buf), "%f", num);
778 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
779 write_elt_name_close (ctx, elt_name);
782 /* This code is intended to be highly optimized. This came at a small
783 * readability cost. Sorry about that.
785 static void
786 save_entry (RhythmDBTree *db,
787 RhythmDBEntry *entry,
788 struct RhythmDBTreeSaveContext *ctx)
790 RhythmDBPropType i;
791 RhythmDBPodcastFields *podcast = NULL;
792 xmlChar *encoded;
794 if (ctx->error)
795 return;
797 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
798 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
799 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
801 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
802 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST entry->type->name);
803 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
804 g_free (encoded);
806 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
808 /* Skip over the first property - the type */
809 for (i = 1; i < RHYTHMDB_NUM_PROPERTIES; i++) {
810 const xmlChar *elt_name;
812 if (ctx->error)
813 return;
815 elt_name = rhythmdb_nice_elt_name_from_propid ((RhythmDB *) ctx->db, i);
817 switch (i) {
818 case RHYTHMDB_PROP_TYPE:
819 break;
820 case RHYTHMDB_PROP_ENTRY_ID:
821 break;
822 case RHYTHMDB_PROP_TITLE:
823 save_entry_string(ctx, elt_name, rb_refstring_get (entry->title));
824 break;
825 case RHYTHMDB_PROP_ALBUM:
826 save_entry_string(ctx, elt_name, rb_refstring_get (entry->album));
827 break;
828 case RHYTHMDB_PROP_ARTIST:
829 save_entry_string(ctx, elt_name, rb_refstring_get (entry->artist));
830 break;
831 case RHYTHMDB_PROP_GENRE:
832 save_entry_string(ctx, elt_name, rb_refstring_get (entry->genre));
833 break;
834 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
835 save_entry_string(ctx, elt_name, rb_refstring_get (entry->musicbrainz_trackid));
836 break;
837 case RHYTHMDB_PROP_TRACK_NUMBER:
838 save_entry_ulong (ctx, elt_name, entry->tracknum, FALSE);
839 break;
840 case RHYTHMDB_PROP_DISC_NUMBER:
841 save_entry_ulong (ctx, elt_name, entry->discnum, FALSE);
842 break;
843 case RHYTHMDB_PROP_DATE:
844 if (g_date_valid (&entry->date))
845 save_entry_ulong (ctx, elt_name, g_date_get_julian (&entry->date), TRUE);
846 else
847 save_entry_ulong (ctx, elt_name, 0, TRUE);
848 break;
849 case RHYTHMDB_PROP_DURATION:
850 save_entry_ulong (ctx, elt_name, entry->duration, FALSE);
851 break;
852 case RHYTHMDB_PROP_BITRATE:
853 save_entry_int(ctx, elt_name, entry->bitrate);
854 break;
855 case RHYTHMDB_PROP_TRACK_GAIN:
856 save_entry_double(ctx, elt_name, entry->track_gain);
857 break;
858 case RHYTHMDB_PROP_TRACK_PEAK:
859 save_entry_double(ctx, elt_name, entry->track_peak);
860 break;
861 case RHYTHMDB_PROP_ALBUM_GAIN:
862 save_entry_double(ctx, elt_name, entry->album_gain);
863 break;
864 case RHYTHMDB_PROP_ALBUM_PEAK:
865 save_entry_double(ctx, elt_name, entry->album_peak);
866 break;
867 case RHYTHMDB_PROP_LOCATION:
868 save_entry_string(ctx, elt_name, rb_refstring_get (entry->location));
869 break;
870 case RHYTHMDB_PROP_MOUNTPOINT:
871 /* Avoid crashes on exit when upgrading from 0.8
872 * and no mountpoint is available from some entries */
873 if (entry->mountpoint) {
874 save_entry_string(ctx, elt_name, rb_refstring_get (entry->mountpoint));
876 break;
877 case RHYTHMDB_PROP_FILE_SIZE:
878 save_entry_uint64(ctx, elt_name, entry->file_size);
879 break;
880 case RHYTHMDB_PROP_MIMETYPE:
881 save_entry_string(ctx, elt_name, rb_refstring_get (entry->mimetype));
882 break;
883 case RHYTHMDB_PROP_MTIME:
884 save_entry_ulong (ctx, elt_name, entry->mtime, FALSE);
885 break;
886 case RHYTHMDB_PROP_FIRST_SEEN:
887 save_entry_ulong (ctx, elt_name, entry->first_seen, FALSE);
888 break;
889 case RHYTHMDB_PROP_LAST_SEEN:
890 save_entry_ulong (ctx, elt_name, entry->last_seen, FALSE);
891 break;
892 case RHYTHMDB_PROP_RATING:
893 save_entry_double(ctx, elt_name, entry->rating);
894 break;
895 case RHYTHMDB_PROP_PLAY_COUNT:
896 save_entry_ulong (ctx, elt_name, entry->play_count, FALSE);
897 break;
898 case RHYTHMDB_PROP_LAST_PLAYED:
899 save_entry_ulong (ctx, elt_name, entry->last_played, FALSE);
900 break;
901 case RHYTHMDB_PROP_HIDDEN:
903 gboolean hidden = ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
904 save_entry_boolean (ctx, elt_name, hidden);
906 break;
907 case RHYTHMDB_PROP_STATUS:
908 if (podcast)
909 save_entry_ulong (ctx, elt_name, podcast->status, FALSE);
910 break;
911 case RHYTHMDB_PROP_DESCRIPTION:
912 if (podcast && podcast->description)
913 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->description));
914 break;
915 case RHYTHMDB_PROP_SUBTITLE:
916 if (podcast && podcast->subtitle)
917 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->subtitle));
918 break;
919 case RHYTHMDB_PROP_SUMMARY:
920 if (podcast && podcast->summary)
921 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->summary));
922 break;
923 case RHYTHMDB_PROP_LANG:
924 if (podcast && podcast->lang)
925 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->lang));
926 break;
927 case RHYTHMDB_PROP_COPYRIGHT:
928 if (podcast && podcast->copyright)
929 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->copyright));
930 break;
931 case RHYTHMDB_PROP_IMAGE:
932 if (podcast && podcast->image)
933 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->image));
934 break;
935 case RHYTHMDB_PROP_POST_TIME:
936 if (podcast)
937 save_entry_ulong (ctx, elt_name, podcast->post_time, FALSE);
938 break;
939 case RHYTHMDB_PROP_TITLE_SORT_KEY:
940 case RHYTHMDB_PROP_GENRE_SORT_KEY:
941 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
942 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
943 case RHYTHMDB_PROP_TITLE_FOLDED:
944 case RHYTHMDB_PROP_GENRE_FOLDED:
945 case RHYTHMDB_PROP_ARTIST_FOLDED:
946 case RHYTHMDB_PROP_ALBUM_FOLDED:
947 case RHYTHMDB_PROP_LAST_PLAYED_STR:
948 case RHYTHMDB_PROP_PLAYBACK_ERROR:
949 case RHYTHMDB_PROP_FIRST_SEEN_STR:
950 case RHYTHMDB_PROP_LAST_SEEN_STR:
951 case RHYTHMDB_PROP_SEARCH_MATCH:
952 case RHYTHMDB_PROP_YEAR:
953 case RHYTHMDB_NUM_PROPERTIES:
954 break;
958 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
961 static void
962 save_entry_type (const char *name,
963 RhythmDBEntryType entry_type,
964 struct RhythmDBTreeSaveContext *ctx)
966 if (entry_type->save_to_disk == FALSE)
967 return;
969 rb_debug ("saving entries of type %s", name);
970 rhythmdb_hash_tree_foreach (RHYTHMDB (ctx->db), entry_type,
971 (RBTreeEntryItFunc) save_entry,
972 NULL, NULL, NULL, ctx);
975 static void
976 save_unknown_entry_type (RBRefString *typename,
977 GList *entries,
978 struct RhythmDBTreeSaveContext *ctx)
980 GList *t;
982 for (t = entries; t != NULL; t = t->next) {
983 RhythmDBUnknownEntry *entry;
984 xmlChar *encoded;
985 GList *p;
987 if (ctx->error)
988 return;
990 entry = (RhythmDBUnknownEntry *)t->data;
992 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
993 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST rb_refstring_get (entry->typename));
994 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
995 g_free (encoded);
997 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
999 for (p = entry->properties; p != NULL; p = p->next) {
1000 RhythmDBUnknownEntryProperty *prop;
1001 prop = (RhythmDBUnknownEntryProperty *) p->data;
1002 save_entry_string(ctx, (const xmlChar *)rb_refstring_get (prop->name), rb_refstring_get (prop->value));
1005 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
1009 static void
1010 rhythmdb_tree_save (RhythmDB *rdb)
1012 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1013 char *name;
1014 GString *savepath;
1015 FILE *f;
1016 struct RhythmDBTreeSaveContext ctx;
1018 g_object_get (G_OBJECT (db), "name", &name, NULL);
1020 savepath = g_string_new (name);
1021 g_string_append (savepath, ".tmp");
1023 f = fopen (savepath->str, "w");
1025 if (!f) {
1026 g_warning ("Can't save XML: %s", g_strerror (errno));
1027 goto out;
1030 ctx.db = db;
1031 ctx.handle = f;
1032 ctx.error = NULL;
1033 RHYTHMDB_FWRITE_STATICSTR ("<?xml version=\"1.0\" standalone=\"yes\"?>\n"
1034 "<rhythmdb version=\"" RHYTHMDB_TREE_XML_VERSION "\">\n",
1035 ctx.handle, ctx.error);
1037 rhythmdb_entry_type_foreach (rdb, (GHFunc) save_entry_type, &ctx);
1038 g_hash_table_foreach (db->priv->unknown_entry_types,
1039 (GHFunc) save_unknown_entry_type,
1040 &ctx);
1042 RHYTHMDB_FWRITE_STATICSTR ("</rhythmdb>\n", ctx.handle, ctx.error);
1044 if (fclose (f) < 0) {
1045 g_warning ("Couldn't close %s: %s",
1046 savepath->str,
1047 g_strerror (errno));
1048 unlink (savepath->str);
1049 goto out;
1052 if (ctx.error != NULL) {
1053 g_warning ("Writing to the database failed: %s", ctx.error);
1054 g_free (ctx.error);
1055 unlink (savepath->str);
1056 } else {
1057 if (rename (savepath->str, name) < 0) {
1058 g_warning ("Couldn't rename %s to %s: %s",
1059 name, savepath->str,
1060 g_strerror (errno));
1061 unlink (savepath->str);
1065 out:
1066 g_string_free (savepath, TRUE);
1067 g_free (name);
1068 return;
1071 #undef RHYTHMDB_FWRITE_ENCODED_STR
1072 #undef RHYTHMDB_FWRITE_STATICSTR
1073 #undef RHYTHMDB_FPUTC
1074 #undef RHYTHMDB_FWRITE
1076 RhythmDB *
1077 rhythmdb_tree_new (const char *name)
1079 RhythmDBTree *db = g_object_new (RHYTHMDB_TYPE_TREE, "name", name, NULL);
1081 g_return_val_if_fail (db->priv != NULL, NULL);
1083 return RHYTHMDB (db);
1086 static void
1087 set_entry_album (RhythmDBTree *db,
1088 RhythmDBEntry *entry,
1089 RhythmDBTreeProperty *artist,
1090 RBRefString *name)
1092 struct RhythmDBTreeProperty *prop;
1093 prop = get_or_create_album (db, artist, name);
1094 g_hash_table_insert (prop->children, entry, NULL);
1095 entry->data = prop;
1098 static void
1099 rhythmdb_tree_entry_new (RhythmDB *rdb,
1100 RhythmDBEntry *entry)
1102 g_mutex_lock (RHYTHMDB_TREE(rdb)->priv->entries_lock);
1103 rhythmdb_tree_entry_new_internal (rdb, entry);
1104 g_mutex_unlock (RHYTHMDB_TREE(rdb)->priv->entries_lock);
1107 static void
1108 rhythmdb_tree_entry_new_internal (RhythmDB *rdb, RhythmDBEntry *entry)
1110 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1111 RhythmDBTreeProperty *artist;
1112 RhythmDBTreeProperty *genre;
1114 g_assert (entry != NULL);
1116 g_return_if_fail (entry->location != NULL);
1118 if (entry->title == NULL) {
1119 g_warning ("Entry %s has missing title", rb_refstring_get (entry->location));
1120 entry->title = rb_refstring_new (_("Unknown"));
1122 if (entry->artist == NULL) {
1123 g_warning ("Entry %s has missing artist", rb_refstring_get (entry->location));
1124 entry->artist = rb_refstring_new (_("Unknown"));
1126 if (entry->album == NULL) {
1127 g_warning ("Entry %s has missing album", rb_refstring_get (entry->location));
1128 entry->album = rb_refstring_new (_("Unknown"));
1130 if (entry->genre == NULL) {
1131 g_warning ("Entry %s has missing genre", rb_refstring_get (entry->location));
1132 entry->genre = rb_refstring_new (_("Unknown"));
1134 if (entry->mimetype == NULL) {
1135 g_warning ("Entry %s has missing mimetype", rb_refstring_get (entry->location));
1136 entry->mimetype = rb_refstring_new ("unknown/unknown");
1139 /* Initialize the tree structure. */
1140 genre = get_or_create_genre (db, entry->type, entry->genre);
1141 artist = get_or_create_artist (db, genre, entry->artist);
1142 set_entry_album (db, entry, artist, entry->album);
1144 /* this accounts for the initial reference on the entry */
1145 g_hash_table_insert (db->priv->entries, entry->location, entry);
1147 entry->flags &= ~RHYTHMDB_ENTRY_TREE_LOADING;
1150 static RhythmDBTreeProperty *
1151 rhythmdb_tree_property_new (RhythmDBTree *db)
1153 RhythmDBTreeProperty *ret = g_new0 (RhythmDBTreeProperty, 1);
1154 #ifndef G_DISABLE_ASSERT
1155 ret->magic = 0xf00dbeef;
1156 #endif
1157 return ret;
1160 static GHashTable *
1161 get_genres_hash_for_type (RhythmDBTree *db,
1162 RhythmDBEntryType type)
1164 GHashTable *table;
1166 table = g_hash_table_lookup (db->priv->genres, type);
1167 if (table == NULL) {
1168 table = g_hash_table_new_full (rb_refstring_hash,
1169 rb_refstring_equal,
1170 (GDestroyNotify) rb_refstring_unref,
1171 NULL);
1172 if (table == NULL) {
1173 g_warning ("Out of memory\n");
1174 return NULL;
1176 g_hash_table_insert (db->priv->genres,
1177 type,
1178 table);
1180 return table;
1183 typedef void (*RBHFunc)(RhythmDBTree *db, GHashTable *genres, gpointer data);
1185 typedef struct {
1186 RhythmDBTree *db;
1187 RBHFunc func;
1188 gpointer data;
1189 } GenresIterCtxt;
1191 static void
1192 genres_process_one (gpointer key,
1193 gpointer value,
1194 gpointer user_data)
1196 GenresIterCtxt *ctxt = (GenresIterCtxt *)user_data;
1197 ctxt->func (ctxt->db, (GHashTable *)value, ctxt->data);
1200 static void
1201 genres_hash_foreach (RhythmDBTree *db, RBHFunc func, gpointer data)
1203 GenresIterCtxt ctxt;
1205 ctxt.db = db;
1206 ctxt.func = func;
1207 ctxt.data = data;
1208 g_hash_table_foreach (db->priv->genres, genres_process_one, &ctxt);
1211 static RhythmDBTreeProperty *
1212 get_or_create_genre (RhythmDBTree *db,
1213 RhythmDBEntryType type,
1214 RBRefString *name)
1216 RhythmDBTreeProperty *genre;
1217 GHashTable *table;
1219 g_mutex_lock (db->priv->genres_lock);
1220 table = get_genres_hash_for_type (db, type);
1221 genre = g_hash_table_lookup (table, name);
1223 if (G_UNLIKELY (genre == NULL)) {
1224 genre = rhythmdb_tree_property_new (db);
1225 genre->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1226 (GDestroyNotify) rb_refstring_unref,
1227 NULL);
1228 rb_refstring_ref (name);
1229 g_hash_table_insert (table, name, genre);
1230 genre->parent = NULL;
1232 g_mutex_unlock (db->priv->genres_lock);
1234 return genre;
1237 static RhythmDBTreeProperty *
1238 get_or_create_artist (RhythmDBTree *db,
1239 RhythmDBTreeProperty *genre,
1240 RBRefString *name)
1242 RhythmDBTreeProperty *artist;
1244 artist = g_hash_table_lookup (genre->children, name);
1246 if (G_UNLIKELY (artist == NULL)) {
1247 artist = rhythmdb_tree_property_new (db);
1248 artist->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1249 (GDestroyNotify) rb_refstring_unref,
1250 NULL);
1251 rb_refstring_ref (name);
1252 g_hash_table_insert (genre->children, name, artist);
1253 artist->parent = genre;
1256 return artist;
1259 static RhythmDBTreeProperty *
1260 get_or_create_album (RhythmDBTree *db,
1261 RhythmDBTreeProperty *artist,
1262 RBRefString *name)
1264 RhythmDBTreeProperty *album;
1266 album = g_hash_table_lookup (artist->children, name);
1268 if (G_UNLIKELY (album == NULL)) {
1269 album = rhythmdb_tree_property_new (db);
1270 album->children = g_hash_table_new (g_direct_hash, g_direct_equal);
1271 rb_refstring_ref (name);
1272 g_hash_table_insert (artist->children, name, album);
1273 album->parent = artist;
1276 return album;
1279 static gboolean
1280 remove_child (RhythmDBTreeProperty *parent,
1281 gconstpointer data)
1283 g_assert (g_hash_table_remove (parent->children, data));
1284 if (g_hash_table_size (parent->children) <= 0) {
1285 return TRUE;
1287 return FALSE;
1290 static void
1291 remove_entry_from_album (RhythmDBTree *db,
1292 RhythmDBEntry *entry)
1294 GHashTable *table;
1296 rb_refstring_ref (entry->genre);
1297 rb_refstring_ref (entry->artist);
1298 rb_refstring_ref (entry->album);
1300 g_mutex_lock (db->priv->genres_lock);
1301 table = get_genres_hash_for_type (db, entry->type);
1302 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry), entry)) {
1303 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent,
1304 entry->album)) {
1306 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent,
1307 entry->artist)) {
1308 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent);
1309 g_assert (g_hash_table_remove (table, entry->genre));
1311 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent);
1314 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry));
1316 g_mutex_unlock (db->priv->genres_lock);
1318 rb_refstring_unref (entry->genre);
1319 rb_refstring_unref (entry->artist);
1320 rb_refstring_unref (entry->album);
1323 static gboolean
1324 rhythmdb_tree_entry_set (RhythmDB *adb,
1325 RhythmDBEntry *entry,
1326 guint propid,
1327 const GValue *value)
1329 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1330 RhythmDBEntryType type;
1332 type = entry->type;
1334 /* don't process changes to entries we're loading, we'll get them
1335 * when the entry is complete.
1337 if (entry->flags & RHYTHMDB_ENTRY_TREE_LOADING)
1338 return FALSE;
1340 /* Handle special properties */
1341 switch (propid)
1343 case RHYTHMDB_PROP_LOCATION:
1345 RBRefString *s;
1346 /* We have to use the string in the entry itself as the hash key,
1347 * otherwise either we leak it, or the string vanishes when the
1348 * GValue is freed; this means we have to do the entry modification
1349 * here, rather than letting rhythmdb_entry_set_internal do it.
1351 g_mutex_lock (db->priv->entries_lock);
1352 g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1354 s = rb_refstring_new (g_value_get_string (value));
1355 rb_refstring_unref (entry->location);
1356 entry->location = s;
1357 g_hash_table_insert (db->priv->entries, entry->location, entry);
1358 g_mutex_unlock (db->priv->entries_lock);
1360 return TRUE;
1362 case RHYTHMDB_PROP_ALBUM:
1364 const char *albumname = g_value_get_string (value);
1366 if (strcmp (rb_refstring_get (entry->album), albumname)) {
1367 RhythmDBTreeProperty *artist;
1368 RhythmDBTreeProperty *genre;
1370 rb_refstring_ref (entry->genre);
1371 rb_refstring_ref (entry->artist);
1372 rb_refstring_ref (entry->album);
1374 remove_entry_from_album (db, entry);
1376 genre = get_or_create_genre (db, type, entry->genre);
1377 artist = get_or_create_artist (db, genre, entry->artist);
1378 set_entry_album (db, entry, artist, rb_refstring_new (albumname));
1380 rb_refstring_unref (entry->genre);
1381 rb_refstring_unref (entry->artist);
1382 rb_refstring_unref (entry->album);
1384 break;
1386 case RHYTHMDB_PROP_ARTIST:
1388 const char *artistname = g_value_get_string (value);
1390 if (strcmp (rb_refstring_get (entry->artist), artistname)) {
1391 RhythmDBTreeProperty *new_artist;
1392 RhythmDBTreeProperty *genre;
1394 rb_refstring_ref (entry->genre);
1395 rb_refstring_ref (entry->artist);
1396 rb_refstring_ref (entry->album);
1398 remove_entry_from_album (db, entry);
1400 genre = get_or_create_genre (db, type, entry->genre);
1401 new_artist = get_or_create_artist (db, genre,
1402 rb_refstring_new (artistname));
1403 set_entry_album (db, entry, new_artist, entry->album);
1405 rb_refstring_unref (entry->genre);
1406 rb_refstring_unref (entry->artist);
1407 rb_refstring_unref (entry->album);
1409 break;
1411 case RHYTHMDB_PROP_GENRE:
1413 const char *genrename = g_value_get_string (value);
1415 if (strcmp (rb_refstring_get (entry->genre), genrename)) {
1416 RhythmDBTreeProperty *new_genre;
1417 RhythmDBTreeProperty *new_artist;
1419 rb_refstring_ref (entry->genre);
1420 rb_refstring_ref (entry->artist);
1421 rb_refstring_ref (entry->album);
1423 remove_entry_from_album (db, entry);
1425 new_genre = get_or_create_genre (db, type,
1426 rb_refstring_new (genrename));
1427 new_artist = get_or_create_artist (db, new_genre, entry->artist);
1428 set_entry_album (db, entry, new_artist, entry->album);
1430 rb_refstring_unref (entry->genre);
1431 rb_refstring_unref (entry->artist);
1432 rb_refstring_unref (entry->album);
1434 break;
1436 default:
1437 break;
1440 return FALSE;
1443 static void
1444 rhythmdb_tree_entry_delete (RhythmDB *adb,
1445 RhythmDBEntry *entry)
1447 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1449 remove_entry_from_album (db, entry);
1451 g_mutex_lock (db->priv->entries_lock);
1452 g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1453 rhythmdb_entry_unref (entry);
1454 g_mutex_unlock (db->priv->entries_lock);
1457 typedef struct {
1458 RhythmDB *db;
1459 RhythmDBEntryType type;
1460 } RbEntryRemovalCtxt;
1462 static gboolean
1463 remove_one_song (gpointer key,
1464 RhythmDBEntry *entry,
1465 RbEntryRemovalCtxt *ctxt)
1467 g_return_val_if_fail (entry != NULL, FALSE);
1469 if (entry->type == ctxt->type) {
1470 rhythmdb_emit_entry_deleted (ctxt->db, entry);
1471 remove_entry_from_album (RHYTHMDB_TREE (ctxt->db), entry);
1472 rhythmdb_entry_unref (entry);
1473 return TRUE;
1475 return FALSE;
1478 static void
1479 rhythmdb_tree_entry_delete_by_type (RhythmDB *adb,
1480 RhythmDBEntryType type)
1482 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1483 RbEntryRemovalCtxt ctxt;
1485 ctxt.db = adb;
1486 ctxt.type = type;
1487 g_mutex_lock (db->priv->entries_lock);
1488 g_hash_table_foreach_remove (db->priv->entries,
1489 (GHRFunc) remove_one_song, &ctxt);
1490 g_mutex_unlock (db->priv->entries_lock);
1493 static void
1494 destroy_tree_property (RhythmDBTreeProperty *prop)
1496 #ifndef G_DISABLE_ASSERT
1497 prop->magic = 0xf33df33d;
1498 #endif
1499 g_hash_table_destroy (prop->children);
1500 g_free (prop);
1503 typedef void (*RhythmDBTreeTraversalFunc) (RhythmDBTree *db, RhythmDBEntry *entry, gpointer data);
1504 typedef void (*RhythmDBTreeAlbumTraversalFunc) (RhythmDBTree *db, RhythmDBTreeProperty *album, gpointer data);
1506 struct RhythmDBTreeTraversalData
1508 RhythmDBTree *db;
1509 GPtrArray *query;
1510 RhythmDBTreeTraversalFunc func;
1511 gpointer data;
1512 gboolean *cancel;
1515 static gboolean
1516 rhythmdb_tree_evaluate_query (RhythmDB *adb,
1517 GPtrArray *query,
1518 RhythmDBEntry *entry)
1520 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1521 guint i;
1522 guint last_disjunction;
1524 for (i = 0, last_disjunction = 0; i < query->len; i++) {
1525 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1527 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1528 if (evaluate_conjunctive_subquery (db, query, last_disjunction, i, entry))
1529 return TRUE;
1531 last_disjunction = i + 1;
1534 if (evaluate_conjunctive_subquery (db, query, last_disjunction, query->len, entry))
1535 return TRUE;
1536 return FALSE;
1539 #define RHYTHMDB_PROPERTY_COMPARE(OP) \
1540 switch (rhythmdb_get_property_type (db, data->propid)) { \
1541 case G_TYPE_STRING: \
1542 if (strcmp (rhythmdb_entry_get_string (entry, data->propid), \
1543 g_value_get_string (data->val)) OP 0) \
1544 return FALSE; \
1545 break; \
1546 case G_TYPE_ULONG: \
1547 if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1548 g_value_get_ulong (data->val)) \
1549 return FALSE; \
1550 break; \
1551 case G_TYPE_BOOLEAN: \
1552 if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1553 g_value_get_boolean (data->val)) \
1554 return FALSE; \
1555 break; \
1556 case G_TYPE_UINT64: \
1557 if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1558 g_value_get_uint64 (data->val)) \
1559 return FALSE; \
1560 break; \
1561 case G_TYPE_DOUBLE: \
1562 if (rhythmdb_entry_get_double (entry, data->propid) OP \
1563 g_value_get_double (data->val)) \
1564 return FALSE; \
1565 break; \
1566 case G_TYPE_POINTER: \
1567 if (rhythmdb_entry_get_pointer (entry, data->propid) OP \
1568 g_value_get_pointer (data->val)) \
1569 return FALSE; \
1570 break; \
1571 default: \
1572 g_warning ("Unexpected type: %s", g_type_name (rhythmdb_get_property_type (db, data->propid))); \
1573 g_assert_not_reached (); \
1576 static gboolean
1577 search_match_properties (RhythmDB *db,
1578 RhythmDBEntry *entry,
1579 gchar **words)
1581 const RhythmDBPropType props[] = {
1582 RHYTHMDB_PROP_TITLE_FOLDED,
1583 RHYTHMDB_PROP_ALBUM_FOLDED,
1584 RHYTHMDB_PROP_ARTIST_FOLDED,
1585 RHYTHMDB_PROP_GENRE_FOLDED
1587 gboolean islike = TRUE;
1588 gchar **current;
1589 int i;
1591 for (current = words; *current != NULL; current++) {
1592 gboolean word_found = FALSE;
1594 for (i = 0; i < G_N_ELEMENTS (props); i++) {
1595 const char *entry_string = rhythmdb_entry_get_string (entry, props[i]);
1596 if (entry_string && (strstr (entry_string, *current) != NULL)) {
1597 /* the word was found, go to the next one */
1598 word_found = TRUE;
1599 break;
1602 if (!word_found) {
1603 /* the word wasn't in any of the properties*/
1604 islike = FALSE;
1605 break;
1609 return islike;
1612 static gboolean
1613 evaluate_conjunctive_subquery (RhythmDBTree *dbtree,
1614 GPtrArray *query,
1615 guint base,
1616 guint max,
1617 RhythmDBEntry *entry)
1620 RhythmDB *db = (RhythmDB *) dbtree;
1621 guint i;
1622 /* Optimization possibility - we may get here without actually having
1623 * anything in the query. It would be faster to instead just merge
1624 * the child hash table into the query result hash.
1626 for (i = base; i < max; i++) {
1627 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1629 switch (data->type) {
1630 case RHYTHMDB_QUERY_SUBQUERY:
1632 gboolean matched = FALSE;
1633 GList *conjunctions = split_query_by_disjunctions (dbtree, data->subquery);
1634 GList *tem;
1636 if (conjunctions == NULL)
1637 matched = TRUE;
1639 for (tem = conjunctions; tem; tem = tem->next) {
1640 GPtrArray *subquery = tem->data;
1641 if (!matched && evaluate_conjunctive_subquery (dbtree, subquery,
1642 0, subquery->len,
1643 entry)) {
1644 matched = TRUE;
1646 g_ptr_array_free (tem->data, TRUE);
1648 g_list_free (conjunctions);
1649 if (!matched)
1650 return FALSE;
1652 break;
1653 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
1654 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
1656 gulong relative_time;
1657 GTimeVal current_time;
1659 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_ULONG);
1661 relative_time = g_value_get_ulong (data->val);
1662 g_get_current_time (&current_time);
1664 if (data->type == RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN) {
1665 if (!(rhythmdb_entry_get_ulong (entry, data->propid) >= (current_time.tv_sec - relative_time)))
1666 return FALSE;
1667 } else {
1668 if (!(rhythmdb_entry_get_ulong (entry, data->propid) < (current_time.tv_sec - relative_time)))
1669 return FALSE;
1671 break;
1673 case RHYTHMDB_QUERY_PROP_PREFIX:
1674 case RHYTHMDB_QUERY_PROP_SUFFIX:
1676 const char *value_s;
1677 const char *entry_s;
1679 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING);
1681 value_s = g_value_get_string (data->val);
1682 entry_s = rhythmdb_entry_get_string (entry, data->propid);
1684 if (data->type == RHYTHMDB_QUERY_PROP_PREFIX && !g_str_has_prefix (entry_s, value_s))
1685 return FALSE;
1686 if (data->type == RHYTHMDB_QUERY_PROP_SUFFIX && !g_str_has_suffix (entry_s, value_s))
1687 return FALSE;
1689 break;
1691 case RHYTHMDB_QUERY_PROP_LIKE:
1692 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
1694 if (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING) {
1695 gboolean islike;
1697 if (data->propid == RHYTHMDB_PROP_SEARCH_MATCH) {
1698 /* this is a special property, that should match several things */
1699 islike = search_match_properties (db, entry, g_value_get_boxed (data->val));
1701 } else {
1702 const gchar *value_string = g_value_get_string (data->val);
1703 const char *entry_string = rhythmdb_entry_get_string (entry, data->propid);
1705 /* check in case the property is NULL, the value should never be NULL */
1706 if (entry_string == NULL)
1707 return FALSE;
1709 islike = (strstr (entry_string, value_string) != NULL);
1712 if ((data->type == RHYTHMDB_QUERY_PROP_LIKE) ^ islike)
1713 return FALSE;
1714 else
1715 continue;
1716 break;
1718 /* Fall through */
1720 case RHYTHMDB_QUERY_PROP_EQUALS:
1721 RHYTHMDB_PROPERTY_COMPARE (!=)
1722 break;
1723 case RHYTHMDB_QUERY_PROP_GREATER:
1724 RHYTHMDB_PROPERTY_COMPARE (<)
1725 break;
1726 case RHYTHMDB_QUERY_PROP_LESS:
1727 RHYTHMDB_PROPERTY_COMPARE (>)
1728 break;
1729 case RHYTHMDB_QUERY_END:
1730 case RHYTHMDB_QUERY_DISJUNCTION:
1731 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
1732 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
1733 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
1734 g_assert_not_reached ();
1735 break;
1738 return TRUE;
1741 static void
1742 do_conjunction (RhythmDBEntry *entry,
1743 gpointer unused,
1744 struct RhythmDBTreeTraversalData *data)
1746 if (G_UNLIKELY (*data->cancel))
1747 return;
1748 /* Finally, we actually evaluate the query! */
1749 if (evaluate_conjunctive_subquery (data->db, data->query, 0, data->query->len,
1750 entry)) {
1751 data->func (data->db, entry, data->data);
1755 static void
1756 conjunctive_query_songs (const char *name,
1757 RhythmDBTreeProperty *album,
1758 struct RhythmDBTreeTraversalData *data)
1760 if (G_UNLIKELY (*data->cancel))
1761 return;
1762 g_hash_table_foreach (album->children, (GHFunc) do_conjunction, data);
1765 static GPtrArray *
1766 clone_remove_ptr_array_index (GPtrArray *arr,
1767 guint index)
1769 GPtrArray *ret = g_ptr_array_new ();
1770 guint i;
1771 for (i = 0; i < arr->len; i++)
1772 if (i != index)
1773 g_ptr_array_add (ret, g_ptr_array_index (arr, i));
1775 return ret;
1778 static void
1779 conjunctive_query_albums (const char *name,
1780 RhythmDBTreeProperty *artist,
1781 struct RhythmDBTreeTraversalData *data)
1783 guint i;
1784 int album_query_idx = -1;
1786 if (G_UNLIKELY (*data->cancel))
1787 return;
1789 for (i = 0; i < data->query->len; i++) {
1790 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1791 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1792 && qdata->propid == RHYTHMDB_PROP_ALBUM) {
1793 if (album_query_idx > 0)
1794 return;
1795 album_query_idx = i;
1800 if (album_query_idx >= 0) {
1801 RhythmDBTreeProperty *album;
1802 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, album_query_idx);
1803 RBRefString *albumname = rb_refstring_new (g_value_get_string (qdata->val));
1804 GPtrArray *oldquery = data->query;
1806 data->query = clone_remove_ptr_array_index (data->query, album_query_idx);
1808 album = g_hash_table_lookup (artist->children, albumname);
1810 if (album != NULL) {
1811 conjunctive_query_songs (rb_refstring_get (albumname), album, data);
1813 g_ptr_array_free (data->query, TRUE);
1814 data->query = oldquery;
1815 return;
1818 g_hash_table_foreach (artist->children, (GHFunc) conjunctive_query_songs, data);
1821 static void
1822 conjunctive_query_artists (const char *name,
1823 RhythmDBTreeProperty *genre,
1824 struct RhythmDBTreeTraversalData *data)
1826 guint i;
1827 int artist_query_idx = -1;
1829 if (G_UNLIKELY (*data->cancel))
1830 return;
1832 for (i = 0; i < data->query->len; i++) {
1833 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1834 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1835 && qdata->propid == RHYTHMDB_PROP_ARTIST) {
1836 if (artist_query_idx > 0)
1837 return;
1838 artist_query_idx = i;
1843 if (artist_query_idx >= 0) {
1844 RhythmDBTreeProperty *artist;
1845 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, artist_query_idx);
1846 RBRefString *artistname = rb_refstring_new (g_value_get_string (qdata->val));
1847 GPtrArray *oldquery = data->query;
1849 data->query = clone_remove_ptr_array_index (data->query, artist_query_idx);
1851 artist = g_hash_table_lookup (genre->children, artistname);
1852 if (artist != NULL) {
1853 conjunctive_query_albums (rb_refstring_get (artistname), artist, data);
1855 g_ptr_array_free (data->query, TRUE);
1856 data->query = oldquery;
1857 return;
1860 g_hash_table_foreach (genre->children, (GHFunc) conjunctive_query_albums, data);
1863 static void
1864 conjunctive_query_genre (RhythmDBTree *db,
1865 GHashTable *genres,
1866 struct RhythmDBTreeTraversalData *data)
1868 int genre_query_idx = -1;
1869 guint i;
1871 if (G_UNLIKELY (*data->cancel))
1872 return;
1874 for (i = 0; i < data->query->len; i++) {
1875 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1876 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1877 && qdata->propid == RHYTHMDB_PROP_GENRE) {
1878 /* A song can't currently have two genres. So
1879 * if we get a conjunctive query for that, we
1880 * know the result must be the empty set. */
1881 if (genre_query_idx > 0)
1882 return;
1883 genre_query_idx = i;
1888 if (genre_query_idx >= 0) {
1889 RhythmDBTreeProperty *genre;
1890 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, genre_query_idx);
1891 RBRefString *genrename = rb_refstring_new (g_value_get_string (qdata->val));
1892 GPtrArray *oldquery = data->query;
1894 data->query = clone_remove_ptr_array_index (data->query, genre_query_idx);
1896 genre = g_hash_table_lookup (genres, genrename);
1897 if (genre != NULL) {
1898 conjunctive_query_artists (rb_refstring_get (genrename), genre, data);
1900 g_ptr_array_free (data->query, TRUE);
1901 data->query = oldquery;
1902 return;
1905 g_hash_table_foreach (genres, (GHFunc) conjunctive_query_artists, data);
1908 static void
1909 conjunctive_query (RhythmDBTree *db,
1910 GPtrArray *query,
1911 RhythmDBTreeTraversalFunc func,
1912 gpointer data,
1913 gboolean *cancel)
1915 int type_query_idx = -1;
1916 guint i;
1917 struct RhythmDBTreeTraversalData *traversal_data;
1919 for (i = 0; i < query->len; i++) {
1920 RhythmDBQueryData *qdata = g_ptr_array_index (query, i);
1921 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1922 && qdata->propid == RHYTHMDB_PROP_TYPE) {
1923 /* A song can't have two types. */
1924 if (type_query_idx > 0)
1925 return;
1926 type_query_idx = i;
1930 traversal_data = g_new (struct RhythmDBTreeTraversalData, 1);
1931 traversal_data->db = db;
1932 traversal_data->query = query;
1933 traversal_data->func = func;
1934 traversal_data->data = data;
1935 traversal_data->cancel = cancel;
1937 g_mutex_lock (db->priv->genres_lock);
1938 if (type_query_idx >= 0) {
1939 GHashTable *genres;
1940 RhythmDBEntryType etype;
1941 RhythmDBQueryData *qdata = g_ptr_array_index (query, type_query_idx);
1943 g_ptr_array_remove_index_fast (query, type_query_idx);
1945 etype = g_value_get_pointer (qdata->val);
1946 genres = get_genres_hash_for_type (db, etype);
1947 if (genres != NULL) {
1948 conjunctive_query_genre (db, genres, traversal_data);
1949 } else {
1950 g_assert_not_reached ();
1952 } else {
1953 /* FIXME */
1954 /* No type was given; punt and query everything */
1955 genres_hash_foreach (db, (RBHFunc)conjunctive_query_genre,
1956 traversal_data);
1958 g_mutex_unlock (db->priv->genres_lock);
1960 g_free (traversal_data);
1963 static GList *
1964 split_query_by_disjunctions (RhythmDBTree *db,
1965 GPtrArray *query)
1967 GList *conjunctions = NULL;
1968 guint i, j;
1969 guint last_disjunction = 0;
1970 GPtrArray *subquery = g_ptr_array_new ();
1972 for (i = 0; i < query->len; i++) {
1973 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1974 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1976 /* Copy the subquery */
1977 for (j = last_disjunction; j < i; j++) {
1978 g_ptr_array_add (subquery, g_ptr_array_index (query, j));
1981 conjunctions = g_list_prepend (conjunctions, subquery);
1982 last_disjunction = i+1;
1983 g_assert (subquery->len > 0);
1984 subquery = g_ptr_array_new ();
1988 /* Copy the last subquery, except for the QUERY_END */
1989 for (i = last_disjunction; i < query->len; i++) {
1990 g_ptr_array_add (subquery, g_ptr_array_index (query, i));
1993 if (subquery->len > 0)
1994 conjunctions = g_list_prepend (conjunctions, subquery);
1995 else
1996 g_ptr_array_free (subquery, TRUE);
1998 return conjunctions;
2001 struct RhythmDBTreeQueryGatheringData
2003 RhythmDBTree *db;
2004 GPtrArray *queue;
2005 GHashTable *entries;
2006 RhythmDBQueryResults *results;
2009 static void
2010 do_query_recurse (RhythmDBTree *db,
2011 GPtrArray *query,
2012 RhythmDBTreeTraversalFunc func,
2013 struct RhythmDBTreeQueryGatheringData *data,
2014 gboolean *cancel)
2016 GList *conjunctions, *tem;
2018 if (query == NULL)
2019 return;
2021 conjunctions = split_query_by_disjunctions (db, query);
2022 rb_debug ("doing recursive query, %d conjunctions", g_list_length (conjunctions));
2024 if (conjunctions == NULL)
2025 return;
2027 /* If there is a disjunction involved, we must uniquify the entry hits. */
2028 if (conjunctions->next != NULL)
2029 data->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
2030 else
2031 data->entries = NULL;
2033 for (tem = conjunctions; tem; tem = tem->next) {
2034 if (G_UNLIKELY (*cancel))
2035 break;
2036 conjunctive_query (db, tem->data, func, data, cancel);
2037 g_ptr_array_free (tem->data, TRUE);
2040 if (data->entries != NULL)
2041 g_hash_table_destroy (data->entries);
2043 g_list_free (conjunctions);
2046 static void
2047 handle_entry_match (RhythmDB *db,
2048 RhythmDBEntry *entry,
2049 struct RhythmDBTreeQueryGatheringData *data)
2052 if (data->entries
2053 && g_hash_table_lookup (data->entries, entry))
2054 return;
2056 g_ptr_array_add (data->queue, entry);
2057 if (data->queue->len > RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
2058 rhythmdb_query_results_add_results (data->results, data->queue);
2059 data->queue = g_ptr_array_new ();
2063 static void
2064 rhythmdb_tree_do_full_query (RhythmDB *adb,
2065 GPtrArray *query,
2066 RhythmDBQueryResults *results,
2067 gboolean *cancel)
2069 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2070 struct RhythmDBTreeQueryGatheringData *data = g_new0 (struct RhythmDBTreeQueryGatheringData, 1);
2072 data->results = results;
2073 data->queue = g_ptr_array_new ();
2075 do_query_recurse (db, query, (RhythmDBTreeTraversalFunc) handle_entry_match, data, cancel);
2077 rhythmdb_query_results_add_results (data->results, data->queue);
2079 g_free (data);
2082 static RhythmDBEntry *
2083 rhythmdb_tree_entry_lookup_by_location (RhythmDB *adb,
2084 RBRefString *uri)
2086 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2087 RhythmDBEntry *entry;
2089 g_mutex_lock (db->priv->entries_lock);
2090 entry = g_hash_table_lookup (db->priv->entries, uri);
2091 g_mutex_unlock (db->priv->entries_lock);
2093 return entry;
2096 struct RhythmDBEntryForeachCtxt
2098 RhythmDBTree *db;
2099 GFunc func;
2100 gpointer user_data;
2103 static void
2104 rhythmdb_tree_entry_foreach_func (gpointer key, RhythmDBEntry *val, GPtrArray *list)
2106 rhythmdb_entry_ref (val);
2107 g_ptr_array_add (list, val);
2110 static void
2111 rhythmdb_tree_entry_foreach (RhythmDB *rdb, GFunc foreach_func, gpointer user_data)
2113 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2114 GPtrArray *list;
2115 guint size, i;
2117 g_mutex_lock (db->priv->entries_lock);
2118 size = g_hash_table_size (db->priv->entries);
2119 list = g_ptr_array_sized_new (size);
2120 g_hash_table_foreach (db->priv->entries, (GHFunc)rhythmdb_tree_entry_foreach_func, list);
2121 g_mutex_unlock (db->priv->entries_lock);
2123 for (i = 0; i < size; i++) {
2124 RhythmDBEntry *entry = (RhythmDBEntry*)g_ptr_array_index (list, i);
2125 (*foreach_func) (entry, user_data);
2126 rhythmdb_entry_unref (entry);
2129 g_ptr_array_free (list, TRUE);
2133 struct HashTreeIteratorCtxt {
2134 RhythmDBTree *db;
2135 RBTreeEntryItFunc entry_func;
2136 RBTreePropertyItFunc album_func;
2137 RBTreePropertyItFunc artist_func;
2138 RBTreePropertyItFunc genres_func;
2139 gpointer data;
2142 static void
2143 hash_tree_entries_foreach (gpointer key,
2144 gpointer value,
2145 gpointer data)
2147 RhythmDBEntry *entry = (RhythmDBEntry *) key;
2148 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2150 g_assert (ctxt->entry_func);
2152 ctxt->entry_func (ctxt->db, entry, ctxt->data);
2155 static void
2156 hash_tree_albums_foreach (gpointer key,
2157 gpointer value,
2158 gpointer data)
2160 RhythmDBTreeProperty *album = (RhythmDBTreeProperty *)value;
2161 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2163 if (ctxt->album_func) {
2164 ctxt->album_func (ctxt->db, album, ctxt->data);
2166 if (ctxt->entry_func != NULL) {
2167 g_hash_table_foreach (album->children,
2168 hash_tree_entries_foreach,
2169 ctxt);
2173 static void
2174 hash_tree_artists_foreach (gpointer key,
2175 gpointer value,
2176 gpointer data)
2178 RhythmDBTreeProperty *artist = (RhythmDBTreeProperty *)value;
2179 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2181 if (ctxt->artist_func) {
2182 ctxt->artist_func (ctxt->db, artist, ctxt->data);
2184 if ((ctxt->album_func != NULL) || (ctxt->entry_func != NULL)) {
2185 g_hash_table_foreach (artist->children,
2186 hash_tree_albums_foreach,
2187 ctxt);
2191 static void
2192 hash_tree_genres_foreach (gpointer key,
2193 gpointer value,
2194 gpointer data)
2196 RhythmDBTreeProperty *genre = (RhythmDBTreeProperty *)value;
2197 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2199 if (ctxt->genres_func) {
2200 ctxt->genres_func (ctxt->db, genre, ctxt->data);
2203 if ((ctxt->album_func != NULL)
2204 || (ctxt->artist_func != NULL)
2205 || (ctxt->entry_func != NULL)) {
2206 g_hash_table_foreach (genre->children,
2207 hash_tree_artists_foreach,
2208 ctxt);
2212 static void
2213 rhythmdb_hash_tree_foreach (RhythmDB *adb,
2214 RhythmDBEntryType type,
2215 RBTreeEntryItFunc entry_func,
2216 RBTreePropertyItFunc album_func,
2217 RBTreePropertyItFunc artist_func,
2218 RBTreePropertyItFunc genres_func,
2219 gpointer data)
2221 struct HashTreeIteratorCtxt ctxt;
2222 GHashTable *table;
2224 ctxt.db = RHYTHMDB_TREE (adb);
2225 ctxt.album_func = album_func;
2226 ctxt.artist_func = artist_func;
2227 ctxt.genres_func = genres_func;
2228 ctxt.entry_func = entry_func;
2229 ctxt.data = data;
2231 g_mutex_lock (ctxt.db->priv->genres_lock);
2232 table = get_genres_hash_for_type (RHYTHMDB_TREE (adb), type);
2233 if (table == NULL) {
2234 return;
2236 if ((ctxt.album_func != NULL)
2237 || (ctxt.artist_func != NULL)
2238 || (ctxt.genres_func != NULL)
2239 || (ctxt.entry_func != NULL)) {
2240 g_hash_table_foreach (table, hash_tree_genres_foreach, &ctxt);
2242 g_mutex_unlock (ctxt.db->priv->genres_lock);
2245 static void
2246 rhythmdb_tree_entry_type_registered (RhythmDB *db,
2247 const char *name,
2248 RhythmDBEntryType entry_type)
2250 GList *entries = NULL;
2251 GList *e;
2252 gint count = 0;
2253 RhythmDBTree *rdb;
2254 RBRefString *rs_name;
2256 if (name == NULL)
2257 return;
2259 rdb = RHYTHMDB_TREE (db);
2260 rs_name = rb_refstring_find (name);
2261 if (rs_name)
2262 entries = g_hash_table_lookup (rdb->priv->unknown_entry_types, rs_name);
2263 if (entries == NULL) {
2264 rb_refstring_unref (rs_name);
2265 rb_debug ("no entries of newly registered type %s loaded from db", name);
2266 return;
2269 for (e = entries; e != NULL; e = e->next) {
2270 RhythmDBUnknownEntry *data;
2271 RhythmDBEntry *entry;
2272 GList *p;
2274 data = (RhythmDBUnknownEntry *)e->data;
2275 entry = rhythmdb_entry_allocate (db, entry_type);
2276 entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
2277 for (p = data->properties; p != NULL; p = p->next) {
2278 RhythmDBUnknownEntryProperty *prop;
2279 RhythmDBPropType propid;
2280 GValue value = {0,};
2282 prop = (RhythmDBUnknownEntryProperty *) p->data;
2283 propid = rhythmdb_propid_from_nice_elt_name (db, (const xmlChar *) rb_refstring_get (prop->name));
2285 rhythmdb_read_encoded_property (db, rb_refstring_get (prop->value), propid, &value);
2286 rhythmdb_entry_set_internal (db, entry, FALSE, propid, &value);
2287 g_value_unset (&value);
2289 rhythmdb_tree_entry_new_internal (db, entry);
2290 rhythmdb_entry_insert (db, entry);
2291 count++;
2293 rb_debug ("handled %d entries of newly registered type %s", count, name);
2294 rhythmdb_commit (db);
2296 g_hash_table_remove (rdb->priv->unknown_entry_types, rs_name);
2297 free_unknown_entries (rs_name, entries, NULL);
2298 rb_refstring_unref (rs_name);