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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
24 #ifdef HAVE_GNU_FWRITE_UNLOCKED
28 #ifdef HAVE_GNU_FWRITE_UNLOCKED
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"
48 #include "rb-file-helpers.h"
50 typedef struct RhythmDBTreeProperty
52 #ifndef G_DISABLE_ASSERT
55 struct RhythmDBTreeProperty
*parent
;
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
,
85 typedef void (*RBTreePropertyItFunc
)(RhythmDBTree
*db
,
86 RhythmDBTreeProperty
*property
,
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
,
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
,
102 static RhythmDBTreeProperty
*get_or_create_artist (RhythmDBTree
*db
, RhythmDBTreeProperty
*genre
,
104 static RhythmDBTreeProperty
*get_or_create_genre (RhythmDBTree
*db
, RhythmDBEntryType type
,
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
123 #define RHYTHMDB_TREE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_TREE, RhythmDBTreePrivate))
130 const int RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE
= 512;
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
));
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
);
167 unparent_entries (const char *uri
, RhythmDBEntry
*entry
, RhythmDBTree
*db
)
169 remove_entry_from_album (db
, entry
);
173 rhythmdb_tree_finalize (GObject
*object
)
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
198 xmlParserCtxtPtr xmlctx
;
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
,
207 gboolean in_unknown_elt
;
208 RhythmDBEntry
*entry
;
210 RhythmDBPropType propid
;
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
);
224 if (ctx
->in_unknown_elt
)
229 case RHYTHMDB_TREE_PARSER_STATE_START
:
231 if (!strcmp (name
, "rhythmdb"))
232 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
;
234 ctx
->in_unknown_elt
= TRUE
;
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
;
260 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY
;
261 ctx
->entry
= rhythmdb_entry_allocate (RHYTHMDB (ctx
->db
), type
);
262 ctx
->has_date
= FALSE
;
264 ctx
->in_unknown_elt
= TRUE
;
267 case RHYTHMDB_TREE_PARSER_STATE_ENTRY
:
269 int val
= rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx
->db
), BAD_CAST name
);
271 ctx
->in_unknown_elt
= TRUE
;
275 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
;
277 g_string_truncate (ctx
->buf
, 0);
280 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
281 case RHYTHMDB_TREE_PARSER_STATE_END
:
287 parse_ulong (const char *buffer
)
291 val
= g_ascii_strtoull (buffer
, NULL
, 10);
292 if (val
== G_MAXUINT64
)
295 return MIN (val
, G_MAXUINT32
);
299 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext
*ctx
, const char *name
)
301 if (*ctx
->die
== TRUE
) {
302 xmlStopParser (ctx
->xmlctx
);
306 if (ctx
->in_unknown_elt
) {
307 ctx
->in_unknown_elt
= FALSE
;
313 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
:
314 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_END
;
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
));
339 rb_debug ("found entry without location");
340 rhythmdb_entry_unref (RHYTHMDB (ctx
->db
), ctx
->entry
);
342 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
;
345 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
347 /* Handle indexed properties. */
350 case RHYTHMDB_PROP_TYPE
:
351 g_assert_not_reached ();
353 case RHYTHMDB_PROP_TITLE
:
354 ctx
->entry
->title
= rb_refstring_new (ctx
->buf
->str
);
356 case RHYTHMDB_PROP_GENRE
:
357 ctx
->entry
->genre
= rb_refstring_new (ctx
->buf
->str
);
359 case RHYTHMDB_PROP_ARTIST
:
360 ctx
->entry
->artist
= rb_refstring_new (ctx
->buf
->str
);
362 case RHYTHMDB_PROP_ALBUM
:
363 ctx
->entry
->album
= rb_refstring_new (ctx
->buf
->str
);
365 case RHYTHMDB_PROP_TRACK_NUMBER
:
366 ctx
->entry
->tracknum
= parse_ulong (ctx
->buf
->str
);
368 case RHYTHMDB_PROP_DISC_NUMBER
:
369 ctx
->entry
->discnum
= parse_ulong (ctx
->buf
->str
);
371 case RHYTHMDB_PROP_DATE
:
373 gulong value
= parse_ulong (ctx
->buf
->str
);
376 ctx
->entry
->date
= g_date_new_julian (value
);
379 ctx
->has_date
= TRUE
;
382 case RHYTHMDB_PROP_DURATION
:
383 ctx
->entry
->duration
= parse_ulong (ctx
->buf
->str
);
385 case RHYTHMDB_PROP_FILE_SIZE
:
386 ctx
->entry
->file_size
= parse_ulong (ctx
->buf
->str
);
388 case RHYTHMDB_PROP_LOCATION
:
389 ctx
->entry
->location
= g_strdup (ctx
->buf
->str
);
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
);
396 case RHYTHMDB_PROP_MTIME
:
397 ctx
->entry
->mtime
= parse_ulong (ctx
->buf
->str
);
399 case RHYTHMDB_PROP_FIRST_SEEN
:
400 ctx
->entry
->first_seen
= parse_ulong (ctx
->buf
->str
);
402 case RHYTHMDB_PROP_LAST_SEEN
:
403 ctx
->entry
->last_seen
= parse_ulong (ctx
->buf
->str
);
405 case RHYTHMDB_PROP_RATING
:
406 ctx
->entry
->rating
= g_ascii_strtod (ctx
->buf
->str
, NULL
);
408 case RHYTHMDB_PROP_PLAY_COUNT
:
409 ctx
->entry
->play_count
= parse_ulong (ctx
->buf
->str
);
411 case RHYTHMDB_PROP_LAST_PLAYED
:
412 ctx
->entry
->last_played
= parse_ulong (ctx
->buf
->str
);
414 case RHYTHMDB_PROP_BITRATE
:
415 ctx
->entry
->bitrate
= parse_ulong (ctx
->buf
->str
);
417 case RHYTHMDB_PROP_TRACK_GAIN
:
418 ctx
->entry
->track_gain
= g_ascii_strtod (ctx
->buf
->str
, NULL
);
420 case RHYTHMDB_PROP_TRACK_PEAK
:
421 ctx
->entry
->track_peak
= g_ascii_strtod (ctx
->buf
->str
, NULL
);
423 case RHYTHMDB_PROP_ALBUM_GAIN
:
424 ctx
->entry
->album_gain
= g_ascii_strtod (ctx
->buf
->str
, NULL
);
426 case RHYTHMDB_PROP_ALBUM_PEAK
:
427 ctx
->entry
->album_peak
= g_ascii_strtod (ctx
->buf
->str
, NULL
);
429 case RHYTHMDB_PROP_MIMETYPE
:
430 ctx
->entry
->mimetype
= rb_refstring_new (ctx
->buf
->str
);
432 case RHYTHMDB_PROP_STATUS
:
433 ctx
->entry
->podcast
->status
= parse_ulong (ctx
->buf
->str
);
435 case RHYTHMDB_PROP_DESCRIPTION
:
436 ctx
->entry
->podcast
->description
= rb_refstring_new (ctx
->buf
->str
);
438 case RHYTHMDB_PROP_SUBTITLE
:
439 ctx
->entry
->podcast
->subtitle
= rb_refstring_new (ctx
->buf
->str
);
441 case RHYTHMDB_PROP_SUMMARY
:
442 ctx
->entry
->podcast
->summary
= rb_refstring_new (ctx
->buf
->str
);
444 case RHYTHMDB_PROP_LANG
:
445 ctx
->entry
->podcast
->lang
= rb_refstring_new (ctx
->buf
->str
);
447 case RHYTHMDB_PROP_COPYRIGHT
:
448 ctx
->entry
->podcast
->copyright
= rb_refstring_new (ctx
->buf
->str
);
450 case RHYTHMDB_PROP_IMAGE
:
451 ctx
->entry
->podcast
->image
= rb_refstring_new (ctx
->buf
->str
);
453 case RHYTHMDB_PROP_POST_TIME
:
454 ctx
->entry
->podcast
->post_time
= parse_ulong (ctx
->buf
->str
);
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 ();
474 rhythmdb_entry_sync_mirrored (RHYTHMDB (ctx
->db
), ctx
->entry
, ctx
->propid
);
476 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY
;
479 case RHYTHMDB_TREE_PARSER_STATE_START
:
480 case RHYTHMDB_TREE_PARSER_STATE_END
:
486 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext
*ctx
, const char *data
,
489 if (*ctx
->die
== TRUE
) {
490 xmlStopParser (ctx
->xmlctx
);
496 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
497 g_string_append_len (ctx
->buf
, data
, len
);
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
:
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);
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
;
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
);
531 ctxt
->userData
= ctx
;
532 ctxt
->sax
= sax_handler
;
533 xmlParseDocument (ctxt
);
535 xmlFreeParserCtxt (ctxt
);
538 g_string_free (ctx
->buf
, TRUE
);
540 g_free (sax_handler
);
544 struct RhythmDBTreeSaveContext
551 #ifdef HAVE_GNU_FWRITE_UNLOCKED
552 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
553 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
555 #define RHYTHMDB_FWRITE_REAL fwrite
556 #define RHYTHMDB_FPUTC_REAL fputc
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)); \
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)); \
575 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
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
);
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
);
594 save_entry_string (struct RhythmDBTreeSaveContext
*ctx
,
595 const xmlChar
*elt_name
, const char *str
)
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
);
604 write_elt_name_close (ctx
, elt_name
);
608 save_entry_int (struct RhythmDBTreeSaveContext
*ctx
,
609 const xmlChar
*elt_name
, int num
)
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
);
621 save_entry_ulong (struct RhythmDBTreeSaveContext
*ctx
,
622 const xmlChar
*elt_name
, gulong num
, gboolean save_zeroes
)
625 if (num
== 0 && !save_zeroes
)
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
);
634 save_entry_uint64 (struct RhythmDBTreeSaveContext
*ctx
, const xmlChar
*elt_name
,
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
);
649 save_entry_double (struct RhythmDBTreeSaveContext
*ctx
,
650 const xmlChar
*elt_name
, double num
)
654 if (num
> -0.001 && num
< 0.001)
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.
667 save_entry (RhythmDBTree
*db
, RhythmDBEntry
*entry
, struct RhythmDBTreeSaveContext
*ctx
)
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
);
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
;
696 elt_name
= rhythmdb_nice_elt_name_from_propid ((RhythmDB
*) ctx
->db
, i
);
700 case RHYTHMDB_PROP_TYPE
:
702 case RHYTHMDB_PROP_TITLE
:
703 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->title
));
705 case RHYTHMDB_PROP_ALBUM
:
706 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->album
));
708 case RHYTHMDB_PROP_ARTIST
:
709 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->artist
));
711 case RHYTHMDB_PROP_GENRE
:
712 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->genre
));
714 case RHYTHMDB_PROP_TRACK_NUMBER
:
715 save_entry_ulong (ctx
, elt_name
, entry
->tracknum
, FALSE
);
717 case RHYTHMDB_PROP_DISC_NUMBER
:
718 save_entry_ulong (ctx
, elt_name
, entry
->discnum
, FALSE
);
720 case RHYTHMDB_PROP_DATE
:
722 save_entry_ulong (ctx
, elt_name
, g_date_get_julian (entry
->date
), TRUE
);
724 save_entry_ulong (ctx
, elt_name
, 0, TRUE
);
726 case RHYTHMDB_PROP_DURATION
:
727 save_entry_ulong (ctx
, elt_name
, entry
->duration
, FALSE
);
729 case RHYTHMDB_PROP_BITRATE
:
730 save_entry_int(ctx
, elt_name
, entry
->bitrate
);
732 case RHYTHMDB_PROP_TRACK_GAIN
:
733 save_entry_double(ctx
, elt_name
, entry
->track_gain
);
735 case RHYTHMDB_PROP_TRACK_PEAK
:
736 save_entry_double(ctx
, elt_name
, entry
->track_peak
);
738 case RHYTHMDB_PROP_ALBUM_GAIN
:
739 save_entry_double(ctx
, elt_name
, entry
->album_gain
);
741 case RHYTHMDB_PROP_ALBUM_PEAK
:
742 save_entry_double(ctx
, elt_name
, entry
->album_peak
);
744 case RHYTHMDB_PROP_LOCATION
:
745 save_entry_string(ctx
, elt_name
, entry
->location
);
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
));
754 case RHYTHMDB_PROP_FILE_SIZE
:
755 save_entry_uint64(ctx
, elt_name
, entry
->file_size
);
757 case RHYTHMDB_PROP_MIMETYPE
:
758 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->mimetype
));
760 case RHYTHMDB_PROP_MTIME
:
761 save_entry_ulong (ctx
, elt_name
, entry
->mtime
, FALSE
);
763 case RHYTHMDB_PROP_FIRST_SEEN
:
764 save_entry_ulong (ctx
, elt_name
, entry
->first_seen
, FALSE
);
766 case RHYTHMDB_PROP_LAST_SEEN
:
767 save_entry_ulong (ctx
, elt_name
, entry
->last_seen
, FALSE
);
769 case RHYTHMDB_PROP_RATING
:
770 save_entry_double(ctx
, elt_name
, entry
->rating
);
772 case RHYTHMDB_PROP_PLAY_COUNT
:
773 save_entry_ulong (ctx
, elt_name
, entry
->play_count
, FALSE
);
775 case RHYTHMDB_PROP_LAST_PLAYED
:
776 save_entry_ulong (ctx
, elt_name
, entry
->last_played
, FALSE
);
778 case RHYTHMDB_PROP_STATUS
:
780 save_entry_ulong (ctx
, elt_name
, entry
->podcast
->status
, FALSE
);
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
));
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
));
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
));
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
));
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
));
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
));
806 case RHYTHMDB_PROP_POST_TIME
:
808 save_entry_ulong (ctx
, elt_name
, entry
->podcast
->post_time
, FALSE
);
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
:
828 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx
->handle
, ctx
->error
);
832 rhythmdb_tree_save (RhythmDB
*rdb
)
834 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
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");
848 g_critical ("Can't save XML: %s", g_strerror (errno
));
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",
878 unlink (savepath
->str
);
882 if (ctx
.error
!= NULL
) {
883 g_critical ("Writing to the database failed: %s", ctx
.error
);
885 unlink (savepath
->str
);
887 if (rename (savepath
->str
, name
) < 0) {
888 g_critical ("Couldn't rename %s to %s: %s",
891 unlink (savepath
->str
);
896 g_string_free (savepath
, TRUE
);
901 #undef RHYTHMDB_FWRITE_ENCODED_STR
902 #undef RHYTHMDB_FWRITE_STATICSTR
903 #undef RHYTHMDB_FPUTC
904 #undef RHYTHMDB_FWRITE
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
);
917 set_entry_album (RhythmDBTree
*db
, RhythmDBEntry
*entry
, RhythmDBTreeProperty
*artist
,
920 struct RhythmDBTreeProperty
*prop
;
921 prop
= get_or_create_album (db
, artist
, name
);
922 g_hash_table_insert (prop
->children
, entry
, NULL
);
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;
977 get_genres_hash_for_type (RhythmDBTree
*db
, RhythmDBEntryType type
)
981 table
= g_hash_table_lookup (db
->priv
->genres
, GINT_TO_POINTER (type
));
983 table
= g_hash_table_new_full (rb_refstring_hash
,
985 (GDestroyNotify
) rb_refstring_unref
,
988 g_warning ("Out of memory\n");
991 g_hash_table_insert (db
->priv
->genres
,
992 GINT_TO_POINTER (type
),
998 typedef void (*RBHFunc
)(RhythmDBTree
*db
, GHashTable
*genres
, gpointer data
);
1007 genres_process_one (gpointer key
,
1011 GenresIterCtxt
*ctxt
= (GenresIterCtxt
*)user_data
;
1012 ctxt
->func (ctxt
->db
, (GHashTable
*)value
, ctxt
->data
);
1016 genres_hash_foreach (RhythmDBTree
*db
, RBHFunc func
, gpointer data
)
1018 GenresIterCtxt ctxt
;
1023 g_hash_table_foreach (db
->priv
->genres
, genres_process_one
, &ctxt
);
1026 static RhythmDBTreeProperty
*
1027 get_or_create_genre (RhythmDBTree
*db
, RhythmDBEntryType type
,
1030 RhythmDBTreeProperty
*genre
;
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
,
1041 rb_refstring_ref (name
);
1042 g_hash_table_insert (table
, name
, genre
);
1043 genre
->parent
= NULL
;
1049 static RhythmDBTreeProperty
*
1050 get_or_create_artist (RhythmDBTree
*db
, RhythmDBTreeProperty
*genre
,
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
,
1062 rb_refstring_ref (name
);
1063 g_hash_table_insert (genre
->children
, name
, artist
);
1064 artist
->parent
= genre
;
1070 static RhythmDBTreeProperty
*
1071 get_or_create_album (RhythmDBTree
*db
, RhythmDBTreeProperty
*artist
,
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
;
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) {
1100 remove_entry_from_album (RhythmDBTree
*db
, RhythmDBEntry
*entry
)
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
,
1113 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
->parent
,
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
);
1130 rhythmdb_tree_entry_set (RhythmDB
*adb
, RhythmDBEntry
*entry
,
1131 guint propid
, const GValue
*value
)
1133 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1134 RhythmDBEntryType type
;
1138 /* Handle special properties */
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
);
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
);
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
);
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
);
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
);
1253 RhythmDBEntryType type
;
1254 } RbEntryRemovalCtxt
;
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
);
1272 rhythmdb_tree_entry_delete_by_type (RhythmDB
*adb
, RhythmDBEntryType type
)
1274 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1275 RbEntryRemovalCtxt ctxt
;
1279 g_hash_table_foreach_remove (db
->priv
->entries
,
1280 (GHRFunc
) remove_one_song
, &ctxt
);
1285 destroy_tree_property (RhythmDBTreeProperty
*prop
)
1287 #ifndef G_DISABLE_ASSERT
1288 prop
->magic
= 0xf33df33d;
1290 g_hash_table_destroy (prop
->children
);
1294 typedef void (*RhythmDBTreeTraversalFunc
) (RhythmDBTree
*db
, RhythmDBEntry
*entry
, gpointer data
);
1295 typedef void (*RhythmDBTreeAlbumTraversalFunc
) (RhythmDBTree
*db
, RhythmDBTreeProperty
*album
, gpointer data
);
1297 struct RhythmDBTreeTraversalData
1301 RhythmDBTreeTraversalFunc func
;
1307 rhythmdb_tree_evaluate_query (RhythmDB
*adb
, GPtrArray
*query
,
1308 RhythmDBEntry
*entry
)
1310 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
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
))
1321 last_disjunction
= i
;
1324 if (evaluate_conjunctive_subquery (db
, query
, last_disjunction
, query
->len
, entry
))
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) \
1336 case G_TYPE_ULONG: \
1337 if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1338 g_value_get_ulong (data->val)) \
1341 case G_TYPE_BOOLEAN: \
1342 if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1343 g_value_get_boolean (data->val)) \
1346 case G_TYPE_UINT64: \
1347 if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1348 g_value_get_uint64 (data->val)) \
1351 case G_TYPE_DOUBLE: \
1352 if (rhythmdb_entry_get_double (entry, data->propid) OP \
1353 g_value_get_double (data->val)) \
1357 g_assert_not_reached (); \
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
;
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 */
1385 /* the word wasn't in any of the properties*/
1395 evaluate_conjunctive_subquery (RhythmDBTree
*dbtree
, GPtrArray
*query
,
1396 guint base
, guint max
, RhythmDBEntry
*entry
)
1399 RhythmDB
*db
= (RhythmDB
*) dbtree
;
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
);
1415 if (conjunctions
== NULL
)
1418 for (tem
= conjunctions
; tem
; tem
= tem
->next
) {
1419 GPtrArray
*subquery
= tem
->data
;
1420 if (!matched
&& evaluate_conjunctive_subquery (dbtree
, subquery
,
1425 g_ptr_array_free (tem
->data
, TRUE
);
1427 g_list_free (conjunctions
);
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 (¤t_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
));
1446 return (rhythmdb_entry_get_ulong (entry
, data
->propid
) < (current_time
.tv_sec
- relative_time
));
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
) {
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
));
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
)
1468 islike
= (strstr (entry_string
, value_string
) != NULL
);
1471 if ((data
->type
== RHYTHMDB_QUERY_PROP_LIKE
) ^ islike
)
1479 case RHYTHMDB_QUERY_PROP_EQUALS
:
1481 RHYTHMDB_PROPERTY_COMPARE (!=)
1484 case RHYTHMDB_QUERY_PROP_GREATER
:
1485 RHYTHMDB_PROPERTY_COMPARE (<)
1487 case RHYTHMDB_QUERY_PROP_LESS
:
1488 RHYTHMDB_PROPERTY_COMPARE (>)
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 ();
1503 do_conjunction (RhythmDBEntry
*entry
, gpointer unused
,
1504 struct RhythmDBTreeTraversalData
*data
)
1506 if (G_UNLIKELY (*data
->cancel
))
1508 /* Finally, we actually evaluate the query! */
1509 if (evaluate_conjunctive_subquery (data
->db
, data
->query
, 0, data
->query
->len
,
1511 data
->func (data
->db
, entry
, data
->data
);
1516 conjunctive_query_songs (const char *name
, RhythmDBTreeProperty
*album
,
1517 struct RhythmDBTreeTraversalData
*data
)
1519 if (G_UNLIKELY (*data
->cancel
))
1521 g_hash_table_foreach (album
->children
, (GHFunc
) do_conjunction
, data
);
1525 clone_remove_ptr_array_index (GPtrArray
*arr
, guint index
)
1527 GPtrArray
*ret
= g_ptr_array_new ();
1529 for (i
= 0; i
< arr
->len
; i
++)
1531 g_ptr_array_add (ret
, g_ptr_array_index (arr
, i
));
1537 conjunctive_query_albums (const char *name
, RhythmDBTreeProperty
*artist
,
1538 struct RhythmDBTreeTraversalData
*data
)
1541 int album_query_idx
= -1;
1543 if (G_UNLIKELY (*data
->cancel
))
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)
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
;
1575 g_hash_table_foreach (artist
->children
, (GHFunc
) conjunctive_query_songs
, data
);
1579 conjunctive_query_artists (const char *name
, RhythmDBTreeProperty
*genre
,
1580 struct RhythmDBTreeTraversalData
*data
)
1583 int artist_query_idx
= -1;
1585 if (G_UNLIKELY (*data
->cancel
))
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)
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
;
1616 g_hash_table_foreach (genre
->children
, (GHFunc
) conjunctive_query_albums
, data
);
1620 conjunctive_query_genre (RhythmDBTree
*db
, GHashTable
*genres
,
1621 struct RhythmDBTreeTraversalData
*data
)
1623 int genre_query_idx
= -1;
1626 if (G_UNLIKELY (*data
->cancel
))
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)
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
;
1660 g_hash_table_foreach (genres
, (GHFunc
) conjunctive_query_artists
, data
);
1664 conjunctive_query (RhythmDBTree
*db
, GPtrArray
*query
,
1665 RhythmDBTreeTraversalFunc func
, gpointer data
,
1668 int type_query_idx
= -1;
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)
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) {
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
);
1704 /* No type was given; punt and query everything */
1705 genres_hash_foreach (db
, (RBHFunc
)conjunctive_query_genre
,
1709 g_free (traversal_data
);
1714 split_query_by_disjunctions (RhythmDBTree
*db
, GPtrArray
*query
)
1716 GList
*conjunctions
= NULL
;
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
1752 GHashTable
*entries
;
1753 RhythmDBQueryModel
*main_model
;
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
)
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
);
1773 data
->entries
= NULL
;
1775 for (tem
= conjunctions
; tem
; tem
= tem
->next
) {
1776 if (G_UNLIKELY (*cancel
))
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
);
1789 handle_entry_match (RhythmDB
*db
, RhythmDBEntry
*entry
,
1790 struct RhythmDBTreeQueryGatheringData
*data
)
1794 && g_hash_table_lookup (data
->entries
, entry
))
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 ();
1805 rhythmdb_tree_do_full_query (RhythmDB
*adb
,
1807 GtkTreeModel
*main_model
,
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
);
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
1838 rhythmdb_tree_entry_foreach_func (gpointer key
, gpointer val
, gpointer data
)
1840 struct RhythmDBEntryForeachCtxt
* ctx
= data
;
1841 ctx
->func (val
, ctx
->user_data
);
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);
1851 ctx
->user_data
= user_data
;
1852 g_hash_table_foreach (db
->priv
->entries
,
1853 (GHFunc
) rhythmdb_tree_entry_foreach_func
, ctx
);
1857 struct HashTreeIteratorCtxt
{
1859 RBTreeEntryItFunc entry_func
;
1860 RBTreePropertyItFunc album_func
;
1861 RBTreePropertyItFunc artist_func
;
1862 RBTreePropertyItFunc genres_func
;
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
);
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
,
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
,
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
,
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
,
1942 struct HashTreeIteratorCtxt ctxt
;
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
;
1952 table
= get_genres_hash_for_type (RHYTHMDB_TREE (adb
), type
);
1953 if (table
== NULL
) {
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
);