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 RhythmDBEntry
* rhythmdb_tree_entry_lookup_by_id (RhythmDB
*db
, gint id
);
78 static void rhythmdb_tree_entry_foreach (RhythmDB
*adb
, GFunc func
, gpointer user_data
);
79 static gint64
rhythmdb_tree_entry_count (RhythmDB
*adb
);
80 static void rhythmdb_tree_entry_foreach_by_type (RhythmDB
*adb
, RhythmDBEntryType type
, GFunc func
, gpointer user_data
);
81 static gint64
rhythmdb_tree_entry_count_by_type (RhythmDB
*adb
, RhythmDBEntryType type
);
82 static void rhythmdb_tree_do_full_query (RhythmDB
*db
, GPtrArray
*query
,
83 RhythmDBQueryResults
*results
,
85 static gboolean
rhythmdb_tree_evaluate_query (RhythmDB
*adb
, GPtrArray
*query
,
86 RhythmDBEntry
*aentry
);
87 static void rhythmdb_tree_entry_type_registered (RhythmDB
*db
,
89 RhythmDBEntryType type
);
91 typedef void (*RBTreeEntryItFunc
)(RhythmDBTree
*db
,
95 typedef void (*RBTreePropertyItFunc
)(RhythmDBTree
*db
,
96 RhythmDBTreeProperty
*property
,
98 static void rhythmdb_hash_tree_foreach (RhythmDB
*adb
,
99 RhythmDBEntryType type
,
100 RBTreeEntryItFunc entry_func
,
101 RBTreePropertyItFunc album_func
,
102 RBTreePropertyItFunc artist_func
,
103 RBTreePropertyItFunc genres_func
,
106 #define RHYTHMDB_TREE_XML_VERSION "1.3"
108 static void destroy_tree_property (RhythmDBTreeProperty
*prop
);
109 static RhythmDBTreeProperty
*get_or_create_album (RhythmDBTree
*db
, RhythmDBTreeProperty
*artist
,
111 static RhythmDBTreeProperty
*get_or_create_artist (RhythmDBTree
*db
, RhythmDBTreeProperty
*genre
,
113 static RhythmDBTreeProperty
*get_or_create_genre (RhythmDBTree
*db
, RhythmDBEntryType type
,
116 static void remove_entry_from_album (RhythmDBTree
*db
, RhythmDBEntry
*entry
);
118 static GList
*split_query_by_disjunctions (RhythmDBTree
*db
, GPtrArray
*query
);
119 static gboolean
evaluate_conjunctive_subquery (RhythmDBTree
*db
, GPtrArray
*query
,
120 guint base
, guint max
, RhythmDBEntry
*entry
);
122 struct RhythmDBTreePrivate
125 GHashTable
*entry_ids
;
126 GMutex
*entries_lock
;
129 GHashTable
*unknown_entry_types
;
140 } RhythmDBUnknownEntryProperty
;
144 RBRefString
*typename
;
146 } RhythmDBUnknownEntry
;
148 #define RHYTHMDB_TREE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_TREE, RhythmDBTreePrivate))
155 const int RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE
= 512;
158 rhythmdb_tree_error_quark (void)
162 quark
= g_quark_from_static_string ("rhythmdb_tree_error");
168 rhythmdb_tree_class_init (RhythmDBTreeClass
*klass
)
170 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
171 RhythmDBClass
*rhythmdb_class
= RHYTHMDB_CLASS (klass
);
173 object_class
->finalize
= rhythmdb_tree_finalize
;
175 rhythmdb_class
->impl_load
= rhythmdb_tree_load
;
176 rhythmdb_class
->impl_save
= rhythmdb_tree_save
;
177 rhythmdb_class
->impl_entry_new
= rhythmdb_tree_entry_new
;
178 rhythmdb_class
->impl_entry_set
= rhythmdb_tree_entry_set
;
179 rhythmdb_class
->impl_entry_delete
= rhythmdb_tree_entry_delete
;
180 rhythmdb_class
->impl_entry_delete_by_type
= rhythmdb_tree_entry_delete_by_type
;
181 rhythmdb_class
->impl_lookup_by_location
= rhythmdb_tree_entry_lookup_by_location
;
182 rhythmdb_class
->impl_lookup_by_id
= rhythmdb_tree_entry_lookup_by_id
;
183 rhythmdb_class
->impl_entry_foreach
= rhythmdb_tree_entry_foreach
;
184 rhythmdb_class
->impl_entry_count
= rhythmdb_tree_entry_count
;
185 rhythmdb_class
->impl_entry_foreach_by_type
= rhythmdb_tree_entry_foreach_by_type
;
186 rhythmdb_class
->impl_entry_count_by_type
= rhythmdb_tree_entry_count_by_type
;
187 rhythmdb_class
->impl_evaluate_query
= rhythmdb_tree_evaluate_query
;
188 rhythmdb_class
->impl_do_full_query
= rhythmdb_tree_do_full_query
;
189 rhythmdb_class
->impl_entry_type_registered
= rhythmdb_tree_entry_type_registered
;
191 g_type_class_add_private (klass
, sizeof (RhythmDBTreePrivate
));
195 rhythmdb_tree_init (RhythmDBTree
*db
)
197 db
->priv
= RHYTHMDB_TREE_GET_PRIVATE (db
);
199 db
->priv
->entries
= g_hash_table_new (rb_refstring_hash
, rb_refstring_equal
);
201 db
->priv
->entry_ids
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
203 db
->priv
->genres
= g_hash_table_new_full (g_direct_hash
, g_direct_equal
,
204 NULL
, (GDestroyNotify
)g_hash_table_destroy
);
205 db
->priv
->unknown_entry_types
= g_hash_table_new (rb_refstring_hash
, rb_refstring_equal
);
207 db
->priv
->entries_lock
= g_mutex_new();
208 db
->priv
->genres_lock
= g_mutex_new();
212 unparent_entries (gpointer key
,
213 RhythmDBEntry
*entry
,
216 remove_entry_from_album (db
, entry
);
220 free_unknown_entries (RBRefString
*name
,
225 for (e
= entries
; e
!= NULL
; e
= e
->next
) {
226 RhythmDBUnknownEntry
*entry
;
229 entry
= (RhythmDBUnknownEntry
*)e
->data
;
230 rb_refstring_unref (entry
->typename
);
231 for (p
= entry
->properties
; p
!= NULL
; p
= p
->next
) {
232 RhythmDBUnknownEntryProperty
*prop
;
234 prop
= (RhythmDBUnknownEntryProperty
*)p
->data
;
235 rb_refstring_unref (prop
->name
);
236 rb_refstring_unref (prop
->value
);
240 g_list_free (entry
->properties
);
242 g_list_free (entries
);
246 rhythmdb_tree_finalize (GObject
*object
)
250 g_return_if_fail (object
!= NULL
);
251 g_return_if_fail (RHYTHMDB_IS_TREE (object
));
253 db
= RHYTHMDB_TREE (object
);
255 g_return_if_fail (db
->priv
!= NULL
);
257 db
->priv
->finalizing
= TRUE
;
259 g_hash_table_foreach (db
->priv
->entries
, (GHFunc
) unparent_entries
, db
);
260 g_hash_table_destroy (db
->priv
->entries
);
261 g_hash_table_destroy (db
->priv
->entry_ids
);
262 g_mutex_free (db
->priv
->entries_lock
);
264 g_hash_table_destroy (db
->priv
->genres
);
265 g_mutex_free (db
->priv
->genres_lock
);
267 g_hash_table_foreach (db
->priv
->unknown_entry_types
,
268 (GHFunc
) free_unknown_entries
,
270 g_hash_table_destroy (db
->priv
->unknown_entry_types
);
272 G_OBJECT_CLASS (rhythmdb_tree_parent_class
)->finalize (object
);
275 struct RhythmDBTreeLoadContext
278 xmlParserCtxtPtr xmlctx
;
281 RHYTHMDB_TREE_PARSER_STATE_START
,
282 RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
,
283 RHYTHMDB_TREE_PARSER_STATE_ENTRY
,
284 RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
,
285 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
,
286 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
,
287 RHYTHMDB_TREE_PARSER_STATE_END
,
289 guint in_unknown_elt
;
290 RhythmDBEntry
*entry
;
291 RhythmDBUnknownEntry
*unknown_entry
;
293 RhythmDBPropType propid
;
299 gboolean canonicalise_uris
;
300 gboolean reload_all_metadata
;
304 rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext
*ctx
,
308 if (*ctx
->die
== TRUE
) {
309 xmlStopParser (ctx
->xmlctx
);
313 if (ctx
->in_unknown_elt
) {
314 ctx
->in_unknown_elt
++;
320 case RHYTHMDB_TREE_PARSER_STATE_START
:
322 if (!strcmp (name
, "rhythmdb")) {
323 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
;
324 for (; *attrs
; attrs
+=2) {
325 if (!strcmp (*attrs
, "version")) {
326 const char *version
= *(attrs
+1);
328 if (!strcmp (version
, "1.0") || !strcmp (version
, "1.1")) {
329 ctx
->canonicalise_uris
= TRUE
;
330 rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries");
331 } else if (!strcmp (version
, "1.2")) {
333 rb_debug ("reloading all file metadata to get MusicBrainz tags");
334 ctx
->reload_all_metadata
= TRUE
;
335 } else if (!strcmp (version
, "1.3")) {
338 g_set_error (ctx
->error
,
340 RHYTHMDB_TREE_ERROR_DATABASE_TOO_NEW
,
341 _("The database was created by a later version of rhythmbox."
342 " This version of rhythmbox cannot read the database."));
343 xmlStopParser (ctx
->xmlctx
);
346 g_assert_not_reached ();
351 ctx
->in_unknown_elt
++;
356 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
:
358 if (!strcmp (name
, "entry")) {
359 RhythmDBEntryType type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
360 const char *typename
= NULL
;
361 for (; *attrs
; attrs
+=2) {
362 if (!strcmp (*attrs
, "type")) {
363 typename
= *(attrs
+1);
364 type
= rhythmdb_entry_type_get_by_name (RHYTHMDB (ctx
->db
), typename
);
370 if (type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) {
371 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY
;
372 ctx
->entry
= rhythmdb_entry_allocate (RHYTHMDB (ctx
->db
), type
);
373 ctx
->entry
->flags
|= RHYTHMDB_ENTRY_TREE_LOADING
;
374 ctx
->has_date
= FALSE
;
376 rb_debug ("reading unknown entry");
377 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
;
378 ctx
->unknown_entry
= g_new0 (RhythmDBUnknownEntry
, 1);
379 ctx
->unknown_entry
->typename
= rb_refstring_new (typename
);
382 ctx
->in_unknown_elt
++;
386 case RHYTHMDB_TREE_PARSER_STATE_ENTRY
:
388 int val
= rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx
->db
), BAD_CAST name
);
390 ctx
->in_unknown_elt
++;
394 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
;
396 g_string_truncate (ctx
->buf
, 0);
399 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
:
401 RhythmDBUnknownEntryProperty
*prop
;
403 prop
= g_new0 (RhythmDBUnknownEntryProperty
, 1);
404 prop
->name
= rb_refstring_new (name
);
406 ctx
->unknown_entry
->properties
= g_list_prepend (ctx
->unknown_entry
->properties
, prop
);
407 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
;
408 g_string_truncate (ctx
->buf
, 0);
411 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
:
412 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
413 case RHYTHMDB_TREE_PARSER_STATE_END
:
419 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext
*ctx
,
422 if (*ctx
->die
== TRUE
) {
423 xmlStopParser (ctx
->xmlctx
);
427 if (ctx
->in_unknown_elt
) {
428 ctx
->in_unknown_elt
--;
434 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
:
435 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_END
;
437 case RHYTHMDB_TREE_PARSER_STATE_ENTRY
:
439 if (!ctx
->has_date
| ctx
->reload_all_metadata
) {
440 /* there is no date metadata, so this is from an old version
441 * reset the last-modified timestamp, so that the file is re-read
443 rb_debug ("pre-Date entry found, causing re-read");
444 ctx
->entry
->mtime
= 0;
446 if (ctx
->entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
) {
447 RhythmDBPodcastFields
*podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (ctx
->entry
, RhythmDBPodcastFields
);
448 /* Handle upgrades from 0.9.2.
449 * Previously, last-seen for podcast feeds was the time of the last post,
450 * and post-time was unused. Now, we want last-seen to be the time we
451 * last updated the feed, and post-time to be the time of the last post.
453 if (podcast
->post_time
== 0) {
454 podcast
->post_time
= ctx
->entry
->last_seen
;
458 if (ctx
->entry
->location
!= NULL
) {
459 RhythmDBEntry
*entry
;
461 g_mutex_lock (ctx
->db
->priv
->entries_lock
);
462 entry
= g_hash_table_lookup (ctx
->db
->priv
->entries
, ctx
->entry
->location
);
464 rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx
->db
), ctx
->entry
);
465 rhythmdb_entry_insert (RHYTHMDB (ctx
->db
), ctx
->entry
);
466 if (++ctx
->batch_count
== RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK
) {
467 rhythmdb_commit (RHYTHMDB (ctx
->db
));
468 ctx
->batch_count
= 0;
471 rb_debug ("found entry with duplicate location %s. merging metadata",
472 rb_refstring_get (ctx
->entry
->location
));
473 entry
->play_count
+= ctx
->entry
->play_count
;
475 if (entry
->rating
< 0.01)
476 entry
->rating
= ctx
->entry
->rating
;
477 else if (ctx
->entry
->rating
> 0.01)
478 entry
->rating
= (entry
->rating
+ ctx
->entry
->rating
) / 2;
480 if (ctx
->entry
->last_played
> entry
->last_played
)
481 entry
->last_played
= ctx
->entry
->last_played
;
483 if (ctx
->entry
->first_seen
< entry
->first_seen
)
484 entry
->first_seen
= ctx
->entry
->first_seen
;
486 if (ctx
->entry
->last_seen
> entry
->last_seen
)
487 entry
->last_seen
= ctx
->entry
->last_seen
;
489 rhythmdb_entry_unref (ctx
->entry
);
491 g_mutex_unlock (ctx
->db
->priv
->entries_lock
);
493 rb_debug ("found entry without location");
494 rhythmdb_entry_unref (ctx
->entry
);
496 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
;
500 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
:
504 rb_debug ("finished reading unknown entry");
505 ctx
->unknown_entry
->properties
= g_list_reverse (ctx
->unknown_entry
->properties
);
507 entry_list
= g_hash_table_lookup (ctx
->db
->priv
->unknown_entry_types
, ctx
->unknown_entry
->typename
);
508 entry_list
= g_list_prepend (entry_list
, ctx
->unknown_entry
);
509 g_hash_table_insert (ctx
->db
->priv
->unknown_entry_types
, ctx
->unknown_entry
->typename
, entry_list
);
511 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
;
512 ctx
->unknown_entry
= NULL
;
515 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
518 gboolean set
= FALSE
;
519 gboolean skip
= FALSE
;
521 /* special case some properties for upgrade handling etc. */
522 switch (ctx
->propid
) {
523 case RHYTHMDB_PROP_DATE
:
524 ctx
->has_date
= TRUE
;
526 case RHYTHMDB_PROP_LOCATION
:
527 if (ctx
->canonicalise_uris
) {
528 char *canon
= rb_canonicalise_uri (ctx
->buf
->str
);
530 g_value_init (&value
, G_TYPE_STRING
);
531 g_value_take_string (&value
, canon
);
535 case RHYTHMDB_PROP_MOUNTPOINT
:
536 /* fix old podcast posts */
537 if (g_str_has_prefix (ctx
->buf
->str
, "http://"))
546 rhythmdb_read_encoded_property (RHYTHMDB (ctx
->db
), ctx
->buf
->str
, ctx
->propid
, &value
);
549 rhythmdb_entry_set_internal (RHYTHMDB (ctx
->db
), ctx
->entry
, FALSE
, ctx
->propid
, &value
);
550 g_value_unset (&value
);
553 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_ENTRY
;
556 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
:
558 RhythmDBUnknownEntryProperty
*prop
;
560 g_assert (ctx
->unknown_entry
->properties
);
561 prop
= ctx
->unknown_entry
->properties
->data
;
562 g_assert (prop
->value
== NULL
);
563 prop
->value
= rb_refstring_new (ctx
->buf
->str
);
564 rb_debug ("unknown entry property: %s = %s", rb_refstring_get (prop
->name
), rb_refstring_get (prop
->value
));
566 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
;
569 case RHYTHMDB_TREE_PARSER_STATE_START
:
570 case RHYTHMDB_TREE_PARSER_STATE_END
:
576 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext
*ctx
,
580 if (*ctx
->die
== TRUE
) {
581 xmlStopParser (ctx
->xmlctx
);
587 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY
:
588 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY
:
589 g_string_append_len (ctx
->buf
, data
, len
);
591 case RHYTHMDB_TREE_PARSER_STATE_ENTRY
:
592 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY
:
593 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB
:
594 case RHYTHMDB_TREE_PARSER_STATE_START
:
595 case RHYTHMDB_TREE_PARSER_STATE_END
:
601 rhythmdb_tree_load (RhythmDB
*rdb
,
605 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
606 xmlParserCtxtPtr ctxt
;
607 xmlSAXHandlerPtr sax_handler
;
608 struct RhythmDBTreeLoadContext
*ctx
;
615 sax_handler
= g_new0 (xmlSAXHandler
, 1);
616 ctx
= g_new0 (struct RhythmDBTreeLoadContext
, 1);
618 sax_handler
->startElement
= (startElementSAXFunc
) rhythmdb_tree_parser_start_element
;
619 sax_handler
->endElement
= (endElementSAXFunc
) rhythmdb_tree_parser_end_element
;
620 sax_handler
->characters
= (charactersSAXFunc
) rhythmdb_tree_parser_characters
;
622 ctx
->state
= RHYTHMDB_TREE_PARSER_STATE_START
;
625 ctx
->buf
= g_string_sized_new (RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE
);
626 ctx
->error
= &local_error
;
628 g_object_get (G_OBJECT (db
), "name", &name
, NULL
);
630 if (g_file_test (name
, G_FILE_TEST_EXISTS
)) {
631 ctxt
= xmlCreateFileParserCtxt (name
);
634 ctxt
->userData
= ctx
;
635 ctxt
->sax
= sax_handler
;
636 xmlParseDocument (ctxt
);
638 xmlFreeParserCtxt (ctxt
);
640 if (ctx
->batch_count
)
641 rhythmdb_commit (RHYTHMDB (ctx
->db
));
645 if (local_error
!= NULL
) {
646 g_propagate_error (error
, local_error
);
650 g_string_free (ctx
->buf
, TRUE
);
652 g_free (sax_handler
);
658 struct RhythmDBTreeSaveContext
665 #ifdef HAVE_GNU_FWRITE_UNLOCKED
666 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
667 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
669 #define RHYTHMDB_FWRITE_REAL fwrite
670 #define RHYTHMDB_FPUTC_REAL fputc
673 #define RHYTHMDB_FWRITE(w,x,len,handle,error) do { \
674 if (error == NULL) { \
675 if (RHYTHMDB_FWRITE_REAL (w,x,len,handle) != len) { \
676 error = g_strdup (g_strerror (errno)); \
681 #define RHYTHMDB_FPUTC(x,handle,error) do { \
682 if (error == NULL) { \
683 if (RHYTHMDB_FPUTC_REAL (x,handle) == EOF) { \
684 error = g_strdup (g_strerror (errno)); \
689 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
692 write_elt_name_open (struct RhythmDBTreeSaveContext
*ctx
,
693 const xmlChar
*elt_name
)
695 RHYTHMDB_FWRITE_STATICSTR (" <", ctx
->handle
, ctx
->error
);
696 RHYTHMDB_FWRITE (elt_name
, 1, xmlStrlen (elt_name
), ctx
->handle
, ctx
->error
);
697 RHYTHMDB_FPUTC ('>', ctx
->handle
, ctx
->error
);
701 write_elt_name_close (struct RhythmDBTreeSaveContext
*ctx
,
702 const xmlChar
*elt_name
)
704 RHYTHMDB_FWRITE_STATICSTR ("</", ctx
->handle
, ctx
->error
);
705 RHYTHMDB_FWRITE (elt_name
, 1, xmlStrlen (elt_name
), ctx
->handle
, ctx
->error
);
706 RHYTHMDB_FWRITE_STATICSTR (">\n", ctx
->handle
, ctx
->error
);
710 save_entry_string (struct RhythmDBTreeSaveContext
*ctx
,
711 const xmlChar
*elt_name
,
716 g_return_if_fail (str
!= NULL
);
717 write_elt_name_open (ctx
, elt_name
);
718 encoded
= xmlEncodeEntitiesReentrant (NULL
, BAD_CAST str
);
719 RHYTHMDB_FWRITE (encoded
, 1, xmlStrlen (encoded
), ctx
->handle
, ctx
->error
);
721 write_elt_name_close (ctx
, elt_name
);
725 save_entry_int (struct RhythmDBTreeSaveContext
*ctx
,
726 const xmlChar
*elt_name
,
732 write_elt_name_open (ctx
, elt_name
);
733 g_snprintf (buf
, sizeof (buf
), "%d", num
);
734 RHYTHMDB_FWRITE (buf
, 1, strlen (buf
), ctx
->handle
, ctx
->error
);
735 write_elt_name_close (ctx
, elt_name
);
739 save_entry_ulong (struct RhythmDBTreeSaveContext
*ctx
,
740 const xmlChar
*elt_name
,
742 gboolean save_zeroes
)
746 if (num
== 0 && !save_zeroes
)
748 write_elt_name_open (ctx
, elt_name
);
749 g_snprintf (buf
, sizeof (buf
), "%lu", num
);
750 RHYTHMDB_FWRITE (buf
, 1, strlen (buf
), ctx
->handle
, ctx
->error
);
751 write_elt_name_close (ctx
, elt_name
);
755 save_entry_boolean (struct RhythmDBTreeSaveContext
*ctx
,
756 const xmlChar
*elt_name
,
759 save_entry_ulong (ctx
, elt_name
, val
? 1 : 0, FALSE
);
763 save_entry_uint64 (struct RhythmDBTreeSaveContext
*ctx
,
764 const xmlChar
*elt_name
,
772 write_elt_name_open (ctx
, elt_name
);
773 g_snprintf (buf
, sizeof (buf
), "%" G_GUINT64_FORMAT
, num
);
774 RHYTHMDB_FWRITE (buf
, 1, strlen (buf
), ctx
->handle
, ctx
->error
);
775 write_elt_name_close (ctx
, elt_name
);
779 save_entry_double (struct RhythmDBTreeSaveContext
*ctx
,
780 const xmlChar
*elt_name
,
783 char buf
[G_ASCII_DTOSTR_BUF_SIZE
+1];
785 if (num
> -0.001 && num
< 0.001)
788 write_elt_name_open (ctx
, elt_name
);
789 g_ascii_dtostr (buf
, sizeof (buf
), num
);
790 RHYTHMDB_FWRITE (buf
, 1, strlen (buf
), ctx
->handle
, ctx
->error
);
791 write_elt_name_close (ctx
, elt_name
);
794 /* This code is intended to be highly optimized. This came at a small
795 * readability cost. Sorry about that.
798 save_entry (RhythmDBTree
*db
,
799 RhythmDBEntry
*entry
,
800 struct RhythmDBTreeSaveContext
*ctx
)
803 RhythmDBPodcastFields
*podcast
= NULL
;
809 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
810 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
811 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
813 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx
->handle
, ctx
->error
);
814 encoded
= xmlEncodeEntitiesReentrant (NULL
, BAD_CAST entry
->type
->name
);
815 RHYTHMDB_FWRITE (encoded
, 1, xmlStrlen (encoded
), ctx
->handle
, ctx
->error
);
818 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx
->handle
, ctx
->error
);
820 /* Skip over the first property - the type */
821 for (i
= 1; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
822 const xmlChar
*elt_name
;
827 elt_name
= rhythmdb_nice_elt_name_from_propid ((RhythmDB
*) ctx
->db
, i
);
830 case RHYTHMDB_PROP_TYPE
:
832 case RHYTHMDB_PROP_ENTRY_ID
:
834 case RHYTHMDB_PROP_TITLE
:
835 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->title
));
837 case RHYTHMDB_PROP_ALBUM
:
838 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->album
));
840 case RHYTHMDB_PROP_ARTIST
:
841 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->artist
));
843 case RHYTHMDB_PROP_GENRE
:
844 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->genre
));
846 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
847 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->musicbrainz_trackid
));
849 case RHYTHMDB_PROP_TRACK_NUMBER
:
850 save_entry_ulong (ctx
, elt_name
, entry
->tracknum
, FALSE
);
852 case RHYTHMDB_PROP_DISC_NUMBER
:
853 save_entry_ulong (ctx
, elt_name
, entry
->discnum
, FALSE
);
855 case RHYTHMDB_PROP_DATE
:
856 if (g_date_valid (&entry
->date
))
857 save_entry_ulong (ctx
, elt_name
, g_date_get_julian (&entry
->date
), TRUE
);
859 save_entry_ulong (ctx
, elt_name
, 0, TRUE
);
861 case RHYTHMDB_PROP_DURATION
:
862 save_entry_ulong (ctx
, elt_name
, entry
->duration
, FALSE
);
864 case RHYTHMDB_PROP_BITRATE
:
865 save_entry_int(ctx
, elt_name
, entry
->bitrate
);
867 case RHYTHMDB_PROP_TRACK_GAIN
:
868 save_entry_double(ctx
, elt_name
, entry
->track_gain
);
870 case RHYTHMDB_PROP_TRACK_PEAK
:
871 save_entry_double(ctx
, elt_name
, entry
->track_peak
);
873 case RHYTHMDB_PROP_ALBUM_GAIN
:
874 save_entry_double(ctx
, elt_name
, entry
->album_gain
);
876 case RHYTHMDB_PROP_ALBUM_PEAK
:
877 save_entry_double(ctx
, elt_name
, entry
->album_peak
);
879 case RHYTHMDB_PROP_LOCATION
:
880 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->location
));
882 case RHYTHMDB_PROP_MOUNTPOINT
:
883 /* Avoid crashes on exit when upgrading from 0.8
884 * and no mountpoint is available from some entries */
885 if (entry
->mountpoint
) {
886 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->mountpoint
));
889 case RHYTHMDB_PROP_FILE_SIZE
:
890 save_entry_uint64(ctx
, elt_name
, entry
->file_size
);
892 case RHYTHMDB_PROP_MIMETYPE
:
893 save_entry_string(ctx
, elt_name
, rb_refstring_get (entry
->mimetype
));
895 case RHYTHMDB_PROP_MTIME
:
896 save_entry_ulong (ctx
, elt_name
, entry
->mtime
, FALSE
);
898 case RHYTHMDB_PROP_FIRST_SEEN
:
899 save_entry_ulong (ctx
, elt_name
, entry
->first_seen
, FALSE
);
901 case RHYTHMDB_PROP_LAST_SEEN
:
902 save_entry_ulong (ctx
, elt_name
, entry
->last_seen
, FALSE
);
904 case RHYTHMDB_PROP_RATING
:
905 save_entry_double(ctx
, elt_name
, entry
->rating
);
907 case RHYTHMDB_PROP_PLAY_COUNT
:
908 save_entry_ulong (ctx
, elt_name
, entry
->play_count
, FALSE
);
910 case RHYTHMDB_PROP_LAST_PLAYED
:
911 save_entry_ulong (ctx
, elt_name
, entry
->last_played
, FALSE
);
913 case RHYTHMDB_PROP_HIDDEN
:
915 gboolean hidden
= ((entry
->flags
& RHYTHMDB_ENTRY_HIDDEN
) != 0);
916 save_entry_boolean (ctx
, elt_name
, hidden
);
919 case RHYTHMDB_PROP_STATUS
:
921 save_entry_ulong (ctx
, elt_name
, podcast
->status
, FALSE
);
923 case RHYTHMDB_PROP_DESCRIPTION
:
924 if (podcast
&& podcast
->description
)
925 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->description
));
927 case RHYTHMDB_PROP_SUBTITLE
:
928 if (podcast
&& podcast
->subtitle
)
929 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->subtitle
));
931 case RHYTHMDB_PROP_SUMMARY
:
932 if (podcast
&& podcast
->summary
)
933 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->summary
));
935 case RHYTHMDB_PROP_LANG
:
936 if (podcast
&& podcast
->lang
)
937 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->lang
));
939 case RHYTHMDB_PROP_COPYRIGHT
:
940 if (podcast
&& podcast
->copyright
)
941 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->copyright
));
943 case RHYTHMDB_PROP_IMAGE
:
944 if (podcast
&& podcast
->image
)
945 save_entry_string(ctx
, elt_name
, rb_refstring_get (podcast
->image
));
947 case RHYTHMDB_PROP_POST_TIME
:
949 save_entry_ulong (ctx
, elt_name
, podcast
->post_time
, FALSE
);
951 case RHYTHMDB_PROP_TITLE_SORT_KEY
:
952 case RHYTHMDB_PROP_GENRE_SORT_KEY
:
953 case RHYTHMDB_PROP_ARTIST_SORT_KEY
:
954 case RHYTHMDB_PROP_ALBUM_SORT_KEY
:
955 case RHYTHMDB_PROP_TITLE_FOLDED
:
956 case RHYTHMDB_PROP_GENRE_FOLDED
:
957 case RHYTHMDB_PROP_ARTIST_FOLDED
:
958 case RHYTHMDB_PROP_ALBUM_FOLDED
:
959 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
960 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
961 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
962 case RHYTHMDB_PROP_LAST_SEEN_STR
:
963 case RHYTHMDB_PROP_SEARCH_MATCH
:
964 case RHYTHMDB_PROP_YEAR
:
965 case RHYTHMDB_NUM_PROPERTIES
:
970 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx
->handle
, ctx
->error
);
974 save_entry_type (const char *name
,
975 RhythmDBEntryType entry_type
,
976 struct RhythmDBTreeSaveContext
*ctx
)
978 if (entry_type
->save_to_disk
== FALSE
)
981 rb_debug ("saving entries of type %s", name
);
982 rhythmdb_hash_tree_foreach (RHYTHMDB (ctx
->db
), entry_type
,
983 (RBTreeEntryItFunc
) save_entry
,
984 NULL
, NULL
, NULL
, ctx
);
988 save_unknown_entry_type (RBRefString
*typename
,
990 struct RhythmDBTreeSaveContext
*ctx
)
994 for (t
= entries
; t
!= NULL
; t
= t
->next
) {
995 RhythmDBUnknownEntry
*entry
;
1002 entry
= (RhythmDBUnknownEntry
*)t
->data
;
1004 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx
->handle
, ctx
->error
);
1005 encoded
= xmlEncodeEntitiesReentrant (NULL
, BAD_CAST
rb_refstring_get (entry
->typename
));
1006 RHYTHMDB_FWRITE (encoded
, 1, xmlStrlen (encoded
), ctx
->handle
, ctx
->error
);
1009 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx
->handle
, ctx
->error
);
1011 for (p
= entry
->properties
; p
!= NULL
; p
= p
->next
) {
1012 RhythmDBUnknownEntryProperty
*prop
;
1013 prop
= (RhythmDBUnknownEntryProperty
*) p
->data
;
1014 save_entry_string(ctx
, (const xmlChar
*)rb_refstring_get (prop
->name
), rb_refstring_get (prop
->value
));
1017 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx
->handle
, ctx
->error
);
1022 rhythmdb_tree_save (RhythmDB
*rdb
)
1024 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
1028 struct RhythmDBTreeSaveContext ctx
;
1030 g_object_get (G_OBJECT (db
), "name", &name
, NULL
);
1032 savepath
= g_string_new (name
);
1033 g_string_append (savepath
, ".tmp");
1035 f
= fopen (savepath
->str
, "w");
1038 g_warning ("Can't save XML: %s", g_strerror (errno
));
1045 RHYTHMDB_FWRITE_STATICSTR ("<?xml version=\"1.0\" standalone=\"yes\"?>\n"
1046 "<rhythmdb version=\"" RHYTHMDB_TREE_XML_VERSION
"\">\n",
1047 ctx
.handle
, ctx
.error
);
1049 rhythmdb_entry_type_foreach (rdb
, (GHFunc
) save_entry_type
, &ctx
);
1050 g_hash_table_foreach (db
->priv
->unknown_entry_types
,
1051 (GHFunc
) save_unknown_entry_type
,
1054 RHYTHMDB_FWRITE_STATICSTR ("</rhythmdb>\n", ctx
.handle
, ctx
.error
);
1056 if (fclose (f
) < 0) {
1057 g_warning ("Couldn't close %s: %s",
1059 g_strerror (errno
));
1060 unlink (savepath
->str
);
1064 if (ctx
.error
!= NULL
) {
1065 g_warning ("Writing to the database failed: %s", ctx
.error
);
1067 unlink (savepath
->str
);
1069 if (rename (savepath
->str
, name
) < 0) {
1070 g_warning ("Couldn't rename %s to %s: %s",
1071 name
, savepath
->str
,
1072 g_strerror (errno
));
1073 unlink (savepath
->str
);
1078 g_string_free (savepath
, TRUE
);
1083 #undef RHYTHMDB_FWRITE_ENCODED_STR
1084 #undef RHYTHMDB_FWRITE_STATICSTR
1085 #undef RHYTHMDB_FPUTC
1086 #undef RHYTHMDB_FWRITE
1089 rhythmdb_tree_new (const char *name
)
1091 RhythmDBTree
*db
= g_object_new (RHYTHMDB_TYPE_TREE
, "name", name
, NULL
);
1093 g_return_val_if_fail (db
->priv
!= NULL
, NULL
);
1095 return RHYTHMDB (db
);
1099 set_entry_album (RhythmDBTree
*db
,
1100 RhythmDBEntry
*entry
,
1101 RhythmDBTreeProperty
*artist
,
1104 struct RhythmDBTreeProperty
*prop
;
1105 prop
= get_or_create_album (db
, artist
, name
);
1106 g_hash_table_insert (prop
->children
, entry
, NULL
);
1111 rhythmdb_tree_entry_new (RhythmDB
*rdb
,
1112 RhythmDBEntry
*entry
)
1114 g_mutex_lock (RHYTHMDB_TREE(rdb
)->priv
->entries_lock
);
1115 rhythmdb_tree_entry_new_internal (rdb
, entry
);
1116 g_mutex_unlock (RHYTHMDB_TREE(rdb
)->priv
->entries_lock
);
1120 rhythmdb_tree_entry_new_internal (RhythmDB
*rdb
, RhythmDBEntry
*entry
)
1122 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
1123 RhythmDBTreeProperty
*artist
;
1124 RhythmDBTreeProperty
*genre
;
1126 g_assert (entry
!= NULL
);
1128 g_return_if_fail (entry
->location
!= NULL
);
1130 if (entry
->title
== NULL
) {
1131 g_warning ("Entry %s has missing title", rb_refstring_get (entry
->location
));
1132 entry
->title
= rb_refstring_new (_("Unknown"));
1134 if (entry
->artist
== NULL
) {
1135 g_warning ("Entry %s has missing artist", rb_refstring_get (entry
->location
));
1136 entry
->artist
= rb_refstring_new (_("Unknown"));
1138 if (entry
->album
== NULL
) {
1139 g_warning ("Entry %s has missing album", rb_refstring_get (entry
->location
));
1140 entry
->album
= rb_refstring_new (_("Unknown"));
1142 if (entry
->genre
== NULL
) {
1143 g_warning ("Entry %s has missing genre", rb_refstring_get (entry
->location
));
1144 entry
->genre
= rb_refstring_new (_("Unknown"));
1146 if (entry
->mimetype
== NULL
) {
1147 g_warning ("Entry %s has missing mimetype", rb_refstring_get (entry
->location
));
1148 entry
->mimetype
= rb_refstring_new ("unknown/unknown");
1151 /* Initialize the tree structure. */
1152 genre
= get_or_create_genre (db
, entry
->type
, entry
->genre
);
1153 artist
= get_or_create_artist (db
, genre
, entry
->artist
);
1154 set_entry_album (db
, entry
, artist
, entry
->album
);
1156 /* this accounts for the initial reference on the entry */
1157 g_hash_table_insert (db
->priv
->entries
, entry
->location
, entry
);
1158 g_hash_table_insert (db
->priv
->entry_ids
, GINT_TO_POINTER (entry
->id
), entry
);
1160 entry
->flags
&= ~RHYTHMDB_ENTRY_TREE_LOADING
;
1163 static RhythmDBTreeProperty
*
1164 rhythmdb_tree_property_new (RhythmDBTree
*db
)
1166 RhythmDBTreeProperty
*ret
= g_new0 (RhythmDBTreeProperty
, 1);
1167 #ifndef G_DISABLE_ASSERT
1168 ret
->magic
= 0xf00dbeef;
1174 get_genres_hash_for_type (RhythmDBTree
*db
,
1175 RhythmDBEntryType type
)
1179 table
= g_hash_table_lookup (db
->priv
->genres
, type
);
1180 if (table
== NULL
) {
1181 table
= g_hash_table_new_full (rb_refstring_hash
,
1183 (GDestroyNotify
) rb_refstring_unref
,
1185 if (table
== NULL
) {
1186 g_warning ("Out of memory\n");
1189 g_hash_table_insert (db
->priv
->genres
,
1196 typedef void (*RBHFunc
)(RhythmDBTree
*db
, GHashTable
*genres
, gpointer data
);
1205 genres_process_one (gpointer key
,
1209 GenresIterCtxt
*ctxt
= (GenresIterCtxt
*)user_data
;
1210 ctxt
->func (ctxt
->db
, (GHashTable
*)value
, ctxt
->data
);
1214 genres_hash_foreach (RhythmDBTree
*db
, RBHFunc func
, gpointer data
)
1216 GenresIterCtxt ctxt
;
1221 g_hash_table_foreach (db
->priv
->genres
, genres_process_one
, &ctxt
);
1224 static RhythmDBTreeProperty
*
1225 get_or_create_genre (RhythmDBTree
*db
,
1226 RhythmDBEntryType type
,
1229 RhythmDBTreeProperty
*genre
;
1232 g_mutex_lock (db
->priv
->genres_lock
);
1233 table
= get_genres_hash_for_type (db
, type
);
1234 genre
= g_hash_table_lookup (table
, name
);
1236 if (G_UNLIKELY (genre
== NULL
)) {
1237 genre
= rhythmdb_tree_property_new (db
);
1238 genre
->children
= g_hash_table_new_full (rb_refstring_hash
, rb_refstring_equal
,
1239 (GDestroyNotify
) rb_refstring_unref
,
1241 rb_refstring_ref (name
);
1242 g_hash_table_insert (table
, name
, genre
);
1243 genre
->parent
= NULL
;
1245 g_mutex_unlock (db
->priv
->genres_lock
);
1250 static RhythmDBTreeProperty
*
1251 get_or_create_artist (RhythmDBTree
*db
,
1252 RhythmDBTreeProperty
*genre
,
1255 RhythmDBTreeProperty
*artist
;
1257 artist
= g_hash_table_lookup (genre
->children
, name
);
1259 if (G_UNLIKELY (artist
== NULL
)) {
1260 artist
= rhythmdb_tree_property_new (db
);
1261 artist
->children
= g_hash_table_new_full (rb_refstring_hash
, rb_refstring_equal
,
1262 (GDestroyNotify
) rb_refstring_unref
,
1264 rb_refstring_ref (name
);
1265 g_hash_table_insert (genre
->children
, name
, artist
);
1266 artist
->parent
= genre
;
1272 static RhythmDBTreeProperty
*
1273 get_or_create_album (RhythmDBTree
*db
,
1274 RhythmDBTreeProperty
*artist
,
1277 RhythmDBTreeProperty
*album
;
1279 album
= g_hash_table_lookup (artist
->children
, name
);
1281 if (G_UNLIKELY (album
== NULL
)) {
1282 album
= rhythmdb_tree_property_new (db
);
1283 album
->children
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
1284 rb_refstring_ref (name
);
1285 g_hash_table_insert (artist
->children
, name
, album
);
1286 album
->parent
= artist
;
1293 remove_child (RhythmDBTreeProperty
*parent
,
1296 g_assert (g_hash_table_remove (parent
->children
, data
));
1297 if (g_hash_table_size (parent
->children
) <= 0) {
1304 remove_entry_from_album (RhythmDBTree
*db
,
1305 RhythmDBEntry
*entry
)
1309 rb_refstring_ref (entry
->genre
);
1310 rb_refstring_ref (entry
->artist
);
1311 rb_refstring_ref (entry
->album
);
1313 g_mutex_lock (db
->priv
->genres_lock
);
1314 table
= get_genres_hash_for_type (db
, entry
->type
);
1315 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
), entry
)) {
1316 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
,
1319 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
->parent
,
1321 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
->parent
);
1322 g_assert (g_hash_table_remove (table
, entry
->genre
));
1324 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
)->parent
);
1327 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry
));
1329 g_mutex_unlock (db
->priv
->genres_lock
);
1331 rb_refstring_unref (entry
->genre
);
1332 rb_refstring_unref (entry
->artist
);
1333 rb_refstring_unref (entry
->album
);
1337 rhythmdb_tree_entry_set (RhythmDB
*adb
,
1338 RhythmDBEntry
*entry
,
1340 const GValue
*value
)
1342 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1343 RhythmDBEntryType type
;
1347 /* don't process changes to entries we're loading, we'll get them
1348 * when the entry is complete.
1350 if (entry
->flags
& RHYTHMDB_ENTRY_TREE_LOADING
)
1353 /* Handle special properties */
1356 case RHYTHMDB_PROP_LOCATION
:
1359 /* We have to use the string in the entry itself as the hash key,
1360 * otherwise either we leak it, or the string vanishes when the
1361 * GValue is freed; this means we have to do the entry modification
1362 * here, rather than letting rhythmdb_entry_set_internal do it.
1364 g_mutex_lock (db
->priv
->entries_lock
);
1365 g_assert (g_hash_table_remove (db
->priv
->entries
, entry
->location
));
1367 s
= rb_refstring_new (g_value_get_string (value
));
1368 rb_refstring_unref (entry
->location
);
1369 entry
->location
= s
;
1370 g_hash_table_insert (db
->priv
->entries
, entry
->location
, entry
);
1371 g_mutex_unlock (db
->priv
->entries_lock
);
1375 case RHYTHMDB_PROP_ALBUM
:
1377 const char *albumname
= g_value_get_string (value
);
1379 if (strcmp (rb_refstring_get (entry
->album
), albumname
)) {
1380 RhythmDBTreeProperty
*artist
;
1381 RhythmDBTreeProperty
*genre
;
1383 rb_refstring_ref (entry
->genre
);
1384 rb_refstring_ref (entry
->artist
);
1385 rb_refstring_ref (entry
->album
);
1387 remove_entry_from_album (db
, entry
);
1389 genre
= get_or_create_genre (db
, type
, entry
->genre
);
1390 artist
= get_or_create_artist (db
, genre
, entry
->artist
);
1391 set_entry_album (db
, entry
, artist
, rb_refstring_new (albumname
));
1393 rb_refstring_unref (entry
->genre
);
1394 rb_refstring_unref (entry
->artist
);
1395 rb_refstring_unref (entry
->album
);
1399 case RHYTHMDB_PROP_ARTIST
:
1401 const char *artistname
= g_value_get_string (value
);
1403 if (strcmp (rb_refstring_get (entry
->artist
), artistname
)) {
1404 RhythmDBTreeProperty
*new_artist
;
1405 RhythmDBTreeProperty
*genre
;
1407 rb_refstring_ref (entry
->genre
);
1408 rb_refstring_ref (entry
->artist
);
1409 rb_refstring_ref (entry
->album
);
1411 remove_entry_from_album (db
, entry
);
1413 genre
= get_or_create_genre (db
, type
, entry
->genre
);
1414 new_artist
= get_or_create_artist (db
, genre
,
1415 rb_refstring_new (artistname
));
1416 set_entry_album (db
, entry
, new_artist
, entry
->album
);
1418 rb_refstring_unref (entry
->genre
);
1419 rb_refstring_unref (entry
->artist
);
1420 rb_refstring_unref (entry
->album
);
1424 case RHYTHMDB_PROP_GENRE
:
1426 const char *genrename
= g_value_get_string (value
);
1428 if (strcmp (rb_refstring_get (entry
->genre
), genrename
)) {
1429 RhythmDBTreeProperty
*new_genre
;
1430 RhythmDBTreeProperty
*new_artist
;
1432 rb_refstring_ref (entry
->genre
);
1433 rb_refstring_ref (entry
->artist
);
1434 rb_refstring_ref (entry
->album
);
1436 remove_entry_from_album (db
, entry
);
1438 new_genre
= get_or_create_genre (db
, type
,
1439 rb_refstring_new (genrename
));
1440 new_artist
= get_or_create_artist (db
, new_genre
, entry
->artist
);
1441 set_entry_album (db
, entry
, new_artist
, entry
->album
);
1443 rb_refstring_unref (entry
->genre
);
1444 rb_refstring_unref (entry
->artist
);
1445 rb_refstring_unref (entry
->album
);
1457 rhythmdb_tree_entry_delete (RhythmDB
*adb
,
1458 RhythmDBEntry
*entry
)
1460 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1462 remove_entry_from_album (db
, entry
);
1464 g_mutex_lock (db
->priv
->entries_lock
);
1465 g_assert (g_hash_table_remove (db
->priv
->entries
, entry
->location
));
1466 g_assert (g_hash_table_remove (db
->priv
->entry_ids
, GINT_TO_POINTER (entry
->id
)));
1467 rhythmdb_entry_unref (entry
);
1468 g_mutex_unlock (db
->priv
->entries_lock
);
1473 RhythmDBEntryType type
;
1474 } RbEntryRemovalCtxt
;
1477 remove_one_song (gpointer key
,
1478 RhythmDBEntry
*entry
,
1479 RbEntryRemovalCtxt
*ctxt
)
1481 g_return_val_if_fail (entry
!= NULL
, FALSE
);
1483 if (entry
->type
== ctxt
->type
) {
1484 rhythmdb_emit_entry_deleted (ctxt
->db
, entry
);
1485 remove_entry_from_album (RHYTHMDB_TREE (ctxt
->db
), entry
);
1486 g_hash_table_remove (RHYTHMDB_TREE (ctxt
->db
)->priv
->entry_ids
, GINT_TO_POINTER (entry
->id
));
1487 rhythmdb_entry_unref (entry
);
1494 rhythmdb_tree_entry_delete_by_type (RhythmDB
*adb
,
1495 RhythmDBEntryType type
)
1497 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1498 RbEntryRemovalCtxt ctxt
;
1502 g_mutex_lock (db
->priv
->entries_lock
);
1503 g_hash_table_foreach_remove (db
->priv
->entries
,
1504 (GHRFunc
) remove_one_song
, &ctxt
);
1505 g_mutex_unlock (db
->priv
->entries_lock
);
1509 destroy_tree_property (RhythmDBTreeProperty
*prop
)
1511 #ifndef G_DISABLE_ASSERT
1512 prop
->magic
= 0xf33df33d;
1514 g_hash_table_destroy (prop
->children
);
1518 typedef void (*RhythmDBTreeTraversalFunc
) (RhythmDBTree
*db
, RhythmDBEntry
*entry
, gpointer data
);
1519 typedef void (*RhythmDBTreeAlbumTraversalFunc
) (RhythmDBTree
*db
, RhythmDBTreeProperty
*album
, gpointer data
);
1521 struct RhythmDBTreeTraversalData
1525 RhythmDBTreeTraversalFunc func
;
1531 rhythmdb_tree_evaluate_query (RhythmDB
*adb
,
1533 RhythmDBEntry
*entry
)
1535 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
1537 guint last_disjunction
;
1539 for (i
= 0, last_disjunction
= 0; i
< query
->len
; i
++) {
1540 RhythmDBQueryData
*data
= g_ptr_array_index (query
, i
);
1542 if (data
->type
== RHYTHMDB_QUERY_DISJUNCTION
) {
1543 if (evaluate_conjunctive_subquery (db
, query
, last_disjunction
, i
, entry
))
1546 last_disjunction
= i
+ 1;
1549 if (evaluate_conjunctive_subquery (db
, query
, last_disjunction
, query
->len
, entry
))
1554 #define RHYTHMDB_PROPERTY_COMPARE(OP) \
1555 switch (rhythmdb_get_property_type (db, data->propid)) { \
1556 case G_TYPE_STRING: \
1557 if (strcmp (rhythmdb_entry_get_string (entry, data->propid), \
1558 g_value_get_string (data->val)) OP 0) \
1561 case G_TYPE_ULONG: \
1562 if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1563 g_value_get_ulong (data->val)) \
1566 case G_TYPE_BOOLEAN: \
1567 if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1568 g_value_get_boolean (data->val)) \
1571 case G_TYPE_UINT64: \
1572 if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1573 g_value_get_uint64 (data->val)) \
1576 case G_TYPE_DOUBLE: \
1577 if (rhythmdb_entry_get_double (entry, data->propid) OP \
1578 g_value_get_double (data->val)) \
1581 case G_TYPE_POINTER: \
1582 if (rhythmdb_entry_get_pointer (entry, data->propid) OP \
1583 g_value_get_pointer (data->val)) \
1587 g_warning ("Unexpected type: %s", g_type_name (rhythmdb_get_property_type (db, data->propid))); \
1588 g_assert_not_reached (); \
1592 search_match_properties (RhythmDB
*db
,
1593 RhythmDBEntry
*entry
,
1596 const RhythmDBPropType props
[] = {
1597 RHYTHMDB_PROP_TITLE_FOLDED
,
1598 RHYTHMDB_PROP_ALBUM_FOLDED
,
1599 RHYTHMDB_PROP_ARTIST_FOLDED
,
1600 RHYTHMDB_PROP_GENRE_FOLDED
1602 gboolean islike
= TRUE
;
1606 for (current
= words
; *current
!= NULL
; current
++) {
1607 gboolean word_found
= FALSE
;
1609 for (i
= 0; i
< G_N_ELEMENTS (props
); i
++) {
1610 const char *entry_string
= rhythmdb_entry_get_string (entry
, props
[i
]);
1611 if (entry_string
&& (strstr (entry_string
, *current
) != NULL
)) {
1612 /* the word was found, go to the next one */
1618 /* the word wasn't in any of the properties*/
1628 evaluate_conjunctive_subquery (RhythmDBTree
*dbtree
,
1632 RhythmDBEntry
*entry
)
1635 RhythmDB
*db
= (RhythmDB
*) dbtree
;
1637 /* Optimization possibility - we may get here without actually having
1638 * anything in the query. It would be faster to instead just merge
1639 * the child hash table into the query result hash.
1641 for (i
= base
; i
< max
; i
++) {
1642 RhythmDBQueryData
*data
= g_ptr_array_index (query
, i
);
1644 switch (data
->type
) {
1645 case RHYTHMDB_QUERY_SUBQUERY
:
1647 gboolean matched
= FALSE
;
1648 GList
*conjunctions
= split_query_by_disjunctions (dbtree
, data
->subquery
);
1651 if (conjunctions
== NULL
)
1654 for (tem
= conjunctions
; tem
; tem
= tem
->next
) {
1655 GPtrArray
*subquery
= tem
->data
;
1656 if (!matched
&& evaluate_conjunctive_subquery (dbtree
, subquery
,
1661 g_ptr_array_free (tem
->data
, TRUE
);
1663 g_list_free (conjunctions
);
1668 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
:
1669 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN
:
1671 gulong relative_time
;
1672 GTimeVal current_time
;
1674 g_assert (rhythmdb_get_property_type (db
, data
->propid
) == G_TYPE_ULONG
);
1676 relative_time
= g_value_get_ulong (data
->val
);
1677 g_get_current_time (¤t_time
);
1679 if (data
->type
== RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
) {
1680 if (!(rhythmdb_entry_get_ulong (entry
, data
->propid
) >= (current_time
.tv_sec
- relative_time
)))
1683 if (!(rhythmdb_entry_get_ulong (entry
, data
->propid
) < (current_time
.tv_sec
- relative_time
)))
1688 case RHYTHMDB_QUERY_PROP_PREFIX
:
1689 case RHYTHMDB_QUERY_PROP_SUFFIX
:
1691 const char *value_s
;
1692 const char *entry_s
;
1694 g_assert (rhythmdb_get_property_type (db
, data
->propid
) == G_TYPE_STRING
);
1696 value_s
= g_value_get_string (data
->val
);
1697 entry_s
= rhythmdb_entry_get_string (entry
, data
->propid
);
1699 if (data
->type
== RHYTHMDB_QUERY_PROP_PREFIX
&& !g_str_has_prefix (entry_s
, value_s
))
1701 if (data
->type
== RHYTHMDB_QUERY_PROP_SUFFIX
&& !g_str_has_suffix (entry_s
, value_s
))
1706 case RHYTHMDB_QUERY_PROP_LIKE
:
1707 case RHYTHMDB_QUERY_PROP_NOT_LIKE
:
1709 if (rhythmdb_get_property_type (db
, data
->propid
) == G_TYPE_STRING
) {
1712 if (data
->propid
== RHYTHMDB_PROP_SEARCH_MATCH
) {
1713 /* this is a special property, that should match several things */
1714 islike
= search_match_properties (db
, entry
, g_value_get_boxed (data
->val
));
1717 const gchar
*value_string
= g_value_get_string (data
->val
);
1718 const char *entry_string
= rhythmdb_entry_get_string (entry
, data
->propid
);
1720 /* check in case the property is NULL, the value should never be NULL */
1721 if (entry_string
== NULL
)
1724 islike
= (strstr (entry_string
, value_string
) != NULL
);
1727 if ((data
->type
== RHYTHMDB_QUERY_PROP_LIKE
) ^ islike
)
1735 case RHYTHMDB_QUERY_PROP_EQUALS
:
1736 RHYTHMDB_PROPERTY_COMPARE (!=)
1738 case RHYTHMDB_QUERY_PROP_GREATER
:
1739 RHYTHMDB_PROPERTY_COMPARE (<)
1741 case RHYTHMDB_QUERY_PROP_LESS
:
1742 RHYTHMDB_PROPERTY_COMPARE (>)
1744 case RHYTHMDB_QUERY_END
:
1745 case RHYTHMDB_QUERY_DISJUNCTION
:
1746 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS
:
1747 case RHYTHMDB_QUERY_PROP_YEAR_LESS
:
1748 case RHYTHMDB_QUERY_PROP_YEAR_GREATER
:
1749 g_assert_not_reached ();
1757 do_conjunction (RhythmDBEntry
*entry
,
1759 struct RhythmDBTreeTraversalData
*data
)
1761 if (G_UNLIKELY (*data
->cancel
))
1763 /* Finally, we actually evaluate the query! */
1764 if (evaluate_conjunctive_subquery (data
->db
, data
->query
, 0, data
->query
->len
,
1766 data
->func (data
->db
, entry
, data
->data
);
1771 conjunctive_query_songs (const char *name
,
1772 RhythmDBTreeProperty
*album
,
1773 struct RhythmDBTreeTraversalData
*data
)
1775 if (G_UNLIKELY (*data
->cancel
))
1777 g_hash_table_foreach (album
->children
, (GHFunc
) do_conjunction
, data
);
1781 clone_remove_ptr_array_index (GPtrArray
*arr
,
1784 GPtrArray
*ret
= g_ptr_array_new ();
1786 for (i
= 0; i
< arr
->len
; i
++)
1788 g_ptr_array_add (ret
, g_ptr_array_index (arr
, i
));
1794 conjunctive_query_albums (const char *name
,
1795 RhythmDBTreeProperty
*artist
,
1796 struct RhythmDBTreeTraversalData
*data
)
1799 int album_query_idx
= -1;
1801 if (G_UNLIKELY (*data
->cancel
))
1804 for (i
= 0; i
< data
->query
->len
; i
++) {
1805 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, i
);
1806 if (qdata
->type
== RHYTHMDB_QUERY_PROP_EQUALS
1807 && qdata
->propid
== RHYTHMDB_PROP_ALBUM
) {
1808 if (album_query_idx
> 0)
1810 album_query_idx
= i
;
1815 if (album_query_idx
>= 0) {
1816 RhythmDBTreeProperty
*album
;
1817 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, album_query_idx
);
1818 RBRefString
*albumname
= rb_refstring_new (g_value_get_string (qdata
->val
));
1819 GPtrArray
*oldquery
= data
->query
;
1821 data
->query
= clone_remove_ptr_array_index (data
->query
, album_query_idx
);
1823 album
= g_hash_table_lookup (artist
->children
, albumname
);
1825 if (album
!= NULL
) {
1826 conjunctive_query_songs (rb_refstring_get (albumname
), album
, data
);
1828 g_ptr_array_free (data
->query
, TRUE
);
1829 data
->query
= oldquery
;
1833 g_hash_table_foreach (artist
->children
, (GHFunc
) conjunctive_query_songs
, data
);
1837 conjunctive_query_artists (const char *name
,
1838 RhythmDBTreeProperty
*genre
,
1839 struct RhythmDBTreeTraversalData
*data
)
1842 int artist_query_idx
= -1;
1844 if (G_UNLIKELY (*data
->cancel
))
1847 for (i
= 0; i
< data
->query
->len
; i
++) {
1848 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, i
);
1849 if (qdata
->type
== RHYTHMDB_QUERY_PROP_EQUALS
1850 && qdata
->propid
== RHYTHMDB_PROP_ARTIST
) {
1851 if (artist_query_idx
> 0)
1853 artist_query_idx
= i
;
1858 if (artist_query_idx
>= 0) {
1859 RhythmDBTreeProperty
*artist
;
1860 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, artist_query_idx
);
1861 RBRefString
*artistname
= rb_refstring_new (g_value_get_string (qdata
->val
));
1862 GPtrArray
*oldquery
= data
->query
;
1864 data
->query
= clone_remove_ptr_array_index (data
->query
, artist_query_idx
);
1866 artist
= g_hash_table_lookup (genre
->children
, artistname
);
1867 if (artist
!= NULL
) {
1868 conjunctive_query_albums (rb_refstring_get (artistname
), artist
, data
);
1870 g_ptr_array_free (data
->query
, TRUE
);
1871 data
->query
= oldquery
;
1875 g_hash_table_foreach (genre
->children
, (GHFunc
) conjunctive_query_albums
, data
);
1879 conjunctive_query_genre (RhythmDBTree
*db
,
1881 struct RhythmDBTreeTraversalData
*data
)
1883 int genre_query_idx
= -1;
1886 if (G_UNLIKELY (*data
->cancel
))
1889 for (i
= 0; i
< data
->query
->len
; i
++) {
1890 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, i
);
1891 if (qdata
->type
== RHYTHMDB_QUERY_PROP_EQUALS
1892 && qdata
->propid
== RHYTHMDB_PROP_GENRE
) {
1893 /* A song can't currently have two genres. So
1894 * if we get a conjunctive query for that, we
1895 * know the result must be the empty set. */
1896 if (genre_query_idx
> 0)
1898 genre_query_idx
= i
;
1903 if (genre_query_idx
>= 0) {
1904 RhythmDBTreeProperty
*genre
;
1905 RhythmDBQueryData
*qdata
= g_ptr_array_index (data
->query
, genre_query_idx
);
1906 RBRefString
*genrename
= rb_refstring_new (g_value_get_string (qdata
->val
));
1907 GPtrArray
*oldquery
= data
->query
;
1909 data
->query
= clone_remove_ptr_array_index (data
->query
, genre_query_idx
);
1911 genre
= g_hash_table_lookup (genres
, genrename
);
1912 if (genre
!= NULL
) {
1913 conjunctive_query_artists (rb_refstring_get (genrename
), genre
, data
);
1915 g_ptr_array_free (data
->query
, TRUE
);
1916 data
->query
= oldquery
;
1920 g_hash_table_foreach (genres
, (GHFunc
) conjunctive_query_artists
, data
);
1924 conjunctive_query (RhythmDBTree
*db
,
1926 RhythmDBTreeTraversalFunc func
,
1930 int type_query_idx
= -1;
1932 struct RhythmDBTreeTraversalData
*traversal_data
;
1934 for (i
= 0; i
< query
->len
; i
++) {
1935 RhythmDBQueryData
*qdata
= g_ptr_array_index (query
, i
);
1936 if (qdata
->type
== RHYTHMDB_QUERY_PROP_EQUALS
1937 && qdata
->propid
== RHYTHMDB_PROP_TYPE
) {
1938 /* A song can't have two types. */
1939 if (type_query_idx
> 0)
1945 traversal_data
= g_new (struct RhythmDBTreeTraversalData
, 1);
1946 traversal_data
->db
= db
;
1947 traversal_data
->query
= query
;
1948 traversal_data
->func
= func
;
1949 traversal_data
->data
= data
;
1950 traversal_data
->cancel
= cancel
;
1952 g_mutex_lock (db
->priv
->genres_lock
);
1953 if (type_query_idx
>= 0) {
1955 RhythmDBEntryType etype
;
1956 RhythmDBQueryData
*qdata
= g_ptr_array_index (query
, type_query_idx
);
1958 g_ptr_array_remove_index_fast (query
, type_query_idx
);
1960 etype
= g_value_get_pointer (qdata
->val
);
1961 genres
= get_genres_hash_for_type (db
, etype
);
1962 if (genres
!= NULL
) {
1963 conjunctive_query_genre (db
, genres
, traversal_data
);
1965 g_assert_not_reached ();
1969 /* No type was given; punt and query everything */
1970 genres_hash_foreach (db
, (RBHFunc
)conjunctive_query_genre
,
1973 g_mutex_unlock (db
->priv
->genres_lock
);
1975 g_free (traversal_data
);
1979 split_query_by_disjunctions (RhythmDBTree
*db
,
1982 GList
*conjunctions
= NULL
;
1984 guint last_disjunction
= 0;
1985 GPtrArray
*subquery
= g_ptr_array_new ();
1987 for (i
= 0; i
< query
->len
; i
++) {
1988 RhythmDBQueryData
*data
= g_ptr_array_index (query
, i
);
1989 if (data
->type
== RHYTHMDB_QUERY_DISJUNCTION
) {
1991 /* Copy the subquery */
1992 for (j
= last_disjunction
; j
< i
; j
++) {
1993 g_ptr_array_add (subquery
, g_ptr_array_index (query
, j
));
1996 conjunctions
= g_list_prepend (conjunctions
, subquery
);
1997 last_disjunction
= i
+1;
1998 g_assert (subquery
->len
> 0);
1999 subquery
= g_ptr_array_new ();
2003 /* Copy the last subquery, except for the QUERY_END */
2004 for (i
= last_disjunction
; i
< query
->len
; i
++) {
2005 g_ptr_array_add (subquery
, g_ptr_array_index (query
, i
));
2008 if (subquery
->len
> 0)
2009 conjunctions
= g_list_prepend (conjunctions
, subquery
);
2011 g_ptr_array_free (subquery
, TRUE
);
2013 return conjunctions
;
2016 struct RhythmDBTreeQueryGatheringData
2020 GHashTable
*entries
;
2021 RhythmDBQueryResults
*results
;
2025 do_query_recurse (RhythmDBTree
*db
,
2027 RhythmDBTreeTraversalFunc func
,
2028 struct RhythmDBTreeQueryGatheringData
*data
,
2031 GList
*conjunctions
, *tem
;
2036 conjunctions
= split_query_by_disjunctions (db
, query
);
2037 rb_debug ("doing recursive query, %d conjunctions", g_list_length (conjunctions
));
2039 if (conjunctions
== NULL
)
2042 /* If there is a disjunction involved, we must uniquify the entry hits. */
2043 if (conjunctions
->next
!= NULL
)
2044 data
->entries
= g_hash_table_new (g_direct_hash
, g_direct_equal
);
2046 data
->entries
= NULL
;
2048 for (tem
= conjunctions
; tem
; tem
= tem
->next
) {
2049 if (G_UNLIKELY (*cancel
))
2051 conjunctive_query (db
, tem
->data
, func
, data
, cancel
);
2052 g_ptr_array_free (tem
->data
, TRUE
);
2055 if (data
->entries
!= NULL
)
2056 g_hash_table_destroy (data
->entries
);
2058 g_list_free (conjunctions
);
2062 handle_entry_match (RhythmDB
*db
,
2063 RhythmDBEntry
*entry
,
2064 struct RhythmDBTreeQueryGatheringData
*data
)
2068 && g_hash_table_lookup (data
->entries
, entry
))
2071 g_ptr_array_add (data
->queue
, entry
);
2072 if (data
->queue
->len
> RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK
) {
2073 rhythmdb_query_results_add_results (data
->results
, data
->queue
);
2074 data
->queue
= g_ptr_array_new ();
2079 rhythmdb_tree_do_full_query (RhythmDB
*adb
,
2081 RhythmDBQueryResults
*results
,
2084 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
2085 struct RhythmDBTreeQueryGatheringData
*data
= g_new0 (struct RhythmDBTreeQueryGatheringData
, 1);
2087 data
->results
= results
;
2088 data
->queue
= g_ptr_array_new ();
2090 do_query_recurse (db
, query
, (RhythmDBTreeTraversalFunc
) handle_entry_match
, data
, cancel
);
2092 rhythmdb_query_results_add_results (data
->results
, data
->queue
);
2097 static RhythmDBEntry
*
2098 rhythmdb_tree_entry_lookup_by_location (RhythmDB
*adb
,
2101 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
2102 RhythmDBEntry
*entry
;
2104 g_mutex_lock (db
->priv
->entries_lock
);
2105 entry
= g_hash_table_lookup (db
->priv
->entries
, uri
);
2106 g_mutex_unlock (db
->priv
->entries_lock
);
2111 static RhythmDBEntry
*
2112 rhythmdb_tree_entry_lookup_by_id (RhythmDB
*adb
,
2115 RhythmDBTree
*db
= RHYTHMDB_TREE (adb
);
2116 RhythmDBEntry
*entry
;
2118 g_mutex_lock (db
->priv
->entries_lock
);
2119 entry
= g_hash_table_lookup (db
->priv
->entry_ids
, GINT_TO_POINTER (id
));
2120 g_mutex_unlock (db
->priv
->entries_lock
);
2125 struct RhythmDBEntryForeachCtxt
2133 rhythmdb_tree_entry_foreach_func (gpointer key
, RhythmDBEntry
*val
, GPtrArray
*list
)
2135 rhythmdb_entry_ref (val
);
2136 g_ptr_array_add (list
, val
);
2140 rhythmdb_tree_entry_foreach (RhythmDB
*rdb
, GFunc foreach_func
, gpointer user_data
)
2142 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
2146 g_mutex_lock (db
->priv
->entries_lock
);
2147 size
= g_hash_table_size (db
->priv
->entries
);
2148 list
= g_ptr_array_sized_new (size
);
2149 g_hash_table_foreach (db
->priv
->entries
, (GHFunc
)rhythmdb_tree_entry_foreach_func
, list
);
2150 g_mutex_unlock (db
->priv
->entries_lock
);
2152 for (i
= 0; i
< size
; i
++) {
2153 RhythmDBEntry
*entry
= (RhythmDBEntry
*)g_ptr_array_index (list
, i
);
2154 (*foreach_func
) (entry
, user_data
);
2155 rhythmdb_entry_unref (entry
);
2158 g_ptr_array_free (list
, TRUE
);
2162 rhythmdb_tree_entry_count (RhythmDB
*rdb
)
2164 RhythmDBTree
*db
= RHYTHMDB_TREE (rdb
);
2165 return g_hash_table_size (db
->priv
->entries
);
2169 rhythmdb_tree_entry_foreach_by_type (RhythmDB
*db
,
2170 RhythmDBEntryType type
,
2174 rhythmdb_hash_tree_foreach (db
, type
,
2175 (RBTreeEntryItFunc
) foreach_func
,
2176 NULL
, NULL
, NULL
, data
);
2180 count_entries (RhythmDB
*db
, RhythmDBTreeProperty
*album
, gint64
*count
)
2182 *count
+= g_hash_table_size (album
->children
);
2186 rhythmdb_tree_entry_count_by_type (RhythmDB
*db
,
2187 RhythmDBEntryType type
)
2190 rhythmdb_hash_tree_foreach (db
, type
,
2191 NULL
, (RBTreePropertyItFunc
) count_entries
, NULL
, NULL
,
2197 struct HashTreeIteratorCtxt
{
2199 RBTreeEntryItFunc entry_func
;
2200 RBTreePropertyItFunc album_func
;
2201 RBTreePropertyItFunc artist_func
;
2202 RBTreePropertyItFunc genres_func
;
2207 hash_tree_entries_foreach (gpointer key
,
2211 RhythmDBEntry
*entry
= (RhythmDBEntry
*) key
;
2212 struct HashTreeIteratorCtxt
*ctxt
= (struct HashTreeIteratorCtxt
*)data
;
2214 g_assert (ctxt
->entry_func
);
2216 ctxt
->entry_func (ctxt
->db
, entry
, ctxt
->data
);
2220 hash_tree_albums_foreach (gpointer key
,
2224 RhythmDBTreeProperty
*album
= (RhythmDBTreeProperty
*)value
;
2225 struct HashTreeIteratorCtxt
*ctxt
= (struct HashTreeIteratorCtxt
*)data
;
2227 if (ctxt
->album_func
) {
2228 ctxt
->album_func (ctxt
->db
, album
, ctxt
->data
);
2230 if (ctxt
->entry_func
!= NULL
) {
2231 g_hash_table_foreach (album
->children
,
2232 hash_tree_entries_foreach
,
2238 hash_tree_artists_foreach (gpointer key
,
2242 RhythmDBTreeProperty
*artist
= (RhythmDBTreeProperty
*)value
;
2243 struct HashTreeIteratorCtxt
*ctxt
= (struct HashTreeIteratorCtxt
*)data
;
2245 if (ctxt
->artist_func
) {
2246 ctxt
->artist_func (ctxt
->db
, artist
, ctxt
->data
);
2248 if ((ctxt
->album_func
!= NULL
) || (ctxt
->entry_func
!= NULL
)) {
2249 g_hash_table_foreach (artist
->children
,
2250 hash_tree_albums_foreach
,
2256 hash_tree_genres_foreach (gpointer key
,
2260 RhythmDBTreeProperty
*genre
= (RhythmDBTreeProperty
*)value
;
2261 struct HashTreeIteratorCtxt
*ctxt
= (struct HashTreeIteratorCtxt
*)data
;
2263 if (ctxt
->genres_func
) {
2264 ctxt
->genres_func (ctxt
->db
, genre
, ctxt
->data
);
2267 if ((ctxt
->album_func
!= NULL
)
2268 || (ctxt
->artist_func
!= NULL
)
2269 || (ctxt
->entry_func
!= NULL
)) {
2270 g_hash_table_foreach (genre
->children
,
2271 hash_tree_artists_foreach
,
2277 rhythmdb_hash_tree_foreach (RhythmDB
*adb
,
2278 RhythmDBEntryType type
,
2279 RBTreeEntryItFunc entry_func
,
2280 RBTreePropertyItFunc album_func
,
2281 RBTreePropertyItFunc artist_func
,
2282 RBTreePropertyItFunc genres_func
,
2285 struct HashTreeIteratorCtxt ctxt
;
2288 ctxt
.db
= RHYTHMDB_TREE (adb
);
2289 ctxt
.album_func
= album_func
;
2290 ctxt
.artist_func
= artist_func
;
2291 ctxt
.genres_func
= genres_func
;
2292 ctxt
.entry_func
= entry_func
;
2295 g_mutex_lock (ctxt
.db
->priv
->genres_lock
);
2296 table
= get_genres_hash_for_type (RHYTHMDB_TREE (adb
), type
);
2297 if (table
== NULL
) {
2300 if ((ctxt
.album_func
!= NULL
)
2301 || (ctxt
.artist_func
!= NULL
)
2302 || (ctxt
.genres_func
!= NULL
)
2303 || (ctxt
.entry_func
!= NULL
)) {
2304 g_hash_table_foreach (table
, hash_tree_genres_foreach
, &ctxt
);
2306 g_mutex_unlock (ctxt
.db
->priv
->genres_lock
);
2310 rhythmdb_tree_entry_type_registered (RhythmDB
*db
,
2312 RhythmDBEntryType entry_type
)
2314 GList
*entries
= NULL
;
2318 RBRefString
*rs_name
;
2323 rdb
= RHYTHMDB_TREE (db
);
2324 rs_name
= rb_refstring_find (name
);
2326 entries
= g_hash_table_lookup (rdb
->priv
->unknown_entry_types
, rs_name
);
2327 if (entries
== NULL
) {
2328 rb_refstring_unref (rs_name
);
2329 rb_debug ("no entries of newly registered type %s loaded from db", name
);
2333 for (e
= entries
; e
!= NULL
; e
= e
->next
) {
2334 RhythmDBUnknownEntry
*data
;
2335 RhythmDBEntry
*entry
;
2338 data
= (RhythmDBUnknownEntry
*)e
->data
;
2339 entry
= rhythmdb_entry_allocate (db
, entry_type
);
2340 entry
->flags
|= RHYTHMDB_ENTRY_TREE_LOADING
;
2341 for (p
= data
->properties
; p
!= NULL
; p
= p
->next
) {
2342 RhythmDBUnknownEntryProperty
*prop
;
2343 RhythmDBPropType propid
;
2344 GValue value
= {0,};
2346 prop
= (RhythmDBUnknownEntryProperty
*) p
->data
;
2347 propid
= rhythmdb_propid_from_nice_elt_name (db
, (const xmlChar
*) rb_refstring_get (prop
->name
));
2349 rhythmdb_read_encoded_property (db
, rb_refstring_get (prop
->value
), propid
, &value
);
2350 rhythmdb_entry_set_internal (db
, entry
, FALSE
, propid
, &value
);
2351 g_value_unset (&value
);
2353 rhythmdb_tree_entry_new_internal (db
, entry
);
2354 rhythmdb_entry_insert (db
, entry
);
2357 rb_debug ("handled %d entries of newly registered type %s", count
, name
);
2358 rhythmdb_commit (db
);
2360 g_hash_table_remove (rdb
->priv
->unknown_entry_types
, rs_name
);
2361 free_unknown_entries (rs_name
, entries
, NULL
);
2362 rb_refstring_unref (rs_name
);