No issues found
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003, 2004 Colin Walters <walters@verbum.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 #include "config.h"
30
31 #ifdef HAVE_GNU_FWRITE_UNLOCKED
32 #define _GNU_SOURCE
33 #endif
34 #include <stdio.h>
35 #ifdef HAVE_GNU_FWRITE_UNLOCKED
36 #undef _GNU_SOURCE
37 #endif
38 #include <unistd.h>
39 #include <stdlib.h>
40 #include <errno.h>
41 #include <string.h>
42 #include <math.h>
43 #include <glib/gprintf.h>
44 #include <glib.h>
45 #include <glib/gi18n.h>
46 #include <gtk/gtk.h>
47 #include <libxml/entities.h>
48 #include <libxml/SAX.h>
49 #include <libxml/parserInternals.h>
50
51 #include "rhythmdb-private.h"
52 #include "rhythmdb-tree.h"
53 #include "rhythmdb-property-model.h"
54 #include "rb-debug.h"
55 #include "rb-util.h"
56 #include "rb-file-helpers.h"
57 #include "rb-podcast-entry-types.h"
58
59 typedef struct RhythmDBTreeProperty
60 {
61 #ifndef G_DISABLE_ASSERT
62 guint magic;
63 #endif
64 struct RhythmDBTreeProperty *parent;
65 GHashTable *children;
66 } RhythmDBTreeProperty;
67
68 #define RHYTHMDB_TREE_PROPERTY_FROM_ENTRY(entry) ((RhythmDBTreeProperty *) entry->data)
69
70 G_DEFINE_TYPE(RhythmDBTree, rhythmdb_tree, RHYTHMDB_TYPE)
71
72 static void rhythmdb_tree_finalize (GObject *object);
73
74 static gboolean rhythmdb_tree_load (RhythmDB *rdb, GCancellable *cancel, GError **error);
75 static void rhythmdb_tree_save (RhythmDB *rdb);
76 static void rhythmdb_tree_entry_new (RhythmDB *db, RhythmDBEntry *entry);
77 static void rhythmdb_tree_entry_new_internal (RhythmDB *db, RhythmDBEntry *entry);
78 static gboolean rhythmdb_tree_entry_set (RhythmDB *db, RhythmDBEntry *entry,
79 guint propid, const GValue *value);
80
81 static void rhythmdb_tree_entry_delete (RhythmDB *db, RhythmDBEntry *entry);
82 static void rhythmdb_tree_entry_delete_by_type (RhythmDB *adb, RhythmDBEntryType *type);
83
84 static RhythmDBEntry * rhythmdb_tree_entry_lookup_by_location (RhythmDB *db, RBRefString *uri);
85 static RhythmDBEntry * rhythmdb_tree_entry_lookup_by_id (RhythmDB *db, gint id);
86 static void rhythmdb_tree_entry_foreach (RhythmDB *adb, GFunc func, gpointer user_data);
87 static gint64 rhythmdb_tree_entry_count (RhythmDB *adb);
88 static void rhythmdb_tree_entry_foreach_by_type (RhythmDB *adb, RhythmDBEntryType *type, GFunc func, gpointer user_data);
89 static gint64 rhythmdb_tree_entry_count_by_type (RhythmDB *adb, RhythmDBEntryType *type);
90 static gboolean rhythmdb_tree_entry_keyword_add (RhythmDB *adb, RhythmDBEntry *entry, RBRefString *keyword);
91 static gboolean rhythmdb_tree_entry_keyword_remove (RhythmDB *adb, RhythmDBEntry *entry, RBRefString *keyword);
92 static gboolean rhythmdb_tree_entry_keyword_has (RhythmDB *adb, RhythmDBEntry *entry, RBRefString *keyword);
93 static GList* rhythmdb_tree_entry_keywords_get (RhythmDB *adb, RhythmDBEntry *entry);
94 static void rhythmdb_tree_do_full_query (RhythmDB *db, GPtrArray *query,
95 RhythmDBQueryResults *results,
96 gboolean *cancel);
97 static gboolean rhythmdb_tree_evaluate_query (RhythmDB *adb, GPtrArray *query,
98 RhythmDBEntry *aentry);
99 static void rhythmdb_tree_entry_type_registered (RhythmDB *db,
100 RhythmDBEntryType *type);
101
102 typedef void (*RBTreeEntryItFunc)(RhythmDBTree *db,
103 RhythmDBEntry *entry,
104 gpointer data);
105
106 typedef void (*RBTreePropertyItFunc)(RhythmDBTree *db,
107 RhythmDBTreeProperty *property,
108 gpointer data);
109 static void rhythmdb_hash_tree_foreach (RhythmDB *adb,
110 RhythmDBEntryType *type,
111 RBTreeEntryItFunc entry_func,
112 RBTreePropertyItFunc album_func,
113 RBTreePropertyItFunc artist_func,
114 RBTreePropertyItFunc genres_func,
115 gpointer data);
116
117 /* Update both of those! */
118 #define RHYTHMDB_TREE_XML_VERSION "1.8"
119 #define RHYTHMDB_TREE_XML_VERSION_INT 180
120
121 static void destroy_tree_property (RhythmDBTreeProperty *prop);
122 static RhythmDBTreeProperty *get_or_create_album (RhythmDBTree *db, RhythmDBTreeProperty *artist,
123 RBRefString *name);
124 static RhythmDBTreeProperty *get_or_create_artist (RhythmDBTree *db, RhythmDBTreeProperty *genre,
125 RBRefString *name);
126 static RhythmDBTreeProperty *get_or_create_genre (RhythmDBTree *db, RhythmDBEntryType *type,
127 RBRefString *name);
128
129 static void remove_entry_from_album (RhythmDBTree *db, RhythmDBEntry *entry);
130 static void remove_entry_from_keywords (RhythmDBTree *db, RhythmDBEntry *entry);
131
132 static GList *split_query_by_disjunctions (RhythmDBTree *db, GPtrArray *query);
133 static gboolean evaluate_conjunctive_subquery (RhythmDBTree *db, GPtrArray *query,
134 guint base, guint max, RhythmDBEntry *entry);
135
136 struct RhythmDBTreePrivate
137 {
138 GHashTable *entries;
139 GHashTable *entry_ids;
140 GMutex entries_lock;
141
142 GHashTable *keywords; /* GHashTable<RBRefString, GHashTable<RhyhmDBEntry, 1>> */
143 GMutex keywords_lock;
144
145 GHashTable *genres;
146 GMutex genres_lock; /* must be held while using the tree */
147
148 GHashTable *unknown_entry_types;
149 gboolean finalizing;
150
151 guint idle_load_id;
152 };
153
154 typedef struct
155 {
156 RBRefString *name;
157 RBRefString *value;
158 } RhythmDBUnknownEntryProperty;
159
160 typedef struct
161 {
162 RBRefString *typename;
163 GList *properties;
164 } RhythmDBUnknownEntry;
165
166 #define RHYTHMDB_TREE_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE_TREE, RhythmDBTreePrivate))
167
168 enum
169 {
170 PROP_0,
171 };
172
173 const int RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE = 512;
174
175 GQuark
176 rhythmdb_tree_error_quark (void)
177 {
178 static GQuark quark;
179 if (!quark)
180 quark = g_quark_from_static_string ("rhythmdb_tree_error");
181
182 return quark;
183 }
184
185 static void
186 rhythmdb_tree_class_init (RhythmDBTreeClass *klass)
187 {
188 GObjectClass *object_class = G_OBJECT_CLASS (klass);
189 RhythmDBClass *rhythmdb_class = RHYTHMDB_CLASS (klass);
190
191 object_class->finalize = rhythmdb_tree_finalize;
192
193 rhythmdb_class->impl_load = rhythmdb_tree_load;
194 rhythmdb_class->impl_save = rhythmdb_tree_save;
195 rhythmdb_class->impl_entry_new = rhythmdb_tree_entry_new;
196 rhythmdb_class->impl_entry_set = rhythmdb_tree_entry_set;
197 rhythmdb_class->impl_entry_delete = rhythmdb_tree_entry_delete;
198 rhythmdb_class->impl_entry_delete_by_type = rhythmdb_tree_entry_delete_by_type;
199 rhythmdb_class->impl_lookup_by_location = rhythmdb_tree_entry_lookup_by_location;
200 rhythmdb_class->impl_lookup_by_id = rhythmdb_tree_entry_lookup_by_id;
201 rhythmdb_class->impl_entry_foreach = rhythmdb_tree_entry_foreach;
202 rhythmdb_class->impl_entry_count = rhythmdb_tree_entry_count;
203 rhythmdb_class->impl_entry_foreach_by_type = rhythmdb_tree_entry_foreach_by_type;
204 rhythmdb_class->impl_entry_count_by_type = rhythmdb_tree_entry_count_by_type;
205 rhythmdb_class->impl_entry_keyword_add = rhythmdb_tree_entry_keyword_add;
206 rhythmdb_class->impl_entry_keyword_remove = rhythmdb_tree_entry_keyword_remove;
207 rhythmdb_class->impl_entry_keyword_has = rhythmdb_tree_entry_keyword_has;
208 rhythmdb_class->impl_entry_keywords_get = rhythmdb_tree_entry_keywords_get;
209 rhythmdb_class->impl_evaluate_query = rhythmdb_tree_evaluate_query;
210 rhythmdb_class->impl_do_full_query = rhythmdb_tree_do_full_query;
211 rhythmdb_class->impl_entry_type_registered = rhythmdb_tree_entry_type_registered;
212
213 g_type_class_add_private (klass, sizeof (RhythmDBTreePrivate));
214 }
215
216 static void
217 rhythmdb_tree_init (RhythmDBTree *db)
218 {
219 db->priv = RHYTHMDB_TREE_GET_PRIVATE (db);
220
221 db->priv->entries = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
222 db->priv->entry_ids = g_hash_table_new (g_direct_hash, g_direct_equal);
223
224 db->priv->keywords = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
225 (GDestroyNotify)rb_refstring_unref, (GDestroyNotify)g_hash_table_destroy);
226
227 db->priv->genres = g_hash_table_new_full (g_direct_hash, g_direct_equal,
228 NULL, (GDestroyNotify)g_hash_table_destroy);
229
230 db->priv->unknown_entry_types = g_hash_table_new (rb_refstring_hash, rb_refstring_equal);
231 }
232
233 /* must be called with the genres lock held */
234 static void
235 unparent_entries (gpointer key,
236 RhythmDBEntry *entry,
237 RhythmDBTree *db)
238 {
239 rb_assert_locked (&db->priv->genres_lock);
240
241 remove_entry_from_album (db, entry);
242 }
243
244 static void
245 free_unknown_entries (RBRefString *name,
246 GList *entries,
247 gpointer nah)
248 {
249 GList *e;
250 for (e = entries; e != NULL; e = e->next) {
251 RhythmDBUnknownEntry *entry;
252 GList *p;
253
254 entry = (RhythmDBUnknownEntry *)e->data;
255 rb_refstring_unref (entry->typename);
256 for (p = entry->properties; p != NULL; p = p->next) {
257 RhythmDBUnknownEntryProperty *prop;
258
259 prop = (RhythmDBUnknownEntryProperty *)p->data;
260 rb_refstring_unref (prop->name);
261 rb_refstring_unref (prop->value);
262 g_free (prop);
263 }
264
265 g_list_free (entry->properties);
266 }
267 g_list_free (entries);
268 }
269
270 static void
271 rhythmdb_tree_finalize (GObject *object)
272 {
273 RhythmDBTree *db;
274
275 g_return_if_fail (object != NULL);
276 g_return_if_fail (RHYTHMDB_IS_TREE (object));
277
278 db = RHYTHMDB_TREE (object);
279
280 g_return_if_fail (db->priv != NULL);
281
282 db->priv->finalizing = TRUE;
283
284 g_mutex_lock (&db->priv->genres_lock);
285 g_hash_table_foreach (db->priv->entries, (GHFunc) unparent_entries, db);
286 g_mutex_unlock (&db->priv->genres_lock);
287
288 g_hash_table_destroy (db->priv->entries);
289 g_hash_table_destroy (db->priv->entry_ids);
290
291 g_hash_table_destroy (db->priv->keywords);
292
293 g_hash_table_destroy (db->priv->genres);
294
295 g_hash_table_foreach (db->priv->unknown_entry_types,
296 (GHFunc) free_unknown_entries,
297 NULL);
298 g_hash_table_destroy (db->priv->unknown_entry_types);
299
300 G_OBJECT_CLASS (rhythmdb_tree_parent_class)->finalize (object);
301 }
302
303 struct RhythmDBTreeLoadContext
304 {
305 RhythmDBTree *db;
306 xmlParserCtxtPtr xmlctx;
307 GCancellable *cancel;
308 enum {
309 RHYTHMDB_TREE_PARSER_STATE_START,
310 RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB,
311 RHYTHMDB_TREE_PARSER_STATE_ENTRY,
312 RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY,
313 RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD,
314 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY,
315 RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY,
316 RHYTHMDB_TREE_PARSER_STATE_END,
317 } state;
318 guint in_unknown_elt;
319 RhythmDBEntry *entry;
320 RhythmDBUnknownEntry *unknown_entry;
321 GString *buf;
322 RhythmDBPropType propid;
323 gint batch_count;
324 GError **error;
325
326 /* updating */
327 guint has_date : 1;
328 guint canonicalise_uris : 1;
329 guint reload_all_metadata : 1;
330 guint update_podcasts : 1;
331 guint update_local_mountpoints : 1;
332 };
333
334 /* Returns the version as an int, multiplied by 100,
335 * eg. "1.4" becomes 140 */
336 static int
337 version_to_int (const char *version)
338 {
339 float ver;
340 char *eptr;
341
342 ver = g_ascii_strtod (version, &eptr);
343 if (eptr == version) {
344 return (int) (1.0 * 100);
345 }
346 return (int)roundf(ver * 100);
347 }
348
349 static void
350 rhythmdb_tree_parser_start_element (struct RhythmDBTreeLoadContext *ctx,
351 const char *name,
352 const char **attrs)
353 {
354 if (g_cancellable_is_cancelled (ctx->cancel)) {
355 xmlStopParser (ctx->xmlctx);
356 return;
357 }
358
359 if (ctx->in_unknown_elt) {
360 ctx->in_unknown_elt++;
361 return;
362 }
363
364 switch (ctx->state)
365 {
366 case RHYTHMDB_TREE_PARSER_STATE_START:
367 {
368 if (!strcmp (name, "rhythmdb")) {
369 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
370 for (; *attrs; attrs +=2) {
371 if (!strcmp (*attrs, "version")) {
372 const char *version = *(attrs+1);
373
374 rb_debug ("loading database version %s (%d)", version, version_to_int (version));
375 switch (version_to_int (version)) {
376 case 100:
377 case 110:
378 rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries (DB version 1.0 or 1.1)");
379 ctx->canonicalise_uris = TRUE;
380 case 120:
381 rb_debug ("reloading all file metadata to get MusicBrainz tags (DB version 1.2)");
382 ctx->reload_all_metadata = TRUE;
383 case 130:
384 case 140:
385 /* Avoid being warned twice for very old DBs */
386 if (ctx->canonicalise_uris == FALSE) {
387 rb_debug ("old version of rhythmdb, performing URI canonicalisation for all entries (DB version %s)", version);
388 ctx->canonicalise_uris = TRUE;
389 }
390 case 150:
391 rb_debug ("Upgrade Podcasts remote vs. local locations");
392 ctx->update_podcasts = TRUE;
393 case 160:
394 rb_debug ("reloading all file metadata to get sortnames, album artist, comments, bpm and updating mountpoints");
395 ctx->reload_all_metadata = TRUE;
396 ctx->update_local_mountpoints = TRUE;
397 case 170:
398 rb_debug ("reloading all file metadata to get new media types");
399 ctx->reload_all_metadata = TRUE;
400 case RHYTHMDB_TREE_XML_VERSION_INT:
401 /* current version */
402 break;
403 default:
404 if (version_to_int (version) > RHYTHMDB_TREE_XML_VERSION_INT) {
405 g_set_error (ctx->error,
406 RHYTHMDB_TREE_ERROR,
407 RHYTHMDB_TREE_ERROR_DATABASE_TOO_NEW,
408 _("The database was created by a later version of Rhythmbox."
409 " This version of Rhythmbox cannot read the database."));
410 xmlStopParser (ctx->xmlctx);
411 }
412 }
413 } else {
414 g_assert_not_reached ();
415 }
416 }
417
418 } else {
419 ctx->in_unknown_elt++;
420 }
421
422 break;
423 }
424 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
425 {
426 if (!strcmp (name, "entry")) {
427 RhythmDBEntryType *type = NULL;
428 const char *typename = NULL;
429 for (; *attrs; attrs +=2) {
430 if (!strcmp (*attrs, "type")) {
431 typename = *(attrs+1);
432 type = rhythmdb_entry_type_get_by_name (RHYTHMDB (ctx->db), typename);
433 break;
434 }
435 }
436
437 g_assert (typename);
438 if (type != NULL) {
439 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
440 ctx->entry = rhythmdb_entry_allocate (RHYTHMDB (ctx->db), type);
441 ctx->entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
442 ctx->has_date = FALSE;
443 } else {
444 rb_debug ("reading unknown entry");
445 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
446 ctx->unknown_entry = g_new0 (RhythmDBUnknownEntry, 1);
447 ctx->unknown_entry->typename = rb_refstring_new (typename);
448 }
449 } else {
450 ctx->in_unknown_elt++;
451 }
452 break;
453 }
454 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
455 if (strcmp (name, "keyword") == 0) {
456 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD;
457 } else {
458 int val = rhythmdb_propid_from_nice_elt_name (RHYTHMDB (ctx->db), BAD_CAST name);
459 if (val < 0) {
460 ctx->in_unknown_elt++;
461 break;
462 }
463
464 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY;
465 ctx->propid = val;
466 }
467 g_string_truncate (ctx->buf, 0);
468 break;
469 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
470 {
471 RhythmDBUnknownEntryProperty *prop;
472
473 prop = g_new0 (RhythmDBUnknownEntryProperty, 1);
474 prop->name = rb_refstring_new (name);
475
476 ctx->unknown_entry->properties = g_list_prepend (ctx->unknown_entry->properties, prop);
477 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY;
478 g_string_truncate (ctx->buf, 0);
479 break;
480 }
481 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
482 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
483 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD:
484 case RHYTHMDB_TREE_PARSER_STATE_END:
485 break;
486 }
487 }
488
489 static void
490 rhythmdb_tree_parser_end_element (struct RhythmDBTreeLoadContext *ctx,
491 const char *name)
492 {
493 if (g_cancellable_is_cancelled (ctx->cancel)) {
494 xmlStopParser (ctx->xmlctx);
495 return;
496 }
497
498 if (ctx->in_unknown_elt) {
499 ctx->in_unknown_elt--;
500 return;
501 }
502
503 switch (ctx->state)
504 {
505 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
506 ctx->state = RHYTHMDB_TREE_PARSER_STATE_END;
507 break;
508 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
509 {
510 if (!ctx->has_date || ctx->reload_all_metadata) {
511 /* there is no date metadata, so this is from an old version
512 * reset the last-modified timestamp, so that the file is re-read
513 */
514 rb_debug ("pre-Date entry found, causing re-read");
515 ctx->entry->mtime = 0;
516 }
517 if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED) {
518 RhythmDBPodcastFields *podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (ctx->entry, RhythmDBPodcastFields);
519 /* Handle upgrades from 0.9.2.
520 * Previously, last-seen for podcast feeds was the time of the last post,
521 * and post-time was unused. Now, we want last-seen to be the time we
522 * last updated the feed, and post-time to be the time of the last post.
523 */
524 if (podcast->post_time == 0) {
525 podcast->post_time = ctx->entry->last_seen;
526 }
527 }
528 if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) {
529 /* When upgrading Podcasts from 0.11.6 and prior, we need to
530 * swap mountpoint and location if there is a mountpoint */
531 if (ctx->update_podcasts && ctx->entry->mountpoint != NULL) {
532 RBRefString *tmp;
533
534 rb_debug ("pre-Podcast avoidance found, swapping location/mountpoint");
535
536 tmp = ctx->entry->location;
537 ctx->entry->location = ctx->entry->mountpoint;
538 ctx->entry->mountpoint = tmp;
539 }
540 }
541 if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
542 /* Since we now care about mountpoints for all local entries, not just
543 * those on things that actually get mounted and unmounted, we need to
544 * ensure they're all correct.
545 */
546 if (ctx->update_local_mountpoints) {
547 const char *loc = rb_refstring_get (ctx->entry->location);
548 if (loc == NULL || g_str_has_prefix (loc, "file:///")) {
549 char *nmp;
550 nmp = rb_uri_get_mount_point (loc);
551 if (ctx->entry->mountpoint != NULL) {
552 rb_refstring_unref (ctx->entry->mountpoint);
553 ctx->entry->mountpoint = NULL;
554 }
555
556 if (nmp != NULL) {
557 ctx->entry->mountpoint = rb_refstring_new (nmp);
558 g_free (nmp);
559 }
560 }
561 }
562 }
563
564 if (ctx->entry->location != NULL && rb_refstring_get (ctx->entry->location)[0] != '\0') {
565 RhythmDBEntry *entry;
566
567 g_mutex_lock (&ctx->db->priv->entries_lock);
568 entry = g_hash_table_lookup (ctx->db->priv->entries, ctx->entry->location);
569 if (entry == NULL) {
570 rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx->db), ctx->entry);
571 rhythmdb_entry_insert (RHYTHMDB (ctx->db), ctx->entry);
572 if (++ctx->batch_count == RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
573 rhythmdb_commit (RHYTHMDB (ctx->db));
574 ctx->batch_count = 0;
575 }
576 } else if (ctx->entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST &&
577 entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
578 rb_debug ("found song entry with duplicate location for Podcast post %s. merging metadata",
579 rb_refstring_get (ctx->entry->location));
580
581 ctx->entry->play_count += entry->play_count;
582 if (ctx->entry->last_played < entry->last_played)
583 ctx->entry->last_played = entry->last_played;
584
585 /* Remove the song entry,
586 * deleting requires relinquishing the locks */
587 g_mutex_unlock (&ctx->db->priv->entries_lock);
588 rhythmdb_entry_delete (RHYTHMDB(ctx->db), entry);
589 g_mutex_lock (&ctx->db->priv->entries_lock);
590 rhythmdb_commit (RHYTHMDB (ctx->db));
591
592 /* And add the Podcast entry to the database */
593 rhythmdb_tree_entry_new_internal (RHYTHMDB (ctx->db), ctx->entry);
594 rhythmdb_entry_insert (RHYTHMDB (ctx->db), ctx->entry);
595 if (++ctx->batch_count == RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
596 rhythmdb_commit (RHYTHMDB (ctx->db));
597 ctx->batch_count = 0;
598 }
599 } else {
600 rb_debug ("found entry with duplicate location %s. merging metadata",
601 rb_refstring_get (ctx->entry->location));
602
603 entry->play_count += ctx->entry->play_count;
604
605 if (entry->rating < 0.01)
606 entry->rating = ctx->entry->rating;
607 else if (ctx->entry->rating > 0.01)
608 entry->rating = (entry->rating + ctx->entry->rating) / 2;
609
610 if (ctx->entry->last_played > entry->last_played)
611 entry->last_played = ctx->entry->last_played;
612
613 if (ctx->entry->first_seen < entry->first_seen)
614 entry->first_seen = ctx->entry->first_seen;
615
616 if (ctx->entry->last_seen > entry->last_seen)
617 entry->last_seen = ctx->entry->last_seen;
618
619 rhythmdb_entry_unref (ctx->entry);
620 }
621 g_mutex_unlock (&ctx->db->priv->entries_lock);
622 } else {
623 rb_debug ("found entry without location");
624 rhythmdb_entry_unref (ctx->entry);
625 }
626 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
627 ctx->entry = NULL;
628 break;
629 }
630 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
631 {
632 GList *entry_list;
633
634 rb_debug ("finished reading unknown entry");
635 ctx->unknown_entry->properties = g_list_reverse (ctx->unknown_entry->properties);
636
637 g_mutex_lock (&ctx->db->priv->entries_lock);
638 entry_list = g_hash_table_lookup (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename);
639 entry_list = g_list_prepend (entry_list, ctx->unknown_entry);
640 g_hash_table_insert (ctx->db->priv->unknown_entry_types, ctx->unknown_entry->typename, entry_list);
641 g_mutex_unlock (&ctx->db->priv->entries_lock);
642
643 ctx->state = RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB;
644 ctx->unknown_entry = NULL;
645 break;
646 }
647 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
648 {
649 GValue value = {0,};
650 gboolean set = FALSE;
651 gboolean skip = FALSE;
652
653 /* special case some properties for upgrade handling etc. */
654 switch (ctx->propid) {
655 case RHYTHMDB_PROP_DATE:
656 ctx->has_date = TRUE;
657 break;
658 case RHYTHMDB_PROP_LOCATION:
659 if (ctx->canonicalise_uris) {
660 char *canon = rb_canonicalise_uri (ctx->buf->str);
661
662 g_value_init (&value, G_TYPE_STRING);
663 g_value_take_string (&value, canon);
664 set = TRUE;
665 }
666 break;
667 /* drop replaygain properties */
668 case RHYTHMDB_PROP_TRACK_GAIN:
669 case RHYTHMDB_PROP_TRACK_PEAK:
670 case RHYTHMDB_PROP_ALBUM_GAIN:
671 case RHYTHMDB_PROP_ALBUM_PEAK:
672 skip = TRUE;
673 break;
674 default:
675 break;
676 }
677
678 if (!skip) {
679 if (!set) {
680 rhythmdb_read_encoded_property (RHYTHMDB (ctx->db), ctx->buf->str, ctx->propid, &value);
681 }
682
683 rhythmdb_entry_set_internal (RHYTHMDB (ctx->db), ctx->entry, FALSE, ctx->propid, &value);
684 g_value_unset (&value);
685 }
686
687 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
688 break;
689 }
690 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD:
691 {
692 RBRefString *keyword;
693
694 keyword = rb_refstring_new (ctx->buf->str);
695 rhythmdb_entry_keyword_add (RHYTHMDB(ctx->db), ctx->entry, keyword);
696 rb_refstring_unref (keyword);
697
698 ctx->state = RHYTHMDB_TREE_PARSER_STATE_ENTRY;
699 break;
700 }
701 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
702 {
703 RhythmDBUnknownEntryProperty *prop;
704
705 g_assert (ctx->unknown_entry->properties);
706 prop = ctx->unknown_entry->properties->data;
707 g_assert (prop->value == NULL);
708 prop->value = rb_refstring_new (ctx->buf->str);
709 rb_debug ("unknown entry property: %s = %s", rb_refstring_get (prop->name), rb_refstring_get (prop->value));
710
711 ctx->state = RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY;
712 break;
713 }
714 case RHYTHMDB_TREE_PARSER_STATE_START:
715 case RHYTHMDB_TREE_PARSER_STATE_END:
716 break;
717 }
718 }
719
720 static void
721 rhythmdb_tree_parser_characters (struct RhythmDBTreeLoadContext *ctx,
722 const char *data,
723 guint len)
724 {
725 if (g_cancellable_is_cancelled (ctx->cancel)) {
726 xmlStopParser (ctx->xmlctx);
727 return;
728 }
729
730 switch (ctx->state)
731 {
732 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_PROPERTY:
733 case RHYTHMDB_TREE_PARSER_STATE_ENTRY_KEYWORD:
734 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY_PROPERTY:
735 g_string_append_len (ctx->buf, data, len);
736 break;
737 case RHYTHMDB_TREE_PARSER_STATE_ENTRY:
738 case RHYTHMDB_TREE_PARSER_STATE_UNKNOWN_ENTRY:
739 case RHYTHMDB_TREE_PARSER_STATE_RHYTHMDB:
740 case RHYTHMDB_TREE_PARSER_STATE_START:
741 case RHYTHMDB_TREE_PARSER_STATE_END:
742 break;
743 }
744 }
745
746 static gboolean
747 rhythmdb_tree_load (RhythmDB *rdb,
748 GCancellable *cancel,
749 GError **error)
750 {
751 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
752 xmlParserCtxtPtr ctxt;
753 xmlSAXHandlerPtr sax_handler;
754 struct RhythmDBTreeLoadContext *ctx;
755 char *name;
756 GError *local_error;
757 gboolean ret;
758
759 local_error = NULL;
760
761 sax_handler = g_new0 (xmlSAXHandler, 1);
762 ctx = g_new0 (struct RhythmDBTreeLoadContext, 1);
763
764 sax_handler->startElement = (startElementSAXFunc) rhythmdb_tree_parser_start_element;
765 sax_handler->endElement = (endElementSAXFunc) rhythmdb_tree_parser_end_element;
766 sax_handler->characters = (charactersSAXFunc) rhythmdb_tree_parser_characters;
767
768 ctx->state = RHYTHMDB_TREE_PARSER_STATE_START;
769 ctx->db = db;
770 ctx->cancel = cancel;
771 ctx->buf = g_string_sized_new (RHYTHMDB_TREE_PARSER_INITIAL_BUFFER_SIZE);
772 ctx->error = &local_error;
773
774 g_object_get (G_OBJECT (db), "name", &name, NULL);
775
776 if (g_file_test (name, G_FILE_TEST_EXISTS)) {
777 ctxt = xmlCreateFileParserCtxt (name);
778 ctx->xmlctx = ctxt;
779 xmlFree (ctxt->sax);
780 ctxt->userData = ctx;
781 ctxt->sax = sax_handler;
782 xmlParseDocument (ctxt);
783 ctxt->sax = NULL;
784 xmlFreeParserCtxt (ctxt);
785
786 if (ctx->batch_count)
787 rhythmdb_commit (RHYTHMDB (ctx->db));
788 }
789
790 ret = TRUE;
791 if (local_error != NULL) {
792 g_propagate_error (error, local_error);
793 ret = FALSE;
794 }
795
796 g_string_free (ctx->buf, TRUE);
797 g_free (name);
798 g_free (sax_handler);
799 g_free (ctx);
800
801 return ret;
802 }
803
804 struct RhythmDBTreeSaveContext
805 {
806 RhythmDBTree *db;
807 FILE *handle;
808 char *error;
809 };
810
811 #ifdef HAVE_GNU_FWRITE_UNLOCKED
812 #define RHYTHMDB_FWRITE_REAL fwrite_unlocked
813 #define RHYTHMDB_FPUTC_REAL fputc_unlocked
814 #else
815 #define RHYTHMDB_FWRITE_REAL fwrite
816 #define RHYTHMDB_FPUTC_REAL fputc
817 #endif
818
819 #define RHYTHMDB_FWRITE(w,x,len,handle,error) do { \
820 if (error == NULL) { \
821 if (RHYTHMDB_FWRITE_REAL (w,x,len,handle) != len) { \
822 error = g_strdup (g_strerror (errno)); \
823 } \
824 } \
825 } while (0)
826
827 #define RHYTHMDB_FPUTC(x,handle,error) do { \
828 if (error == NULL) { \
829 if (RHYTHMDB_FPUTC_REAL (x,handle) == EOF) { \
830 error = g_strdup (g_strerror (errno)); \
831 } \
832 } \
833 } while (0)
834
835 #define RHYTHMDB_FWRITE_STATICSTR(STR, HANDLE, ERROR) RHYTHMDB_FWRITE(STR, 1, sizeof(STR)-1, HANDLE, ERROR)
836
837 static void
838 write_elt_name_open (struct RhythmDBTreeSaveContext *ctx,
839 const xmlChar *elt_name)
840 {
841 RHYTHMDB_FWRITE_STATICSTR (" <", ctx->handle, ctx->error);
842 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
843 RHYTHMDB_FPUTC ('>', ctx->handle, ctx->error);
844 }
845
846 static void
847 write_elt_name_close (struct RhythmDBTreeSaveContext *ctx,
848 const xmlChar *elt_name)
849 {
850 RHYTHMDB_FWRITE_STATICSTR ("</", ctx->handle, ctx->error);
851 RHYTHMDB_FWRITE (elt_name, 1, xmlStrlen (elt_name), ctx->handle, ctx->error);
852 RHYTHMDB_FWRITE_STATICSTR (">\n", ctx->handle, ctx->error);
853 }
854
855 static void
856 save_entry_string (struct RhythmDBTreeSaveContext *ctx,
857 const xmlChar *elt_name,
858 const char *str)
859 {
860 xmlChar *encoded;
861
862 g_return_if_fail (str != NULL);
863 write_elt_name_open (ctx, elt_name);
864 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST str);
865 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
866 g_free (encoded);
867 write_elt_name_close (ctx, elt_name);
868 }
869
870 static void
871 save_entry_string_if_set (struct RhythmDBTreeSaveContext *ctx,
872 const xmlChar *elt_name,
873 const char *str)
874 {
875 if (str == NULL || str[0] == '\0')
876 return;
877 save_entry_string (ctx, elt_name, str);
878 }
879
880 static void
881 save_entry_int (struct RhythmDBTreeSaveContext *ctx,
882 const xmlChar *elt_name,
883 int num)
884 {
885 char buf[92];
886 if (num == 0)
887 return;
888 write_elt_name_open (ctx, elt_name);
889 g_snprintf (buf, sizeof (buf), "%d", num);
890 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
891 write_elt_name_close (ctx, elt_name);
892 }
893
894 static void
895 save_entry_ulong (struct RhythmDBTreeSaveContext *ctx,
896 const xmlChar *elt_name,
897 gulong num,
898 gboolean save_zeroes)
899 {
900 char buf[92];
901
902 if (num == 0 && !save_zeroes)
903 return;
904 write_elt_name_open (ctx, elt_name);
905 g_snprintf (buf, sizeof (buf), "%lu", num);
906 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
907 write_elt_name_close (ctx, elt_name);
908 }
909
910 static void
911 save_entry_boolean (struct RhythmDBTreeSaveContext *ctx,
912 const xmlChar *elt_name,
913 gboolean val)
914 {
915 save_entry_ulong (ctx, elt_name, val ? 1 : 0, FALSE);
916 }
917
918 static void
919 save_entry_uint64 (struct RhythmDBTreeSaveContext *ctx,
920 const xmlChar *elt_name,
921 guint64 num)
922 {
923 char buf[92];
924
925 if (num == 0)
926 return;
927
928 write_elt_name_open (ctx, elt_name);
929 g_snprintf (buf, sizeof (buf), "%" G_GUINT64_FORMAT, num);
930 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
931 write_elt_name_close (ctx, elt_name);
932 }
933
934 static void
935 save_entry_double (struct RhythmDBTreeSaveContext *ctx,
936 const xmlChar *elt_name,
937 double num)
938 {
939 char buf[G_ASCII_DTOSTR_BUF_SIZE+1];
940
941 if (num > -0.001 && num < 0.001)
942 return;
943
944 write_elt_name_open (ctx, elt_name);
945 g_ascii_dtostr (buf, sizeof (buf), num);
946 RHYTHMDB_FWRITE (buf, 1, strlen (buf), ctx->handle, ctx->error);
947 write_elt_name_close (ctx, elt_name);
948 }
949
950 /* This code is intended to be highly optimized. This came at a small
951 * readability cost. Sorry about that.
952 */
953 static void
954 save_entry (RhythmDBTree *db,
955 RhythmDBEntry *entry,
956 struct RhythmDBTreeSaveContext *ctx)
957 {
958 RhythmDBPropType i;
959 RhythmDBPodcastFields *podcast = NULL;
960 xmlChar *encoded;
961 GList *keywords, *l;
962
963 if (ctx->error)
964 return;
965
966 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
967 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
968 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
969
970 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
971 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST rhythmdb_entry_type_get_name (entry->type));
972 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
973 g_free (encoded);
974
975 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
976
977 /* Skip over the first property - the type */
978 for (i = 1; i < RHYTHMDB_NUM_PROPERTIES; i++) {
979 const xmlChar *elt_name;
980
981 if (ctx->error)
982 return;
983
984 elt_name = rhythmdb_nice_elt_name_from_propid ((RhythmDB *) ctx->db, i);
985
986 switch (i) {
987 case RHYTHMDB_PROP_TYPE:
988 break;
989 case RHYTHMDB_PROP_ENTRY_ID:
990 break;
991 case RHYTHMDB_PROP_TITLE:
992 save_entry_string(ctx, elt_name, rb_refstring_get (entry->title));
993 break;
994 case RHYTHMDB_PROP_ALBUM:
995 save_entry_string(ctx, elt_name, rb_refstring_get (entry->album));
996 break;
997 case RHYTHMDB_PROP_ARTIST:
998 save_entry_string(ctx, elt_name, rb_refstring_get (entry->artist));
999 break;
1000 case RHYTHMDB_PROP_ALBUM_ARTIST:
1001 save_entry_string_if_set(ctx, elt_name, rb_refstring_get (entry->album_artist));
1002 break;
1003 case RHYTHMDB_PROP_GENRE:
1004 save_entry_string(ctx, elt_name, rb_refstring_get (entry->genre));
1005 break;
1006 case RHYTHMDB_PROP_COMMENT:
1007 save_entry_string_if_set(ctx, elt_name, rb_refstring_get (entry->comment));
1008 break;
1009 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
1010 save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->musicbrainz_trackid));
1011 break;
1012 case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID:
1013 save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->musicbrainz_artistid));
1014 break;
1015 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID:
1016 save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->musicbrainz_albumid));
1017 break;
1018 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID:
1019 save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->musicbrainz_albumartistid));
1020 break;
1021 case RHYTHMDB_PROP_ARTIST_SORTNAME:
1022 save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->artist_sortname));
1023 break;
1024 case RHYTHMDB_PROP_ALBUM_SORTNAME:
1025 save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->album_sortname));
1026 break;
1027 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
1028 save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->album_artist_sortname));
1029 break;
1030 case RHYTHMDB_PROP_TRACK_NUMBER:
1031 save_entry_ulong (ctx, elt_name, entry->tracknum, FALSE);
1032 break;
1033 case RHYTHMDB_PROP_DISC_NUMBER:
1034 save_entry_ulong (ctx, elt_name, entry->discnum, FALSE);
1035 break;
1036 case RHYTHMDB_PROP_DATE:
1037 if (g_date_valid (&entry->date))
1038 save_entry_ulong (ctx, elt_name, g_date_get_julian (&entry->date), TRUE);
1039 else
1040 save_entry_ulong (ctx, elt_name, 0, TRUE);
1041 break;
1042 case RHYTHMDB_PROP_DURATION:
1043 save_entry_ulong (ctx, elt_name, entry->duration, FALSE);
1044 break;
1045 case RHYTHMDB_PROP_BITRATE:
1046 save_entry_int(ctx, elt_name, entry->bitrate);
1047 break;
1048 case RHYTHMDB_PROP_LOCATION:
1049 save_entry_string(ctx, elt_name, rb_refstring_get (entry->location));
1050 break;
1051 case RHYTHMDB_PROP_BPM:
1052 save_entry_double(ctx, elt_name, entry->bpm);
1053 break;
1054 case RHYTHMDB_PROP_MOUNTPOINT:
1055 save_entry_string_if_set (ctx, elt_name, rb_refstring_get (entry->mountpoint));
1056 break;
1057 case RHYTHMDB_PROP_FILE_SIZE:
1058 save_entry_uint64(ctx, elt_name, entry->file_size);
1059 break;
1060 case RHYTHMDB_PROP_MEDIA_TYPE:
1061 save_entry_string(ctx, elt_name, rb_refstring_get (entry->media_type));
1062 break;
1063 case RHYTHMDB_PROP_MTIME:
1064 save_entry_ulong (ctx, elt_name, entry->mtime, FALSE);
1065 break;
1066 case RHYTHMDB_PROP_FIRST_SEEN:
1067 save_entry_ulong (ctx, elt_name, entry->first_seen, FALSE);
1068 break;
1069 case RHYTHMDB_PROP_LAST_SEEN:
1070 save_entry_ulong (ctx, elt_name, entry->last_seen, FALSE);
1071 break;
1072 case RHYTHMDB_PROP_RATING:
1073 save_entry_double(ctx, elt_name, entry->rating);
1074 break;
1075 case RHYTHMDB_PROP_PLAY_COUNT:
1076 save_entry_ulong (ctx, elt_name, entry->play_count, FALSE);
1077 break;
1078 case RHYTHMDB_PROP_LAST_PLAYED:
1079 save_entry_ulong (ctx, elt_name, entry->last_played, FALSE);
1080 break;
1081 case RHYTHMDB_PROP_HIDDEN:
1082 {
1083 gboolean hidden = ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
1084 save_entry_boolean (ctx, elt_name, hidden);
1085 }
1086 break;
1087 case RHYTHMDB_PROP_STATUS:
1088 if (podcast)
1089 save_entry_ulong (ctx, elt_name, podcast->status, FALSE);
1090 break;
1091 case RHYTHMDB_PROP_DESCRIPTION:
1092 if (podcast && podcast->description)
1093 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->description));
1094 break;
1095 case RHYTHMDB_PROP_SUBTITLE:
1096 if (podcast && podcast->subtitle)
1097 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->subtitle));
1098 break;
1099 case RHYTHMDB_PROP_SUMMARY:
1100 if (podcast && podcast->summary)
1101 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->summary));
1102 break;
1103 case RHYTHMDB_PROP_LANG:
1104 if (podcast && podcast->lang)
1105 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->lang));
1106 break;
1107 case RHYTHMDB_PROP_COPYRIGHT:
1108 if (podcast && podcast->copyright)
1109 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->copyright));
1110 break;
1111 case RHYTHMDB_PROP_IMAGE:
1112 if (podcast && podcast->image)
1113 save_entry_string(ctx, elt_name, rb_refstring_get (podcast->image));
1114 break;
1115 case RHYTHMDB_PROP_POST_TIME:
1116 if (podcast)
1117 save_entry_ulong (ctx, elt_name, podcast->post_time, FALSE);
1118 break;
1119 case RHYTHMDB_PROP_KEYWORD:
1120 keywords = rhythmdb_entry_keywords_get (RHYTHMDB (db), entry);
1121
1122 for (l = keywords; l != NULL; l = g_list_next (l)) {
1123 RBRefString *keyword = (RBRefString*)l->data;
1124
1125 RHYTHMDB_FWRITE_STATICSTR (" <keyword>", ctx->handle, ctx->error);
1126 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST rb_refstring_get (keyword));
1127 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
1128 g_free (encoded);
1129 RHYTHMDB_FWRITE_STATICSTR ("</keyword>\n", ctx->handle, ctx->error);
1130
1131 rb_refstring_unref (keyword);
1132 }
1133
1134 g_list_free (keywords);
1135 break;
1136 case RHYTHMDB_PROP_TITLE_SORT_KEY:
1137 case RHYTHMDB_PROP_GENRE_SORT_KEY:
1138 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
1139 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
1140 case RHYTHMDB_PROP_ALBUM_ARTIST_SORT_KEY:
1141 case RHYTHMDB_PROP_ARTIST_SORTNAME_SORT_KEY:
1142 case RHYTHMDB_PROP_ALBUM_SORTNAME_SORT_KEY:
1143 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME_SORT_KEY:
1144 case RHYTHMDB_PROP_TITLE_FOLDED:
1145 case RHYTHMDB_PROP_GENRE_FOLDED:
1146 case RHYTHMDB_PROP_ARTIST_FOLDED:
1147 case RHYTHMDB_PROP_ALBUM_FOLDED:
1148 case RHYTHMDB_PROP_ALBUM_ARTIST_FOLDED:
1149 case RHYTHMDB_PROP_ARTIST_SORTNAME_FOLDED:
1150 case RHYTHMDB_PROP_ALBUM_SORTNAME_FOLDED:
1151 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME_FOLDED:
1152 case RHYTHMDB_PROP_LAST_PLAYED_STR:
1153 case RHYTHMDB_PROP_PLAYBACK_ERROR:
1154 case RHYTHMDB_PROP_FIRST_SEEN_STR:
1155 case RHYTHMDB_PROP_LAST_SEEN_STR:
1156 case RHYTHMDB_PROP_SEARCH_MATCH:
1157 case RHYTHMDB_PROP_YEAR:
1158 case RHYTHMDB_NUM_PROPERTIES:
1159 /* obsolete replaygain properties */
1160 case RHYTHMDB_PROP_TRACK_GAIN:
1161 case RHYTHMDB_PROP_TRACK_PEAK:
1162 case RHYTHMDB_PROP_ALBUM_GAIN:
1163 case RHYTHMDB_PROP_ALBUM_PEAK:
1164 break;
1165 }
1166 }
1167
1168 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
1169 }
1170
1171 static void
1172 save_entry_type (const char *name,
1173 RhythmDBEntryType *entry_type,
1174 struct RhythmDBTreeSaveContext *ctx)
1175 {
1176 gboolean save_to_disk = FALSE;
1177 g_object_get (entry_type, "save-to-disk", &save_to_disk, NULL);
1178 if (save_to_disk == FALSE)
1179 return;
1180
1181 rb_debug ("saving entries of type %s", name);
1182 rhythmdb_hash_tree_foreach (RHYTHMDB (ctx->db), entry_type,
1183 (RBTreeEntryItFunc) save_entry,
1184 NULL, NULL, NULL, ctx);
1185 }
1186
1187 static void
1188 save_unknown_entry_type (RBRefString *typename,
1189 GList *entries,
1190 struct RhythmDBTreeSaveContext *ctx)
1191 {
1192 GList *t;
1193
1194 for (t = entries; t != NULL; t = t->next) {
1195 RhythmDBUnknownEntry *entry;
1196 xmlChar *encoded;
1197 GList *p;
1198
1199 if (ctx->error)
1200 return;
1201
1202 entry = (RhythmDBUnknownEntry *)t->data;
1203
1204 RHYTHMDB_FWRITE_STATICSTR (" <entry type=\"", ctx->handle, ctx->error);
1205 encoded = xmlEncodeEntitiesReentrant (NULL, BAD_CAST rb_refstring_get (entry->typename));
1206 RHYTHMDB_FWRITE (encoded, 1, xmlStrlen (encoded), ctx->handle, ctx->error);
1207 g_free (encoded);
1208
1209 RHYTHMDB_FWRITE_STATICSTR ("\">\n", ctx->handle, ctx->error);
1210
1211 for (p = entry->properties; p != NULL; p = p->next) {
1212 RhythmDBUnknownEntryProperty *prop;
1213 prop = (RhythmDBUnknownEntryProperty *) p->data;
1214 save_entry_string(ctx, (const xmlChar *)rb_refstring_get (prop->name), rb_refstring_get (prop->value));
1215 }
1216
1217 RHYTHMDB_FWRITE_STATICSTR (" </entry>\n", ctx->handle, ctx->error);
1218 }
1219 }
1220
1221 static void
1222 rhythmdb_tree_save (RhythmDB *rdb)
1223 {
1224 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1225 char *name;
1226 GString *savepath;
1227 FILE *f;
1228 struct RhythmDBTreeSaveContext ctx;
1229
1230 g_object_get (G_OBJECT (db), "name", &name, NULL);
1231
1232 savepath = g_string_new (name);
1233 g_string_append (savepath, ".tmp");
1234
1235 f = fopen (savepath->str, "w");
1236
1237 if (!f) {
1238 g_warning ("Can't save XML: %s", g_strerror (errno));
1239 goto out;
1240 }
1241
1242 ctx.db = db;
1243 ctx.handle = f;
1244 ctx.error = NULL;
1245 RHYTHMDB_FWRITE_STATICSTR ("<?xml version=\"1.0\" standalone=\"yes\"?>\n"
1246 "<rhythmdb version=\"" RHYTHMDB_TREE_XML_VERSION "\">\n",
1247 ctx.handle, ctx.error);
1248
1249 rhythmdb_entry_type_foreach (rdb, (GHFunc) save_entry_type, &ctx);
1250 g_mutex_lock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
1251 g_hash_table_foreach (db->priv->unknown_entry_types,
1252 (GHFunc) save_unknown_entry_type,
1253 &ctx);
1254 g_mutex_unlock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
1255
1256 RHYTHMDB_FWRITE_STATICSTR ("</rhythmdb>\n", ctx.handle, ctx.error);
1257
1258 if (fclose (f) < 0) {
1259 g_warning ("Couldn't close %s: %s",
1260 savepath->str,
1261 g_strerror (errno));
1262 unlink (savepath->str);
1263 goto out;
1264 }
1265
1266 if (ctx.error != NULL) {
1267 g_warning ("Writing to the database failed: %s", ctx.error);
1268 g_free (ctx.error);
1269 unlink (savepath->str);
1270 } else {
1271 if (rename (savepath->str, name) < 0) {
1272 g_warning ("Couldn't rename %s to %s: %s",
1273 name, savepath->str,
1274 g_strerror (errno));
1275 unlink (savepath->str);
1276 }
1277 }
1278
1279 out:
1280 g_string_free (savepath, TRUE);
1281 g_free (name);
1282 return;
1283 }
1284
1285 #undef RHYTHMDB_FWRITE_ENCODED_STR
1286 #undef RHYTHMDB_FWRITE_STATICSTR
1287 #undef RHYTHMDB_FPUTC
1288 #undef RHYTHMDB_FWRITE
1289
1290 RhythmDB *
1291 rhythmdb_tree_new (const char *name)
1292 {
1293 RhythmDBTree *db = g_object_new (RHYTHMDB_TYPE_TREE, "name", name, NULL);
1294
1295 g_return_val_if_fail (db->priv != NULL, NULL);
1296
1297 return RHYTHMDB (db);
1298 }
1299
1300 /* must be called with the genres_lock held */
1301 static void
1302 set_entry_album (RhythmDBTree *db,
1303 RhythmDBEntry *entry,
1304 RhythmDBTreeProperty *artist,
1305 RBRefString *name)
1306 {
1307 struct RhythmDBTreeProperty *prop;
1308
1309 prop = get_or_create_album (db, artist, name);
1310 g_hash_table_insert (prop->children, entry, NULL);
1311 entry->data = prop;
1312 }
1313
1314 static void
1315 rhythmdb_tree_entry_new (RhythmDB *rdb,
1316 RhythmDBEntry *entry)
1317 {
1318 g_mutex_lock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
1319 rhythmdb_tree_entry_new_internal (rdb, entry);
1320 g_mutex_unlock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
1321 }
1322
1323 /* must be called with the entry lock held */
1324 static void
1325 rhythmdb_tree_entry_new_internal (RhythmDB *rdb, RhythmDBEntry *entry)
1326 {
1327 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
1328 RhythmDBTreeProperty *artist;
1329 RhythmDBTreeProperty *genre;
1330
1331 rb_assert_locked (&db->priv->entries_lock);
1332 g_assert (entry != NULL);
1333
1334 g_return_if_fail (entry->location != NULL);
1335
1336 if (entry->title == NULL) {
1337 g_warning ("Entry %s has missing title", rb_refstring_get (entry->location));
1338 entry->title = rb_refstring_new (_("Unknown"));
1339 }
1340 if (entry->artist == NULL) {
1341 g_warning ("Entry %s has missing artist", rb_refstring_get (entry->location));
1342 entry->artist = rb_refstring_new (_("Unknown"));
1343 }
1344 if (entry->album == NULL) {
1345 g_warning ("Entry %s has missing album", rb_refstring_get (entry->location));
1346 entry->album = rb_refstring_new (_("Unknown"));
1347 }
1348 if (entry->genre == NULL) {
1349 g_warning ("Entry %s has missing genre", rb_refstring_get (entry->location));
1350 entry->genre = rb_refstring_new (_("Unknown"));
1351 }
1352 if (entry->media_type == NULL) {
1353 g_warning ("Entry %s has missing media type", rb_refstring_get (entry->location));
1354 entry->media_type = rb_refstring_new ("unknown/unknown");
1355 }
1356
1357 /* Initialize the tree structure. */
1358 g_mutex_lock (&db->priv->genres_lock);
1359 genre = get_or_create_genre (db, entry->type, entry->genre);
1360 artist = get_or_create_artist (db, genre, entry->artist);
1361 set_entry_album (db, entry, artist, entry->album);
1362 g_mutex_unlock (&db->priv->genres_lock);
1363
1364 /* this accounts for the initial reference on the entry */
1365 g_hash_table_insert (db->priv->entries, entry->location, entry);
1366 g_hash_table_insert (db->priv->entry_ids, GINT_TO_POINTER (entry->id), entry);
1367
1368 entry->flags &= ~RHYTHMDB_ENTRY_TREE_LOADING;
1369 }
1370
1371 static RhythmDBTreeProperty *
1372 rhythmdb_tree_property_new (RhythmDBTree *db)
1373 {
1374 RhythmDBTreeProperty *ret = g_new0 (RhythmDBTreeProperty, 1);
1375 #ifndef G_DISABLE_ASSERT
1376 ret->magic = 0xf00dbeef;
1377 #endif
1378 return ret;
1379 }
1380
1381 static GHashTable *
1382 get_genres_hash_for_type (RhythmDBTree *db,
1383 RhythmDBEntryType *type)
1384 {
1385 GHashTable *table;
1386
1387 table = g_hash_table_lookup (db->priv->genres, type);
1388 if (table == NULL) {
1389 table = g_hash_table_new_full (rb_refstring_hash,
1390 rb_refstring_equal,
1391 (GDestroyNotify) rb_refstring_unref,
1392 NULL);
1393 if (table == NULL) {
1394 g_warning ("Out of memory\n");
1395 return NULL;
1396 }
1397 g_hash_table_insert (db->priv->genres,
1398 type,
1399 table);
1400 }
1401 return table;
1402 }
1403
1404 typedef void (*RBHFunc)(RhythmDBTree *db, GHashTable *genres, gpointer data);
1405
1406 typedef struct {
1407 RhythmDBTree *db;
1408 RBHFunc func;
1409 gpointer data;
1410 } GenresIterCtxt;
1411
1412 static void
1413 genres_process_one (gpointer key,
1414 gpointer value,
1415 gpointer user_data)
1416 {
1417 GenresIterCtxt *ctxt = (GenresIterCtxt *)user_data;
1418 ctxt->func (ctxt->db, (GHashTable *)value, ctxt->data);
1419 }
1420
1421 static void
1422 genres_hash_foreach (RhythmDBTree *db, RBHFunc func, gpointer data)
1423 {
1424 GenresIterCtxt ctxt;
1425
1426 ctxt.db = db;
1427 ctxt.func = func;
1428 ctxt.data = data;
1429 g_hash_table_foreach (db->priv->genres, genres_process_one, &ctxt);
1430 }
1431
1432 /* must be called with the genres lock held */
1433 static RhythmDBTreeProperty *
1434 get_or_create_genre (RhythmDBTree *db,
1435 RhythmDBEntryType *type,
1436 RBRefString *name)
1437 {
1438 RhythmDBTreeProperty *genre;
1439 GHashTable *table;
1440
1441 rb_assert_locked (&db->priv->genres_lock);
1442
1443 table = get_genres_hash_for_type (db, type);
1444 genre = g_hash_table_lookup (table, name);
1445
1446 if (G_UNLIKELY (genre == NULL)) {
1447 genre = rhythmdb_tree_property_new (db);
1448 genre->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1449 (GDestroyNotify) rb_refstring_unref,
1450 NULL);
1451 rb_refstring_ref (name);
1452 g_hash_table_insert (table, name, genre);
1453 genre->parent = NULL;
1454 }
1455
1456 return genre;
1457 }
1458
1459 /* must be called with the genres lock held */
1460 static RhythmDBTreeProperty *
1461 get_or_create_artist (RhythmDBTree *db,
1462 RhythmDBTreeProperty *genre,
1463 RBRefString *name)
1464 {
1465 RhythmDBTreeProperty *artist;
1466
1467 rb_assert_locked (&db->priv->genres_lock);
1468
1469 artist = g_hash_table_lookup (genre->children, name);
1470
1471 if (G_UNLIKELY (artist == NULL)) {
1472 artist = rhythmdb_tree_property_new (db);
1473 artist->children = g_hash_table_new_full (rb_refstring_hash, rb_refstring_equal,
1474 (GDestroyNotify) rb_refstring_unref,
1475 NULL);
1476 rb_refstring_ref (name);
1477 g_hash_table_insert (genre->children, name, artist);
1478 artist->parent = genre;
1479 }
1480
1481 return artist;
1482 }
1483
1484 /* must be called with the genres lock held */
1485 static RhythmDBTreeProperty *
1486 get_or_create_album (RhythmDBTree *db,
1487 RhythmDBTreeProperty *artist,
1488 RBRefString *name)
1489 {
1490 RhythmDBTreeProperty *album;
1491
1492 rb_assert_locked (&db->priv->genres_lock);
1493
1494 album = g_hash_table_lookup (artist->children, name);
1495
1496 if (G_UNLIKELY (album == NULL)) {
1497 album = rhythmdb_tree_property_new (db);
1498 album->children = g_hash_table_new (g_direct_hash, g_direct_equal);
1499 rb_refstring_ref (name);
1500 g_hash_table_insert (artist->children, name, album);
1501 album->parent = artist;
1502 }
1503
1504 return album;
1505 }
1506
1507 static gboolean
1508 remove_child (RhythmDBTreeProperty *parent,
1509 gconstpointer data)
1510 {
1511 g_assert (g_hash_table_remove (parent->children, data));
1512 if (g_hash_table_size (parent->children) <= 0) {
1513 return TRUE;
1514 }
1515 return FALSE;
1516 }
1517
1518 /* must be called with the genres lock held */
1519 static void
1520 remove_entry_from_album (RhythmDBTree *db,
1521 RhythmDBEntry *entry)
1522 {
1523 GHashTable *table;
1524
1525 rb_assert_locked (&db->priv->genres_lock);
1526
1527 rb_refstring_ref (entry->genre);
1528 rb_refstring_ref (entry->artist);
1529 rb_refstring_ref (entry->album);
1530
1531 table = get_genres_hash_for_type (db, entry->type);
1532 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry), entry)) {
1533 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent,
1534 entry->album)) {
1535
1536 if (remove_child (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent,
1537 entry->artist)) {
1538 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent->parent);
1539 g_assert (g_hash_table_remove (table, entry->genre));
1540 }
1541 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry)->parent);
1542 }
1543
1544 destroy_tree_property (RHYTHMDB_TREE_PROPERTY_FROM_ENTRY (entry));
1545 }
1546
1547 rb_refstring_unref (entry->genre);
1548 rb_refstring_unref (entry->artist);
1549 rb_refstring_unref (entry->album);
1550 }
1551
1552 static gboolean
1553 rhythmdb_tree_entry_set (RhythmDB *adb,
1554 RhythmDBEntry *entry,
1555 guint propid,
1556 const GValue *value)
1557 {
1558 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1559 RhythmDBEntryType *type;
1560
1561 type = entry->type;
1562
1563 /* don't process changes to entries we're loading, we'll get them
1564 * when the entry is complete. don't process changes for entries that
1565 * have been removed either.
1566 */
1567 if (entry->flags & (RHYTHMDB_ENTRY_TREE_LOADING | RHYTHMDB_ENTRY_TREE_REMOVED))
1568 return FALSE;
1569
1570 /* Handle special properties */
1571 switch (propid)
1572 {
1573 case RHYTHMDB_PROP_TYPE:
1574 {
1575 RhythmDBTreeProperty *artist;
1576 RhythmDBTreeProperty *genre;
1577
1578 g_mutex_lock (&db->priv->genres_lock);
1579 remove_entry_from_album (db, entry);
1580
1581 entry->type = g_value_get_object (value);
1582
1583 genre = get_or_create_genre (db, entry->type, entry->genre);
1584 artist = get_or_create_artist (db, genre, entry->artist);
1585 set_entry_album (db, entry, artist, entry->album);
1586 g_mutex_unlock (&db->priv->genres_lock);
1587
1588 return TRUE;
1589 }
1590 case RHYTHMDB_PROP_LOCATION:
1591 {
1592 RBRefString *s;
1593 /* We have to use the string in the entry itself as the hash key,
1594 * otherwise either we leak it, or the string vanishes when the
1595 * GValue is freed; this means we have to do the entry modification
1596 * here, rather than letting rhythmdb_entry_set_internal do it.
1597 */
1598 g_mutex_lock (&db->priv->entries_lock);
1599 g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1600
1601 s = rb_refstring_new (g_value_get_string (value));
1602 rb_refstring_unref (entry->location);
1603 entry->location = s;
1604 g_hash_table_insert (db->priv->entries, entry->location, entry);
1605 g_mutex_unlock (&db->priv->entries_lock);
1606
1607 return TRUE;
1608 }
1609 case RHYTHMDB_PROP_ALBUM:
1610 {
1611 const char *albumname = g_value_get_string (value);
1612
1613 if (strcmp (rb_refstring_get (entry->album), albumname)) {
1614 RhythmDBTreeProperty *artist;
1615 RhythmDBTreeProperty *genre;
1616
1617 rb_refstring_ref (entry->genre);
1618 rb_refstring_ref (entry->artist);
1619 rb_refstring_ref (entry->album);
1620
1621 g_mutex_lock (&db->priv->genres_lock);
1622 remove_entry_from_album (db, entry);
1623 genre = get_or_create_genre (db, type, entry->genre);
1624 artist = get_or_create_artist (db, genre, entry->artist);
1625 set_entry_album (db, entry, artist, rb_refstring_new (albumname));
1626 g_mutex_unlock (&db->priv->genres_lock);
1627
1628 rb_refstring_unref (entry->genre);
1629 rb_refstring_unref (entry->artist);
1630 rb_refstring_unref (entry->album);
1631 }
1632 break;
1633 }
1634 case RHYTHMDB_PROP_ARTIST:
1635 {
1636 const char *artistname = g_value_get_string (value);
1637
1638 if (strcmp (rb_refstring_get (entry->artist), artistname)) {
1639 RhythmDBTreeProperty *new_artist;
1640 RhythmDBTreeProperty *genre;
1641
1642 rb_refstring_ref (entry->genre);
1643 rb_refstring_ref (entry->artist);
1644 rb_refstring_ref (entry->album);
1645
1646 g_mutex_lock (&db->priv->genres_lock);
1647 remove_entry_from_album (db, entry);
1648 genre = get_or_create_genre (db, type, entry->genre);
1649 new_artist = get_or_create_artist (db, genre,
1650 rb_refstring_new (artistname));
1651 set_entry_album (db, entry, new_artist, entry->album);
1652 g_mutex_unlock (&db->priv->genres_lock);
1653
1654 rb_refstring_unref (entry->genre);
1655 rb_refstring_unref (entry->artist);
1656 rb_refstring_unref (entry->album);
1657 }
1658 break;
1659 }
1660 case RHYTHMDB_PROP_GENRE:
1661 {
1662 const char *genrename = g_value_get_string (value);
1663
1664 if (strcmp (rb_refstring_get (entry->genre), genrename)) {
1665 RhythmDBTreeProperty *new_genre;
1666 RhythmDBTreeProperty *new_artist;
1667
1668 rb_refstring_ref (entry->genre);
1669 rb_refstring_ref (entry->artist);
1670 rb_refstring_ref (entry->album);
1671
1672 g_mutex_lock (&db->priv->genres_lock);
1673 remove_entry_from_album (db, entry);
1674 new_genre = get_or_create_genre (db, type,
1675 rb_refstring_new (genrename));
1676 new_artist = get_or_create_artist (db, new_genre, entry->artist);
1677 set_entry_album (db, entry, new_artist, entry->album);
1678 g_mutex_unlock (&db->priv->genres_lock);
1679
1680 rb_refstring_unref (entry->genre);
1681 rb_refstring_unref (entry->artist);
1682 rb_refstring_unref (entry->album);
1683 }
1684 break;
1685 }
1686 default:
1687 break;
1688 }
1689
1690 return FALSE;
1691 }
1692
1693 static void
1694 rhythmdb_tree_entry_delete (RhythmDB *adb,
1695 RhythmDBEntry *entry)
1696 {
1697 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1698
1699 g_mutex_lock (&db->priv->genres_lock);
1700 remove_entry_from_album (db, entry);
1701 g_mutex_unlock (&db->priv->genres_lock);
1702
1703 /* remove all keywords */
1704 g_mutex_lock (&db->priv->keywords_lock);
1705 remove_entry_from_keywords (db, entry);
1706 g_mutex_unlock (&db->priv->keywords_lock);
1707
1708 g_mutex_lock (&db->priv->entries_lock);
1709 g_assert (g_hash_table_remove (db->priv->entries, entry->location));
1710 g_assert (g_hash_table_remove (db->priv->entry_ids, GINT_TO_POINTER (entry->id)));
1711
1712 entry->flags |= RHYTHMDB_ENTRY_TREE_REMOVED;
1713 rhythmdb_entry_unref (entry);
1714 g_mutex_unlock (&db->priv->entries_lock);
1715 }
1716
1717 typedef struct {
1718 RhythmDB *db;
1719 RhythmDBEntryType *type;
1720 } RbEntryRemovalCtxt;
1721
1722 /* must be called with the entries and genres locks held */
1723 static gboolean
1724 remove_one_song (gpointer key,
1725 RhythmDBEntry *entry,
1726 RbEntryRemovalCtxt *ctxt)
1727 {
1728 RhythmDBTree *db = RHYTHMDB_TREE(ctxt->db);
1729
1730 rb_assert_locked (&db->priv->entries_lock);
1731 rb_assert_locked (&db->priv->genres_lock);
1732
1733 g_return_val_if_fail (entry != NULL, FALSE);
1734
1735 if (entry->type == ctxt->type) {
1736 rhythmdb_emit_entry_deleted (ctxt->db, entry);
1737 g_mutex_lock (&db->priv->keywords_lock);
1738 remove_entry_from_keywords (db, entry);
1739 g_mutex_unlock (&db->priv->keywords_lock);
1740 remove_entry_from_album (db, entry);
1741 g_hash_table_remove (db->priv->entry_ids, GINT_TO_POINTER (entry->id));
1742 entry->flags |= RHYTHMDB_ENTRY_TREE_REMOVED;
1743 rhythmdb_entry_unref (entry);
1744 return TRUE;
1745 }
1746 return FALSE;
1747 }
1748
1749 static void
1750 rhythmdb_tree_entry_delete_by_type (RhythmDB *adb,
1751 RhythmDBEntryType *type)
1752 {
1753 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1754 RbEntryRemovalCtxt ctxt;
1755
1756 ctxt.db = adb;
1757 ctxt.type = type;
1758 g_mutex_lock (&db->priv->entries_lock);
1759 g_mutex_lock (&db->priv->genres_lock);
1760 g_hash_table_foreach_remove (db->priv->entries,
1761 (GHRFunc) remove_one_song, &ctxt);
1762 g_mutex_unlock (&db->priv->genres_lock);
1763 g_mutex_unlock (&db->priv->entries_lock);
1764 }
1765
1766 static void
1767 destroy_tree_property (RhythmDBTreeProperty *prop)
1768 {
1769 #ifndef G_DISABLE_ASSERT
1770 prop->magic = 0xf33df33d;
1771 #endif
1772 g_hash_table_destroy (prop->children);
1773 g_free (prop);
1774 }
1775
1776 typedef void (*RhythmDBTreeTraversalFunc) (RhythmDBTree *db, RhythmDBEntry *entry, gpointer data);
1777 typedef void (*RhythmDBTreeAlbumTraversalFunc) (RhythmDBTree *db, RhythmDBTreeProperty *album, gpointer data);
1778
1779 struct RhythmDBTreeTraversalData
1780 {
1781 RhythmDBTree *db;
1782 GPtrArray *query;
1783 RhythmDBTreeTraversalFunc func;
1784 gpointer data;
1785 gboolean *cancel;
1786 };
1787
1788 static gboolean
1789 rhythmdb_tree_evaluate_query (RhythmDB *adb,
1790 GPtrArray *query,
1791 RhythmDBEntry *entry)
1792 {
1793 RhythmDBTree *db = RHYTHMDB_TREE (adb);
1794 guint i;
1795 guint last_disjunction;
1796
1797 for (i = 0, last_disjunction = 0; i < query->len; i++) {
1798 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1799
1800 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
1801 if (evaluate_conjunctive_subquery (db, query, last_disjunction, i, entry))
1802 return TRUE;
1803
1804 last_disjunction = i + 1;
1805 }
1806 }
1807 if (evaluate_conjunctive_subquery (db, query, last_disjunction, query->len, entry))
1808 return TRUE;
1809 return FALSE;
1810 }
1811
1812 #define RHYTHMDB_PROPERTY_COMPARE(OP) \
1813 switch (rhythmdb_get_property_type (db, data->propid)) { \
1814 case G_TYPE_STRING: \
1815 if (g_strcmp0 (rhythmdb_entry_get_string (entry, data->propid), \
1816 g_value_get_string (data->val)) OP 0) \
1817 return FALSE; \
1818 break; \
1819 case G_TYPE_ULONG: \
1820 if (rhythmdb_entry_get_ulong (entry, data->propid) OP \
1821 g_value_get_ulong (data->val)) \
1822 return FALSE; \
1823 break; \
1824 case G_TYPE_BOOLEAN: \
1825 if (rhythmdb_entry_get_boolean (entry, data->propid) OP \
1826 g_value_get_boolean (data->val)) \
1827 return FALSE; \
1828 break; \
1829 case G_TYPE_UINT64: \
1830 if (rhythmdb_entry_get_uint64 (entry, data->propid) OP \
1831 g_value_get_uint64 (data->val)) \
1832 return FALSE; \
1833 break; \
1834 case G_TYPE_DOUBLE: \
1835 if (rhythmdb_entry_get_double (entry, data->propid) OP \
1836 g_value_get_double (data->val)) \
1837 return FALSE; \
1838 break; \
1839 case G_TYPE_OBJECT: \
1840 if ((gpointer)rhythmdb_entry_get_object (entry, data->propid) OP \
1841 g_value_get_object (data->val)) \
1842 return FALSE; \
1843 break; \
1844 default: \
1845 g_warning ("Unexpected type: %s", g_type_name (rhythmdb_get_property_type (db, data->propid))); \
1846 g_assert_not_reached (); \
1847 }
1848
1849 static gboolean
1850 search_match_properties (RhythmDB *db,
1851 RhythmDBEntry *entry,
1852 gchar **words)
1853 {
1854 const RhythmDBPropType props[] = {
1855 RHYTHMDB_PROP_TITLE_FOLDED,
1856 RHYTHMDB_PROP_ALBUM_FOLDED,
1857 RHYTHMDB_PROP_ARTIST_FOLDED,
1858 RHYTHMDB_PROP_GENRE_FOLDED
1859 };
1860 gboolean islike = TRUE;
1861 gchar **current;
1862 int i;
1863
1864 for (current = words; *current != NULL; current++) {
1865 gboolean word_found = FALSE;
1866
1867 for (i = 0; i < G_N_ELEMENTS (props); i++) {
1868 const char *entry_string = rhythmdb_entry_get_string (entry, props[i]);
1869 if (entry_string && (strstr (entry_string, *current) != NULL)) {
1870 /* the word was found, go to the next one */
1871 word_found = TRUE;
1872 break;
1873 }
1874 }
1875 if (!word_found) {
1876 /* the word wasn't in any of the properties*/
1877 islike = FALSE;
1878 break;
1879 }
1880 }
1881
1882 return islike;
1883 }
1884
1885 static gboolean
1886 evaluate_conjunctive_subquery (RhythmDBTree *dbtree,
1887 GPtrArray *query,
1888 guint base,
1889 guint max,
1890 RhythmDBEntry *entry)
1891
1892 {
1893 RhythmDB *db = (RhythmDB *) dbtree;
1894 guint i;
1895 /* Optimization possibility - we may get here without actually having
1896 * anything in the query. It would be faster to instead just merge
1897 * the child hash table into the query result hash.
1898 */
1899 for (i = base; i < max; i++) {
1900 RhythmDBQueryData *data = g_ptr_array_index (query, i);
1901
1902 switch (data->type) {
1903 case RHYTHMDB_QUERY_SUBQUERY:
1904 {
1905 gboolean matched = FALSE;
1906 GList *conjunctions = split_query_by_disjunctions (dbtree, data->subquery);
1907 GList *tem;
1908
1909 if (conjunctions == NULL)
1910 matched = TRUE;
1911
1912 for (tem = conjunctions; tem; tem = tem->next) {
1913 GPtrArray *subquery = tem->data;
1914 if (!matched && evaluate_conjunctive_subquery (dbtree, subquery,
1915 0, subquery->len,
1916 entry)) {
1917 matched = TRUE;
1918 }
1919 g_ptr_array_free (tem->data, TRUE);
1920 }
1921 g_list_free (conjunctions);
1922 if (!matched)
1923 return FALSE;
1924 }
1925 break;
1926 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN:
1927 case RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN:
1928 {
1929 gulong relative_time;
1930 GTimeVal current_time;
1931
1932 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_ULONG);
1933
1934 relative_time = g_value_get_ulong (data->val);
1935 g_get_current_time (¤t_time);
1936
1937 if (data->type == RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN) {
1938 if (!(rhythmdb_entry_get_ulong (entry, data->propid) >= (current_time.tv_sec - relative_time)))
1939 return FALSE;
1940 } else {
1941 if (!(rhythmdb_entry_get_ulong (entry, data->propid) < (current_time.tv_sec - relative_time)))
1942 return FALSE;
1943 }
1944 break;
1945 }
1946 case RHYTHMDB_QUERY_PROP_PREFIX:
1947 case RHYTHMDB_QUERY_PROP_SUFFIX:
1948 {
1949 const char *value_s;
1950 const char *entry_s;
1951
1952 g_assert (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING);
1953
1954 value_s = g_value_get_string (data->val);
1955 entry_s = rhythmdb_entry_get_string (entry, data->propid);
1956
1957 if (data->type == RHYTHMDB_QUERY_PROP_PREFIX && !g_str_has_prefix (entry_s, value_s))
1958 return FALSE;
1959 if (data->type == RHYTHMDB_QUERY_PROP_SUFFIX && !g_str_has_suffix (entry_s, value_s))
1960 return FALSE;
1961
1962 break;
1963 }
1964 case RHYTHMDB_QUERY_PROP_LIKE:
1965 case RHYTHMDB_QUERY_PROP_NOT_LIKE:
1966 {
1967 if (data->propid == RHYTHMDB_PROP_KEYWORD) {
1968 const char *str;
1969 RBRefString *keyword;
1970 gboolean has;
1971
1972 str = g_value_get_string (data->val);
1973 keyword = rb_refstring_find (str);
1974 if (keyword != NULL) {
1975 has = rhythmdb_tree_entry_keyword_has (db, entry, keyword);
1976 } else {
1977 has = FALSE;
1978 }
1979 if ((data->type == RHYTHMDB_QUERY_PROP_LIKE) ^ has)
1980 return FALSE;
1981 else
1982 continue;
1983 break;
1984 } else if (rhythmdb_get_property_type (db, data->propid) == G_TYPE_STRING) {
1985 gboolean islike;
1986
1987 if (data->propid == RHYTHMDB_PROP_SEARCH_MATCH) {
1988 /* this is a special property, that should match several things */
1989 islike = search_match_properties (db, entry, g_value_get_boxed (data->val));
1990
1991 } else {
1992 const gchar *value_string = g_value_get_string (data->val);
1993 const char *entry_string = rhythmdb_entry_get_string (entry, data->propid);
1994
1995 /* check in case the property is NULL, the value should never be NULL */
1996 if (entry_string == NULL)
1997 return FALSE;
1998
1999 islike = (strstr (entry_string, value_string) != NULL);
2000 }
2001
2002 if ((data->type == RHYTHMDB_QUERY_PROP_LIKE) ^ islike)
2003 return FALSE;
2004 else
2005 continue;
2006 break;
2007 }
2008 /* Fall through */
2009 }
2010 case RHYTHMDB_QUERY_PROP_EQUALS:
2011 RHYTHMDB_PROPERTY_COMPARE (!=)
2012 break;
2013 case RHYTHMDB_QUERY_PROP_NOT_EQUAL:
2014 RHYTHMDB_PROPERTY_COMPARE (==)
2015 break;
2016 case RHYTHMDB_QUERY_PROP_GREATER:
2017 RHYTHMDB_PROPERTY_COMPARE (<)
2018 break;
2019 case RHYTHMDB_QUERY_PROP_LESS:
2020 RHYTHMDB_PROPERTY_COMPARE (>)
2021 break;
2022 case RHYTHMDB_QUERY_END:
2023 case RHYTHMDB_QUERY_DISJUNCTION:
2024 case RHYTHMDB_QUERY_PROP_YEAR_EQUALS:
2025 case RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL:
2026 case RHYTHMDB_QUERY_PROP_YEAR_LESS:
2027 case RHYTHMDB_QUERY_PROP_YEAR_GREATER:
2028 g_assert_not_reached ();
2029 break;
2030 }
2031 }
2032 return TRUE;
2033 }
2034
2035 static void
2036 do_conjunction (RhythmDBEntry *entry,
2037 gpointer unused,
2038 struct RhythmDBTreeTraversalData *data)
2039 {
2040 if (G_UNLIKELY (*data->cancel))
2041 return;
2042 /* Finally, we actually evaluate the query! */
2043 if (evaluate_conjunctive_subquery (data->db, data->query, 0, data->query->len,
2044 entry)) {
2045 data->func (data->db, entry, data->data);
2046 }
2047 }
2048
2049 static void
2050 conjunctive_query_songs (const char *name,
2051 RhythmDBTreeProperty *album,
2052 struct RhythmDBTreeTraversalData *data)
2053 {
2054 if (G_UNLIKELY (*data->cancel))
2055 return;
2056 g_hash_table_foreach (album->children, (GHFunc) do_conjunction, data);
2057 }
2058
2059 static GPtrArray *
2060 clone_remove_ptr_array_index (GPtrArray *arr,
2061 guint index)
2062 {
2063 GPtrArray *ret = g_ptr_array_new ();
2064 guint i;
2065 for (i = 0; i < arr->len; i++)
2066 if (i != index)
2067 g_ptr_array_add (ret, g_ptr_array_index (arr, i));
2068
2069 return ret;
2070 }
2071
2072 static void
2073 conjunctive_query_albums (const char *name,
2074 RhythmDBTreeProperty *artist,
2075 struct RhythmDBTreeTraversalData *data)
2076 {
2077 guint i;
2078 int album_query_idx = -1;
2079
2080 if (G_UNLIKELY (*data->cancel))
2081 return;
2082
2083 for (i = 0; i < data->query->len; i++) {
2084 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
2085 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
2086 && qdata->propid == RHYTHMDB_PROP_ALBUM) {
2087 if (album_query_idx > 0)
2088 return;
2089 album_query_idx = i;
2090
2091 }
2092 }
2093
2094 if (album_query_idx >= 0) {
2095 RhythmDBTreeProperty *album;
2096 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, album_query_idx);
2097 RBRefString *albumname = rb_refstring_new (g_value_get_string (qdata->val));
2098 GPtrArray *oldquery = data->query;
2099
2100 data->query = clone_remove_ptr_array_index (data->query, album_query_idx);
2101
2102 album = g_hash_table_lookup (artist->children, albumname);
2103
2104 if (album != NULL) {
2105 conjunctive_query_songs (rb_refstring_get (albumname), album, data);
2106 }
2107 g_ptr_array_free (data->query, TRUE);
2108 data->query = oldquery;
2109 return;
2110 }
2111
2112 g_hash_table_foreach (artist->children, (GHFunc) conjunctive_query_songs, data);
2113 }
2114
2115 static void
2116 conjunctive_query_artists (const char *name,
2117 RhythmDBTreeProperty *genre,
2118 struct RhythmDBTreeTraversalData *data)
2119 {
2120 guint i;
2121 int artist_query_idx = -1;
2122
2123 if (G_UNLIKELY (*data->cancel))
2124 return;
2125
2126 for (i = 0; i < data->query->len; i++) {
2127 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
2128 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
2129 && qdata->propid == RHYTHMDB_PROP_ARTIST) {
2130 if (artist_query_idx > 0)
2131 return;
2132 artist_query_idx = i;
2133
2134 }
2135 }
2136
2137 if (artist_query_idx >= 0) {
2138 RhythmDBTreeProperty *artist;
2139 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, artist_query_idx);
2140 RBRefString *artistname = rb_refstring_new (g_value_get_string (qdata->val));
2141 GPtrArray *oldquery = data->query;
2142
2143 data->query = clone_remove_ptr_array_index (data->query, artist_query_idx);
2144
2145 artist = g_hash_table_lookup (genre->children, artistname);
2146 if (artist != NULL) {
2147 conjunctive_query_albums (rb_refstring_get (artistname), artist, data);
2148 }
2149 g_ptr_array_free (data->query, TRUE);
2150 data->query = oldquery;
2151 return;
2152 }
2153
2154 g_hash_table_foreach (genre->children, (GHFunc) conjunctive_query_albums, data);
2155 }
2156
2157 static void
2158 conjunctive_query_genre (RhythmDBTree *db,
2159 GHashTable *genres,
2160 struct RhythmDBTreeTraversalData *data)
2161 {
2162 int genre_query_idx = -1;
2163 guint i;
2164
2165 if (G_UNLIKELY (*data->cancel))
2166 return;
2167
2168 for (i = 0; i < data->query->len; i++) {
2169 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, i);
2170 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
2171 && qdata->propid == RHYTHMDB_PROP_GENRE) {
2172 /* A song can't currently have two genres. So
2173 * if we get a conjunctive query for that, we
2174 * know the result must be the empty set. */
2175 if (genre_query_idx > 0)
2176 return;
2177 genre_query_idx = i;
2178
2179 }
2180 }
2181
2182 if (genre_query_idx >= 0) {
2183 RhythmDBTreeProperty *genre;
2184 RhythmDBQueryData *qdata = g_ptr_array_index (data->query, genre_query_idx);
2185 RBRefString *genrename = rb_refstring_new (g_value_get_string (qdata->val));
2186 GPtrArray *oldquery = data->query;
2187
2188 data->query = clone_remove_ptr_array_index (data->query, genre_query_idx);
2189
2190 genre = g_hash_table_lookup (genres, genrename);
2191 if (genre != NULL) {
2192 conjunctive_query_artists (rb_refstring_get (genrename), genre, data);
2193 }
2194 g_ptr_array_free (data->query, TRUE);
2195 data->query = oldquery;
2196 return;
2197 }
2198
2199 g_hash_table_foreach (genres, (GHFunc) conjunctive_query_artists, data);
2200 }
2201
2202 static void
2203 conjunctive_query (RhythmDBTree *db,
2204 GPtrArray *query,
2205 RhythmDBTreeTraversalFunc func,
2206 gpointer data,
2207 gboolean *cancel)
2208 {
2209 int type_query_idx = -1;
2210 guint i;
2211 struct RhythmDBTreeTraversalData *traversal_data;
2212
2213 for (i = 0; i < query->len; i++) {
2214 RhythmDBQueryData *qdata = g_ptr_array_index (query, i);
2215 if (qdata->type == RHYTHMDB_QUERY_PROP_EQUALS
2216 && qdata->propid == RHYTHMDB_PROP_TYPE) {
2217 /* A song can't have two types. */
2218 if (type_query_idx > 0)
2219 return;
2220 type_query_idx = i;
2221 }
2222 }
2223
2224 traversal_data = g_new (struct RhythmDBTreeTraversalData, 1);
2225 traversal_data->db = db;
2226 traversal_data->query = query;
2227 traversal_data->func = func;
2228 traversal_data->data = data;
2229 traversal_data->cancel = cancel;
2230
2231 g_mutex_lock (&db->priv->genres_lock);
2232 if (type_query_idx >= 0) {
2233 GHashTable *genres;
2234 RhythmDBEntryType *etype;
2235 RhythmDBQueryData *qdata = g_ptr_array_index (query, type_query_idx);
2236
2237 g_ptr_array_remove_index_fast (query, type_query_idx);
2238
2239 etype = g_value_get_object (qdata->val);
2240 genres = get_genres_hash_for_type (db, etype);
2241 if (genres != NULL) {
2242 conjunctive_query_genre (db, genres, traversal_data);
2243 } else {
2244 g_assert_not_reached ();
2245 }
2246 } else {
2247 /* FIXME */
2248 /* No type was given; punt and query everything */
2249 genres_hash_foreach (db, (RBHFunc)conjunctive_query_genre,
2250 traversal_data);
2251 }
2252 g_mutex_unlock (&db->priv->genres_lock);
2253
2254 g_free (traversal_data);
2255 }
2256
2257 static GList *
2258 split_query_by_disjunctions (RhythmDBTree *db,
2259 GPtrArray *query)
2260 {
2261 GList *conjunctions = NULL;
2262 guint i, j;
2263 guint last_disjunction = 0;
2264 GPtrArray *subquery = g_ptr_array_new ();
2265
2266 for (i = 0; i < query->len; i++) {
2267 RhythmDBQueryData *data = g_ptr_array_index (query, i);
2268 if (data->type == RHYTHMDB_QUERY_DISJUNCTION) {
2269
2270 /* Copy the subquery */
2271 for (j = last_disjunction; j < i; j++) {
2272 g_ptr_array_add (subquery, g_ptr_array_index (query, j));
2273 }
2274
2275 conjunctions = g_list_prepend (conjunctions, subquery);
2276 last_disjunction = i+1;
2277 g_assert (subquery->len > 0);
2278 subquery = g_ptr_array_new ();
2279 }
2280 }
2281
2282 /* Copy the last subquery, except for the QUERY_END */
2283 for (i = last_disjunction; i < query->len; i++) {
2284 g_ptr_array_add (subquery, g_ptr_array_index (query, i));
2285 }
2286
2287 if (subquery->len > 0)
2288 conjunctions = g_list_prepend (conjunctions, subquery);
2289 else
2290 g_ptr_array_free (subquery, TRUE);
2291
2292 return conjunctions;
2293 }
2294
2295 struct RhythmDBTreeQueryGatheringData
2296 {
2297 RhythmDBTree *db;
2298 GPtrArray *queue;
2299 GHashTable *entries;
2300 RhythmDBQueryResults *results;
2301 };
2302
2303 static void
2304 do_query_recurse (RhythmDBTree *db,
2305 GPtrArray *query,
2306 RhythmDBTreeTraversalFunc func,
2307 struct RhythmDBTreeQueryGatheringData *data,
2308 gboolean *cancel)
2309 {
2310 GList *conjunctions, *tem;
2311
2312 if (query == NULL)
2313 return;
2314
2315 conjunctions = split_query_by_disjunctions (db, query);
2316 rb_debug ("doing recursive query, %d conjunctions", g_list_length (conjunctions));
2317
2318 if (conjunctions == NULL)
2319 return;
2320
2321 /* If there is a disjunction involved, we must uniquify the entry hits. */
2322 if (conjunctions->next != NULL)
2323 data->entries = g_hash_table_new (g_direct_hash, g_direct_equal);
2324 else
2325 data->entries = NULL;
2326
2327 for (tem = conjunctions; tem; tem = tem->next) {
2328 if (G_UNLIKELY (*cancel))
2329 break;
2330 conjunctive_query (db, tem->data, func, data, cancel);
2331 g_ptr_array_free (tem->data, TRUE);
2332 }
2333
2334 if (data->entries != NULL)
2335 g_hash_table_destroy (data->entries);
2336
2337 g_list_free (conjunctions);
2338 }
2339
2340 static void
2341 handle_entry_match (RhythmDB *db,
2342 RhythmDBEntry *entry,
2343 struct RhythmDBTreeQueryGatheringData *data)
2344 {
2345
2346 if (data->entries
2347 && g_hash_table_lookup (data->entries, entry))
2348 return;
2349
2350 g_ptr_array_add (data->queue, entry);
2351 if (data->queue->len > RHYTHMDB_QUERY_MODEL_SUGGESTED_UPDATE_CHUNK) {
2352 rhythmdb_query_results_add_results (data->results, data->queue);
2353 data->queue = g_ptr_array_new ();
2354 }
2355 }
2356
2357 static void
2358 rhythmdb_tree_do_full_query (RhythmDB *adb,
2359 GPtrArray *query,
2360 RhythmDBQueryResults *results,
2361 gboolean *cancel)
2362 {
2363 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2364 struct RhythmDBTreeQueryGatheringData *data = g_new0 (struct RhythmDBTreeQueryGatheringData, 1);
2365
2366 data->results = results;
2367 data->queue = g_ptr_array_new ();
2368
2369 do_query_recurse (db, query, (RhythmDBTreeTraversalFunc) handle_entry_match, data, cancel);
2370
2371 rhythmdb_query_results_add_results (data->results, data->queue);
2372
2373 g_free (data);
2374 }
2375
2376 static RhythmDBEntry *
2377 rhythmdb_tree_entry_lookup_by_location (RhythmDB *adb,
2378 RBRefString *uri)
2379 {
2380 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2381 RhythmDBEntry *entry;
2382
2383 g_mutex_lock (&db->priv->entries_lock);
2384 entry = g_hash_table_lookup (db->priv->entries, uri);
2385 g_mutex_unlock (&db->priv->entries_lock);
2386
2387 return entry;
2388 }
2389
2390 static RhythmDBEntry *
2391 rhythmdb_tree_entry_lookup_by_id (RhythmDB *adb,
2392 gint id)
2393 {
2394 RhythmDBTree *db = RHYTHMDB_TREE (adb);
2395 RhythmDBEntry *entry;
2396
2397 g_mutex_lock (&db->priv->entries_lock);
2398 entry = g_hash_table_lookup (db->priv->entry_ids, GINT_TO_POINTER (id));
2399 g_mutex_unlock (&db->priv->entries_lock);
2400
2401 return entry;
2402 }
2403
2404 struct RhythmDBEntryForeachCtxt
2405 {
2406 RhythmDBTree *db;
2407 GFunc func;
2408 gpointer user_data;
2409 };
2410
2411 static void
2412 rhythmdb_tree_entry_foreach_func (gpointer key, RhythmDBEntry *val, GPtrArray *list)
2413 {
2414 rhythmdb_entry_ref (val);
2415 g_ptr_array_add (list, val);
2416 }
2417
2418 static void
2419 rhythmdb_tree_entry_foreach (RhythmDB *rdb, GFunc foreach_func, gpointer user_data)
2420 {
2421 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2422 GPtrArray *list;
2423 guint size, i;
2424
2425 g_mutex_lock (&db->priv->entries_lock);
2426 size = g_hash_table_size (db->priv->entries);
2427 list = g_ptr_array_sized_new (size);
2428 g_hash_table_foreach (db->priv->entries, (GHFunc)rhythmdb_tree_entry_foreach_func, list);
2429 g_mutex_unlock (&db->priv->entries_lock);
2430
2431 for (i = 0; i < size; i++) {
2432 RhythmDBEntry *entry = (RhythmDBEntry*)g_ptr_array_index (list, i);
2433 (*foreach_func) (entry, user_data);
2434 rhythmdb_entry_unref (entry);
2435 }
2436
2437 g_ptr_array_free (list, TRUE);
2438 }
2439
2440 static gint64
2441 rhythmdb_tree_entry_count (RhythmDB *rdb)
2442 {
2443 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2444 return g_hash_table_size (db->priv->entries);
2445 }
2446
2447 typedef struct {
2448 GFunc foreach_func;
2449 gpointer data;
2450 } ForeachTypeData;
2451
2452 static void
2453 _foreach_by_type_cb (RhythmDB *db, RhythmDBEntry *entry, ForeachTypeData *data)
2454 {
2455 data->foreach_func (entry, data->data);
2456 }
2457
2458 static void
2459 rhythmdb_tree_entry_foreach_by_type (RhythmDB *db,
2460 RhythmDBEntryType *type,
2461 GFunc foreach_func,
2462 gpointer data)
2463 {
2464 ForeachTypeData ftdata = {foreach_func, data};
2465
2466 rhythmdb_hash_tree_foreach (db, type,
2467 (RBTreeEntryItFunc) _foreach_by_type_cb,
2468 NULL, NULL, NULL, &ftdata);
2469 }
2470
2471 static void
2472 count_entries (RhythmDB *db, RhythmDBTreeProperty *album, gint64 *count)
2473 {
2474 *count += g_hash_table_size (album->children);
2475 }
2476
2477 static gint64
2478 rhythmdb_tree_entry_count_by_type (RhythmDB *db,
2479 RhythmDBEntryType *type)
2480 {
2481 gint64 count = 0;
2482 rhythmdb_hash_tree_foreach (db, type,
2483 NULL, (RBTreePropertyItFunc) count_entries, NULL, NULL,
2484 &count);
2485 return count;
2486 }
2487
2488
2489 /* this is called with keywords_lock held */
2490 static gboolean
2491 remove_entry_from_keyword_table (RBRefString *keyword,
2492 GHashTable *table,
2493 RhythmDBEntry *entry)
2494 {
2495 gboolean present;
2496
2497 present = g_hash_table_remove (table, entry);
2498 /* TODO: remove entry hash tables */
2499 return present;
2500 }
2501
2502 static gboolean
2503 rhythmdb_tree_entry_keyword_add (RhythmDB *rdb,
2504 RhythmDBEntry *entry,
2505 RBRefString *keyword)
2506 {
2507 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2508 GHashTable *keyword_table;
2509 gboolean present;
2510
2511 g_mutex_lock (&db->priv->keywords_lock);
2512 keyword_table = g_hash_table_lookup (db->priv->keywords, keyword);
2513 if (keyword_table != NULL) {
2514 /* it would be nice if _insert told us whether it was replacing a value */
2515 present = (g_hash_table_lookup (keyword_table, entry) != NULL);
2516 g_hash_table_insert (keyword_table, entry, GINT_TO_POINTER(1));
2517 } else {
2518 /* new keyword */
2519 present = FALSE;
2520
2521 keyword_table = g_hash_table_new (g_direct_hash, g_direct_equal);
2522 g_hash_table_insert (keyword_table, entry, GINT_TO_POINTER(1));
2523
2524 g_hash_table_insert (db->priv->keywords, rb_refstring_ref (keyword), keyword_table);
2525 }
2526
2527 g_mutex_unlock (&db->priv->keywords_lock);
2528
2529 return present;
2530 }
2531
2532 static gboolean
2533 rhythmdb_tree_entry_keyword_remove (RhythmDB *rdb,
2534 RhythmDBEntry *entry,
2535 RBRefString *keyword)
2536 {
2537 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2538 GHashTable *keyword_table;
2539 gboolean ret;
2540
2541 g_mutex_lock (&db->priv->keywords_lock);
2542 keyword_table = g_hash_table_lookup (db->priv->keywords, keyword);
2543 if (keyword_table != NULL) {
2544 ret = remove_entry_from_keyword_table (keyword, keyword_table, entry);
2545 } else {
2546 ret = FALSE;
2547 }
2548 g_mutex_unlock (&db->priv->keywords_lock);
2549
2550 return ret;
2551 }
2552
2553 static gboolean
2554 rhythmdb_tree_entry_keyword_has (RhythmDB *rdb,
2555 RhythmDBEntry *entry,
2556 RBRefString *keyword)
2557 {
2558 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2559 GHashTable *keyword_table;
2560 gboolean ret;
2561
2562 g_mutex_lock (&db->priv->keywords_lock);
2563 keyword_table = g_hash_table_lookup (db->priv->keywords, keyword);
2564 if (keyword_table != NULL) {
2565 ret = (g_hash_table_lookup (keyword_table, entry) != NULL);
2566 } else {
2567 ret = FALSE;
2568 }
2569 g_mutex_unlock (&db->priv->keywords_lock);
2570
2571 return ret;
2572 }
2573
2574 /* this is called with keywords_lock held */
2575 static void
2576 remove_entry_from_keywords (RhythmDBTree *db,
2577 RhythmDBEntry *entry)
2578 {
2579 g_hash_table_foreach (db->priv->keywords, (GHFunc)remove_entry_from_keyword_table, entry);
2580 }
2581
2582 struct RhythmDBTreeKeywordsGetData {
2583 RhythmDBTree *db;
2584 RhythmDBEntry *entry;
2585 GList *keywords;
2586 };
2587
2588 static void
2589 check_entry_existance (RBRefString *keyword,
2590 GHashTable *keyword_table,
2591 struct RhythmDBTreeKeywordsGetData *data)
2592 {
2593 gboolean present;
2594
2595 present = (g_hash_table_lookup (keyword_table, data->entry) != NULL);
2596 if (present) {
2597 data->keywords = g_list_prepend (data->keywords, rb_refstring_ref (keyword));
2598 }
2599 }
2600
2601 static GList*
2602 rhythmdb_tree_entry_keywords_get (RhythmDB *rdb,
2603 RhythmDBEntry *entry)
2604 {
2605 RhythmDBTree *db = RHYTHMDB_TREE (rdb);
2606 struct RhythmDBTreeKeywordsGetData data;
2607
2608 data.db = db;
2609 data.entry = entry;
2610 data.keywords = NULL;
2611
2612 g_mutex_lock (&db->priv->keywords_lock);
2613 g_hash_table_foreach (db->priv->keywords, (GHFunc)check_entry_existance, &data);
2614 g_mutex_unlock (&db->priv->keywords_lock);
2615
2616 return data.keywords;
2617 }
2618
2619
2620 struct HashTreeIteratorCtxt {
2621 RhythmDBTree *db;
2622 RBTreeEntryItFunc entry_func;
2623 RBTreePropertyItFunc album_func;
2624 RBTreePropertyItFunc artist_func;
2625 RBTreePropertyItFunc genres_func;
2626 gpointer data;
2627 };
2628
2629 static void
2630 hash_tree_entries_foreach (gpointer key,
2631 gpointer value,
2632 gpointer data)
2633 {
2634 RhythmDBEntry *entry = (RhythmDBEntry *) key;
2635 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2636
2637 g_assert (ctxt->entry_func);
2638
2639 ctxt->entry_func (ctxt->db, entry, ctxt->data);
2640 }
2641
2642 static void
2643 hash_tree_albums_foreach (gpointer key,
2644 gpointer value,
2645 gpointer data)
2646 {
2647 RhythmDBTreeProperty *album = (RhythmDBTreeProperty *)value;
2648 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2649
2650 if (ctxt->album_func) {
2651 ctxt->album_func (ctxt->db, album, ctxt->data);
2652 }
2653 if (ctxt->entry_func != NULL) {
2654 g_hash_table_foreach (album->children,
2655 hash_tree_entries_foreach,
2656 ctxt);
2657 }
2658 }
2659
2660 static void
2661 hash_tree_artists_foreach (gpointer key,
2662 gpointer value,
2663 gpointer data)
2664 {
2665 RhythmDBTreeProperty *artist = (RhythmDBTreeProperty *)value;
2666 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2667
2668 if (ctxt->artist_func) {
2669 ctxt->artist_func (ctxt->db, artist, ctxt->data);
2670 }
2671 if ((ctxt->album_func != NULL) || (ctxt->entry_func != NULL)) {
2672 g_hash_table_foreach (artist->children,
2673 hash_tree_albums_foreach,
2674 ctxt);
2675 }
2676 }
2677
2678 static void
2679 hash_tree_genres_foreach (gpointer key,
2680 gpointer value,
2681 gpointer data)
2682 {
2683 RhythmDBTreeProperty *genre = (RhythmDBTreeProperty *)value;
2684 struct HashTreeIteratorCtxt *ctxt = (struct HashTreeIteratorCtxt*)data;
2685
2686 if (ctxt->genres_func) {
2687 ctxt->genres_func (ctxt->db, genre, ctxt->data);
2688 }
2689
2690 if ((ctxt->album_func != NULL)
2691 || (ctxt->artist_func != NULL)
2692 || (ctxt->entry_func != NULL)) {
2693 g_hash_table_foreach (genre->children,
2694 hash_tree_artists_foreach,
2695 ctxt);
2696 }
2697 }
2698
2699 static void
2700 rhythmdb_hash_tree_foreach (RhythmDB *adb,
2701 RhythmDBEntryType *type,
2702 RBTreeEntryItFunc entry_func,
2703 RBTreePropertyItFunc album_func,
2704 RBTreePropertyItFunc artist_func,
2705 RBTreePropertyItFunc genres_func,
2706 gpointer data)
2707 {
2708 struct HashTreeIteratorCtxt ctxt;
2709 GHashTable *table;
2710
2711 ctxt.db = RHYTHMDB_TREE (adb);
2712 ctxt.album_func = album_func;
2713 ctxt.artist_func = artist_func;
2714 ctxt.genres_func = genres_func;
2715 ctxt.entry_func = entry_func;
2716 ctxt.data = data;
2717
2718 g_mutex_lock (&ctxt.db->priv->genres_lock);
2719 table = get_genres_hash_for_type (RHYTHMDB_TREE (adb), type);
2720 if (table == NULL) {
2721 return;
2722 }
2723 if ((ctxt.album_func != NULL)
2724 || (ctxt.artist_func != NULL)
2725 || (ctxt.genres_func != NULL)
2726 || (ctxt.entry_func != NULL)) {
2727 g_hash_table_foreach (table, hash_tree_genres_foreach, &ctxt);
2728 }
2729 g_mutex_unlock (&ctxt.db->priv->genres_lock);
2730 }
2731
2732 static void
2733 rhythmdb_tree_entry_type_registered (RhythmDB *db,
2734 RhythmDBEntryType *entry_type)
2735 {
2736 GList *entries = NULL;
2737 GList *e;
2738 gint count = 0;
2739 RhythmDBTree *rdb;
2740 char *name;
2741 RBRefString *rs_name;
2742
2743 rdb = RHYTHMDB_TREE (db);
2744 g_mutex_lock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
2745
2746 /* ugh, this sucks, maybe store the name as a refstring in the object? */
2747 g_object_get (entry_type, "name", &name, NULL);
2748 rs_name = rb_refstring_find (name);
2749
2750 if (rs_name)
2751 entries = g_hash_table_lookup (rdb->priv->unknown_entry_types, rs_name);
2752 if (entries == NULL) {
2753 g_mutex_unlock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
2754 rb_refstring_unref (rs_name);
2755 rb_debug ("no entries of newly registered type %s loaded from db", name);
2756 g_free (name);
2757 return;
2758 }
2759 g_free (name);
2760
2761 for (e = entries; e != NULL; e = e->next) {
2762 RhythmDBUnknownEntry *data;
2763 RhythmDBEntry *entry;
2764 GList *p;
2765
2766 data = (RhythmDBUnknownEntry *)e->data;
2767 entry = rhythmdb_entry_allocate (db, entry_type);
2768 entry->flags |= RHYTHMDB_ENTRY_TREE_LOADING;
2769 for (p = data->properties; p != NULL; p = p->next) {
2770 RhythmDBUnknownEntryProperty *prop;
2771 RhythmDBPropType propid;
2772 GValue value = {0,};
2773
2774 prop = (RhythmDBUnknownEntryProperty *) p->data;
2775 propid = rhythmdb_propid_from_nice_elt_name (db, (const xmlChar *) rb_refstring_get (prop->name));
2776
2777 rhythmdb_read_encoded_property (db, rb_refstring_get (prop->value), propid, &value);
2778 rhythmdb_entry_set_internal (db, entry, FALSE, propid, &value);
2779 g_value_unset (&value);
2780 }
2781 rhythmdb_tree_entry_new_internal (db, entry);
2782 rhythmdb_entry_insert (db, entry);
2783 count++;
2784 }
2785 rb_debug ("handled %d entries of newly registered type %s", count, name);
2786 rhythmdb_commit (db);
2787
2788 g_hash_table_remove (rdb->priv->unknown_entry_types, rs_name);
2789 g_mutex_unlock (&RHYTHMDB_TREE(rdb)->priv->entries_lock);
2790 free_unknown_entries (rs_name, entries, NULL);
2791 rb_refstring_unref (rs_name);
2792 }