Updated Macedonian translation <arangela@cvs.gnome.org>
[rhythmbox.git] / rhythmdb / rhythmdb-tree.c
blob07dd8150d51d378034d6bf1aa53aa1f2e8565c28
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23 #include <config.h>
24 #ifdef HAVE_GNU_FWRITE_UNLOCKED
25 #define _GNU_SOURCE
26 #endif
27 #include <stdio.h>
28 #ifdef HAVE_GNU_FWRITE_UNLOCKED
29 #undef _GNU_SOURCE
30 #endif
31 #include <unistd.h>
32 #include <stdlib.h>
33 #include <errno.h>
34 #include <string.h>
35 #include <glib/gprintf.h>
36 #include <glib/gatomic.h>
37 #include <glib/gi18n.h>
38 #include <gtk/gtkliststore.h>
39 #include <libxml/entities.h>
40 #include <libxml/SAX.h>
41 #include <libxml/parserInternals.h>
43 #include "rhythmdb-tree.h"
44 #include "rhythmdb-query-model.h"
45 #include "rhythmdb-property-model.h"
46 #include "rb-debug.h"
47 #include "rb-util.h"
48 #include "rb-file-helpers.h"
50 typedef struct RhythmDBTreeProperty
52 #ifndef G_DISABLE_ASSERT
53 guint magic;
54 #endif
55 struct RhythmDBTreeProperty *parent;
56 GHashTable *children;
57 } RhythmDBTreeProperty;
59 #define RHYTHMDB_TREE_PROPERTY_FROM_ENTRY(entry) ((RhythmDBTreeProperty *) entry->data)
61 G_DEFINE_TYPE(RhythmDBTree, rhythmdb_tree, RHYTHMDB_TYPE)
63 static void rhythmdb_tree_finalize (GObject *object);
65 static void rhythmdb_tree_load (RhythmDB *rdb, gboolean *die);
66 static void rhythmdb_tree_save (RhythmDB *rdb);
67 static void rhythmdb_tree_entry_new (RhythmDB *db, RhythmDBEntry *entry);
68 static gboolean rhythmdb_tree_entry_set (RhythmDB *db, RhythmDBEntry *entry,
69 guint propid, const GValue *value);
71 static void rhythmdb_tree_entry_delete (RhythmDB *db, RhythmDBEntry *entry);
72 static void rhythmdb_tree_entry_delete_by_type (RhythmDB *adb, RhythmDBEntryType type);
74 static RhythmDBEntry * rhythmdb_tree_entry_lookup_by_location (RhythmDB *db, const char *uri);
75 static void rhythmdb_tree_entry_foreach (RhythmDB *adb, GFunc func, gpointer user_data);
76 static void rhythmdb_tree_do_full_query (RhythmDB *db, GPtrArray *query,
77 GtkTreeModel *main_model, gboolean *cancel);
78 static gboolean rhythmdb_tree_evaluate_query (RhythmDB *adb, GPtrArray *query,
79 RhythmDBEntry *aentry);
81 typedef void (*RBTreeEntryItFunc)(RhythmDBTree *db,
82 RhythmDBEntry *entry,
83 gpointer data);
85 typedef void (*RBTreePropertyItFunc)(RhythmDBTree *db,
86 RhythmDBTreeProperty *property,
87 gpointer data);
88 static void rhythmdb_hash_tree_foreach (RhythmDB *adb,
89 RhythmDBEntryType type,
90 RBTreeEntryItFunc entry_func,
91 RBTreePropertyItFunc album_func,
92 RBTreePropertyItFunc artist_func,
93 RBTreePropertyItFunc genres_func,
94 gpointer data);
97 #define RHYTHMDB_TREE_XML_VERSION "1.0"
99 static void destroy_tree_property (RhythmDBTreeProperty *prop);
100 static RhythmDBTreeProperty *get_or_create_album (RhythmDBTree *db, RhythmDBTreeProperty *artist,
101 RBRefString *name);
102 static RhythmDBTreeProperty *get_or_create_artist (RhythmDBTree *db, RhythmDBTreeProperty *genre,
103 RBRefString *name);
104 static RhythmDBTreeProperty *get_or_create_genre (RhythmDBTree *db, RhythmDBEntryType type,
105 RBRefString *name);
107 static void remove_entry_from_album (RhythmDBTree *db, RhythmDBEntry *entry);
109 static GList *split_query_by_disjunctions (RhythmDBTree *db, GPtrArray *query);
110 static gboolean evaluate_conjunctive_subquery (RhythmDBTree *db, GPtrArray *query,
111 guint base, guint max, RhythmDBEntry *entry);
113 struct RhythmDBTreePrivate
115 GHashTable *entries;
116 GHashTable *genres;
117 GMutex *genres_lock;
118 gboolean finalizing;
120 guint idle_load_id;
123 #define RHYTHMDB_TREE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_TREE, RhythmDBTreePrivate))
125 enum
127 PROP_0,
130 const int RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE = 512;
133 static void
134 rhythmdb_tree_class_init (RhythmDBTreeClass *klass)
136 GObjectClass *object_class = G_OBJECT_CLASS (klass);
137 RhythmDBClass *rhythmdb_class = RHYTHMDB_CLASS (klass);
139 object_class->finalize = rhythmdb_tree_finalize;
141 rhythmdb_class->impl_load = rhythmdb_tree_load;
142 rhythmdb_class->impl_save = rhythmdb_tree_save;
143 rhythmdb_class->impl_entry_new = rhythmdb_tree_entry_new;
144 rhythmdb_class->impl_entry_set = rhythmdb_tree_entry_set;
145 rhythmdb_class->impl_entry_delete = rhythmdb_tree_entry_delete;
146 rhythmdb_class->impl_entry_delete_by_type = rhythmdb_tree_entry_delete_by_type;
147 rhythmdb_class->impl_lookup_by_location = rhythmdb_tree_entry_lookup_by_location;
148 rhythmdb_class->impl_entry_foreach = rhythmdb_tree_entry_foreach;
149 rhythmdb_class->impl_evaluate_query = rhythmdb_tree_evaluate_query;
150 rhythmdb_class->impl_do_full_query = rhythmdb_tree_do_full_query;
152 g_type_class_add_private (klass, sizeof (RhythmDBTreePrivate));
155 static void
156 rhythmdb_tree_init (RhythmDBTree *db)
158 db->priv = RHYTHMDB_TREE_GET_PRIVATE (db);
160 db->priv->entries = g_hash_table_new (g_str_hash, g_str_equal);
162 db->priv->genres = g_hash_table_new_full (g_direct_hash, g_direct_equal,
163 NULL, (GDestroyNotify)g_hash_table_destroy);
166 static void
167 unparent_entries (const char *uri, RhythmDBEntry *entry, RhythmDBTree *db)
169 remove_entry_from_album (db, entry);
172 static void
173 rhythmdb_tree_finalize (GObject *object)
175 RhythmDBTree *db;
177 g_return_if_fail (object != NULL);
178 g_return_if_fail (RHYTHMDB_IS_TREE (object));
180 db = RHYTHMDB_TREE (object);
182 g_return_if_fail (db->priv != NULL);
184 db->priv->finalizing = TRUE;
186 g_hash_table_foreach (db->priv->entries, (GHFunc) unparent_entries, db);
188 g_hash_table_destroy (db->priv->entries);
190 g_hash_table_destroy (db->priv->genres);
192 G_OBJECT_CLASS (rhythmdb_tree_parent_class)->finalize (object);
195 struct RhythmDBTreeLoadContext
197 RhythmDBTree *db;
198 xmlParserCtxtPtr xmlctx;
199 gboolean *die;
200 enum {
201 RHYTHMDB_TREE_PARSER_STATE_START,
202 RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB,
203 RHYTHMDB_TREE_PARSER_STATE_ENTRY,
204 RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY,
205 RHYTHMDB_TREE_PARSER_STATE_END,
206 } state;
207 gboolean in_unknown_elt;
208 RhythmDBEntry *entry;
209 GString *buf;
210 RhythmDBPropType propid;
212 gboolean has_date;
215 static void
216 rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext *ctx,
217 const char *name, const char **attrs)
219 if (*ctx->die == TRUE) {
220 xmlStopParser (ctx->xmlctx);
221 return;
224 if (ctx->in_unknown_elt)
225 return;
227 switch (ctx->state)
229 case RHYTHMDB_TREE_PARSER_STATE_START:
231 if (!strcmp (name, "rhythmdb"))
232 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
233 else
234 ctx->in_unknown_elt = TRUE;
235 break;
237 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
239 if (!strcmp (name, "entry")) {
240 RhythmDBEntryType type = -1;
241 gboolean type_set = FALSE;
242 for (; *attrs; attrs +=2) {
243 if (!strcmp (*attrs, "type")) {
244 const char *typename = *(attrs+1);
245 if (!strcmp (typename, "song"))
246 type = RHYTHMDB_ENTRY_TYPE_SONG;
247 else if (!strcmp (typename, "iradio"))
248 type = RHYTHMDB_ENTRY_TYPE_IRADIO_STATION;
249 else if (!strcmp (typename, "podcast-post"))
250 type = RHYTHMDB_ENTRY_TYPE_PODCAST_POST;
251 else if (!strcmp (typename, "podcast-feed"))
252 type = RHYTHMDB_ENTRY_TYPE_PODCAST_FEED;
253 else
254 return;
255 type_set = TRUE;
256 break;
259 g_assert (type_set);
260 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
261 ctx->entry = rhythmdb_entry_allocate (RHYTHMDB (ctx->db), type);
262 ctx->has_date = FALSE;
263 } else
264 ctx->in_unknown_elt = TRUE;
265 break;
267 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
269 int val = rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx->db), BAD_CAST name);
270 if (val < 0) {
271 ctx->in_unknown_elt = TRUE;
272 break;
275 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY;
276 ctx->propid = val;
277 g_string_truncate (ctx->buf, 0);
278 break;
280 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
281 case RHYTHMDB_TREE_PARSER_STATE_END:
282 break;
286 static gulong
287 parse_ulong (const char *buffer)
289 guint64 val;
291 val = g_ascii_strtoull (buffer, NULL, 10);
292 if (val == G_MAXUINT64)
293 return 0;
294 else
295 return MIN (val, G_MAXUINT32);
298 static void
299 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext *ctx, const char *name)
301 if (*ctx->die == TRUE) {
302 xmlStopParser (ctx->xmlctx);
303 return;
306 if (ctx->in_unknown_elt) {
307 ctx->in_unknown_elt = FALSE;
308 return;
311 switch (ctx->state)
313 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
314 ctx->state = RHYTHMDB_TREE_PARSER_STATE_END;
315 break;
316 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
318 if (!ctx->has_date) {
319 /* there is no date metadata, so this is from an old version
320 * reset the last-modified timestamp, so that the file is re-read
322 rb_debug ("pre-Date entry found, causing re-read");
323 ctx->entry->mtime = 0;
325 if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED && ctx->entry->podcast->post_time == 0) {
326 /* Handle upgrades from 0.9.2.
327 * Previously, last-seen for podcast feeds was the time of the last post,
328 * and post-time was unused. Now, we want last-seen to be the time we
329 * last updated the feed, and post-time to be the time of the last post.
331 ctx->entry->podcast->post_time = ctx->entry->last_seen;
334 if (ctx->entry->location != NULL) {
335 rhythmdb_tree_entry_new (RHYTHMDB (ctx->db), ctx->entry);
336 rhythmdb_entry_insert (RHYTHMDB (ctx->db), ctx->entry);
337 rhythmdb_commit (RHYTHMDB (ctx->db));
338 } else {
339 rb_debug ("found entry without location");
340 rhythmdb_entry_unref (RHYTHMDB (ctx->db), ctx->entry);
342 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
343 break;
345 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
347 /* Handle indexed properties. */
348 switch (ctx->propid)
350 case RHYTHMDB_PROP_TYPE:
351 g_assert_not_reached ();
352 break;
353 case RHYTHMDB_PROP_TITLE:
354 ctx->entry->title = rb_refstring_new (ctx->buf->str);
355 break;
356 case RHYTHMDB_PROP_GENRE:
357 ctx->entry->genre = rb_refstring_new (ctx->buf->str);
358 break;
359 case RHYTHMDB_PROP_ARTIST:
360 ctx->entry->artist = rb_refstring_new (ctx->buf->str);
361 break;
362 case RHYTHMDB_PROP_ALBUM:
363 ctx->entry->album = rb_refstring_new (ctx->buf->str);
364 break;
365 case RHYTHMDB_PROP_TRACK_NUMBER:
366 ctx->entry->tracknum = parse_ulong (ctx->buf->str);
367 break;
368 case RHYTHMDB_PROP_DISC_NUMBER:
369 ctx->entry->discnum = parse_ulong (ctx->buf->str);
370 break;
371 case RHYTHMDB_PROP_DATE:
373 gulong value = parse_ulong (ctx->buf->str);
375 if (value > 0)
376 ctx->entry->date = g_date_new_julian (value);
377 else
379 ctx->has_date = TRUE;
380 break;
382 case RHYTHMDB_PROP_DURATION:
383 ctx->entry->duration = parse_ulong (ctx->buf->str);
384 break;
385 case RHYTHMDB_PROP_FILE_SIZE:
386 ctx->entry->file_size = parse_ulong (ctx->buf->str);
387 break;
388 case RHYTHMDB_PROP_LOCATION:
389 ctx->entry->location = g_strdup (ctx->buf->str);
390 break;
391 case RHYTHMDB_PROP_MOUNTPOINT:
392 /* remove this from old podcast-post entries */
393 if (!g_str_has_prefix (ctx->buf->str, "http://"))
394 ctx->entry->mountpoint = rb_refstring_new (ctx->buf->str);
395 break;
396 case RHYTHMDB_PROP_MTIME:
397 ctx->entry->mtime = parse_ulong (ctx->buf->str);
398 break;
399 case RHYTHMDB_PROP_FIRST_SEEN:
400 ctx->entry->first_seen = parse_ulong (ctx->buf->str);
401 break;
402 case RHYTHMDB_PROP_LAST_SEEN:
403 ctx->entry->last_seen = parse_ulong (ctx->buf->str);
404 break;
405 case RHYTHMDB_PROP_RATING:
406 ctx->entry->rating = g_ascii_strtod (ctx->buf->str, NULL);
407 break;
408 case RHYTHMDB_PROP_PLAY_COUNT:
409 ctx->entry->play_count = parse_ulong (ctx->buf->str);
410 break;
411 case RHYTHMDB_PROP_LAST_PLAYED:
412 ctx->entry->last_played = parse_ulong (ctx->buf->str);
413 break;
414 case RHYTHMDB_PROP_BITRATE:
415 ctx->entry->bitrate = parse_ulong (ctx->buf->str);
416 break;
417 case RHYTHMDB_PROP_TRACK_GAIN:
418 ctx->entry->track_gain = g_ascii_strtod (ctx->buf->str, NULL);
419 break;
420 case RHYTHMDB_PROP_TRACK_PEAK:
421 ctx->entry->track_peak = g_ascii_strtod (ctx->buf->str, NULL);
422 break;
423 case RHYTHMDB_PROP_ALBUM_GAIN:
424 ctx->entry->album_gain = g_ascii_strtod (ctx->buf->str, NULL);
425 break;
426 case RHYTHMDB_PROP_ALBUM_PEAK:
427 ctx->entry->album_peak = g_ascii_strtod (ctx->buf->str, NULL);
428 break;
429 case RHYTHMDB_PROP_MIMETYPE:
430 ctx->entry->mimetype = rb_refstring_new (ctx->buf->str);
431 break;
432 case RHYTHMDB_PROP_STATUS:
433 ctx->entry->podcast->status = parse_ulong (ctx->buf->str);
434 break;
435 case RHYTHMDB_PROP_DESCRIPTION:
436 ctx->entry->podcast->description = rb_refstring_new (ctx->buf->str);
437 break;
438 case RHYTHMDB_PROP_SUBTITLE:
439 ctx->entry->podcast->subtitle = rb_refstring_new (ctx->buf->str);
440 break;
441 case RHYTHMDB_PROP_SUMMARY:
442 ctx->entry->podcast->summary = rb_refstring_new (ctx->buf->str);
443 break;
444 case RHYTHMDB_PROP_LANG:
445 ctx->entry->podcast->lang = rb_refstring_new (ctx->buf->str);
446 break;
447 case RHYTHMDB_PROP_COPYRIGHT:
448 ctx->entry->podcast->copyright = rb_refstring_new (ctx->buf->str);
449 break;
450 case RHYTHMDB_PROP_IMAGE:
451 ctx->entry->podcast->image = rb_refstring_new (ctx->buf->str);
452 break;
453 case RHYTHMDB_PROP_POST_TIME:
454 ctx->entry->podcast->post_time = parse_ulong (ctx->buf->str);
455 break;
456 case RHYTHMDB_PROP_TITLE_SORT_KEY:
457 case RHYTHMDB_PROP_GENRE_SORT_KEY:
458 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
459 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
460 case RHYTHMDB_PROP_TITLE_FOLDED:
461 case RHYTHMDB_PROP_GENRE_FOLDED:
462 case RHYTHMDB_PROP_ARTIST_FOLDED:
463 case RHYTHMDB_PROP_ALBUM_FOLDED:
464 case RHYTHMDB_PROP_LAST_PLAYED_STR:
465 case RHYTHMDB_PROP_HIDDEN:
466 case RHYTHMDB_PROP_PLAYBACK_ERROR:
467 case RHYTHMDB_PROP_FIRST_SEEN_STR:
468 case RHYTHMDB_PROP_SEARCH_MATCH:
469 case RHYTHMDB_NUM_PROPERTIES:
470 g_assert_not_reached ();
471 break;
474 rhythmdb_entry_sync_mirrored (RHYTHMDB (ctx->db), ctx->entry, ctx->propid);
476 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
477 break;
479 case RHYTHMDB_TREE_PARSER_STATE_START:
480 case RHYTHMDB_TREE_PARSER_STATE_END:
481 break;
485 static void
486 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext *ctx, const char *data,
487 guint len)
489 if (*ctx->die == TRUE) {
490 xmlStopParser (ctx->xmlctx);
491 return;
494 switch (ctx->state)
496 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
497 g_string_append_len (ctx->buf, data, len);
498 break;
499 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
500 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
501 case RHYTHMDB_TREE_PARSER_STATE_START:
502 case RHYTHMDB_TREE_PARSER_STATE_END:
503 break;
507 static void
508 rhythmdb_tree_load (RhythmDB *rdb, gboolean *die)
510 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
511 xmlParserCtxtPtr ctxt;
512 xmlSAXHandlerPtr sax_handler = g_new0 (xmlSAXHandler, 1);
513 struct RhythmDBTreeLoadContext *ctx = g_new0 (struct RhythmDBTreeLoadContext, 1);
514 char *name;
516 sax_handler->startElement = (startElementSAXFunc) rhythmdb_tree_parser_start_element;
517 sax_handler->endElement = (endElementSAXFunc) rhythmdb_tree_parser_end_element;
518 sax_handler->characters = (charactersSAXFunc) rhythmdb_tree_parser_characters;
520 ctx->state = RHYTHMDB_TREE_PARSER_STATE_START;
521 ctx->db = db;
522 ctx->die = die;
523 ctx->buf = g_string_sized_new (RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE);
525 g_object_get (G_OBJECT (db), "name", &name, NULL);
527 if (g_file_test (name, G_FILE_TEST_EXISTS)) {
528 ctxt = xmlCreateFileParserCtxt (name);
529 ctx->xmlctx = ctxt;
530 xmlFree (ctxt->sax);
531 ctxt->userData = ctx;
532 ctxt->sax = sax_handler;
533 xmlParseDocument (ctxt);
534 ctxt->sax = NULL;
535 xmlFreeParserCtxt (ctxt);
538 g_string_free (ctx->buf, TRUE);
539 g_free (name);
540 g_free (sax_handler);
541 g_free (ctx);
544 struct RhythmDBTreeSaveContext
546 RhythmDBTree *db;
547 FILE *handle;
548 char *error;
551 #ifdef HAVE_GNU_FWRITE_UNLOCKED
552 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
553 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
554 #else
555 #define RHYTHMDB_FWRITE_REAL fwrite
556 #define RHYTHMDB_FPUTC_REAL fputc
557 #endif
559 #define RHYTHMDB_FWRITE(w,x,len,handle,error) do { \
560 if (error == NULL) { \
561 if (RHYTHMDB_FWRITE_REAL (w,x,len,handle) != len) { \
562 error = g_strdup (g_strerror (errno)); \
565 } while (0)
567 #define RHYTHMDB_FPUTC(x,handle,error) do { \
568 if (error == NULL) { \
569 if (RHYTHMDB_FPUTC_REAL (x,handle) == EOF) { \
570 error = g_strdup (g_strerror (errno)); \
573 } while (0)
575 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
577 static void
578 write_elt_name_open (struct RhythmDBTreeSaveContext *ctx, const xmlChar *elt_name)
580 RHYTHMDB_FWRITE_STATICSTR (" <", ctx->handle, ctx->error);
581 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
582 RHYTHMDB_FPUTC ('>', ctx->handle, ctx->error);
585 static void
586 write_elt_name_close (struct RhythmDBTreeSaveContext *ctx, const xmlChar *elt_name)
588 RHYTHMDB_FWRITE_STATICSTR ("</", ctx->handle, ctx->error);
589 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
590 RHYTHMDB_FWRITE_STATICSTR (">\n", ctx->handle, ctx->error);
593 static void
594 save_entry_string (struct RhythmDBTreeSaveContext *ctx,
595 const xmlChar *elt_name, const char *str)
597 xmlChar *encoded;
599 g_return_if_fail (str != NULL);
600 write_elt_name_open (ctx, elt_name);
601 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST str);
602 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
603 g_free (encoded);
604 write_elt_name_close (ctx, elt_name);
607 static void
608 save_entry_int (struct RhythmDBTreeSaveContext *ctx,
609 const xmlChar *elt_name, int num)
611 char buf[92];
612 if (num == 0)
613 return;
614 write_elt_name_open (ctx, elt_name);
615 g_snprintf (buf, sizeof (buf), "%d", num);
616 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
617 write_elt_name_close (ctx, elt_name);
620 static void
621 save_entry_ulong (struct RhythmDBTreeSaveContext *ctx,
622 const xmlChar *elt_name, gulong num, gboolean save_zeroes)
624 char buf[92];
625 if (num == 0 && !save_zeroes)
626 return;
627 write_elt_name_open (ctx, elt_name);
628 g_snprintf (buf, sizeof (buf), "%lu", num);
629 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
630 write_elt_name_close (ctx, elt_name);
633 static void
634 save_entry_uint64 (struct RhythmDBTreeSaveContext *ctx, const xmlChar *elt_name,
635 guint64 num)
637 char buf[92];
639 if (num == 0)
640 return;
642 write_elt_name_open (ctx, elt_name);
643 g_snprintf (buf, sizeof (buf), "%" G_GUINT64_FORMAT, num);
644 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
645 write_elt_name_close (ctx, elt_name);
648 static void
649 save_entry_double (struct RhythmDBTreeSaveContext *ctx,
650 const xmlChar *elt_name, double num)
652 char buf[92];
654 if (num > -0.001 && num < 0.001)
655 return;
657 write_elt_name_open (ctx, elt_name);
658 g_snprintf (buf, sizeof (buf), "%f", num);
659 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
660 write_elt_name_close (ctx, elt_name);
663 /* This code is intended to be highly optimized. This came at a small
664 * readability cost. Sorry about that.
666 static void
667 save_entry (RhythmDBTree *db, RhythmDBEntry *entry, struct RhythmDBTreeSaveContext *ctx)
669 RhythmDBPropType i;
671 if (ctx->error)
672 return;
674 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
676 if (entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
677 RHYTHMDB_FWRITE_STATICSTR ("song", ctx->handle, ctx->error);
678 } else if (entry->type == RHYTHMDB_ENTRY_TYPE_IRADIO_STATION) {
679 RHYTHMDB_FWRITE_STATICSTR ("iradio", ctx->handle, ctx->error);
680 } else if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) {
681 RHYTHMDB_FWRITE_STATICSTR ("podcast-post", ctx->handle, ctx->error);
682 } else if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
683 RHYTHMDB_FWRITE_STATICSTR ("podcast-feed", ctx->handle, ctx->error);
684 } else
685 g_assert_not_reached ();
687 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
689 /* Skip over the first property - the type */
690 for (i = 1; i < RHYTHMDB_NUM_PROPERTIES; i++) {
691 const xmlChar *elt_name;
693 if (ctx->error)
694 return;
696 elt_name = rhythmdb_nice_elt_name_from_propid ((RhythmDB *) ctx->db, i);
698 switch (i)
700 case RHYTHMDB_PROP_TYPE:
701 break;
702 case RHYTHMDB_PROP_TITLE:
703 save_entry_string(ctx, elt_name, rb_refstring_get (entry->title));
704 break;
705 case RHYTHMDB_PROP_ALBUM:
706 save_entry_string(ctx, elt_name, rb_refstring_get (entry->album));
707 break;
708 case RHYTHMDB_PROP_ARTIST:
709 save_entry_string(ctx, elt_name, rb_refstring_get (entry->artist));
710 break;
711 case RHYTHMDB_PROP_GENRE:
712 save_entry_string(ctx, elt_name, rb_refstring_get (entry->genre));
713 break;
714 case RHYTHMDB_PROP_TRACK_NUMBER:
715 save_entry_ulong (ctx, elt_name, entry->tracknum, FALSE);
716 break;
717 case RHYTHMDB_PROP_DISC_NUMBER:
718 save_entry_ulong (ctx, elt_name, entry->discnum, FALSE);
719 break;
720 case RHYTHMDB_PROP_DATE:
721 if (entry->date)
722 save_entry_ulong (ctx, elt_name, g_date_get_julian (entry->date), TRUE);
723 else
724 save_entry_ulong (ctx, elt_name, 0, TRUE);
725 break;
726 case RHYTHMDB_PROP_DURATION:
727 save_entry_ulong (ctx, elt_name, entry->duration, FALSE);
728 break;
729 case RHYTHMDB_PROP_BITRATE:
730 save_entry_int(ctx, elt_name, entry->bitrate);
731 break;
732 case RHYTHMDB_PROP_TRACK_GAIN:
733 save_entry_double(ctx, elt_name, entry->track_gain);
734 break;
735 case RHYTHMDB_PROP_TRACK_PEAK:
736 save_entry_double(ctx, elt_name, entry->track_peak);
737 break;
738 case RHYTHMDB_PROP_ALBUM_GAIN:
739 save_entry_double(ctx, elt_name, entry->album_gain);
740 break;
741 case RHYTHMDB_PROP_ALBUM_PEAK:
742 save_entry_double(ctx, elt_name, entry->album_peak);
743 break;
744 case RHYTHMDB_PROP_LOCATION:
745 save_entry_string(ctx, elt_name, entry->location);
746 break;
747 case RHYTHMDB_PROP_MOUNTPOINT:
748 /* Avoid crashes on exit when upgrading from 0.8
749 * and no mountpoint is available from some entries */
750 if (entry->mountpoint) {
751 save_entry_string(ctx, elt_name, rb_refstring_get (entry->mountpoint));
753 break;
754 case RHYTHMDB_PROP_FILE_SIZE:
755 save_entry_uint64(ctx, elt_name, entry->file_size);
756 break;
757 case RHYTHMDB_PROP_MIMETYPE:
758 save_entry_string(ctx, elt_name, rb_refstring_get (entry->mimetype));
759 break;
760 case RHYTHMDB_PROP_MTIME:
761 save_entry_ulong (ctx, elt_name, entry->mtime, FALSE);
762 break;
763 case RHYTHMDB_PROP_FIRST_SEEN:
764 save_entry_ulong (ctx, elt_name, entry->first_seen, FALSE);
765 break;
766 case RHYTHMDB_PROP_LAST_SEEN:
767 save_entry_ulong (ctx, elt_name, entry->last_seen, FALSE);
768 break;
769 case RHYTHMDB_PROP_RATING:
770 save_entry_double(ctx, elt_name, entry->rating);
771 break;
772 case RHYTHMDB_PROP_PLAY_COUNT:
773 save_entry_ulong (ctx, elt_name, entry->play_count, FALSE);
774 break;
775 case RHYTHMDB_PROP_LAST_PLAYED:
776 save_entry_ulong (ctx, elt_name, entry->last_played, FALSE);
777 break;
778 case RHYTHMDB_PROP_STATUS:
779 if (entry->podcast)
780 save_entry_ulong (ctx, elt_name, entry->podcast->status, FALSE);
781 break;
782 case RHYTHMDB_PROP_DESCRIPTION:
783 if (entry->podcast && entry->podcast->description)
784 save_entry_string(ctx, elt_name, rb_refstring_get (entry->podcast->description));
785 break;
786 case RHYTHMDB_PROP_SUBTITLE:
787 if (entry->podcast && entry->podcast->subtitle)
788 save_entry_string(ctx, elt_name, rb_refstring_get (entry->podcast->subtitle));
789 break;
790 case RHYTHMDB_PROP_SUMMARY:
791 if (entry->podcast && entry->podcast->summary)
792 save_entry_string(ctx, elt_name, rb_refstring_get (entry->podcast->summary));
793 break;
794 case RHYTHMDB_PROP_LANG:
795 if (entry->podcast && entry->podcast->lang)
796 save_entry_string(ctx, elt_name, rb_refstring_get (entry->podcast->lang));
797 break;
798 case RHYTHMDB_PROP_COPYRIGHT:
799 if (entry->podcast && entry->podcast->copyright)
800 save_entry_string(ctx, elt_name, rb_refstring_get (entry->podcast->copyright));
801 break;
802 case RHYTHMDB_PROP_IMAGE:
803 if (entry->podcast && entry->podcast->image)
804 save_entry_string(ctx, elt_name, rb_refstring_get (entry->podcast->image));
805 break;
806 case RHYTHMDB_PROP_POST_TIME:
807 if (entry->podcast)
808 save_entry_ulong (ctx, elt_name, entry->podcast->post_time, FALSE);
809 break;
810 case RHYTHMDB_PROP_TITLE_SORT_KEY:
811 case RHYTHMDB_PROP_GENRE_SORT_KEY:
812 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
813 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
814 case RHYTHMDB_PROP_TITLE_FOLDED:
815 case RHYTHMDB_PROP_GENRE_FOLDED:
816 case RHYTHMDB_PROP_ARTIST_FOLDED:
817 case RHYTHMDB_PROP_ALBUM_FOLDED:
818 case RHYTHMDB_PROP_LAST_PLAYED_STR:
819 case RHYTHMDB_PROP_HIDDEN:
820 case RHYTHMDB_PROP_PLAYBACK_ERROR:
821 case RHYTHMDB_PROP_FIRST_SEEN_STR:
822 case RHYTHMDB_PROP_SEARCH_MATCH:
823 case RHYTHMDB_NUM_PROPERTIES:
824 break;
828 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
831 static void
832 rhythmdb_tree_save (RhythmDB *rdb)
834 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
835 char *name;
836 GString *savepath;
837 FILE *f;
838 struct RhythmDBTreeSaveContext ctx;
840 g_object_get (G_OBJECT (db), "name", &name, NULL);
842 savepath = g_string_new (name);
843 g_string_append (savepath, ".tmp");
845 f = fopen (savepath->str, "w");
847 if (!f) {
848 g_critical ("Can't save XML: %s", g_strerror (errno));
849 goto out;
852 ctx.db = db;
853 ctx.handle = f;
854 ctx.error = NULL;
855 RHYTHMDB_FWRITE_STATICSTR ("<?xml version=\"1.0\" standalone=\"yes\"?>\n"
856 "<rhythmdb version=\"" RHYTHMDB_TREE_XML_VERSION "\">",
857 ctx.handle, ctx.error);
859 rhythmdb_hash_tree_foreach (rdb, RHYTHMDB_ENTRY_TYPE_SONG,
860 (RBTreeEntryItFunc)save_entry,
861 NULL, NULL, NULL, &ctx);
862 rhythmdb_hash_tree_foreach (rdb, RHYTHMDB_ENTRY_TYPE_IRADIO_STATION,
863 (RBTreeEntryItFunc)save_entry,
864 NULL, NULL, NULL, &ctx);
865 rhythmdb_hash_tree_foreach (rdb, RHYTHMDB_ENTRY_TYPE_PODCAST_POST,
866 (RBTreeEntryItFunc)save_entry,
867 NULL, NULL, NULL, &ctx);
868 rhythmdb_hash_tree_foreach (rdb, RHYTHMDB_ENTRY_TYPE_PODCAST_FEED,
869 (RBTreeEntryItFunc)save_entry,
870 NULL, NULL, NULL, &ctx);
872 RHYTHMDB_FWRITE_STATICSTR ("</rhythmdb>\n", ctx.handle, ctx.error);
874 if (fclose (f) < 0) {
875 g_critical ("Couldn't close %s: %s",
876 savepath->str,
877 g_strerror (errno));
878 unlink (savepath->str);
879 goto out;
882 if (ctx.error != NULL) {
883 g_critical ("Writing to the database failed: %s", ctx.error);
884 g_free (ctx.error);
885 unlink (savepath->str);
886 } else {
887 if (rename (savepath->str, name) < 0) {
888 g_critical ("Couldn't rename %s to %s: %s",
889 name, savepath->str,
890 g_strerror (errno));
891 unlink (savepath->str);
895 out:
896 g_string_free (savepath, TRUE);
897 g_free (name);
898 return;
901 #undef RHYTHMDB_FWRITE_ENCODED_STR
902 #undef RHYTHMDB_FWRITE_STATICSTR
903 #undef RHYTHMDB_FPUTC
904 #undef RHYTHMDB_FWRITE
906 RhythmDB *
907 rhythmdb_tree_new (const char *name)
909 RhythmDBTree *db = g_object_new (RHYTHMDB_TYPE_TREE, "name", name, NULL);
911 g_return_val_if_fail (db->priv != NULL, NULL);
913 return RHYTHMDB (db);
916 static void
917 set_entry_album (RhythmDBTree *db, RhythmDBEntry *entry, RhythmDBTreeProperty *artist,
918 RBRefString *name)
920 struct RhythmDBTreeProperty *prop;
921 prop = get_or_create_album (db, artist, name);
922 g_hash_table_insert (prop->children, entry, NULL);
923 entry->data = prop;
926 static void
927 rhythmdb_tree_entry_new (RhythmDB *rdb, RhythmDBEntry *entry)
929 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
930 RhythmDBTreeProperty *artist;
931 RhythmDBTreeProperty *genre;
933 g_assert (entry != NULL);
935 g_return_if_fail (entry->location != NULL);
937 if (entry->title == NULL) {
938 g_warning ("Entry %s has missing title",entry->location);
939 entry->title = rb_refstring_new (_("Unknown"));
941 if (entry->artist == NULL) {
942 g_warning ("Entry %s has missing artist",entry->location);
943 entry->artist = rb_refstring_new (_("Unknown"));
945 if (entry->album == NULL) {
946 g_warning ("Entry %s has missing album",entry->location);
947 entry->album = rb_refstring_new (_("Unknown"));
949 if (entry->genre == NULL) {
950 g_warning ("Entry %s has missing genre",entry->location);
951 entry->genre = rb_refstring_new (_("Unknown"));
953 if (entry->mimetype == NULL) {
954 g_warning ("Entry %s has missing mimetype",entry->location);
955 entry->mimetype = rb_refstring_new ("unknown/unknown");
958 /* Initialize the tree structure. */
959 genre = get_or_create_genre (db, entry->type, entry->genre);
960 artist = get_or_create_artist (db, genre, entry->artist);
961 set_entry_album (db, entry, artist, entry->album);
963 g_hash_table_insert (db->priv->entries, entry->location, entry);
966 static RhythmDBTreeProperty *
967 rhythmdb_tree_property_new (RhythmDBTree *db)
969 RhythmDBTreeProperty *ret = g_new0 (RhythmDBTreeProperty, 1);
970 #ifndef G_DISABLE_ASSERT
971 ret->magic = 0xf00dbeef;
972 #endif
973 return ret;
976 static GHashTable *
977 get_genres_hash_for_type (RhythmDBTree *db, RhythmDBEntryType type)
979 GHashTable *table;
981 table = g_hash_table_lookup (db->priv->genres, GINT_TO_POINTER (type));
982 if (table == NULL) {
983 table = g_hash_table_new_full (rb_refstring_hash,
984 rb_refstring_equal,
985 (GDestroyNotify) rb_refstring_unref,
986 NULL);
987 if (table == NULL) {
988 g_warning ("Out of memory\n");
989 return NULL;
991 g_hash_table_insert (db->priv->genres,
992 GINT_TO_POINTER (type),
993 table);
995 return table;
998 typedef void (*RBHFunc)(RhythmDBTree *db, GHashTable *genres, gpointer data);
1000 typedef struct {
1001 RhythmDBTree *db;
1002 RBHFunc func;
1003 gpointer data;
1004 } GenresIterCtxt;
1006 static void
1007 genres_process_one (gpointer key,
1008 gpointer value,
1009 gpointer user_data)
1011 GenresIterCtxt *ctxt = (GenresIterCtxt *)user_data;
1012 ctxt->func (ctxt->db, (GHashTable *)value, ctxt->data);
1015 static void
1016 genres_hash_foreach (RhythmDBTree *db, RBHFunc func, gpointer data)
1018 GenresIterCtxt ctxt;
1020 ctxt.db = db;
1021 ctxt.func = func;
1022 ctxt.data = data;
1023 g_hash_table_foreach (db->priv->genres, genres_process_one, &ctxt);
1026 static RhythmDBTreeProperty *
1027 get_or_create_genre (RhythmDBTree *db, RhythmDBEntryType type,
1028 RBRefString *name)
1030 RhythmDBTreeProperty *genre;
1031 GHashTable *table;
1033 table = get_genres_hash_for_type (db, type);
1034 genre = g_hash_table_lookup (table, name);
1036 if (G_UNLIKELY (genre == NULL)) {
1037 genre = rhythmdb_tree_property_new (db);
1038 genre->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1039 (GDestroyNotify) rb_refstring_unref,
1040 NULL);
1041 rb_refstring_ref (name);
1042 g_hash_table_insert (table, name, genre);
1043 genre->parent = NULL;
1046 return genre;
1049 static RhythmDBTreeProperty *
1050 get_or_create_artist (RhythmDBTree *db, RhythmDBTreeProperty *genre,
1051 RBRefString *name)
1053 RhythmDBTreeProperty *artist;
1055 artist = g_hash_table_lookup (genre->children, name);
1057 if (G_UNLIKELY (artist == NULL)) {
1058 artist = rhythmdb_tree_property_new (db);
1059 artist->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1060 (GDestroyNotify) rb_refstring_unref,
1061 NULL);
1062 rb_refstring_ref (name);
1063 g_hash_table_insert (genre->children, name, artist);
1064 artist->parent = genre;
1067 return artist;
1070 static RhythmDBTreeProperty *
1071 get_or_create_album (RhythmDBTree *db, RhythmDBTreeProperty *artist,
1072 RBRefString *name)
1074 RhythmDBTreeProperty *album;
1076 album = g_hash_table_lookup (artist->children, name);
1078 if (G_UNLIKELY (album == NULL)) {
1079 album = rhythmdb_tree_property_new (db);
1080 album->children = g_hash_table_new (g_direct_hash, g_direct_equal);
1081 rb_refstring_ref (name);
1082 g_hash_table_insert (artist->children, name, album);
1083 album->parent = artist;
1086 return album;
1089 static gboolean
1090 remove_child (RhythmDBTreeProperty *parent, gconstpointer data)
1092 g_assert (g_hash_table_remove (parent->children, data));
1093 if (g_hash_table_size (parent->children) <= 0) {
1094 return TRUE;
1096 return FALSE;
1099 static void
1100 remove_entry_from_album (RhythmDBTree *db, RhythmDBEntry *entry)
1102 GHashTable *table;
1104 rb_refstring_ref (entry->genre);
1105 rb_refstring_ref (entry->artist);
1106 rb_refstring_ref (entry->album);
1108 table = get_genres_hash_for_type (db, entry->type);
1109 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry), entry)) {
1110 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent,
1111 entry->album)) {
1113 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent,
1114 entry->artist)) {
1115 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent);
1116 g_assert (g_hash_table_remove (table, entry->genre));
1118 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent);
1121 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry));
1124 rb_refstring_unref (entry->genre);
1125 rb_refstring_unref (entry->artist);
1126 rb_refstring_unref (entry->album);
1129 static gboolean
1130 rhythmdb_tree_entry_set (RhythmDB *adb, RhythmDBEntry *entry,
1131 guint propid, const GValue *value)
1133 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1134 RhythmDBEntryType type;
1136 type = entry->type;
1138 /* Handle special properties */
1139 switch (propid)
1141 case RHYTHMDB_PROP_LOCATION:
1143 /* We have to use the string in the entry itself as the hash key,
1144 * otherwise either we leak it, or the string vanishes when the
1145 * GValue is freed; this means we have to do the entry modification
1146 * here, rather than letting rhythmdb_entry_set_internal do it.
1148 g_assert (g_hash_table_lookup (db->priv->entries, entry->location) != NULL);
1150 g_hash_table_remove (db->priv->entries, entry->location);
1152 g_free (entry->location);
1153 entry->location = g_strdup (g_value_get_string (value));
1154 g_hash_table_insert (db->priv->entries, entry->location, entry);
1156 return TRUE;
1158 case RHYTHMDB_PROP_ALBUM:
1160 const char *albumname = g_value_get_string (value);
1162 if (strcmp (rb_refstring_get (entry->album), albumname)) {
1163 RhythmDBTreeProperty *artist;
1164 RhythmDBTreeProperty *genre;
1166 rb_refstring_ref (entry->genre);
1167 rb_refstring_ref (entry->artist);
1168 rb_refstring_ref (entry->album);
1170 remove_entry_from_album (db, entry);
1172 genre = get_or_create_genre (db, type, entry->genre);
1173 artist = get_or_create_artist (db, genre, entry->artist);
1174 set_entry_album (db, entry, artist, rb_refstring_new (albumname));
1176 rb_refstring_unref (entry->genre);
1177 rb_refstring_unref (entry->artist);
1178 rb_refstring_unref (entry->album);
1180 break;
1182 case RHYTHMDB_PROP_ARTIST:
1184 const char *artistname = g_value_get_string (value);
1186 if (strcmp (rb_refstring_get (entry->artist), artistname)) {
1187 RhythmDBTreeProperty *new_artist;
1188 RhythmDBTreeProperty *genre;
1190 rb_refstring_ref (entry->genre);
1191 rb_refstring_ref (entry->artist);
1192 rb_refstring_ref (entry->album);
1194 remove_entry_from_album (db, entry);
1196 genre = get_or_create_genre (db, type, entry->genre);
1197 new_artist = get_or_create_artist (db, genre,
1198 rb_refstring_new (artistname));
1199 set_entry_album (db, entry, new_artist, entry->album);
1201 rb_refstring_unref (entry->genre);
1202 rb_refstring_unref (entry->artist);
1203 rb_refstring_unref (entry->album);
1205 break;
1207 case RHYTHMDB_PROP_GENRE:
1209 const char *genrename = g_value_get_string (value);
1211 if (strcmp (rb_refstring_get (entry->genre), genrename)) {
1212 RhythmDBTreeProperty *new_genre;
1213 RhythmDBTreeProperty *new_artist;
1215 rb_refstring_ref (entry->genre);
1216 rb_refstring_ref (entry->artist);
1217 rb_refstring_ref (entry->album);
1219 remove_entry_from_album (db, entry);
1221 new_genre = get_or_create_genre (db, type,
1222 rb_refstring_new (genrename));
1223 new_artist = get_or_create_artist (db, new_genre, entry->artist);
1224 set_entry_album (db, entry, new_artist, entry->album);
1226 rb_refstring_unref (entry->genre);
1227 rb_refstring_unref (entry->artist);
1228 rb_refstring_unref (entry->album);
1230 break;
1232 default:
1233 break;
1236 return FALSE;
1239 static void
1240 rhythmdb_tree_entry_delete (RhythmDB *adb, RhythmDBEntry *entry)
1242 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1244 remove_entry_from_album (db, entry);
1246 g_assert (g_hash_table_lookup (db->priv->entries, entry->location) != NULL);
1248 g_hash_table_remove (db->priv->entries, entry->location);
1251 typedef struct {
1252 RhythmDB *db;
1253 RhythmDBEntryType type;
1254 } RbEntryRemovalCtxt;
1256 static gboolean
1257 remove_one_song (gchar *uri, RhythmDBEntry *entry,
1258 RbEntryRemovalCtxt *ctxt)
1260 g_return_val_if_fail (entry != NULL, FALSE);
1262 if (entry->type == ctxt->type) {
1263 rhythmdb_emit_entry_deleted (ctxt->db, entry);
1264 remove_entry_from_album (RHYTHMDB_TREE (ctxt->db), entry);
1265 return TRUE;
1267 return FALSE;
1271 static void
1272 rhythmdb_tree_entry_delete_by_type (RhythmDB *adb, RhythmDBEntryType type)
1274 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1275 RbEntryRemovalCtxt ctxt;
1277 ctxt.db = adb;
1278 ctxt.type = type;
1279 g_hash_table_foreach_remove (db->priv->entries,
1280 (GHRFunc) remove_one_song, &ctxt);
1284 static void
1285 destroy_tree_property (RhythmDBTreeProperty *prop)
1287 #ifndef G_DISABLE_ASSERT
1288 prop->magic = 0xf33df33d;
1289 #endif
1290 g_hash_table_destroy (prop->children);
1291 g_free (prop);
1294 typedef void (*RhythmDBTreeTraversalFunc) (RhythmDBTree *db, RhythmDBEntry *entry, gpointer data);
1295 typedef void (*RhythmDBTreeAlbumTraversalFunc) (RhythmDBTree *db, RhythmDBTreeProperty *album, gpointer data);
1297 struct RhythmDBTreeTraversalData
1299 RhythmDBTree *db;
1300 GPtrArray *query;
1301 RhythmDBTreeTraversalFunc func;
1302 gpointer data;
1303 gboolean *cancel;
1306 static gboolean
1307 rhythmdb_tree_evaluate_query (RhythmDB *adb, GPtrArray *query,
1308 RhythmDBEntry *entry)
1310 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1311 guint i;
1312 guint last_disjunction;
1314 for (i = 0, last_disjunction = 0; i < query->len; i++) {
1315 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1317 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1318 if (evaluate_conjunctive_subquery (db, query, last_disjunction, i, entry))
1319 return TRUE;
1321 last_disjunction = i;
1324 if (evaluate_conjunctive_subquery (db, query, last_disjunction, query->len, entry))
1325 return TRUE;
1326 return FALSE;
1329 #define RHYTHMDB_PROPERTY_COMPARE(OP) \
1330 switch (rhythmdb_get_property_type (db, data->propid)) { \
1331 case G_TYPE_STRING: \
1332 if (strcmp (rhythmdb_entry_get_string (entry, data->propid), \
1333 g_value_get_string (data->val)) OP 0) \
1334 return FALSE; \
1335 break; \
1336 case G_TYPE_ULONG: \
1337 if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1338 g_value_get_ulong (data->val)) \
1339 return FALSE; \
1340 break; \
1341 case G_TYPE_BOOLEAN: \
1342 if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1343 g_value_get_boolean (data->val)) \
1344 return FALSE; \
1345 break; \
1346 case G_TYPE_UINT64: \
1347 if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1348 g_value_get_uint64 (data->val)) \
1349 return FALSE; \
1350 break; \
1351 case G_TYPE_DOUBLE: \
1352 if (rhythmdb_entry_get_double (entry, data->propid) OP \
1353 g_value_get_double (data->val)) \
1354 return FALSE; \
1355 break; \
1356 default: \
1357 g_assert_not_reached (); \
1360 static gboolean
1361 search_match_properties (RhythmDB *db, RhythmDBEntry *entry, gchar **words)
1363 const RhythmDBPropType props[] = {
1364 RHYTHMDB_PROP_TITLE_FOLDED,
1365 RHYTHMDB_PROP_ALBUM_FOLDED,
1366 RHYTHMDB_PROP_ARTIST_FOLDED,
1367 RHYTHMDB_PROP_GENRE_FOLDED
1369 gboolean islike = TRUE;
1370 gchar **current;
1371 int i;
1373 for (current = words; *current != NULL; current++) {
1374 gboolean word_found = FALSE;
1376 for (i = 0; i < G_N_ELEMENTS (props); i++) {
1377 const char *entry_string = rhythmdb_entry_get_string (entry, props[i]);
1378 if (entry_string && (strstr (entry_string, *current) != NULL)) {
1379 /* the word was found, go to the next one */
1380 word_found = TRUE;
1381 break;
1384 if (!word_found) {
1385 /* the word wasn't in any of the properties*/
1386 islike = FALSE;
1387 break;
1391 return islike;
1394 static gboolean
1395 evaluate_conjunctive_subquery (RhythmDBTree *dbtree, GPtrArray *query,
1396 guint base, guint max, RhythmDBEntry *entry)
1399 RhythmDB *db = (RhythmDB *) dbtree;
1400 guint i;
1401 /* Optimization possibility - we may get here without actually having
1402 * anything in the query. It would be faster to instead just merge
1403 * the child hash table into the query result hash.
1405 for (i = base; i < max; i++) {
1406 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1408 switch (data->type) {
1409 case RHYTHMDB_QUERY_SUBQUERY:
1411 gboolean matched = FALSE;
1412 GList *conjunctions = split_query_by_disjunctions (dbtree, data->subquery);
1413 GList *tem;
1415 if (conjunctions == NULL)
1416 matched = TRUE;
1418 for (tem = conjunctions; tem; tem = tem->next) {
1419 GPtrArray *subquery = tem->data;
1420 if (!matched && evaluate_conjunctive_subquery (dbtree, subquery,
1421 0, subquery->len,
1422 entry)) {
1423 matched = TRUE;
1425 g_ptr_array_free (tem->data, TRUE);
1427 g_list_free (conjunctions);
1428 if (!matched)
1429 return FALSE;
1431 break;
1432 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
1433 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
1435 gulong relative_time;
1436 GTimeVal current_time;
1438 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_ULONG);
1440 relative_time = g_value_get_ulong (data->val);
1441 g_get_current_time (&current_time);
1443 if (data->type == RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN)
1444 return (rhythmdb_entry_get_ulong (entry, data->propid) >= (current_time.tv_sec - relative_time));
1445 else
1446 return (rhythmdb_entry_get_ulong (entry, data->propid) < (current_time.tv_sec - relative_time));
1448 break;
1450 case RHYTHMDB_QUERY_PROP_LIKE:
1451 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
1453 if (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING) {
1454 gboolean islike;
1456 if (data->propid == RHYTHMDB_PROP_SEARCH_MATCH) {
1457 /* this is a special property, that should match several things */
1458 islike = search_match_properties (db, entry, g_value_get_boxed (data->val));
1460 } else {
1461 const gchar *value_string = g_value_get_string (data->val);
1462 const char *entry_string = rhythmdb_entry_get_string (entry, data->propid);
1464 /* check in case the property is NULL, the value should never be NULL */
1465 if (entry_string == NULL)
1466 return FALSE;
1468 islike = (strstr (entry_string, value_string) != NULL);
1471 if ((data->type == RHYTHMDB_QUERY_PROP_LIKE) ^ islike)
1472 return FALSE;
1473 else
1474 continue;
1475 break;
1477 /* Fall through */
1479 case RHYTHMDB_QUERY_PROP_EQUALS:
1481 RHYTHMDB_PROPERTY_COMPARE (!=)
1482 break;
1484 case RHYTHMDB_QUERY_PROP_GREATER:
1485 RHYTHMDB_PROPERTY_COMPARE (<)
1486 break;
1487 case RHYTHMDB_QUERY_PROP_LESS:
1488 RHYTHMDB_PROPERTY_COMPARE (>)
1489 break;
1490 case RHYTHMDB_QUERY_END:
1491 case RHYTHMDB_QUERY_DISJUNCTION:
1492 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
1493 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
1494 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
1495 g_assert_not_reached ();
1496 break;
1499 return TRUE;
1502 static void
1503 do_conjunction (RhythmDBEntry *entry, gpointer unused,
1504 struct RhythmDBTreeTraversalData *data)
1506 if (G_UNLIKELY (*data->cancel))
1507 return;
1508 /* Finally, we actually evaluate the query! */
1509 if (evaluate_conjunctive_subquery (data->db, data->query, 0, data->query->len,
1510 entry)) {
1511 data->func (data->db, entry, data->data);
1515 static void
1516 conjunctive_query_songs (const char *name, RhythmDBTreeProperty *album,
1517 struct RhythmDBTreeTraversalData *data)
1519 if (G_UNLIKELY (*data->cancel))
1520 return;
1521 g_hash_table_foreach (album->children, (GHFunc) do_conjunction, data);
1524 static GPtrArray *
1525 clone_remove_ptr_array_index (GPtrArray *arr, guint index)
1527 GPtrArray *ret = g_ptr_array_new ();
1528 guint i;
1529 for (i = 0; i < arr->len; i++)
1530 if (i != index)
1531 g_ptr_array_add (ret, g_ptr_array_index (arr, i));
1533 return ret;
1536 static void
1537 conjunctive_query_albums (const char *name, RhythmDBTreeProperty *artist,
1538 struct RhythmDBTreeTraversalData *data)
1540 guint i;
1541 int album_query_idx = -1;
1543 if (G_UNLIKELY (*data->cancel))
1544 return;
1546 for (i = 0; i < data->query->len; i++) {
1547 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1548 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1549 && qdata->propid == RHYTHMDB_PROP_ALBUM) {
1550 if (album_query_idx > 0)
1551 return;
1552 album_query_idx = i;
1557 if (album_query_idx >= 0) {
1558 RhythmDBTreeProperty *album;
1559 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, album_query_idx);
1560 RBRefString *albumname = rb_refstring_new (g_value_get_string (qdata->val));
1561 GPtrArray *oldquery = data->query;
1563 data->query = clone_remove_ptr_array_index (data->query, album_query_idx);
1565 album = g_hash_table_lookup (artist->children, albumname);
1567 if (album != NULL) {
1568 conjunctive_query_songs (rb_refstring_get (albumname), album, data);
1570 g_ptr_array_free (data->query, TRUE);
1571 data->query = oldquery;
1572 return;
1575 g_hash_table_foreach (artist->children, (GHFunc) conjunctive_query_songs, data);
1578 static void
1579 conjunctive_query_artists (const char *name, RhythmDBTreeProperty *genre,
1580 struct RhythmDBTreeTraversalData *data)
1582 guint i;
1583 int artist_query_idx = -1;
1585 if (G_UNLIKELY (*data->cancel))
1586 return;
1588 for (i = 0; i < data->query->len; i++) {
1589 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1590 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1591 && qdata->propid == RHYTHMDB_PROP_ARTIST) {
1592 if (artist_query_idx > 0)
1593 return;
1594 artist_query_idx = i;
1599 if (artist_query_idx >= 0) {
1600 RhythmDBTreeProperty *artist;
1601 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, artist_query_idx);
1602 RBRefString *artistname = rb_refstring_new (g_value_get_string (qdata->val));
1603 GPtrArray *oldquery = data->query;
1605 data->query = clone_remove_ptr_array_index (data->query, artist_query_idx);
1607 artist = g_hash_table_lookup (genre->children, artistname);
1608 if (artist != NULL) {
1609 conjunctive_query_albums (rb_refstring_get (artistname), artist, data);
1611 g_ptr_array_free (data->query, TRUE);
1612 data->query = oldquery;
1613 return;
1616 g_hash_table_foreach (genre->children, (GHFunc) conjunctive_query_albums, data);
1619 static void
1620 conjunctive_query_genre (RhythmDBTree *db, GHashTable *genres,
1621 struct RhythmDBTreeTraversalData *data)
1623 int genre_query_idx = -1;
1624 guint i;
1626 if (G_UNLIKELY (*data->cancel))
1627 return;
1629 for (i = 0; i < data->query->len; i++) {
1630 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
1631 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1632 && qdata->propid == RHYTHMDB_PROP_GENRE) {
1633 /* A song can't currently have two genres. So
1634 * if we get a conjunctive query for that, we
1635 * know the result must be the empty set. */
1636 if (genre_query_idx > 0)
1637 return;
1638 genre_query_idx = i;
1643 if (genre_query_idx >= 0) {
1644 RhythmDBTreeProperty *genre;
1645 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, genre_query_idx);
1646 RBRefString *genrename = rb_refstring_new (g_value_get_string (qdata->val));
1647 GPtrArray *oldquery = data->query;
1649 data->query = clone_remove_ptr_array_index (data->query, genre_query_idx);
1651 genre = g_hash_table_lookup (genres, genrename);
1652 if (genre != NULL) {
1653 conjunctive_query_artists (rb_refstring_get (genrename), genre, data);
1655 g_ptr_array_free (data->query, TRUE);
1656 data->query = oldquery;
1657 return;
1660 g_hash_table_foreach (genres, (GHFunc) conjunctive_query_artists, data);
1663 static void
1664 conjunctive_query (RhythmDBTree *db, GPtrArray *query,
1665 RhythmDBTreeTraversalFunc func, gpointer data,
1666 gboolean *cancel)
1668 int type_query_idx = -1;
1669 guint i;
1670 struct RhythmDBTreeTraversalData *traversal_data;
1672 for (i = 0; i < query->len; i++) {
1673 RhythmDBQueryData *qdata = g_ptr_array_index (query, i);
1674 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
1675 && qdata->propid == RHYTHMDB_PROP_TYPE) {
1676 /* A song can't have two types. */
1677 if (type_query_idx > 0)
1678 return;
1679 type_query_idx = i;
1683 traversal_data = g_new (struct RhythmDBTreeTraversalData, 1);
1684 traversal_data->db = db;
1685 traversal_data->query = query;
1686 traversal_data->func = func;
1687 traversal_data->data = data;
1688 traversal_data->cancel = cancel;
1690 if (type_query_idx >= 0) {
1691 GHashTable *genres;
1692 RhythmDBEntryType etype;
1693 RhythmDBQueryData *qdata = g_ptr_array_index (query, type_query_idx);
1695 g_ptr_array_remove_index_fast (query, type_query_idx);
1697 etype = g_value_get_ulong (qdata->val);
1698 genres = get_genres_hash_for_type (db, etype);
1699 if (genres != NULL) {
1700 conjunctive_query_genre (db, genres, traversal_data);
1702 } else {
1703 /* FIXME */
1704 /* No type was given; punt and query everything */
1705 genres_hash_foreach (db, (RBHFunc)conjunctive_query_genre,
1706 traversal_data);
1709 g_free (traversal_data);
1713 static GList *
1714 split_query_by_disjunctions (RhythmDBTree *db, GPtrArray *query)
1716 GList *conjunctions = NULL;
1717 guint i, j;
1718 guint last_disjunction = 0;
1719 GPtrArray *subquery = g_ptr_array_new ();
1721 for (i = 0; i < query->len; i++) {
1722 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1723 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1725 /* Copy the subquery */
1726 for (j = last_disjunction; j < i; j++) {
1727 g_ptr_array_add (subquery, g_ptr_array_index (query, j));
1730 conjunctions = g_list_prepend (conjunctions, subquery);
1731 last_disjunction = i+1;
1732 g_assert (subquery->len > 0);
1733 subquery = g_ptr_array_new ();
1737 /* Copy the last subquery, except for the QUERY_END */
1738 for (i = last_disjunction; i < query->len; i++) {
1739 g_ptr_array_add (subquery, g_ptr_array_index (query, i));
1742 if (subquery->len > 0)
1743 conjunctions = g_list_prepend (conjunctions, subquery);
1745 return conjunctions;
1748 struct RhythmDBTreeQueryGatheringData
1750 RhythmDBTree *db;
1751 GPtrArray *queue;
1752 GHashTable *entries;
1753 RhythmDBQueryModel *main_model;
1756 static void
1757 do_query_recurse (RhythmDBTree *db, GPtrArray *query, RhythmDBTreeTraversalFunc func,
1758 struct RhythmDBTreeQueryGatheringData *data, gboolean *cancel)
1760 GList *conjunctions, *tem;
1762 conjunctions = split_query_by_disjunctions (db, query);
1764 rb_debug ("doing recursive query, %d conjunctions", g_list_length (conjunctions));
1766 if (conjunctions == NULL)
1767 return;
1769 /* If there is a disjunction involved, we must uniquify the entry hits. */
1770 if (conjunctions->next != NULL)
1771 data->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
1772 else
1773 data->entries = NULL;
1775 for (tem = conjunctions; tem; tem = tem->next) {
1776 if (G_UNLIKELY (*cancel))
1777 break;
1778 conjunctive_query (db, tem->data, func, data, cancel);
1779 g_ptr_array_free (tem->data, TRUE);
1782 if (data->entries != NULL)
1783 g_hash_table_destroy (data->entries);
1785 g_list_free (conjunctions);
1788 static void
1789 handle_entry_match (RhythmDB *db, RhythmDBEntry *entry,
1790 struct RhythmDBTreeQueryGatheringData *data)
1793 if (data->entries
1794 && g_hash_table_lookup (data->entries, entry))
1795 return;
1797 g_ptr_array_add (data->queue, entry);
1798 if (data->queue->len > RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
1799 rhythmdb_query_model_add_entries (data->main_model, data->queue);
1800 data->queue = g_ptr_array_new ();
1804 static void
1805 rhythmdb_tree_do_full_query (RhythmDB *adb,
1806 GPtrArray *query,
1807 GtkTreeModel *main_model,
1808 gboolean *cancel)
1810 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1811 struct RhythmDBTreeQueryGatheringData *data = g_new0 (struct RhythmDBTreeQueryGatheringData, 1);
1813 data->main_model = RHYTHMDB_QUERY_MODEL (main_model);
1814 data->queue = g_ptr_array_new ();
1816 do_query_recurse (db, query, (RhythmDBTreeTraversalFunc) handle_entry_match, data, cancel);
1818 rhythmdb_query_model_add_entries (data->main_model, data->queue);
1820 g_free (data);
1823 static RhythmDBEntry *
1824 rhythmdb_tree_entry_lookup_by_location (RhythmDB *adb, const char *uri)
1826 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1827 return g_hash_table_lookup (db->priv->entries, uri);
1830 struct RhythmDBEntryForeachCtxt
1832 RhythmDBTree *db;
1833 GFunc func;
1834 gpointer user_data;
1837 static void
1838 rhythmdb_tree_entry_foreach_func (gpointer key, gpointer val, gpointer data)
1840 struct RhythmDBEntryForeachCtxt * ctx = data;
1841 ctx->func (val, ctx->user_data);
1844 static void
1845 rhythmdb_tree_entry_foreach (RhythmDB *adb, GFunc func, gpointer user_data)
1847 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1848 struct RhythmDBEntryForeachCtxt *ctx = g_new0 (struct RhythmDBEntryForeachCtxt, 1);
1849 ctx->db = db;
1850 ctx->func = func;
1851 ctx->user_data = user_data;
1852 g_hash_table_foreach (db->priv->entries,
1853 (GHFunc) rhythmdb_tree_entry_foreach_func, ctx);
1854 g_free (ctx);
1857 struct HashTreeIteratorCtxt {
1858 RhythmDBTree *db;
1859 RBTreeEntryItFunc entry_func;
1860 RBTreePropertyItFunc album_func;
1861 RBTreePropertyItFunc artist_func;
1862 RBTreePropertyItFunc genres_func;
1863 gpointer data;
1866 static void
1867 hash_tree_entries_foreach (gpointer key, gpointer value, gpointer data)
1869 RhythmDBEntry *entry = (RhythmDBEntry *) key;
1870 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
1872 g_assert (ctxt->entry_func);
1874 ctxt->entry_func (ctxt->db, entry, ctxt->data);
1878 static void
1879 hash_tree_albums_foreach (gpointer key, gpointer value, gpointer data)
1881 RhythmDBTreeProperty *album = (RhythmDBTreeProperty *)value;
1882 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
1884 if (ctxt->album_func) {
1885 ctxt->album_func (ctxt->db, album, ctxt->data);
1887 if (ctxt->entry_func != NULL) {
1888 g_hash_table_foreach (album->children,
1889 hash_tree_entries_foreach,
1890 ctxt);
1895 static void
1896 hash_tree_artists_foreach (gpointer key, gpointer value, gpointer data)
1898 RhythmDBTreeProperty *artist = (RhythmDBTreeProperty *)value;
1899 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
1902 if (ctxt->artist_func) {
1903 ctxt->artist_func (ctxt->db, artist, ctxt->data);
1905 if ((ctxt->album_func != NULL) || (ctxt->entry_func != NULL)) {
1906 g_hash_table_foreach (artist->children,
1907 hash_tree_albums_foreach,
1908 ctxt);
1913 static void
1914 hash_tree_genres_foreach (gpointer key, gpointer value, gpointer data)
1916 RhythmDBTreeProperty *genre = (RhythmDBTreeProperty *)value;
1917 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
1920 if (ctxt->genres_func) {
1921 ctxt->genres_func (ctxt->db, genre, ctxt->data);
1924 if ((ctxt->album_func != NULL)
1925 || (ctxt->artist_func != NULL)
1926 || (ctxt->entry_func != NULL)) {
1927 g_hash_table_foreach (genre->children,
1928 hash_tree_artists_foreach,
1929 ctxt);
1933 static void
1934 rhythmdb_hash_tree_foreach (RhythmDB *adb,
1935 RhythmDBEntryType type,
1936 RBTreeEntryItFunc entry_func,
1937 RBTreePropertyItFunc album_func,
1938 RBTreePropertyItFunc artist_func,
1939 RBTreePropertyItFunc genres_func,
1940 gpointer data)
1942 struct HashTreeIteratorCtxt ctxt;
1943 GHashTable *table;
1945 ctxt.db = RHYTHMDB_TREE (adb);
1946 ctxt.album_func = album_func;
1947 ctxt.artist_func = artist_func;
1948 ctxt.genres_func = genres_func;
1949 ctxt.entry_func = entry_func;
1950 ctxt.data = data;
1952 table = get_genres_hash_for_type (RHYTHMDB_TREE (adb), type);
1953 if (table == NULL) {
1954 return;
1956 if ((ctxt.album_func != NULL)
1957 || (ctxt.artist_func != NULL)
1958 || (ctxt.genres_func != NULL)
1959 || (ctxt.entry_func != NULL)) {
1960 g_hash_table_foreach (table, hash_tree_genres_foreach, &ctxt);