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