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 gboolean
rhythmdb_tree_load (RhythmDB
*rdb
, gboolean
*die
, GError
**error
);
67 static void rhythmdb_tree_save (RhythmDB
*rdb
);
68 static void rhythmdb_tree_entry_new (RhythmDB
*db
, RhythmDBEntry
*entry
);
69 static void rhythmdb_tree_entry_new_internal (RhythmDB
*db
, RhythmDBEntry
*entry
);
70 static gboolean
rhythmdb_tree_entry_set (RhythmDB
*db
, RhythmDBEntry
*entry
,
71 guint propid
, const GValue
*value
);
73 static void rhythmdb_tree_entry_delete (RhythmDB
*db
, RhythmDBEntry
*entry
);
74 static void rhythmdb_tree_entry_delete_by_type (RhythmDB
*adb
, RhythmDBEntryType type
);
76 static RhythmDBEntry
* rhythmdb_tree_entry_lookup_by_location (RhythmDB
*db
, RBRefString
*uri
);
77 static void rhythmdb_tree_entry_foreach (RhythmDB
*adb
, GFunc func
, gpointer user_data
);
78 static void rhythmdb_tree_do_full_query (RhythmDB
*db
, GPtrArray
*query
,
79 RhythmDBQueryResults
*results
,
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_error_quark (void)
157 quark
= g_quark_from_static_string ("rhythmdb_tree_error");
163 rhythmdb_tree_class_init (RhythmDBTreeClass
*klass
)
165 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
166 RhythmDBClass
*rhythmdb_class
= RHYTHMDB_CLASS (klass
);
168 object_class
->finalize
= rhythmdb_tree_finalize
;
170 rhythmdb_class
->impl_load
= rhythmdb_tree_load
;
171 rhythmdb_class
->impl_save
= rhythmdb_tree_save
;
172 rhythmdb_class
->impl_entry_new
= rhythmdb_tree_entry_new
;
173 rhythmdb_class
->impl_entry_set
= rhythmdb_tree_entry_set
;
174 rhythmdb_class
->impl_entry_delete
= rhythmdb_tree_entry_delete
;
175 rhythmdb_class
->impl_entry_delete_by_type
= rhythmdb_tree_entry_delete_by_type
;
176 rhythmdb_class
->impl_lookup_by_location
= rhythmdb_tree_entry_lookup_by_location
;
177 rhythmdb_class
->impl_entry_foreach
= rhythmdb_tree_entry_foreach
;
178 rhythmdb_class
->impl_evaluate_query
= rhythmdb_tree_evaluate_query
;
179 rhythmdb_class
->impl_do_full_query
= rhythmdb_tree_do_full_query
;
180 rhythmdb_class
->impl_entry_type_registered
= rhythmdb_tree_entry_type_registered
;
182 g_type_class_add_private (klass
, sizeof (RhythmDBTreePrivate
));
186 rhythmdb_tree_init (RhythmDBTree
*db
)
188 db
->priv
= RHYTHMDB_TREE_GET_PRIVATE (db
);
190 db
->priv
->entries
= g_hash_table_new (rb_refstring_hash
, rb_refstring_equal
);
192 db
->priv
->genres
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
,
193 NULL
, (GDestroyNotify
)g_hash_table_destroy
);
194 db
->priv
->unknown_entry_types
= g_hash_table_new (rb_refstring_hash
, rb_refstring_equal
);
196 db
->priv
->entries_lock
= g_mutex_new();
197 db
->priv
->genres_lock
= g_mutex_new();
201 unparent_entries (gpointer key
,
202 RhythmDBEntry
*entry
,
205 remove_entry_from_album (db
, entry
);
209 free_unknown_entries (RBRefString
*name
,
214 for (e
= entries
; e
!= NULL
; e
= e
->next
) {
215 RhythmDBUnknownEntry
*entry
;
218 entry
= (RhythmDBUnknownEntry
*)e
->data
;
219 rb_refstring_unref (entry
->typename
);
220 for (p
= entry
->properties
; p
!= NULL
; p
= p
->next
) {
221 RhythmDBUnknownEntryProperty
*prop
;
223 prop
= (RhythmDBUnknownEntryProperty
*)p
->data
;
224 rb_refstring_unref (prop
->name
);
225 rb_refstring_unref (prop
->value
);
229 g_list_free (entry
->properties
);
231 g_list_free (entries
);
235 rhythmdb_tree_finalize (GObject
*object
)
239 g_return_if_fail (object
!= NULL
);
240 g_return_if_fail (RHYTHMDB_IS_TREE (object
));
242 db
= RHYTHMDB_TREE (object
);
244 g_return_if_fail (db
->priv
!= NULL
);
246 db
->priv
->finalizing
= TRUE
;
248 g_hash_table_foreach (db
->priv
->entries
, (GHFunc
) unparent_entries
, db
);
249 g_hash_table_destroy (db
->priv
->entries
);
250 g_mutex_free (db
->priv
->entries_lock
);
252 g_hash_table_destroy (db
->priv
->genres
);
253 g_mutex_free (db
->priv
->genres_lock
);
255 g_hash_table_foreach (db
->priv
->unknown_entry_types
,
256 (GHFunc
) free_unknown_entries
,
258 g_hash_table_destroy (db
->priv
->unknown_entry_types
);
260 G_OBJECT_CLASS (rhythmdb_tree_parent_class
)->finalize (object
);
263 struct RhythmDBTreeLoadContext
266 xmlParserCtxtPtr xmlctx
;
269 RHYTHMDB_TREE_PARSER_STATE_START
,
270 RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
,
271 RHYTHMDB_TREE_PARSER_STATE_ENTRY
,
272 RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
,
273 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
,
274 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
,
275 RHYTHMDB_TREE_PARSER_STATE_END
,
277 guint in_unknown_elt
;
278 RhythmDBEntry
*entry
;
279 RhythmDBUnknownEntry
*unknown_entry
;
281 RhythmDBPropType propid
;
287 gboolean canonicalise_uris
;
288 gboolean reload_all_metadata
;
292 rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext
*ctx
,
296 if (*ctx
->die
== TRUE
) {
297 xmlStopParser (ctx
->xmlctx
);
301 if (ctx
->in_unknown_elt
) {
302 ctx
->in_unknown_elt
++;
308 case RHYTHMDB_TREE_PARSER_STATE_START
:
310 if (!strcmp (name
, "rhythmdb")) {
311 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
;
312 for (; *attrs
; attrs
+=2) {
313 if (!strcmp (*attrs
, "version")) {
314 const char *version
= *(attrs
+1);
316 if (!strcmp (version
, "1.0") || !strcmp (version
, "1.1")) {
317 ctx
->canonicalise_uris
= TRUE
;
318 rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries");
319 } else if (!strcmp (version
, "1.2")) {
321 rb_debug ("reloading all file metadata to get MusicBrainz tags");
322 ctx
->reload_all_metadata
= TRUE
;
323 } else if (!strcmp (version
, "1.3")) {
326 g_set_error (ctx
->error
,
328 RHYTHMDB_TREE_ERROR_DATABASE_TOO_NEW
,
329 _("The database was created by a later version of rhythmbox."
330 " This version of rhythmbox cannot read the database."));
331 xmlStopParser (ctx
->xmlctx
);
334 g_assert_not_reached ();
339 ctx
->in_unknown_elt
++;
344 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
:
346 if (!strcmp (name
, "entry")) {
347 RhythmDBEntryType type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
348 const char *typename
= NULL
;
349 for (; *attrs
; attrs
+=2) {
350 if (!strcmp (*attrs
, "type")) {
351 typename
= *(attrs
+1);
352 type
= rhythmdb_entry_type_get_by_name (RHYTHMDB (ctx
->db
), typename
);
358 if (type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) {
359 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY
;
360 ctx
->entry
= rhythmdb_entry_allocate (RHYTHMDB (ctx
->db
), type
);
361 ctx
->entry
->flags
|= RHYTHMDB_ENTRY_TREE_LOADING
;
362 ctx
->has_date
= FALSE
;
364 rb_debug ("reading unknown entry");
365 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
;
366 ctx
->unknown_entry
= g_new0 (RhythmDBUnknownEntry
, 1);
367 ctx
->unknown_entry
->typename
= rb_refstring_new (typename
);
370 ctx
->in_unknown_elt
++;
374 case RHYTHMDB_TREE_PARSER_STATE_ENTRY
:
376 int val
= rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx
->db
), BAD_CAST name
);
378 ctx
->in_unknown_elt
++;
382 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
;
384 g_string_truncate (ctx
->buf
, 0);
387 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
:
389 RhythmDBUnknownEntryProperty
*prop
;
391 prop
= g_new0 (RhythmDBUnknownEntryProperty
, 1);
392 prop
->name
= rb_refstring_new (name
);
394 ctx
->unknown_entry
->properties
= g_list_prepend (ctx
->unknown_entry
->properties
, prop
);
395 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
;
396 g_string_truncate (ctx
->buf
, 0);
399 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
:
400 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
401 case RHYTHMDB_TREE_PARSER_STATE_END
:
407 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext
*ctx
,
410 if (*ctx
->die
== TRUE
) {
411 xmlStopParser (ctx
->xmlctx
);
415 if (ctx
->in_unknown_elt
) {
416 ctx
->in_unknown_elt
--;
422 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
:
423 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_END
;
425 case RHYTHMDB_TREE_PARSER_STATE_ENTRY
:
427 if (!ctx
->has_date
| ctx
->reload_all_metadata
) {
428 /* there is no date metadata, so this is from an old version
429 * reset the last-modified timestamp, so that the file is re-read
431 rb_debug ("pre-Date entry found, causing re-read");
432 ctx
->entry
->mtime
= 0;
434 if (ctx
->entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
) {
435 RhythmDBPodcastFields
*podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (ctx
->entry
, RhythmDBPodcastFields
);
436 /* Handle upgrades from 0.9.2.
437 * Previously, last-seen for podcast feeds was the time of the last post,
438 * and post-time was unused. Now, we want last-seen to be the time we
439 * last updated the feed, and post-time to be the time of the last post.
441 if (podcast
->post_time
== 0) {
442 podcast
->post_time
= ctx
->entry
->last_seen
;
446 if (ctx
->entry
->location
!= NULL
) {
447 RhythmDBEntry
*entry
;
449 g_mutex_lock (ctx
->db
->priv
->entries_lock
);
450 entry
= g_hash_table_lookup (ctx
->db
->priv
->entries
, ctx
->entry
->location
);
452 rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx
->db
), ctx
->entry
);
453 rhythmdb_entry_insert (RHYTHMDB (ctx
->db
), ctx
->entry
);
454 if (++ctx
->batch_count
== RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK
) {
455 rhythmdb_commit (RHYTHMDB (ctx
->db
));
456 ctx
->batch_count
= 0;
459 rb_debug ("found entry with duplicate location %s. merging metadata",
460 rb_refstring_get (ctx
->entry
->location
));
461 entry
->play_count
+= ctx
->entry
->play_count
;
463 if (entry
->rating
< 0.01)
464 entry
->rating
= ctx
->entry
->rating
;
465 else if (ctx
->entry
->rating
> 0.01)
466 entry
->rating
= (entry
->rating
+ ctx
->entry
->rating
) / 2;
468 if (ctx
->entry
->last_played
> entry
->last_played
)
469 entry
->last_played
= ctx
->entry
->last_played
;
471 if (ctx
->entry
->first_seen
< entry
->first_seen
)
472 entry
->first_seen
= ctx
->entry
->first_seen
;
474 if (ctx
->entry
->last_seen
> entry
->last_seen
)
475 entry
->last_seen
= ctx
->entry
->last_seen
;
477 rhythmdb_entry_unref (ctx
->entry
);
479 g_mutex_unlock (ctx
->db
->priv
->entries_lock
);
481 rb_debug ("found entry without location");
482 rhythmdb_entry_unref (ctx
->entry
);
484 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
;
488 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
:
492 rb_debug ("finished reading unknown entry");
493 ctx
->unknown_entry
->properties
= g_list_reverse (ctx
->unknown_entry
->properties
);
495 entry_list
= g_hash_table_lookup (ctx
->db
->priv
->unknown_entry_types
, ctx
->unknown_entry
->typename
);
496 entry_list
= g_list_prepend (entry_list
, ctx
->unknown_entry
);
497 g_hash_table_insert (ctx
->db
->priv
->unknown_entry_types
, ctx
->unknown_entry
->typename
, entry_list
);
499 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
;
500 ctx
->unknown_entry
= NULL
;
503 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
506 gboolean set
= FALSE
;
507 gboolean skip
= FALSE
;
509 /* special case some properties for upgrade handling etc. */
510 switch (ctx
->propid
) {
511 case RHYTHMDB_PROP_DATE
:
512 ctx
->has_date
= TRUE
;
514 case RHYTHMDB_PROP_LOCATION
:
515 if (ctx
->canonicalise_uris
) {
516 char *canon
= rb_canonicalise_uri (ctx
->buf
->str
);
518 g_value_init (&value
, G_TYPE_STRING
);
519 g_value_take_string (&value
, canon
);
523 case RHYTHMDB_PROP_MOUNTPOINT
:
524 /* fix old podcast posts */
525 if (g_str_has_prefix (ctx
->buf
->str
, "http://"))
534 rhythmdb_read_encoded_property (RHYTHMDB (ctx
->db
), ctx
->buf
->str
, ctx
->propid
, &value
);
537 rhythmdb_entry_set_internal (RHYTHMDB (ctx
->db
), ctx
->entry
, FALSE
, ctx
->propid
, &value
);
538 g_value_unset (&value
);
541 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY
;
544 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
:
546 RhythmDBUnknownEntryProperty
*prop
;
548 g_assert (ctx
->unknown_entry
->properties
);
549 prop
= ctx
->unknown_entry
->properties
->data
;
550 g_assert (prop
->value
== NULL
);
551 prop
->value
= rb_refstring_new (ctx
->buf
->str
);
552 rb_debug ("unknown entry property: %s = %s", rb_refstring_get (prop
->name
), rb_refstring_get (prop
->value
));
554 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
;
557 case RHYTHMDB_TREE_PARSER_STATE_START
:
558 case RHYTHMDB_TREE_PARSER_STATE_END
:
564 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext
*ctx
,
568 if (*ctx
->die
== TRUE
) {
569 xmlStopParser (ctx
->xmlctx
);
575 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
576 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
:
577 g_string_append_len (ctx
->buf
, data
, len
);
579 case RHYTHMDB_TREE_PARSER_STATE_ENTRY
:
580 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
:
581 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
:
582 case RHYTHMDB_TREE_PARSER_STATE_START
:
583 case RHYTHMDB_TREE_PARSER_STATE_END
:
589 rhythmdb_tree_load (RhythmDB
*rdb
,
593 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
594 xmlParserCtxtPtr ctxt
;
595 xmlSAXHandlerPtr sax_handler
;
596 struct RhythmDBTreeLoadContext
*ctx
;
603 sax_handler
= g_new0 (xmlSAXHandler
, 1);
604 ctx
= g_new0 (struct RhythmDBTreeLoadContext
, 1);
606 sax_handler
->startElement
= (startElementSAXFunc
) rhythmdb_tree_parser_start_element
;
607 sax_handler
->endElement
= (endElementSAXFunc
) rhythmdb_tree_parser_end_element
;
608 sax_handler
->characters
= (charactersSAXFunc
) rhythmdb_tree_parser_characters
;
610 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_START
;
613 ctx
->buf
= g_string_sized_new (RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE
);
614 ctx
->error
= &local_error
;
616 g_object_get (G_OBJECT (db
), "name", &name
, NULL
);
618 if (g_file_test (name
, G_FILE_TEST_EXISTS
)) {
619 ctxt
= xmlCreateFileParserCtxt (name
);
622 ctxt
->userData
= ctx
;
623 ctxt
->sax
= sax_handler
;
624 xmlParseDocument (ctxt
);
626 xmlFreeParserCtxt (ctxt
);
628 if (ctx
->batch_count
)
629 rhythmdb_commit (RHYTHMDB (ctx
->db
));
633 if (local_error
!= NULL
) {
634 g_propagate_error (error
, local_error
);
638 g_string_free (ctx
->buf
, TRUE
);
640 g_free (sax_handler
);
646 struct RhythmDBTreeSaveContext
653 #ifdef HAVE_GNU_FWRITE_UNLOCKED
654 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
655 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
657 #define RHYTHMDB_FWRITE_REAL fwrite
658 #define RHYTHMDB_FPUTC_REAL fputc
661 #define RHYTHMDB_FWRITE(w,x,len,handle,error) do { \
662 if (error == NULL) { \
663 if (RHYTHMDB_FWRITE_REAL (w,x,len,handle) != len) { \
664 error = g_strdup (g_strerror (errno)); \
669 #define RHYTHMDB_FPUTC(x,handle,error) do { \
670 if (error == NULL) { \
671 if (RHYTHMDB_FPUTC_REAL (x,handle) == EOF) { \
672 error = g_strdup (g_strerror (errno)); \
677 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
680 write_elt_name_open (struct RhythmDBTreeSaveContext
*ctx
,
681 const xmlChar
*elt_name
)
683 RHYTHMDB_FWRITE_STATICSTR (" <", ctx
->handle
, ctx
->error
);
684 RHYTHMDB_FWRITE (elt_name
, 1, xmlStrlen (elt_name
), ctx
->handle
, ctx
->error
);
685 RHYTHMDB_FPUTC ('>', ctx
->handle
, ctx
->error
);
689 write_elt_name_close (struct RhythmDBTreeSaveContext
*ctx
,
690 const xmlChar
*elt_name
)
692 RHYTHMDB_FWRITE_STATICSTR ("</", ctx
->handle
, ctx
->error
);
693 RHYTHMDB_FWRITE (elt_name
, 1, xmlStrlen (elt_name
), ctx
->handle
, ctx
->error
);
694 RHYTHMDB_FWRITE_STATICSTR (">\n", ctx
->handle
, ctx
->error
);
698 save_entry_string (struct RhythmDBTreeSaveContext
*ctx
,
699 const xmlChar
*elt_name
,
704 g_return_if_fail (str
!= NULL
);
705 write_elt_name_open (ctx
, elt_name
);
706 encoded
= xmlEncodeEntitiesReentrant (NULL
, BAD_CAST str
);
707 RHYTHMDB_FWRITE (encoded
, 1, xmlStrlen (encoded
), ctx
->handle
, ctx
->error
);
709 write_elt_name_close (ctx
, elt_name
);
713 save_entry_int (struct RhythmDBTreeSaveContext
*ctx
,
714 const xmlChar
*elt_name
,
720 write_elt_name_open (ctx
, elt_name
);
721 g_snprintf (buf
, sizeof (buf
), "%d", num
);
722 RHYTHMDB_FWRITE (buf
, 1, strlen (buf
), ctx
->handle
, ctx
->error
);
723 write_elt_name_close (ctx
, elt_name
);
727 save_entry_ulong (struct RhythmDBTreeSaveContext
*ctx
,
728 const xmlChar
*elt_name
,
730 gboolean save_zeroes
)
734 if (num
== 0 && !save_zeroes
)
736 write_elt_name_open (ctx
, elt_name
);
737 g_snprintf (buf
, sizeof (buf
), "%lu", num
);
738 RHYTHMDB_FWRITE (buf
, 1, strlen (buf
), ctx
->handle
, ctx
->error
);
739 write_elt_name_close (ctx
, elt_name
);
743 save_entry_boolean (struct RhythmDBTreeSaveContext
*ctx
,
744 const xmlChar
*elt_name
,
747 save_entry_ulong (ctx
, elt_name
, val
? 1 : 0, FALSE
);
751 save_entry_uint64 (struct RhythmDBTreeSaveContext
*ctx
,
752 const xmlChar
*elt_name
,
760 write_elt_name_open (ctx
, elt_name
);
761 g_snprintf (buf
, sizeof (buf
), "%" G_GUINT64_FORMAT
, num
);
762 RHYTHMDB_FWRITE (buf
, 1, strlen (buf
), ctx
->handle
, ctx
->error
);
763 write_elt_name_close (ctx
, elt_name
);
767 save_entry_double (struct RhythmDBTreeSaveContext
*ctx
,
768 const xmlChar
*elt_name
,
773 if (num
> -0.001 && num
< 0.001)
776 write_elt_name_open (ctx
, elt_name
);
777 g_snprintf (buf
, sizeof (buf
), "%f", num
);
778 RHYTHMDB_FWRITE (buf
, 1, strlen (buf
), ctx
->handle
, ctx
->error
);
779 write_elt_name_close (ctx
, elt_name
);
782 /* This code is intended to be highly optimized. This came at a small
783 * readability cost. Sorry about that.
786 save_entry (RhythmDBTree
*db
,
787 RhythmDBEntry
*entry
,
788 struct RhythmDBTreeSaveContext
*ctx
)
791 RhythmDBPodcastFields
*podcast
= NULL
;
797 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
798 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
799 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
801 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx
->handle
, ctx
->error
);
802 encoded
= xmlEncodeEntitiesReentrant (NULL
, BAD_CAST entry
->type
->name
);
803 RHYTHMDB_FWRITE (encoded
, 1, xmlStrlen (encoded
), ctx
->handle
, ctx
->error
);
806 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx
->handle
, ctx
->error
);
808 /* Skip over the first property - the type */
809 for (i
= 1; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
810 const xmlChar
*elt_name
;
815 elt_name
= rhythmdb_nice_elt_name_from_propid ((RhythmDB
*) ctx
->db
, i
);
818 case RHYTHMDB_PROP_TYPE
:
820 case RHYTHMDB_PROP_ENTRY_ID
:
822 case RHYTHMDB_PROP_TITLE
:
823 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->title
));
825 case RHYTHMDB_PROP_ALBUM
:
826 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->album
));
828 case RHYTHMDB_PROP_ARTIST
:
829 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->artist
));
831 case RHYTHMDB_PROP_GENRE
:
832 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->genre
));
834 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
835 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->musicbrainz_trackid
));
837 case RHYTHMDB_PROP_TRACK_NUMBER
:
838 save_entry_ulong (ctx
, elt_name
, entry
->tracknum
, FALSE
);
840 case RHYTHMDB_PROP_DISC_NUMBER
:
841 save_entry_ulong (ctx
, elt_name
, entry
->discnum
, FALSE
);
843 case RHYTHMDB_PROP_DATE
:
844 if (g_date_valid (&entry
->date
))
845 save_entry_ulong (ctx
, elt_name
, g_date_get_julian (&entry
->date
), TRUE
);
847 save_entry_ulong (ctx
, elt_name
, 0, TRUE
);
849 case RHYTHMDB_PROP_DURATION
:
850 save_entry_ulong (ctx
, elt_name
, entry
->duration
, FALSE
);
852 case RHYTHMDB_PROP_BITRATE
:
853 save_entry_int(ctx
, elt_name
, entry
->bitrate
);
855 case RHYTHMDB_PROP_TRACK_GAIN
:
856 save_entry_double(ctx
, elt_name
, entry
->track_gain
);
858 case RHYTHMDB_PROP_TRACK_PEAK
:
859 save_entry_double(ctx
, elt_name
, entry
->track_peak
);
861 case RHYTHMDB_PROP_ALBUM_GAIN
:
862 save_entry_double(ctx
, elt_name
, entry
->album_gain
);
864 case RHYTHMDB_PROP_ALBUM_PEAK
:
865 save_entry_double(ctx
, elt_name
, entry
->album_peak
);
867 case RHYTHMDB_PROP_LOCATION
:
868 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->location
));
870 case RHYTHMDB_PROP_MOUNTPOINT
:
871 /* Avoid crashes on exit when upgrading from 0.8
872 * and no mountpoint is available from some entries */
873 if (entry
->mountpoint
) {
874 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->mountpoint
));
877 case RHYTHMDB_PROP_FILE_SIZE
:
878 save_entry_uint64(ctx
, elt_name
, entry
->file_size
);
880 case RHYTHMDB_PROP_MIMETYPE
:
881 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->mimetype
));
883 case RHYTHMDB_PROP_MTIME
:
884 save_entry_ulong (ctx
, elt_name
, entry
->mtime
, FALSE
);
886 case RHYTHMDB_PROP_FIRST_SEEN
:
887 save_entry_ulong (ctx
, elt_name
, entry
->first_seen
, FALSE
);
889 case RHYTHMDB_PROP_LAST_SEEN
:
890 save_entry_ulong (ctx
, elt_name
, entry
->last_seen
, FALSE
);
892 case RHYTHMDB_PROP_RATING
:
893 save_entry_double(ctx
, elt_name
, entry
->rating
);
895 case RHYTHMDB_PROP_PLAY_COUNT
:
896 save_entry_ulong (ctx
, elt_name
, entry
->play_count
, FALSE
);
898 case RHYTHMDB_PROP_LAST_PLAYED
:
899 save_entry_ulong (ctx
, elt_name
, entry
->last_played
, FALSE
);
901 case RHYTHMDB_PROP_HIDDEN
:
903 gboolean hidden
= ((entry
->flags
& RHYTHMDB_ENTRY_HIDDEN
) != 0);
904 save_entry_boolean (ctx
, elt_name
, hidden
);
907 case RHYTHMDB_PROP_STATUS
:
909 save_entry_ulong (ctx
, elt_name
, podcast
->status
, FALSE
);
911 case RHYTHMDB_PROP_DESCRIPTION
:
912 if (podcast
&& podcast
->description
)
913 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->description
));
915 case RHYTHMDB_PROP_SUBTITLE
:
916 if (podcast
&& podcast
->subtitle
)
917 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->subtitle
));
919 case RHYTHMDB_PROP_SUMMARY
:
920 if (podcast
&& podcast
->summary
)
921 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->summary
));
923 case RHYTHMDB_PROP_LANG
:
924 if (podcast
&& podcast
->lang
)
925 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->lang
));
927 case RHYTHMDB_PROP_COPYRIGHT
:
928 if (podcast
&& podcast
->copyright
)
929 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->copyright
));
931 case RHYTHMDB_PROP_IMAGE
:
932 if (podcast
&& podcast
->image
)
933 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->image
));
935 case RHYTHMDB_PROP_POST_TIME
:
937 save_entry_ulong (ctx
, elt_name
, podcast
->post_time
, FALSE
);
939 case RHYTHMDB_PROP_TITLE_SORT_KEY
:
940 case RHYTHMDB_PROP_GENRE_SORT_KEY
:
941 case RHYTHMDB_PROP_ARTIST_SORT_KEY
:
942 case RHYTHMDB_PROP_ALBUM_SORT_KEY
:
943 case RHYTHMDB_PROP_TITLE_FOLDED
:
944 case RHYTHMDB_PROP_GENRE_FOLDED
:
945 case RHYTHMDB_PROP_ARTIST_FOLDED
:
946 case RHYTHMDB_PROP_ALBUM_FOLDED
:
947 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
948 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
949 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
950 case RHYTHMDB_PROP_LAST_SEEN_STR
:
951 case RHYTHMDB_PROP_SEARCH_MATCH
:
952 case RHYTHMDB_PROP_YEAR
:
953 case RHYTHMDB_NUM_PROPERTIES
:
958 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx
->handle
, ctx
->error
);
962 save_entry_type (const char *name
,
963 RhythmDBEntryType entry_type
,
964 struct RhythmDBTreeSaveContext
*ctx
)
966 if (entry_type
->save_to_disk
== FALSE
)
969 rb_debug ("saving entries of type %s", name
);
970 rhythmdb_hash_tree_foreach (RHYTHMDB (ctx
->db
), entry_type
,
971 (RBTreeEntryItFunc
) save_entry
,
972 NULL
, NULL
, NULL
, ctx
);
976 save_unknown_entry_type (RBRefString
*typename
,
978 struct RhythmDBTreeSaveContext
*ctx
)
982 for (t
= entries
; t
!= NULL
; t
= t
->next
) {
983 RhythmDBUnknownEntry
*entry
;
990 entry
= (RhythmDBUnknownEntry
*)t
->data
;
992 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx
->handle
, ctx
->error
);
993 encoded
= xmlEncodeEntitiesReentrant (NULL
, BAD_CAST
rb_refstring_get (entry
->typename
));
994 RHYTHMDB_FWRITE (encoded
, 1, xmlStrlen (encoded
), ctx
->handle
, ctx
->error
);
997 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx
->handle
, ctx
->error
);
999 for (p
= entry
->properties
; p
!= NULL
; p
= p
->next
) {
1000 RhythmDBUnknownEntryProperty
*prop
;
1001 prop
= (RhythmDBUnknownEntryProperty
*) p
->data
;
1002 save_entry_string(ctx
, (const xmlChar
*)rb_refstring_get (prop
->name
), rb_refstring_get (prop
->value
));
1005 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx
->handle
, ctx
->error
);
1010 rhythmdb_tree_save (RhythmDB
*rdb
)
1012 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
1016 struct RhythmDBTreeSaveContext ctx
;
1018 g_object_get (G_OBJECT (db
), "name", &name
, NULL
);
1020 savepath
= g_string_new (name
);
1021 g_string_append (savepath
, ".tmp");
1023 f
= fopen (savepath
->str
, "w");
1026 g_warning ("Can't save XML: %s", g_strerror (errno
));
1033 RHYTHMDB_FWRITE_STATICSTR ("<?xml version=\"1.0\" standalone=\"yes\"?>\n"
1034 "<rhythmdb version=\"" RHYTHMDB_TREE_XML_VERSION
"\">\n",
1035 ctx
.handle
, ctx
.error
);
1037 rhythmdb_entry_type_foreach (rdb
, (GHFunc
) save_entry_type
, &ctx
);
1038 g_hash_table_foreach (db
->priv
->unknown_entry_types
,
1039 (GHFunc
) save_unknown_entry_type
,
1042 RHYTHMDB_FWRITE_STATICSTR ("</rhythmdb>\n", ctx
.handle
, ctx
.error
);
1044 if (fclose (f
) < 0) {
1045 g_warning ("Couldn't close %s: %s",
1047 g_strerror (errno
));
1048 unlink (savepath
->str
);
1052 if (ctx
.error
!= NULL
) {
1053 g_warning ("Writing to the database failed: %s", ctx
.error
);
1055 unlink (savepath
->str
);
1057 if (rename (savepath
->str
, name
) < 0) {
1058 g_warning ("Couldn't rename %s to %s: %s",
1059 name
, savepath
->str
,
1060 g_strerror (errno
));
1061 unlink (savepath
->str
);
1066 g_string_free (savepath
, TRUE
);
1071 #undef RHYTHMDB_FWRITE_ENCODED_STR
1072 #undef RHYTHMDB_FWRITE_STATICSTR
1073 #undef RHYTHMDB_FPUTC
1074 #undef RHYTHMDB_FWRITE
1077 rhythmdb_tree_new (const char *name
)
1079 RhythmDBTree
*db
= g_object_new (RHYTHMDB_TYPE_TREE
, "name", name
, NULL
);
1081 g_return_val_if_fail (db
->priv
!= NULL
, NULL
);
1083 return RHYTHMDB (db
);
1087 set_entry_album (RhythmDBTree
*db
,
1088 RhythmDBEntry
*entry
,
1089 RhythmDBTreeProperty
*artist
,
1092 struct RhythmDBTreeProperty
*prop
;
1093 prop
= get_or_create_album (db
, artist
, name
);
1094 g_hash_table_insert (prop
->children
, entry
, NULL
);
1099 rhythmdb_tree_entry_new (RhythmDB
*rdb
,
1100 RhythmDBEntry
*entry
)
1102 g_mutex_lock (RHYTHMDB_TREE(rdb
)->priv
->entries_lock
);
1103 rhythmdb_tree_entry_new_internal (rdb
, entry
);
1104 g_mutex_unlock (RHYTHMDB_TREE(rdb
)->priv
->entries_lock
);
1108 rhythmdb_tree_entry_new_internal (RhythmDB
*rdb
, RhythmDBEntry
*entry
)
1110 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
1111 RhythmDBTreeProperty
*artist
;
1112 RhythmDBTreeProperty
*genre
;
1114 g_assert (entry
!= NULL
);
1116 g_return_if_fail (entry
->location
!= NULL
);
1118 if (entry
->title
== NULL
) {
1119 g_warning ("Entry %s has missing title", rb_refstring_get (entry
->location
));
1120 entry
->title
= rb_refstring_new (_("Unknown"));
1122 if (entry
->artist
== NULL
) {
1123 g_warning ("Entry %s has missing artist", rb_refstring_get (entry
->location
));
1124 entry
->artist
= rb_refstring_new (_("Unknown"));
1126 if (entry
->album
== NULL
) {
1127 g_warning ("Entry %s has missing album", rb_refstring_get (entry
->location
));
1128 entry
->album
= rb_refstring_new (_("Unknown"));
1130 if (entry
->genre
== NULL
) {
1131 g_warning ("Entry %s has missing genre", rb_refstring_get (entry
->location
));
1132 entry
->genre
= rb_refstring_new (_("Unknown"));
1134 if (entry
->mimetype
== NULL
) {
1135 g_warning ("Entry %s has missing mimetype", rb_refstring_get (entry
->location
));
1136 entry
->mimetype
= rb_refstring_new ("unknown/unknown");
1139 /* Initialize the tree structure. */
1140 genre
= get_or_create_genre (db
, entry
->type
, entry
->genre
);
1141 artist
= get_or_create_artist (db
, genre
, entry
->artist
);
1142 set_entry_album (db
, entry
, artist
, entry
->album
);
1144 /* this accounts for the initial reference on the entry */
1145 g_hash_table_insert (db
->priv
->entries
, entry
->location
, entry
);
1147 entry
->flags
&= ~RHYTHMDB_ENTRY_TREE_LOADING
;
1150 static RhythmDBTreeProperty
*
1151 rhythmdb_tree_property_new (RhythmDBTree
*db
)
1153 RhythmDBTreeProperty
*ret
= g_new0 (RhythmDBTreeProperty
, 1);
1154 #ifndef G_DISABLE_ASSERT
1155 ret
->magic
= 0xf00dbeef;
1161 get_genres_hash_for_type (RhythmDBTree
*db
,
1162 RhythmDBEntryType type
)
1166 table
= g_hash_table_lookup (db
->priv
->genres
, type
);
1167 if (table
== NULL
) {
1168 table
= g_hash_table_new_full (rb_refstring_hash
,
1170 (GDestroyNotify
) rb_refstring_unref
,
1172 if (table
== NULL
) {
1173 g_warning ("Out of memory\n");
1176 g_hash_table_insert (db
->priv
->genres
,
1183 typedef void (*RBHFunc
)(RhythmDBTree
*db
, GHashTable
*genres
, gpointer data
);
1192 genres_process_one (gpointer key
,
1196 GenresIterCtxt
*ctxt
= (GenresIterCtxt
*)user_data
;
1197 ctxt
->func (ctxt
->db
, (GHashTable
*)value
, ctxt
->data
);
1201 genres_hash_foreach (RhythmDBTree
*db
, RBHFunc func
, gpointer data
)
1203 GenresIterCtxt ctxt
;
1208 g_hash_table_foreach (db
->priv
->genres
, genres_process_one
, &ctxt
);
1211 static RhythmDBTreeProperty
*
1212 get_or_create_genre (RhythmDBTree
*db
,
1213 RhythmDBEntryType type
,
1216 RhythmDBTreeProperty
*genre
;
1219 g_mutex_lock (db
->priv
->genres_lock
);
1220 table
= get_genres_hash_for_type (db
, type
);
1221 genre
= g_hash_table_lookup (table
, name
);
1223 if (G_UNLIKELY (genre
== NULL
)) {
1224 genre
= rhythmdb_tree_property_new (db
);
1225 genre
->children
= g_hash_table_new_full (rb_refstring_hash
, rb_refstring_equal
,
1226 (GDestroyNotify
) rb_refstring_unref
,
1228 rb_refstring_ref (name
);
1229 g_hash_table_insert (table
, name
, genre
);
1230 genre
->parent
= NULL
;
1232 g_mutex_unlock (db
->priv
->genres_lock
);
1237 static RhythmDBTreeProperty
*
1238 get_or_create_artist (RhythmDBTree
*db
,
1239 RhythmDBTreeProperty
*genre
,
1242 RhythmDBTreeProperty
*artist
;
1244 artist
= g_hash_table_lookup (genre
->children
, name
);
1246 if (G_UNLIKELY (artist
== NULL
)) {
1247 artist
= rhythmdb_tree_property_new (db
);
1248 artist
->children
= g_hash_table_new_full (rb_refstring_hash
, rb_refstring_equal
,
1249 (GDestroyNotify
) rb_refstring_unref
,
1251 rb_refstring_ref (name
);
1252 g_hash_table_insert (genre
->children
, name
, artist
);
1253 artist
->parent
= genre
;
1259 static RhythmDBTreeProperty
*
1260 get_or_create_album (RhythmDBTree
*db
,
1261 RhythmDBTreeProperty
*artist
,
1264 RhythmDBTreeProperty
*album
;
1266 album
= g_hash_table_lookup (artist
->children
, name
);
1268 if (G_UNLIKELY (album
== NULL
)) {
1269 album
= rhythmdb_tree_property_new (db
);
1270 album
->children
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
1271 rb_refstring_ref (name
);
1272 g_hash_table_insert (artist
->children
, name
, album
);
1273 album
->parent
= artist
;
1280 remove_child (RhythmDBTreeProperty
*parent
,
1283 g_assert (g_hash_table_remove (parent
->children
, data
));
1284 if (g_hash_table_size (parent
->children
) <= 0) {
1291 remove_entry_from_album (RhythmDBTree
*db
,
1292 RhythmDBEntry
*entry
)
1296 rb_refstring_ref (entry
->genre
);
1297 rb_refstring_ref (entry
->artist
);
1298 rb_refstring_ref (entry
->album
);
1300 g_mutex_lock (db
->priv
->genres_lock
);
1301 table
= get_genres_hash_for_type (db
, entry
->type
);
1302 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
), entry
)) {
1303 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
,
1306 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
->parent
,
1308 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
->parent
);
1309 g_assert (g_hash_table_remove (table
, entry
->genre
));
1311 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
);
1314 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
));
1316 g_mutex_unlock (db
->priv
->genres_lock
);
1318 rb_refstring_unref (entry
->genre
);
1319 rb_refstring_unref (entry
->artist
);
1320 rb_refstring_unref (entry
->album
);
1324 rhythmdb_tree_entry_set (RhythmDB
*adb
,
1325 RhythmDBEntry
*entry
,
1327 const GValue
*value
)
1329 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1330 RhythmDBEntryType type
;
1334 /* don't process changes to entries we're loading, we'll get them
1335 * when the entry is complete.
1337 if (entry
->flags
& RHYTHMDB_ENTRY_TREE_LOADING
)
1340 /* Handle special properties */
1343 case RHYTHMDB_PROP_LOCATION
:
1346 /* We have to use the string in the entry itself as the hash key,
1347 * otherwise either we leak it, or the string vanishes when the
1348 * GValue is freed; this means we have to do the entry modification
1349 * here, rather than letting rhythmdb_entry_set_internal do it.
1351 g_mutex_lock (db
->priv
->entries_lock
);
1352 g_assert (g_hash_table_remove (db
->priv
->entries
, entry
->location
));
1354 s
= rb_refstring_new (g_value_get_string (value
));
1355 rb_refstring_unref (entry
->location
);
1356 entry
->location
= s
;
1357 g_hash_table_insert (db
->priv
->entries
, entry
->location
, entry
);
1358 g_mutex_unlock (db
->priv
->entries_lock
);
1362 case RHYTHMDB_PROP_ALBUM
:
1364 const char *albumname
= g_value_get_string (value
);
1366 if (strcmp (rb_refstring_get (entry
->album
), albumname
)) {
1367 RhythmDBTreeProperty
*artist
;
1368 RhythmDBTreeProperty
*genre
;
1370 rb_refstring_ref (entry
->genre
);
1371 rb_refstring_ref (entry
->artist
);
1372 rb_refstring_ref (entry
->album
);
1374 remove_entry_from_album (db
, entry
);
1376 genre
= get_or_create_genre (db
, type
, entry
->genre
);
1377 artist
= get_or_create_artist (db
, genre
, entry
->artist
);
1378 set_entry_album (db
, entry
, artist
, rb_refstring_new (albumname
));
1380 rb_refstring_unref (entry
->genre
);
1381 rb_refstring_unref (entry
->artist
);
1382 rb_refstring_unref (entry
->album
);
1386 case RHYTHMDB_PROP_ARTIST
:
1388 const char *artistname
= g_value_get_string (value
);
1390 if (strcmp (rb_refstring_get (entry
->artist
), artistname
)) {
1391 RhythmDBTreeProperty
*new_artist
;
1392 RhythmDBTreeProperty
*genre
;
1394 rb_refstring_ref (entry
->genre
);
1395 rb_refstring_ref (entry
->artist
);
1396 rb_refstring_ref (entry
->album
);
1398 remove_entry_from_album (db
, entry
);
1400 genre
= get_or_create_genre (db
, type
, entry
->genre
);
1401 new_artist
= get_or_create_artist (db
, genre
,
1402 rb_refstring_new (artistname
));
1403 set_entry_album (db
, entry
, new_artist
, entry
->album
);
1405 rb_refstring_unref (entry
->genre
);
1406 rb_refstring_unref (entry
->artist
);
1407 rb_refstring_unref (entry
->album
);
1411 case RHYTHMDB_PROP_GENRE
:
1413 const char *genrename
= g_value_get_string (value
);
1415 if (strcmp (rb_refstring_get (entry
->genre
), genrename
)) {
1416 RhythmDBTreeProperty
*new_genre
;
1417 RhythmDBTreeProperty
*new_artist
;
1419 rb_refstring_ref (entry
->genre
);
1420 rb_refstring_ref (entry
->artist
);
1421 rb_refstring_ref (entry
->album
);
1423 remove_entry_from_album (db
, entry
);
1425 new_genre
= get_or_create_genre (db
, type
,
1426 rb_refstring_new (genrename
));
1427 new_artist
= get_or_create_artist (db
, new_genre
, entry
->artist
);
1428 set_entry_album (db
, entry
, new_artist
, entry
->album
);
1430 rb_refstring_unref (entry
->genre
);
1431 rb_refstring_unref (entry
->artist
);
1432 rb_refstring_unref (entry
->album
);
1444 rhythmdb_tree_entry_delete (RhythmDB
*adb
,
1445 RhythmDBEntry
*entry
)
1447 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1449 remove_entry_from_album (db
, entry
);
1451 g_mutex_lock (db
->priv
->entries_lock
);
1452 g_assert (g_hash_table_remove (db
->priv
->entries
, entry
->location
));
1453 rhythmdb_entry_unref (entry
);
1454 g_mutex_unlock (db
->priv
->entries_lock
);
1459 RhythmDBEntryType type
;
1460 } RbEntryRemovalCtxt
;
1463 remove_one_song (gpointer key
,
1464 RhythmDBEntry
*entry
,
1465 RbEntryRemovalCtxt
*ctxt
)
1467 g_return_val_if_fail (entry
!= NULL
, FALSE
);
1469 if (entry
->type
== ctxt
->type
) {
1470 rhythmdb_emit_entry_deleted (ctxt
->db
, entry
);
1471 remove_entry_from_album (RHYTHMDB_TREE (ctxt
->db
), entry
);
1472 rhythmdb_entry_unref (entry
);
1479 rhythmdb_tree_entry_delete_by_type (RhythmDB
*adb
,
1480 RhythmDBEntryType type
)
1482 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1483 RbEntryRemovalCtxt ctxt
;
1487 g_mutex_lock (db
->priv
->entries_lock
);
1488 g_hash_table_foreach_remove (db
->priv
->entries
,
1489 (GHRFunc
) remove_one_song
, &ctxt
);
1490 g_mutex_unlock (db
->priv
->entries_lock
);
1494 destroy_tree_property (RhythmDBTreeProperty
*prop
)
1496 #ifndef G_DISABLE_ASSERT
1497 prop
->magic
= 0xf33df33d;
1499 g_hash_table_destroy (prop
->children
);
1503 typedef void (*RhythmDBTreeTraversalFunc
) (RhythmDBTree
*db
, RhythmDBEntry
*entry
, gpointer data
);
1504 typedef void (*RhythmDBTreeAlbumTraversalFunc
) (RhythmDBTree
*db
, RhythmDBTreeProperty
*album
, gpointer data
);
1506 struct RhythmDBTreeTraversalData
1510 RhythmDBTreeTraversalFunc func
;
1516 rhythmdb_tree_evaluate_query (RhythmDB
*adb
,
1518 RhythmDBEntry
*entry
)
1520 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1522 guint last_disjunction
;
1524 for (i
= 0, last_disjunction
= 0; i
< query
->len
; i
++) {
1525 RhythmDBQueryData
*data
= g_ptr_array_index (query
, i
);
1527 if (data
->type
== RHYTHMDB_QUERY_DISJUNCTION
) {
1528 if (evaluate_conjunctive_subquery (db
, query
, last_disjunction
, i
, entry
))
1531 last_disjunction
= i
+ 1;
1534 if (evaluate_conjunctive_subquery (db
, query
, last_disjunction
, query
->len
, entry
))
1539 #define RHYTHMDB_PROPERTY_COMPARE(OP) \
1540 switch (rhythmdb_get_property_type (db, data->propid)) { \
1541 case G_TYPE_STRING: \
1542 if (strcmp (rhythmdb_entry_get_string (entry, data->propid), \
1543 g_value_get_string (data->val)) OP 0) \
1546 case G_TYPE_ULONG: \
1547 if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1548 g_value_get_ulong (data->val)) \
1551 case G_TYPE_BOOLEAN: \
1552 if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1553 g_value_get_boolean (data->val)) \
1556 case G_TYPE_UINT64: \
1557 if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1558 g_value_get_uint64 (data->val)) \
1561 case G_TYPE_DOUBLE: \
1562 if (rhythmdb_entry_get_double (entry, data->propid) OP \
1563 g_value_get_double (data->val)) \
1566 case G_TYPE_POINTER: \
1567 if (rhythmdb_entry_get_pointer (entry, data->propid) OP \
1568 g_value_get_pointer (data->val)) \
1572 g_warning ("Unexpected type: %s", g_type_name (rhythmdb_get_property_type (db, data->propid))); \
1573 g_assert_not_reached (); \
1577 search_match_properties (RhythmDB
*db
,
1578 RhythmDBEntry
*entry
,
1581 const RhythmDBPropType props
[] = {
1582 RHYTHMDB_PROP_TITLE_FOLDED
,
1583 RHYTHMDB_PROP_ALBUM_FOLDED
,
1584 RHYTHMDB_PROP_ARTIST_FOLDED
,
1585 RHYTHMDB_PROP_GENRE_FOLDED
1587 gboolean islike
= TRUE
;
1591 for (current
= words
; *current
!= NULL
; current
++) {
1592 gboolean word_found
= FALSE
;
1594 for (i
= 0; i
< G_N_ELEMENTS (props
); i
++) {
1595 const char *entry_string
= rhythmdb_entry_get_string (entry
, props
[i
]);
1596 if (entry_string
&& (strstr (entry_string
, *current
) != NULL
)) {
1597 /* the word was found, go to the next one */
1603 /* the word wasn't in any of the properties*/
1613 evaluate_conjunctive_subquery (RhythmDBTree
*dbtree
,
1617 RhythmDBEntry
*entry
)
1620 RhythmDB
*db
= (RhythmDB
*) dbtree
;
1622 /* Optimization possibility - we may get here without actually having
1623 * anything in the query. It would be faster to instead just merge
1624 * the child hash table into the query result hash.
1626 for (i
= base
; i
< max
; i
++) {
1627 RhythmDBQueryData
*data
= g_ptr_array_index (query
, i
);
1629 switch (data
->type
) {
1630 case RHYTHMDB_QUERY_SUBQUERY
:
1632 gboolean matched
= FALSE
;
1633 GList
*conjunctions
= split_query_by_disjunctions (dbtree
, data
->subquery
);
1636 if (conjunctions
== NULL
)
1639 for (tem
= conjunctions
; tem
; tem
= tem
->next
) {
1640 GPtrArray
*subquery
= tem
->data
;
1641 if (!matched
&& evaluate_conjunctive_subquery (dbtree
, subquery
,
1646 g_ptr_array_free (tem
->data
, TRUE
);
1648 g_list_free (conjunctions
);
1653 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
:
1654 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN
:
1656 gulong relative_time
;
1657 GTimeVal current_time
;
1659 g_assert (rhythmdb_get_property_type (db
, data
->propid
) == G_TYPE_ULONG
);
1661 relative_time
= g_value_get_ulong (data
->val
);
1662 g_get_current_time (¤t_time
);
1664 if (data
->type
== RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
) {
1665 if (!(rhythmdb_entry_get_ulong (entry
, data
->propid
) >= (current_time
.tv_sec
- relative_time
)))
1668 if (!(rhythmdb_entry_get_ulong (entry
, data
->propid
) < (current_time
.tv_sec
- relative_time
)))
1673 case RHYTHMDB_QUERY_PROP_PREFIX
:
1674 case RHYTHMDB_QUERY_PROP_SUFFIX
:
1676 const char *value_s
;
1677 const char *entry_s
;
1679 g_assert (rhythmdb_get_property_type (db
, data
->propid
) == G_TYPE_STRING
);
1681 value_s
= g_value_get_string (data
->val
);
1682 entry_s
= rhythmdb_entry_get_string (entry
, data
->propid
);
1684 if (data
->type
== RHYTHMDB_QUERY_PROP_PREFIX
&& !g_str_has_prefix (entry_s
, value_s
))
1686 if (data
->type
== RHYTHMDB_QUERY_PROP_SUFFIX
&& !g_str_has_suffix (entry_s
, value_s
))
1691 case RHYTHMDB_QUERY_PROP_LIKE
:
1692 case RHYTHMDB_QUERY_PROP_NOT_LIKE
:
1694 if (rhythmdb_get_property_type (db
, data
->propid
) == G_TYPE_STRING
) {
1697 if (data
->propid
== RHYTHMDB_PROP_SEARCH_MATCH
) {
1698 /* this is a special property, that should match several things */
1699 islike
= search_match_properties (db
, entry
, g_value_get_boxed (data
->val
));
1702 const gchar
*value_string
= g_value_get_string (data
->val
);
1703 const char *entry_string
= rhythmdb_entry_get_string (entry
, data
->propid
);
1705 /* check in case the property is NULL, the value should never be NULL */
1706 if (entry_string
== NULL
)
1709 islike
= (strstr (entry_string
, value_string
) != NULL
);
1712 if ((data
->type
== RHYTHMDB_QUERY_PROP_LIKE
) ^ islike
)
1720 case RHYTHMDB_QUERY_PROP_EQUALS
:
1721 RHYTHMDB_PROPERTY_COMPARE (!=)
1723 case RHYTHMDB_QUERY_PROP_GREATER
:
1724 RHYTHMDB_PROPERTY_COMPARE (<)
1726 case RHYTHMDB_QUERY_PROP_LESS
:
1727 RHYTHMDB_PROPERTY_COMPARE (>)
1729 case RHYTHMDB_QUERY_END
:
1730 case RHYTHMDB_QUERY_DISJUNCTION
:
1731 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS
:
1732 case RHYTHMDB_QUERY_PROP_YEAR_LESS
:
1733 case RHYTHMDB_QUERY_PROP_YEAR_GREATER
:
1734 g_assert_not_reached ();
1742 do_conjunction (RhythmDBEntry
*entry
,
1744 struct RhythmDBTreeTraversalData
*data
)
1746 if (G_UNLIKELY (*data
->cancel
))
1748 /* Finally, we actually evaluate the query! */
1749 if (evaluate_conjunctive_subquery (data
->db
, data
->query
, 0, data
->query
->len
,
1751 data
->func (data
->db
, entry
, data
->data
);
1756 conjunctive_query_songs (const char *name
,
1757 RhythmDBTreeProperty
*album
,
1758 struct RhythmDBTreeTraversalData
*data
)
1760 if (G_UNLIKELY (*data
->cancel
))
1762 g_hash_table_foreach (album
->children
, (GHFunc
) do_conjunction
, data
);
1766 clone_remove_ptr_array_index (GPtrArray
*arr
,
1769 GPtrArray
*ret
= g_ptr_array_new ();
1771 for (i
= 0; i
< arr
->len
; i
++)
1773 g_ptr_array_add (ret
, g_ptr_array_index (arr
, i
));
1779 conjunctive_query_albums (const char *name
,
1780 RhythmDBTreeProperty
*artist
,
1781 struct RhythmDBTreeTraversalData
*data
)
1784 int album_query_idx
= -1;
1786 if (G_UNLIKELY (*data
->cancel
))
1789 for (i
= 0; i
< data
->query
->len
; i
++) {
1790 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, i
);
1791 if (qdata
->type
== RHYTHMDB_QUERY_PROP_EQUALS
1792 && qdata
->propid
== RHYTHMDB_PROP_ALBUM
) {
1793 if (album_query_idx
> 0)
1795 album_query_idx
= i
;
1800 if (album_query_idx
>= 0) {
1801 RhythmDBTreeProperty
*album
;
1802 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, album_query_idx
);
1803 RBRefString
*albumname
= rb_refstring_new (g_value_get_string (qdata
->val
));
1804 GPtrArray
*oldquery
= data
->query
;
1806 data
->query
= clone_remove_ptr_array_index (data
->query
, album_query_idx
);
1808 album
= g_hash_table_lookup (artist
->children
, albumname
);
1810 if (album
!= NULL
) {
1811 conjunctive_query_songs (rb_refstring_get (albumname
), album
, data
);
1813 g_ptr_array_free (data
->query
, TRUE
);
1814 data
->query
= oldquery
;
1818 g_hash_table_foreach (artist
->children
, (GHFunc
) conjunctive_query_songs
, data
);
1822 conjunctive_query_artists (const char *name
,
1823 RhythmDBTreeProperty
*genre
,
1824 struct RhythmDBTreeTraversalData
*data
)
1827 int artist_query_idx
= -1;
1829 if (G_UNLIKELY (*data
->cancel
))
1832 for (i
= 0; i
< data
->query
->len
; i
++) {
1833 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, i
);
1834 if (qdata
->type
== RHYTHMDB_QUERY_PROP_EQUALS
1835 && qdata
->propid
== RHYTHMDB_PROP_ARTIST
) {
1836 if (artist_query_idx
> 0)
1838 artist_query_idx
= i
;
1843 if (artist_query_idx
>= 0) {
1844 RhythmDBTreeProperty
*artist
;
1845 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, artist_query_idx
);
1846 RBRefString
*artistname
= rb_refstring_new (g_value_get_string (qdata
->val
));
1847 GPtrArray
*oldquery
= data
->query
;
1849 data
->query
= clone_remove_ptr_array_index (data
->query
, artist_query_idx
);
1851 artist
= g_hash_table_lookup (genre
->children
, artistname
);
1852 if (artist
!= NULL
) {
1853 conjunctive_query_albums (rb_refstring_get (artistname
), artist
, data
);
1855 g_ptr_array_free (data
->query
, TRUE
);
1856 data
->query
= oldquery
;
1860 g_hash_table_foreach (genre
->children
, (GHFunc
) conjunctive_query_albums
, data
);
1864 conjunctive_query_genre (RhythmDBTree
*db
,
1866 struct RhythmDBTreeTraversalData
*data
)
1868 int genre_query_idx
= -1;
1871 if (G_UNLIKELY (*data
->cancel
))
1874 for (i
= 0; i
< data
->query
->len
; i
++) {
1875 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, i
);
1876 if (qdata
->type
== RHYTHMDB_QUERY_PROP_EQUALS
1877 && qdata
->propid
== RHYTHMDB_PROP_GENRE
) {
1878 /* A song can't currently have two genres. So
1879 * if we get a conjunctive query for that, we
1880 * know the result must be the empty set. */
1881 if (genre_query_idx
> 0)
1883 genre_query_idx
= i
;
1888 if (genre_query_idx
>= 0) {
1889 RhythmDBTreeProperty
*genre
;
1890 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, genre_query_idx
);
1891 RBRefString
*genrename
= rb_refstring_new (g_value_get_string (qdata
->val
));
1892 GPtrArray
*oldquery
= data
->query
;
1894 data
->query
= clone_remove_ptr_array_index (data
->query
, genre_query_idx
);
1896 genre
= g_hash_table_lookup (genres
, genrename
);
1897 if (genre
!= NULL
) {
1898 conjunctive_query_artists (rb_refstring_get (genrename
), genre
, data
);
1900 g_ptr_array_free (data
->query
, TRUE
);
1901 data
->query
= oldquery
;
1905 g_hash_table_foreach (genres
, (GHFunc
) conjunctive_query_artists
, data
);
1909 conjunctive_query (RhythmDBTree
*db
,
1911 RhythmDBTreeTraversalFunc func
,
1915 int type_query_idx
= -1;
1917 struct RhythmDBTreeTraversalData
*traversal_data
;
1919 for (i
= 0; i
< query
->len
; i
++) {
1920 RhythmDBQueryData
*qdata
= g_ptr_array_index (query
, i
);
1921 if (qdata
->type
== RHYTHMDB_QUERY_PROP_EQUALS
1922 && qdata
->propid
== RHYTHMDB_PROP_TYPE
) {
1923 /* A song can't have two types. */
1924 if (type_query_idx
> 0)
1930 traversal_data
= g_new (struct RhythmDBTreeTraversalData
, 1);
1931 traversal_data
->db
= db
;
1932 traversal_data
->query
= query
;
1933 traversal_data
->func
= func
;
1934 traversal_data
->data
= data
;
1935 traversal_data
->cancel
= cancel
;
1937 g_mutex_lock (db
->priv
->genres_lock
);
1938 if (type_query_idx
>= 0) {
1940 RhythmDBEntryType etype
;
1941 RhythmDBQueryData
*qdata
= g_ptr_array_index (query
, type_query_idx
);
1943 g_ptr_array_remove_index_fast (query
, type_query_idx
);
1945 etype
= g_value_get_pointer (qdata
->val
);
1946 genres
= get_genres_hash_for_type (db
, etype
);
1947 if (genres
!= NULL
) {
1948 conjunctive_query_genre (db
, genres
, traversal_data
);
1950 g_assert_not_reached ();
1954 /* No type was given; punt and query everything */
1955 genres_hash_foreach (db
, (RBHFunc
)conjunctive_query_genre
,
1958 g_mutex_unlock (db
->priv
->genres_lock
);
1960 g_free (traversal_data
);
1964 split_query_by_disjunctions (RhythmDBTree
*db
,
1967 GList
*conjunctions
= NULL
;
1969 guint last_disjunction
= 0;
1970 GPtrArray
*subquery
= g_ptr_array_new ();
1972 for (i
= 0; i
< query
->len
; i
++) {
1973 RhythmDBQueryData
*data
= g_ptr_array_index (query
, i
);
1974 if (data
->type
== RHYTHMDB_QUERY_DISJUNCTION
) {
1976 /* Copy the subquery */
1977 for (j
= last_disjunction
; j
< i
; j
++) {
1978 g_ptr_array_add (subquery
, g_ptr_array_index (query
, j
));
1981 conjunctions
= g_list_prepend (conjunctions
, subquery
);
1982 last_disjunction
= i
+1;
1983 g_assert (subquery
->len
> 0);
1984 subquery
= g_ptr_array_new ();
1988 /* Copy the last subquery, except for the QUERY_END */
1989 for (i
= last_disjunction
; i
< query
->len
; i
++) {
1990 g_ptr_array_add (subquery
, g_ptr_array_index (query
, i
));
1993 if (subquery
->len
> 0)
1994 conjunctions
= g_list_prepend (conjunctions
, subquery
);
1996 g_ptr_array_free (subquery
, TRUE
);
1998 return conjunctions
;
2001 struct RhythmDBTreeQueryGatheringData
2005 GHashTable
*entries
;
2006 RhythmDBQueryResults
*results
;
2010 do_query_recurse (RhythmDBTree
*db
,
2012 RhythmDBTreeTraversalFunc func
,
2013 struct RhythmDBTreeQueryGatheringData
*data
,
2016 GList
*conjunctions
, *tem
;
2021 conjunctions
= split_query_by_disjunctions (db
, query
);
2022 rb_debug ("doing recursive query, %d conjunctions", g_list_length (conjunctions
));
2024 if (conjunctions
== NULL
)
2027 /* If there is a disjunction involved, we must uniquify the entry hits. */
2028 if (conjunctions
->next
!= NULL
)
2029 data
->entries
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
2031 data
->entries
= NULL
;
2033 for (tem
= conjunctions
; tem
; tem
= tem
->next
) {
2034 if (G_UNLIKELY (*cancel
))
2036 conjunctive_query (db
, tem
->data
, func
, data
, cancel
);
2037 g_ptr_array_free (tem
->data
, TRUE
);
2040 if (data
->entries
!= NULL
)
2041 g_hash_table_destroy (data
->entries
);
2043 g_list_free (conjunctions
);
2047 handle_entry_match (RhythmDB
*db
,
2048 RhythmDBEntry
*entry
,
2049 struct RhythmDBTreeQueryGatheringData
*data
)
2053 && g_hash_table_lookup (data
->entries
, entry
))
2056 g_ptr_array_add (data
->queue
, entry
);
2057 if (data
->queue
->len
> RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK
) {
2058 rhythmdb_query_results_add_results (data
->results
, data
->queue
);
2059 data
->queue
= g_ptr_array_new ();
2064 rhythmdb_tree_do_full_query (RhythmDB
*adb
,
2066 RhythmDBQueryResults
*results
,
2069 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
2070 struct RhythmDBTreeQueryGatheringData
*data
= g_new0 (struct RhythmDBTreeQueryGatheringData
, 1);
2072 data
->results
= results
;
2073 data
->queue
= g_ptr_array_new ();
2075 do_query_recurse (db
, query
, (RhythmDBTreeTraversalFunc
) handle_entry_match
, data
, cancel
);
2077 rhythmdb_query_results_add_results (data
->results
, data
->queue
);
2082 static RhythmDBEntry
*
2083 rhythmdb_tree_entry_lookup_by_location (RhythmDB
*adb
,
2086 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
2087 RhythmDBEntry
*entry
;
2089 g_mutex_lock (db
->priv
->entries_lock
);
2090 entry
= g_hash_table_lookup (db
->priv
->entries
, uri
);
2091 g_mutex_unlock (db
->priv
->entries_lock
);
2096 struct RhythmDBEntryForeachCtxt
2104 rhythmdb_tree_entry_foreach_func (gpointer key
, RhythmDBEntry
*val
, GPtrArray
*list
)
2106 rhythmdb_entry_ref (val
);
2107 g_ptr_array_add (list
, val
);
2111 rhythmdb_tree_entry_foreach (RhythmDB
*rdb
, GFunc foreach_func
, gpointer user_data
)
2113 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
2117 g_mutex_lock (db
->priv
->entries_lock
);
2118 size
= g_hash_table_size (db
->priv
->entries
);
2119 list
= g_ptr_array_sized_new (size
);
2120 g_hash_table_foreach (db
->priv
->entries
, (GHFunc
)rhythmdb_tree_entry_foreach_func
, list
);
2121 g_mutex_unlock (db
->priv
->entries_lock
);
2123 for (i
= 0; i
< size
; i
++) {
2124 RhythmDBEntry
*entry
= (RhythmDBEntry
*)g_ptr_array_index (list
, i
);
2125 (*foreach_func
) (entry
, user_data
);
2126 rhythmdb_entry_unref (entry
);
2129 g_ptr_array_free (list
, TRUE
);
2133 struct HashTreeIteratorCtxt
{
2135 RBTreeEntryItFunc entry_func
;
2136 RBTreePropertyItFunc album_func
;
2137 RBTreePropertyItFunc artist_func
;
2138 RBTreePropertyItFunc genres_func
;
2143 hash_tree_entries_foreach (gpointer key
,
2147 RhythmDBEntry
*entry
= (RhythmDBEntry
*) key
;
2148 struct HashTreeIteratorCtxt
*ctxt
= (struct HashTreeIteratorCtxt
*)data
;
2150 g_assert (ctxt
->entry_func
);
2152 ctxt
->entry_func (ctxt
->db
, entry
, ctxt
->data
);
2156 hash_tree_albums_foreach (gpointer key
,
2160 RhythmDBTreeProperty
*album
= (RhythmDBTreeProperty
*)value
;
2161 struct HashTreeIteratorCtxt
*ctxt
= (struct HashTreeIteratorCtxt
*)data
;
2163 if (ctxt
->album_func
) {
2164 ctxt
->album_func (ctxt
->db
, album
, ctxt
->data
);
2166 if (ctxt
->entry_func
!= NULL
) {
2167 g_hash_table_foreach (album
->children
,
2168 hash_tree_entries_foreach
,
2174 hash_tree_artists_foreach (gpointer key
,
2178 RhythmDBTreeProperty
*artist
= (RhythmDBTreeProperty
*)value
;
2179 struct HashTreeIteratorCtxt
*ctxt
= (struct HashTreeIteratorCtxt
*)data
;
2181 if (ctxt
->artist_func
) {
2182 ctxt
->artist_func (ctxt
->db
, artist
, ctxt
->data
);
2184 if ((ctxt
->album_func
!= NULL
) || (ctxt
->entry_func
!= NULL
)) {
2185 g_hash_table_foreach (artist
->children
,
2186 hash_tree_albums_foreach
,
2192 hash_tree_genres_foreach (gpointer key
,
2196 RhythmDBTreeProperty
*genre
= (RhythmDBTreeProperty
*)value
;
2197 struct HashTreeIteratorCtxt
*ctxt
= (struct HashTreeIteratorCtxt
*)data
;
2199 if (ctxt
->genres_func
) {
2200 ctxt
->genres_func (ctxt
->db
, genre
, ctxt
->data
);
2203 if ((ctxt
->album_func
!= NULL
)
2204 || (ctxt
->artist_func
!= NULL
)
2205 || (ctxt
->entry_func
!= NULL
)) {
2206 g_hash_table_foreach (genre
->children
,
2207 hash_tree_artists_foreach
,
2213 rhythmdb_hash_tree_foreach (RhythmDB
*adb
,
2214 RhythmDBEntryType type
,
2215 RBTreeEntryItFunc entry_func
,
2216 RBTreePropertyItFunc album_func
,
2217 RBTreePropertyItFunc artist_func
,
2218 RBTreePropertyItFunc genres_func
,
2221 struct HashTreeIteratorCtxt ctxt
;
2224 ctxt
.db
= RHYTHMDB_TREE (adb
);
2225 ctxt
.album_func
= album_func
;
2226 ctxt
.artist_func
= artist_func
;
2227 ctxt
.genres_func
= genres_func
;
2228 ctxt
.entry_func
= entry_func
;
2231 g_mutex_lock (ctxt
.db
->priv
->genres_lock
);
2232 table
= get_genres_hash_for_type (RHYTHMDB_TREE (adb
), type
);
2233 if (table
== NULL
) {
2236 if ((ctxt
.album_func
!= NULL
)
2237 || (ctxt
.artist_func
!= NULL
)
2238 || (ctxt
.genres_func
!= NULL
)
2239 || (ctxt
.entry_func
!= NULL
)) {
2240 g_hash_table_foreach (table
, hash_tree_genres_foreach
, &ctxt
);
2242 g_mutex_unlock (ctxt
.db
->priv
->genres_lock
);
2246 rhythmdb_tree_entry_type_registered (RhythmDB
*db
,
2248 RhythmDBEntryType entry_type
)
2250 GList
*entries
= NULL
;
2254 RBRefString
*rs_name
;
2259 rdb
= RHYTHMDB_TREE (db
);
2260 rs_name
= rb_refstring_find (name
);
2262 entries
= g_hash_table_lookup (rdb
->priv
->unknown_entry_types
, rs_name
);
2263 if (entries
== NULL
) {
2264 rb_refstring_unref (rs_name
);
2265 rb_debug ("no entries of newly registered type %s loaded from db", name
);
2269 for (e
= entries
; e
!= NULL
; e
= e
->next
) {
2270 RhythmDBUnknownEntry
*data
;
2271 RhythmDBEntry
*entry
;
2274 data
= (RhythmDBUnknownEntry
*)e
->data
;
2275 entry
= rhythmdb_entry_allocate (db
, entry_type
);
2276 entry
->flags
|= RHYTHMDB_ENTRY_TREE_LOADING
;
2277 for (p
= data
->properties
; p
!= NULL
; p
= p
->next
) {
2278 RhythmDBUnknownEntryProperty
*prop
;
2279 RhythmDBPropType propid
;
2280 GValue value
= {0,};
2282 prop
= (RhythmDBUnknownEntryProperty
*) p
->data
;
2283 propid
= rhythmdb_propid_from_nice_elt_name (db
, (const xmlChar
*) rb_refstring_get (prop
->name
));
2285 rhythmdb_read_encoded_property (db
, rb_refstring_get (prop
->value
), propid
, &value
);
2286 rhythmdb_entry_set_internal (db
, entry
, FALSE
, propid
, &value
);
2287 g_value_unset (&value
);
2289 rhythmdb_tree_entry_new_internal (db
, entry
);
2290 rhythmdb_entry_insert (db
, entry
);
2293 rb_debug ("handled %d entries of newly registered type %s", count
, name
);
2294 rhythmdb_commit (db
);
2296 g_hash_table_remove (rdb
->priv
->unknown_entry_types
, rs_name
);
2297 free_unknown_entries (rs_name
, entries
, NULL
);
2298 rb_refstring_unref (rs_name
);