2006-12-04 James Livingston <doclivingston@gmail.com>
[rhythmbox.git] / rhythmdb / rhythmdb.c
bloba10298b61122d2760e2168c9aafd7a5743399d9c
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of RhythmDB - Rhythmbox backend queryable database
5 * Copyright (C) 2003,2004 Colin Walters <walters@gnome.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.
23 #include "config.h"
25 #define G_IMPLEMENT_INLINES 1
26 #define __RHYTHMDB_C__
27 #include "rhythmdb.h"
28 #undef G_IMPLEMENT_INLINES
30 #include <string.h>
31 #include <libxml/tree.h>
32 #include <glib.h>
33 #include <glib-object.h>
34 #include <glib/gi18n.h>
35 #include <gobject/gvaluecollector.h>
36 #include <gdk/gdk.h>
37 #include <gconf/gconf-client.h>
39 #include "rb-marshal.h"
40 #include "rb-file-helpers.h"
41 #include "rb-debug.h"
42 #include "rb-util.h"
43 #include "rb-cut-and-paste-code.h"
44 #include "rb-preferences.h"
45 #include "eel-gconf-extensions.h"
46 #include "rhythmdb-private.h"
47 #include "rhythmdb-property-model.h"
48 #include "rb-dialog.h"
51 #define RB_PARSE_NICK_START (xmlChar *) "["
52 #define RB_PARSE_NICK_END (xmlChar *) "]"
54 GType rhythmdb_property_type_map[RHYTHMDB_NUM_PROPERTIES];
56 #define RHYTHMDB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE, RhythmDBPrivate))
57 G_DEFINE_ABSTRACT_TYPE(RhythmDB, rhythmdb, G_TYPE_OBJECT)
59 typedef struct
61 RhythmDB *db;
62 GPtrArray *query;
63 guint propid;
64 RhythmDBQueryResults *results;
65 gboolean cancel;
66 } RhythmDBQueryThreadData;
68 typedef struct
70 RhythmDB *db;
71 char *uri;
72 RhythmDBEntryType type;
73 } RhythmDBAddThreadData;
75 typedef struct
77 enum {
78 RHYTHMDB_ACTION_STAT,
79 RHYTHMDB_ACTION_LOAD,
80 RHYTHMDB_ACTION_SYNC
81 } type;
82 RBRefString *uri;
83 RhythmDBEntryType entry_type;
84 } RhythmDBAction;
86 static void rhythmdb_finalize (GObject *object);
87 static void rhythmdb_set_property (GObject *object,
88 guint prop_id,
89 const GValue *value,
90 GParamSpec *pspec);
91 static void rhythmdb_get_property (GObject *object,
92 guint prop_id,
93 GValue *value,
94 GParamSpec *pspec);
95 static void rhythmdb_thread_create (RhythmDB *db,
96 GThreadPool *pool,
97 GThreadFunc func,
98 gpointer data);
99 static void rhythmdb_read_enter (RhythmDB *db);
100 static void rhythmdb_read_leave (RhythmDB *db);
101 static gboolean rhythmdb_idle_poll_events (RhythmDB *db);
102 static gpointer add_thread_main (RhythmDBAddThreadData *data);
103 static gpointer action_thread_main (RhythmDB *db);
104 static gpointer query_thread_main (RhythmDBQueryThreadData *data);
105 static void rhythmdb_entry_set_mount_point (RhythmDB *db,
106 RhythmDBEntry *entry,
107 const gchar *realuri);
109 static gboolean free_entry_changes (RhythmDBEntry *entry,
110 GSList *changes,
111 RhythmDB *db);
112 static gboolean rhythmdb_idle_save (RhythmDB *db);
113 static void library_location_changed_cb (GConfClient *client,
114 guint cnxn_id,
115 GConfEntry *entry,
116 RhythmDB *db);
117 static void rhythmdb_sync_library_location (RhythmDB *db);
118 static void rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
119 guint propid);
120 static void rhythmdb_register_core_entry_types (RhythmDB *db);
121 static gboolean rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
122 GValue *return_accu,
123 const GValue *handler_return,
124 gpointer data);
126 enum
128 PROP_0,
129 PROP_NAME,
130 PROP_DRY_RUN,
131 PROP_NO_UPDATE,
134 enum
136 ENTRY_ADDED,
137 ENTRY_CHANGED,
138 ENTRY_DELETED,
139 ENTRY_EXTRA_METADATA_REQUEST,
140 ENTRY_EXTRA_METADATA_NOTIFY,
141 ENTRY_EXTRA_METADATA_GATHER,
142 LOAD_COMPLETE,
143 SAVE_COMPLETE,
144 SAVE_ERROR,
145 READ_ONLY,
146 LAST_SIGNAL
149 static guint rhythmdb_signals[LAST_SIGNAL] = { 0 };
151 static void
152 rhythmdb_class_init (RhythmDBClass *klass)
154 GObjectClass *object_class = G_OBJECT_CLASS (klass);
156 object_class->finalize = rhythmdb_finalize;
158 object_class->set_property = rhythmdb_set_property;
159 object_class->get_property = rhythmdb_get_property;
161 g_object_class_install_property (object_class,
162 PROP_NAME,
163 g_param_spec_string ("name",
164 "name",
165 "name",
166 NULL,
167 G_PARAM_READWRITE));
169 g_object_class_install_property (object_class,
170 PROP_DRY_RUN,
171 g_param_spec_boolean ("dry-run",
172 "dry run",
173 "Whether or not changes should be saved",
174 FALSE,
175 G_PARAM_READWRITE));
176 g_object_class_install_property (object_class,
177 PROP_NO_UPDATE,
178 g_param_spec_boolean ("no-update",
179 "no update",
180 "Whether or not to update the database",
181 FALSE,
182 G_PARAM_READWRITE));
183 rhythmdb_signals[ENTRY_ADDED] =
184 g_signal_new ("entry_added",
185 RHYTHMDB_TYPE,
186 G_SIGNAL_RUN_LAST,
187 G_STRUCT_OFFSET (RhythmDBClass, entry_added),
188 NULL, NULL,
189 g_cclosure_marshal_VOID__BOXED,
190 G_TYPE_NONE,
191 1, RHYTHMDB_TYPE_ENTRY);
193 rhythmdb_signals[ENTRY_DELETED] =
194 g_signal_new ("entry_deleted",
195 RHYTHMDB_TYPE,
196 G_SIGNAL_RUN_LAST,
197 G_STRUCT_OFFSET (RhythmDBClass, entry_deleted),
198 NULL, NULL,
199 g_cclosure_marshal_VOID__BOXED,
200 G_TYPE_NONE,
201 1, RHYTHMDB_TYPE_ENTRY);
203 rhythmdb_signals[ENTRY_CHANGED] =
204 g_signal_new ("entry_changed",
205 RHYTHMDB_TYPE,
206 G_SIGNAL_RUN_LAST,
207 G_STRUCT_OFFSET (RhythmDBClass, entry_changed),
208 NULL, NULL,
209 rb_marshal_VOID__BOXED_POINTER,
210 G_TYPE_NONE, 2,
211 RHYTHMDB_TYPE_ENTRY, G_TYPE_POINTER);
213 rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST] =
214 g_signal_new ("entry_extra_metadata_request",
215 G_OBJECT_CLASS_TYPE (object_class),
216 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
217 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_request),
218 rhythmdb_entry_extra_metadata_accumulator, NULL,
219 rb_marshal_BOXED__BOXED,
220 G_TYPE_VALUE, 1,
221 RHYTHMDB_TYPE_ENTRY);
223 rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY] =
224 g_signal_new ("entry_extra_metadata_notify",
225 G_OBJECT_CLASS_TYPE (object_class),
226 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
227 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_notify),
228 NULL, NULL,
229 rb_marshal_VOID__BOXED_STRING_BOXED,
230 G_TYPE_NONE, 3,
231 RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_VALUE);
233 rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER] =
234 g_signal_new ("entry_extra_metadata_gather",
235 G_OBJECT_CLASS_TYPE (object_class),
236 G_SIGNAL_RUN_LAST,
237 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_gather),
238 NULL, NULL,
239 #if (GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 10)
240 rb_marshal_VOID__BOXED_BOXED,
241 G_TYPE_NONE, 2,
242 RHYTHMDB_TYPE_ENTRY, G_TYPE_HASH_TABLE);
243 #else
244 /* work with glib < 2.10 */
245 rb_marshal_VOID__BOXED_POINTER,
246 G_TYPE_NONE, 2,
247 RHYTHMDB_TYPE_ENTRY, G_TYPE_POINTER);
248 #endif
250 rhythmdb_signals[LOAD_COMPLETE] =
251 g_signal_new ("load_complete",
252 RHYTHMDB_TYPE,
253 G_SIGNAL_RUN_LAST,
254 G_STRUCT_OFFSET (RhythmDBClass, load_complete),
255 NULL, NULL,
256 g_cclosure_marshal_VOID__VOID,
257 G_TYPE_NONE,
260 rhythmdb_signals[SAVE_COMPLETE] =
261 g_signal_new ("save_complete",
262 RHYTHMDB_TYPE,
263 G_SIGNAL_RUN_LAST,
264 G_STRUCT_OFFSET (RhythmDBClass, save_complete),
265 NULL, NULL,
266 g_cclosure_marshal_VOID__VOID,
267 G_TYPE_NONE,
270 rhythmdb_signals[SAVE_ERROR] =
271 g_signal_new ("save-error",
272 G_OBJECT_CLASS_TYPE (object_class),
273 G_SIGNAL_RUN_LAST,
274 G_STRUCT_OFFSET (RhythmDBClass, save_error),
275 NULL, NULL,
276 rb_marshal_VOID__STRING_POINTER,
277 G_TYPE_NONE,
279 G_TYPE_STRING,
280 G_TYPE_POINTER);
282 rhythmdb_signals[READ_ONLY] =
283 g_signal_new ("read-only",
284 G_OBJECT_CLASS_TYPE (object_class),
285 G_SIGNAL_RUN_LAST,
286 G_STRUCT_OFFSET (RhythmDBClass, read_only),
287 NULL, NULL,
288 g_cclosure_marshal_VOID__BOOLEAN,
289 G_TYPE_NONE,
291 G_TYPE_BOOLEAN);
293 g_type_class_add_private (klass, sizeof (RhythmDBPrivate));
296 static gboolean
297 metadata_field_from_prop (RhythmDBPropType prop,
298 RBMetaDataField *field)
300 switch (prop) {
301 case RHYTHMDB_PROP_TITLE:
302 *field = RB_METADATA_FIELD_TITLE;
303 return TRUE;
304 case RHYTHMDB_PROP_ARTIST:
305 *field = RB_METADATA_FIELD_ARTIST;
306 return TRUE;
307 case RHYTHMDB_PROP_ALBUM:
308 *field = RB_METADATA_FIELD_ALBUM;
309 return TRUE;
310 case RHYTHMDB_PROP_GENRE:
311 *field = RB_METADATA_FIELD_GENRE;
312 return TRUE;
313 case RHYTHMDB_PROP_TRACK_NUMBER:
314 *field = RB_METADATA_FIELD_TRACK_NUMBER;
315 return TRUE;
316 case RHYTHMDB_PROP_DISC_NUMBER:
317 *field = RB_METADATA_FIELD_DISC_NUMBER;
318 return TRUE;
319 case RHYTHMDB_PROP_DATE:
320 *field = RB_METADATA_FIELD_DATE;
321 return TRUE;
322 case RHYTHMDB_PROP_TRACK_GAIN:
323 *field = RB_METADATA_FIELD_TRACK_GAIN;
324 return TRUE;
325 case RHYTHMDB_PROP_TRACK_PEAK:
326 *field = RB_METADATA_FIELD_TRACK_PEAK;
327 return TRUE;
328 case RHYTHMDB_PROP_ALBUM_GAIN:
329 *field = RB_METADATA_FIELD_ALBUM_GAIN;
330 return TRUE;
331 case RHYTHMDB_PROP_ALBUM_PEAK:
332 *field = RB_METADATA_FIELD_ALBUM_PEAK;
333 return TRUE;
334 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
335 *field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
336 return TRUE;
337 default:
338 return FALSE;
342 static GType
343 extract_gtype_from_enum_entry (RhythmDB *db,
344 GEnumClass *klass,
345 guint i)
347 GType ret;
348 GEnumValue *value;
349 RBMetaDataField field;
350 char *typename;
351 char *typename_end;
353 value = g_enum_get_value (klass, i);
355 typename = strstr (value->value_nick, "(");
356 g_assert (typename != NULL);
358 typename_end = strstr (typename, ")");
359 g_assert (typename_end);
361 typename++;
362 typename = g_strndup (typename, typename_end-typename);
363 ret = g_type_from_name (typename);
364 g_free (typename);
366 /* Check to see whether this is a property that maps to
367 a RBMetaData property. */
368 if (metadata_field_from_prop (value->value, &field))
369 g_assert (ret == rb_metadata_get_field_type (field));
370 return ret;
373 static xmlChar *
374 extract_nice_name_from_enum_entry (RhythmDB *db,
375 GEnumClass *klass,
376 guint i)
378 GEnumValue *value;
379 xmlChar *nick;
380 const xmlChar *name;
381 const xmlChar *name_end;
383 value = g_enum_get_value (klass, i);
384 nick = BAD_CAST value->value_nick;
386 name = xmlStrstr (nick, RB_PARSE_NICK_START);
387 g_return_val_if_fail (name != NULL, NULL);
388 name_end = xmlStrstr (name, RB_PARSE_NICK_END);
389 name++;
391 return xmlStrndup (name, name_end - name);
394 static void
395 rhythmdb_init (RhythmDB *db)
397 guint i;
398 GEnumClass *prop_class;
400 db->priv = RHYTHMDB_GET_PRIVATE (db);
402 db->priv->action_queue = g_async_queue_new ();
403 db->priv->event_queue = g_async_queue_new ();
404 db->priv->restored_queue = g_async_queue_new ();
406 db->priv->query_thread_pool = g_thread_pool_new ((GFunc)query_thread_main,
407 NULL,
408 -1, FALSE, NULL);
409 /* Limit this pool to 3 threads. They'll each be thrashing the disk,
410 * so parallelism is limited.
412 db->priv->add_thread_pool = g_thread_pool_new ((GFunc)add_thread_main,
413 NULL,
414 3, FALSE, NULL);
416 db->priv->metadata = rb_metadata_new ();
418 prop_class = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
420 g_assert (prop_class->n_values == RHYTHMDB_NUM_PROPERTIES);
421 db->priv->column_xml_names = g_new0 (xmlChar *, RHYTHMDB_NUM_PROPERTIES);
423 /* Now, extract the GType and XML tag of each column from the
424 * enum descriptions, and cache that for later use. */
425 for (i = 0; i < prop_class->n_values; i++) {
426 rhythmdb_property_type_map[i] = extract_gtype_from_enum_entry (db, prop_class, i);
427 g_assert (rhythmdb_property_type_map[i] != G_TYPE_INVALID);
429 db->priv->column_xml_names[i] = extract_nice_name_from_enum_entry (db, prop_class, i);
430 g_assert (db->priv->column_xml_names[i]);
433 g_type_class_unref (prop_class);
435 db->priv->propname_map = g_hash_table_new (g_str_hash, g_str_equal);
437 for (i = 0; i < RHYTHMDB_NUM_PROPERTIES; i++) {
438 const xmlChar *name = rhythmdb_nice_elt_name_from_propid (db, i);
439 g_hash_table_insert (db->priv->propname_map, (gpointer) name, GINT_TO_POINTER (i));
442 db->priv->entry_type_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
443 db->priv->entry_type_map_mutex = g_mutex_new ();
444 db->priv->entry_type_mutex = g_mutex_new ();
445 rhythmdb_register_core_entry_types (db);
447 db->priv->stat_events = g_hash_table_new_full (gnome_vfs_uri_hash,
448 (GEqualFunc) gnome_vfs_uri_equal,
449 (GDestroyNotify) gnome_vfs_uri_unref,
450 NULL);
451 db->priv->stat_mutex = g_mutex_new ();
453 db->priv->change_mutex = g_mutex_new ();
455 db->priv->changed_entries = g_hash_table_new_full (NULL,
456 NULL,
457 (GDestroyNotify) rhythmdb_entry_unref,
458 NULL);
459 db->priv->added_entries = g_hash_table_new_full (NULL,
460 NULL,
461 (GDestroyNotify) rhythmdb_entry_unref,
462 NULL);
463 db->priv->deleted_entries = g_hash_table_new_full (NULL,
464 NULL,
465 (GDestroyNotify) rhythmdb_entry_unref,
466 NULL);
468 db->priv->event_poll_id = g_idle_add ((GSourceFunc) rhythmdb_idle_poll_events, db);
470 db->priv->saving_condition = g_cond_new ();
471 db->priv->saving_mutex = g_mutex_new ();
473 db->priv->can_save = TRUE;
474 db->priv->exiting = FALSE;
475 db->priv->saving = FALSE;
476 db->priv->dirty = FALSE;
478 db->priv->empty_string = rb_refstring_new ("");
479 db->priv->octet_stream_str = rb_refstring_new ("application/octet-stream");
481 db->priv->next_entry_id = 1;
483 rhythmdb_init_monitoring (db);
486 static GError *
487 make_access_failed_error (const char *uri, GnomeVFSResult result)
489 char *unescaped;
490 char *utf8ised;
491 GError *error;
493 /* make sure the URI we put in the error message is valid utf8 */
494 unescaped = gnome_vfs_unescape_string_for_display (uri);
495 utf8ised = rb_make_valid_utf8 (unescaped, '?');
497 error = g_error_new (RHYTHMDB_ERROR,
498 RHYTHMDB_ERROR_ACCESS_FAILED,
499 _("Couldn't access %s: %s"),
500 utf8ised,
501 gnome_vfs_result_to_string (result));
502 rb_debug ("got error on %s: %s", utf8ised, error->message);
503 g_free (unescaped);
504 g_free (utf8ised);
505 return error;
508 static void
509 rhythmdb_execute_multi_stat_info_cb (GnomeVFSAsyncHandle *handle,
510 GList *results,
511 /* GnomeVFSGetFileInfoResult* items */
512 RhythmDB *db)
514 g_mutex_lock (db->priv->stat_mutex);
515 while (results != NULL) {
516 GnomeVFSGetFileInfoResult *info_result = results->data;
517 RhythmDBEvent *event;
519 event = g_hash_table_lookup (db->priv->stat_events, info_result->uri);
520 if (event == NULL) {
521 char *uri_string;
522 uri_string = gnome_vfs_uri_to_string (info_result->uri, GNOME_VFS_URI_HIDE_NONE);
523 rb_debug ("ignoring unexpected uri in gnome_vfs_async_get_file_info response: %s",
524 uri_string);
525 g_free (uri_string);
526 results = results->next;
527 continue;
529 g_hash_table_remove (db->priv->stat_events, info_result->uri);
531 if (info_result->result == GNOME_VFS_OK) {
532 event->vfsinfo = gnome_vfs_file_info_dup (info_result->file_info);
533 } else {
534 event->error = make_access_failed_error (rb_refstring_get (event->real_uri),
535 info_result->result);
536 event->vfsinfo = NULL;
538 g_async_queue_push (db->priv->event_queue, event);
540 results = results->next;
542 db->priv->stat_handle = NULL;
543 g_mutex_unlock (db->priv->stat_mutex);
546 void
547 rhythmdb_start_action_thread (RhythmDB *db)
549 g_mutex_lock (db->priv->stat_mutex);
550 db->priv->action_thread_running = TRUE;
551 rhythmdb_thread_create (db, NULL, (GThreadFunc) action_thread_main, db);
553 if (db->priv->stat_list != NULL) {
554 gnome_vfs_async_get_file_info (&db->priv->stat_handle, db->priv->stat_list,
555 GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
556 GNOME_VFS_PRIORITY_MIN,
557 (GnomeVFSAsyncGetFileInfoCallback) rhythmdb_execute_multi_stat_info_cb,
558 db);
559 g_list_free (db->priv->stat_list);
560 db->priv->stat_list = NULL;
563 g_mutex_unlock (db->priv->stat_mutex);
566 static void
567 rhythmdb_action_free (RhythmDB *db,
568 RhythmDBAction *action)
570 rb_refstring_unref (action->uri);
571 g_free (action);
574 static void
575 rhythmdb_event_free (RhythmDB *db,
576 RhythmDBEvent *result)
578 switch (result->type) {
579 case RHYTHMDB_EVENT_THREAD_EXITED:
580 g_object_unref (db);
581 g_assert (g_atomic_int_dec_and_test (&db->priv->outstanding_threads) >= 0);
582 g_async_queue_unref (db->priv->action_queue);
583 g_async_queue_unref (db->priv->event_queue);
584 break;
585 case RHYTHMDB_EVENT_STAT:
586 case RHYTHMDB_EVENT_METADATA_LOAD:
587 case RHYTHMDB_EVENT_DB_LOAD:
588 case RHYTHMDB_EVENT_DB_SAVED:
589 case RHYTHMDB_EVENT_QUERY_COMPLETE:
590 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED:
591 case RHYTHMDB_EVENT_FILE_DELETED:
592 break;
593 case RHYTHMDB_EVENT_ENTRY_SET:
594 g_value_unset (&result->change.new);
595 break;
597 if (result->error)
598 g_error_free (result->error);
599 rb_refstring_unref (result->uri);
600 rb_refstring_unref (result->real_uri);
601 if (result->vfsinfo)
602 gnome_vfs_file_info_unref (result->vfsinfo);
603 if (result->metadata)
604 g_object_unref (result->metadata);
605 if (result->results)
606 g_object_unref (result->results);
607 if (result->handle)
608 gnome_vfs_async_cancel (result->handle);
609 if (result->entry != NULL) {
610 rhythmdb_entry_unref (result->entry);
612 g_free (result);
616 * rhythmdb_shutdown:
618 * Ceases all #RhythmDB operations, including stopping all directory monitoring, and
619 * removing all actions and events currently queued.
621 static void
622 _shutdown_foreach_swapped (RhythmDBEvent *event, RhythmDB *db)
624 rhythmdb_event_free (db, event);
627 void
628 rhythmdb_shutdown (RhythmDB *db)
630 RhythmDBEvent *result;
631 RhythmDBAction *action;
633 g_return_if_fail (RHYTHMDB_IS (db));
635 db->priv->exiting = TRUE;
637 eel_gconf_notification_remove (db->priv->library_location_notify_id);
638 g_slist_foreach (db->priv->library_locations, (GFunc) g_free, NULL);
639 g_slist_free (db->priv->library_locations);
640 db->priv->library_locations = NULL;
642 /* abort all async vfs operations */
643 g_mutex_lock (db->priv->stat_mutex);
644 if (db->priv->stat_handle) {
645 gnome_vfs_async_cancel (db->priv->stat_handle);
646 db->priv->stat_handle = NULL;
648 g_list_foreach (db->priv->outstanding_stats, (GFunc)_shutdown_foreach_swapped, db);
649 g_list_free (db->priv->outstanding_stats);
650 db->priv->outstanding_stats = NULL;
651 g_mutex_unlock (db->priv->stat_mutex);
653 while ((action = g_async_queue_try_pop (db->priv->action_queue)) != NULL) {
654 rhythmdb_action_free (db, action);
657 rb_debug ("%d outstanding threads", g_atomic_int_get (&db->priv->outstanding_threads));
658 while (g_atomic_int_get (&db->priv->outstanding_threads) > 0) {
659 result = g_async_queue_pop (db->priv->event_queue);
660 rhythmdb_event_free (db, result);
663 /* FIXME */
664 while ((result = g_async_queue_try_pop (db->priv->event_queue)) != NULL)
665 rhythmdb_event_free (db, result);
668 static void
669 _shutdown_foreach_hash (gpointer uri, RhythmDBEvent *event, RhythmDB *db)
671 rhythmdb_event_free (db, event);
674 static void
675 rhythmdb_finalize (GObject *object)
677 RhythmDB *db;
678 int i;
680 g_return_if_fail (object != NULL);
681 g_return_if_fail (RHYTHMDB_IS (object));
683 rb_debug ("finalizing rhythmdb");
684 db = RHYTHMDB (object);
686 g_return_if_fail (db->priv != NULL);
688 rhythmdb_finalize_monitoring (db);
690 g_source_remove (db->priv->event_poll_id);
691 if (db->priv->save_timeout_id > 0)
692 g_source_remove (db->priv->save_timeout_id);
693 if (db->priv->emit_entry_signals_id > 0) {
694 g_source_remove (db->priv->emit_entry_signals_id);
695 g_list_foreach (db->priv->added_entries_to_emit, (GFunc)rhythmdb_entry_unref, NULL);
696 g_list_foreach (db->priv->deleted_entries_to_emit, (GFunc)rhythmdb_entry_unref, NULL);
699 g_thread_pool_free (db->priv->query_thread_pool, FALSE, TRUE);
700 g_thread_pool_free (db->priv->add_thread_pool, FALSE, TRUE);
701 g_async_queue_unref (db->priv->action_queue);
702 g_async_queue_unref (db->priv->event_queue);
703 g_async_queue_unref (db->priv->restored_queue);
705 g_mutex_free (db->priv->saving_mutex);
706 g_cond_free (db->priv->saving_condition);
708 g_list_free (db->priv->stat_list);
709 g_hash_table_foreach (db->priv->stat_events, (GHFunc)_shutdown_foreach_hash, db);
710 g_hash_table_destroy (db->priv->stat_events);
711 g_mutex_free (db->priv->stat_mutex);
713 g_mutex_free (db->priv->change_mutex);
715 g_hash_table_destroy (db->priv->propname_map);
717 g_hash_table_destroy (db->priv->added_entries);
718 g_hash_table_destroy (db->priv->deleted_entries);
719 g_hash_table_destroy (db->priv->changed_entries);
721 rb_refstring_unref (db->priv->empty_string);
722 rb_refstring_unref (db->priv->octet_stream_str);
724 g_hash_table_destroy (db->priv->entry_type_map);
725 g_mutex_free (db->priv->entry_type_map_mutex);
726 g_mutex_free (db->priv->entry_type_mutex);
728 g_object_unref (db->priv->metadata);
730 for (i = 0; i < RHYTHMDB_NUM_PROPERTIES; i++) {
731 xmlFree (db->priv->column_xml_names[i]);
733 g_free (db->priv->column_xml_names);
735 g_free (db->priv->name);
737 G_OBJECT_CLASS (rhythmdb_parent_class)->finalize (object);
740 static void
741 rhythmdb_set_property (GObject *object,
742 guint prop_id,
743 const GValue *value,
744 GParamSpec *pspec)
746 RhythmDB *source = RHYTHMDB (object);
748 switch (prop_id) {
749 case PROP_NAME:
750 source->priv->name = g_strdup (g_value_get_string (value));
751 break;
752 case PROP_DRY_RUN:
753 source->priv->dry_run = g_value_get_boolean (value);
754 break;
755 case PROP_NO_UPDATE:
756 source->priv->no_update = g_value_get_boolean (value);
757 break;
758 default:
759 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
760 break;
764 static void
765 rhythmdb_get_property (GObject *object,
766 guint prop_id,
767 GValue *value,
768 GParamSpec *pspec)
770 RhythmDB *source = RHYTHMDB (object);
772 switch (prop_id) {
773 case PROP_NAME:
774 g_value_set_string (value, source->priv->name);
775 break;
776 case PROP_DRY_RUN:
777 g_value_set_boolean (value, source->priv->dry_run);
778 break;
779 case PROP_NO_UPDATE:
780 g_value_set_boolean (value, source->priv->no_update);
781 break;
782 default:
783 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
784 break;
788 static void
789 rhythmdb_thread_create (RhythmDB *db,
790 GThreadPool *pool,
791 GThreadFunc func,
792 gpointer data)
794 g_object_ref (db);
795 g_atomic_int_inc (&db->priv->outstanding_threads);
796 g_async_queue_ref (db->priv->action_queue);
797 g_async_queue_ref (db->priv->event_queue);
799 if (pool)
800 g_thread_pool_push (pool, data, NULL);
801 else
802 g_thread_create ((GThreadFunc) func, data, FALSE, NULL);
805 static gboolean
806 rhythmdb_get_readonly (RhythmDB *db)
808 return (g_atomic_int_get (&db->priv->read_counter) > 0);
811 static void
812 rhythmdb_read_enter (RhythmDB *db)
814 gint count;
815 g_return_if_fail (g_atomic_int_get (&db->priv->read_counter) >= 0);
816 g_assert (rb_is_main_thread ());
818 count = g_atomic_int_exchange_and_add (&db->priv->read_counter, 1);
819 rb_debug ("counter: %d", count+1);
820 if (count == 0)
821 g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY],
822 0, TRUE);
825 static void
826 rhythmdb_read_leave (RhythmDB *db)
828 gint count;
829 g_return_if_fail (rhythmdb_get_readonly (db));
830 g_assert (rb_is_main_thread ());
832 count = g_atomic_int_exchange_and_add (&db->priv->read_counter, -1);
833 rb_debug ("counter: %d", count-1);
834 if (count == 1)
835 g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY],
836 0, FALSE);
839 static gboolean
840 free_entry_changes (RhythmDBEntry *entry,
841 GSList *changes,
842 RhythmDB *db)
844 GSList *t;
845 for (t = changes; t; t = t->next) {
846 RhythmDBEntryChange *change = t->data;
847 g_value_unset (&change->old);
848 g_value_unset (&change->new);
849 g_free (change);
851 g_slist_free (changes);
853 return TRUE;
856 static void
857 emit_entry_changed (RhythmDBEntry *entry,
858 GSList *changes,
859 RhythmDB *db)
861 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_CHANGED], 0, entry, changes);
864 static void
865 sync_entry_changed (RhythmDBEntry *entry,
866 GSList *changes,
867 RhythmDB *db)
869 GSList *t;
871 for (t = changes; t; t = t->next) {
872 RBMetaDataField field;
873 RhythmDBEntryChange *change = t->data;
875 if (metadata_field_from_prop (change->prop, &field)) {
876 RhythmDBAction *action;
878 if (!rhythmdb_entry_is_editable (db, entry)) {
879 g_warning ("trying to sync properties of non-editable file");
880 break;
883 action = g_new0 (RhythmDBAction, 1);
884 action->type = RHYTHMDB_ACTION_SYNC;
885 action->uri = rb_refstring_ref (entry->location);
886 g_async_queue_push (db->priv->action_queue, action);
887 break;
892 static gboolean
893 rhythmdb_emit_entry_signals_idle (RhythmDB *db)
895 GList *added_entries;
896 GList *deleted_entries;
897 GList *l;
899 /* get lists of entries to emit, reset source id value */
900 g_mutex_lock (db->priv->change_mutex);
902 added_entries = db->priv->added_entries_to_emit;
903 db->priv->added_entries_to_emit = NULL;
905 deleted_entries = db->priv->deleted_entries_to_emit;
906 db->priv->deleted_entries_to_emit = NULL;
908 db->priv->emit_entry_signals_id = 0;
910 g_mutex_unlock (db->priv->change_mutex);
912 /* emit added entries */
913 for (l = added_entries; l; l = g_list_next (l)) {
914 RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
915 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_ADDED], 0, entry);
916 rhythmdb_entry_unref (entry);
919 /* emit deleted entries */
920 for (l = deleted_entries; l; l = g_list_next (l)) {
921 RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
922 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry);
923 rhythmdb_entry_unref (entry);
926 g_list_free (added_entries);
927 g_list_free (deleted_entries);
928 return FALSE;
931 static gboolean
932 process_added_entries_cb (RhythmDBEntry *entry,
933 GThread *thread,
934 RhythmDB *db)
936 if (thread != g_thread_self ())
937 return FALSE;
939 if (entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
940 const gchar *uri;
942 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
943 if (uri == NULL)
944 return TRUE;
946 #ifdef HAVE_GSTREAMER_0_8
947 /* always start remote files hidden*/
948 if (!g_str_has_prefix (uri, "file://")) {
949 entry->flags |= RHYTHMDB_ENTRY_HIDDEN;
951 #endif
953 queue_stat_uri (uri, db, RHYTHMDB_ENTRY_TYPE_INVALID);
956 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) == 0);
957 entry->flags |= RHYTHMDB_ENTRY_INSERTED;
959 rhythmdb_entry_ref (entry);
960 db->priv->added_entries_to_emit = g_list_prepend (db->priv->added_entries_to_emit, entry);
962 return TRUE;
965 static gboolean
966 process_deleted_entries_cb (RhythmDBEntry *entry,
967 GThread *thread,
968 RhythmDB *db)
970 if (thread != g_thread_self ())
971 return FALSE;
973 rhythmdb_entry_ref (entry);
974 db->priv->deleted_entries_to_emit = g_list_prepend (db->priv->deleted_entries_to_emit, entry);
976 return TRUE;
979 static void
980 rhythmdb_commit_internal (RhythmDB *db,
981 gboolean sync_changes,
982 GThread *thread)
984 g_mutex_lock (db->priv->change_mutex);
986 g_hash_table_foreach (db->priv->changed_entries, (GHFunc) emit_entry_changed, db);
987 if (sync_changes)
988 g_hash_table_foreach (db->priv->changed_entries, (GHFunc) sync_entry_changed, db);
989 g_hash_table_foreach_remove (db->priv->changed_entries, (GHRFunc) free_entry_changes, db);
991 /* update the lists of entry added/deleted signals to emit */
992 g_hash_table_foreach_remove (db->priv->added_entries, (GHRFunc) process_added_entries_cb, db);
993 g_hash_table_foreach_remove (db->priv->deleted_entries, (GHRFunc) process_deleted_entries_cb, db);
995 /* if there are some signals to emit, add a new idle callback if required */
996 if (db->priv->added_entries_to_emit || db->priv->deleted_entries_to_emit) {
997 if (db->priv->emit_entry_signals_id == 0)
998 db->priv->emit_entry_signals_id = g_idle_add ((GSourceFunc) rhythmdb_emit_entry_signals_idle, db);
1001 g_mutex_unlock (db->priv->change_mutex);
1004 typedef struct {
1005 RhythmDB *db;
1006 gboolean sync;
1007 GThread *thread;
1008 } RhythmDBTimeoutCommitData;
1010 static gboolean
1011 timeout_rhythmdb_commit (RhythmDBTimeoutCommitData *data)
1013 rhythmdb_commit_internal (data->db, data->sync, data->thread);
1014 g_object_unref (data->db);
1015 g_free (data);
1016 return FALSE;
1019 static void
1020 rhythmdb_add_timeout_commit (RhythmDB *db,
1021 gboolean sync)
1023 RhythmDBTimeoutCommitData *data;
1025 g_assert (rb_is_main_thread ());
1027 data = g_new0 (RhythmDBTimeoutCommitData, 1);
1028 data->db = g_object_ref (db);
1029 data->sync = sync;
1030 data->thread = g_thread_self ();
1031 g_timeout_add (100, (GSourceFunc)timeout_rhythmdb_commit, data);
1035 * rhythmdb_commit:
1036 * @db: a #RhythmDB.
1038 * Apply all database changes, and send notification of changes and new entries.
1039 * This needs to be called after any changes have been made, such as a group of
1040 * rhythmdb_entry_set() calls, or a new entry has been added.
1042 void
1043 rhythmdb_commit (RhythmDB *db)
1045 rhythmdb_commit_internal (db, TRUE, g_thread_self ());
1048 GQuark
1049 rhythmdb_error_quark (void)
1051 static GQuark quark;
1052 if (!quark)
1053 quark = g_quark_from_static_string ("rhythmdb_error");
1055 return quark;
1058 /* structure alignment magic, stolen from glib */
1059 #define STRUCT_ALIGNMENT (2 * sizeof (gsize))
1060 #define ALIGN_STRUCT(offset) \
1061 ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
1064 * rhythmdb_entry_allocate:
1065 * @db: a #RhythmDB.
1066 * @type: type of entry to allocate
1068 * Allocate and initialise memory for a new #RhythmDBEntry of the type @type.
1069 * The entry's initial properties needs to be set with rhythmdb_entry_set (),
1070 * the entry added to the database with rhythmdb_entry_insert(), and committed with
1071 * rhythmdb_commit().
1073 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
1075 * Returns: the newly allocated #RhythmDBEntry
1077 RhythmDBEntry *
1078 rhythmdb_entry_allocate (RhythmDB *db,
1079 RhythmDBEntryType type)
1081 RhythmDBEntry *ret;
1082 gsize size = sizeof (RhythmDBEntry);
1084 if (type->entry_type_data_size) {
1085 size = ALIGN_STRUCT (sizeof (RhythmDBEntry)) + type->entry_type_data_size;
1087 ret = g_malloc0 (size);
1088 ret->id = (guint) g_atomic_int_exchange_and_add (&db->priv->next_entry_id, 1);
1090 ret->type = type;
1091 ret->title = rb_refstring_ref (db->priv->empty_string);
1092 ret->genre = rb_refstring_ref (db->priv->empty_string);
1093 ret->artist = rb_refstring_ref (db->priv->empty_string);
1094 ret->album = rb_refstring_ref (db->priv->empty_string);
1095 ret->musicbrainz_trackid = rb_refstring_ref (db->priv->empty_string);
1096 ret->mimetype = rb_refstring_ref (db->priv->octet_stream_str);
1098 ret->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY |
1099 RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY |
1100 RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
1102 /* The refcount is initially 0, we want to set it to 1 */
1103 ret->refcount = 1;
1105 if (type->post_entry_create)
1106 (type->post_entry_create)(ret, type->post_entry_create_data);
1108 return ret;
1112 * rhythmdb_entry_get_type_data:
1113 * @entry: a #RhythmDBEntry
1114 * @expected_size: expected size of the type-specific data.
1116 * Returns a pointer to the entry's type-specific data, checking that
1117 * the size of the data structure matches what is expected.
1118 * Callers should use the RHYTHMDB_ENTRY_GET_TYPE_DATA macro for
1119 * a slightly more friendly interface to this functionality.
1121 gpointer
1122 rhythmdb_entry_get_type_data (RhythmDBEntry *entry,
1123 guint expected_size)
1125 g_return_val_if_fail (entry != NULL, NULL);
1127 g_assert (expected_size == entry->type->entry_type_data_size);
1128 gsize offset = ALIGN_STRUCT (sizeof (RhythmDBEntry));
1130 return (gpointer) (((guint8 *)entry) + offset);
1134 * rhythmdb_entry_insert:
1135 * @db: a #RhythmDB.
1136 * @entry: the entry to insert.
1138 * Inserts a newly-created entry into the database.
1140 * Note that you must call rhythmdb_commit() at some point after invoking
1141 * this function.
1143 void
1144 rhythmdb_entry_insert (RhythmDB *db,
1145 RhythmDBEntry *entry)
1147 g_return_if_fail (RHYTHMDB_IS (db));
1148 g_return_if_fail (entry != NULL);
1150 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) == 0);
1151 g_return_if_fail (entry->location != NULL);
1153 /* ref the entry before adding to hash, it is unreffed when removed */
1154 rhythmdb_entry_ref (entry);
1155 g_mutex_lock (db->priv->change_mutex);
1156 g_hash_table_insert (db->priv->added_entries, entry, g_thread_self ());
1157 g_mutex_unlock (db->priv->change_mutex);
1161 * rhythmdb_entry_new:
1162 * @db: a #RhythmDB.
1163 * @type: type of entry to create
1164 * @uri: the location of the entry, this be unique amongst all entries.
1166 * Creates a new entry of type @type and location @uri, and inserts
1167 * it into the database. You must call rhythmdb_commit() at some point
1168 * after invoking this function.
1170 * This may return NULL if entry creation fails. This can occur if there is
1171 * already an entry with the given uri.
1173 * Returns: the newly created #RhythmDBEntry
1175 RhythmDBEntry *
1176 rhythmdb_entry_new (RhythmDB *db,
1177 RhythmDBEntryType type,
1178 const char *uri)
1180 RhythmDBEntry *ret;
1181 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
1183 ret = rhythmdb_entry_lookup_by_location (db, uri);
1184 if (ret) {
1185 g_warning ("attempting to create entry that already exists: %s", uri);
1186 return NULL;
1189 ret = rhythmdb_entry_allocate (db, type);
1190 ret->location = rb_refstring_new (uri);
1191 klass->impl_entry_new (db, ret);
1192 rb_debug ("emitting entry added");
1193 rhythmdb_entry_insert (db, ret);
1195 return ret;
1199 * rhythmdb_entry_example_new:
1200 * @db: a #RhythmDB.
1201 * @type: type of entry to create
1202 * @uri: the location of the entry, this be unique amongst all entries.
1204 * Creates a new sample entry of type @type and location @uri, it does not insert
1205 * it into the database. This is indended for use as a example entry.
1207 * This may return NULL if entry creation fails.
1209 * Returns: the newly created #RhythmDBEntry
1211 RhythmDBEntry *
1212 rhythmdb_entry_example_new (RhythmDB *db,
1213 RhythmDBEntryType type,
1214 const char *uri)
1216 RhythmDBEntry *ret;
1218 ret = rhythmdb_entry_allocate (db, type);
1219 if (uri)
1220 ret->location = rb_refstring_new (uri);
1222 if (type == RHYTHMDB_ENTRY_TYPE_SONG) {
1223 rb_refstring_unref (ret->artist);
1224 ret->artist = rb_refstring_new ("The Beatles");
1225 rb_refstring_unref (ret->album);
1226 ret->album = rb_refstring_new ("Help!");
1227 rb_refstring_unref (ret->title);
1228 ret->title = rb_refstring_new ("Ticket To Ride");
1229 ret->tracknum = 7;
1230 } else {
1233 return ret;
1237 * rhythmdb_entry_ref:
1238 * @db: a #RhythmDB.
1239 * @entry: a #RhythmDBEntry.
1241 * Increase the reference count of the entry.
1243 RhythmDBEntry *
1244 rhythmdb_entry_ref (RhythmDBEntry *entry)
1246 g_return_val_if_fail (entry != NULL, NULL);
1247 g_return_val_if_fail (entry->refcount > 0, NULL);
1249 g_atomic_int_inc (&entry->refcount);
1251 return entry;
1254 static void
1255 rhythmdb_entry_finalize (RhythmDBEntry *entry)
1257 RhythmDBEntryType type;
1259 type = rhythmdb_entry_get_entry_type (entry);
1261 if (type->pre_entry_destroy)
1262 (type->pre_entry_destroy)(entry, type->pre_entry_destroy_data);
1264 rb_refstring_unref (entry->location);
1265 rb_refstring_unref (entry->playback_error);
1266 rb_refstring_unref (entry->title);
1267 rb_refstring_unref (entry->genre);
1268 rb_refstring_unref (entry->artist);
1269 rb_refstring_unref (entry->album);
1270 rb_refstring_unref (entry->musicbrainz_trackid);
1271 rb_refstring_unref (entry->mimetype);
1273 g_free (entry);
1277 * rhythmdb_entry_unref:
1278 * @db: a #RhythmDB.
1279 * @entry: a #RhythmDBEntry.
1281 * Decrease the reference count of the entry, and destroy it if there are
1282 * no references left.
1284 void
1285 rhythmdb_entry_unref (RhythmDBEntry *entry)
1287 gboolean is_zero;
1289 g_return_if_fail (entry != NULL);
1290 g_return_if_fail (entry->refcount > 0);
1292 is_zero = g_atomic_int_dec_and_test (&entry->refcount);
1293 if (G_UNLIKELY (is_zero)) {
1294 rhythmdb_entry_finalize (entry);
1299 * rhythmdb_entry_is_editable:
1300 * @db: a #RhythmDB.
1301 * @entry: a #RhythmDBEntry.
1303 * This determines whether any changes to the entries metadata can be saved.
1304 * Usually this is only true for entries backed by files, where tag-writing is
1305 * enabled, and the appropriate tag-writing facilities are available.
1307 * Returns: whether the entries metadata can be changed.
1310 gboolean
1311 rhythmdb_entry_is_editable (RhythmDB *db,
1312 RhythmDBEntry *entry)
1314 RhythmDBEntryType entry_type;
1316 g_return_val_if_fail (RHYTHMDB_IS (db), FALSE);
1317 g_return_val_if_fail (entry != NULL, FALSE);
1319 entry_type = rhythmdb_entry_get_entry_type (entry);
1320 return entry_type->can_sync_metadata (db, entry, entry_type->can_sync_metadata_data);
1323 static void
1324 set_metadata_string_default_unknown (RhythmDB *db,
1325 RBMetaData *metadata,
1326 RhythmDBEntry *entry,
1327 RBMetaDataField field,
1328 RhythmDBPropType prop)
1330 const char *unknown = _("Unknown");
1331 GValue val = {0, };
1333 if (!(rb_metadata_get (metadata,
1334 field,
1335 &val))) {
1336 g_value_init (&val, G_TYPE_STRING);
1337 g_value_set_static_string (&val, unknown);
1338 } else {
1339 const gchar *str = g_value_get_string (&val);
1340 if (str == NULL || str[0] == '\0')
1341 g_value_set_static_string (&val, unknown);
1343 rhythmdb_entry_set_internal (db, entry, TRUE, prop, &val);
1344 g_value_unset (&val);
1347 static void
1348 set_props_from_metadata (RhythmDB *db,
1349 RhythmDBEntry *entry,
1350 GnomeVFSFileInfo *vfsinfo,
1351 RBMetaData *metadata)
1353 const char *mime;
1354 GValue val = {0,};
1356 g_value_init (&val, G_TYPE_STRING);
1357 mime = rb_metadata_get_mime (metadata);
1358 if (mime) {
1359 g_value_set_string (&val, mime);
1360 rhythmdb_entry_set_internal (db, entry, TRUE,
1361 RHYTHMDB_PROP_MIMETYPE, &val);
1363 g_value_unset (&val);
1365 /* track number */
1366 if (!rb_metadata_get (metadata,
1367 RB_METADATA_FIELD_TRACK_NUMBER,
1368 &val)) {
1369 g_value_init (&val, G_TYPE_ULONG);
1370 g_value_set_ulong (&val, 0);
1372 rhythmdb_entry_set_internal (db, entry, TRUE,
1373 RHYTHMDB_PROP_TRACK_NUMBER, &val);
1374 g_value_unset (&val);
1376 /* disc number */
1377 if (!rb_metadata_get (metadata,
1378 RB_METADATA_FIELD_DISC_NUMBER,
1379 &val)) {
1380 g_value_init (&val, G_TYPE_ULONG);
1381 g_value_set_ulong (&val, 0);
1383 rhythmdb_entry_set_internal (db, entry, TRUE,
1384 RHYTHMDB_PROP_DISC_NUMBER, &val);
1385 g_value_unset (&val);
1387 /* duration */
1388 if (rb_metadata_get (metadata,
1389 RB_METADATA_FIELD_DURATION,
1390 &val)) {
1391 rhythmdb_entry_set_internal (db, entry, TRUE,
1392 RHYTHMDB_PROP_DURATION, &val);
1393 g_value_unset (&val);
1396 /* bitrate */
1397 if (rb_metadata_get (metadata,
1398 RB_METADATA_FIELD_BITRATE,
1399 &val)) {
1400 rhythmdb_entry_set_internal (db, entry, TRUE,
1401 RHYTHMDB_PROP_BITRATE, &val);
1402 g_value_unset (&val);
1405 /* date */
1406 if (rb_metadata_get (metadata,
1407 RB_METADATA_FIELD_DATE,
1408 &val)) {
1409 rhythmdb_entry_set_internal (db, entry, TRUE,
1410 RHYTHMDB_PROP_DATE, &val);
1411 g_value_unset (&val);
1414 /* musicbrainz trackid */
1415 if (rb_metadata_get (metadata,
1416 RB_METADATA_FIELD_MUSICBRAINZ_TRACKID,
1417 &val)) {
1418 rhythmdb_entry_set_internal (db, entry, TRUE,
1419 RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, &val);
1420 g_value_unset (&val);
1423 /* filesize */
1424 g_value_init (&val, G_TYPE_UINT64);
1425 g_value_set_uint64 (&val, vfsinfo->size);
1426 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_FILE_SIZE, &val);
1427 g_value_unset (&val);
1429 /* title */
1430 if (!rb_metadata_get (metadata,
1431 RB_METADATA_FIELD_TITLE,
1432 &val) || g_value_get_string (&val)[0] == '\0') {
1433 char *utf8name;
1434 utf8name = g_filename_to_utf8 (vfsinfo->name, -1, NULL, NULL, NULL);
1435 if (!utf8name) {
1436 utf8name = g_strdup (_("<invalid filename>"));
1438 if (G_VALUE_HOLDS_STRING (&val))
1439 g_value_reset (&val);
1440 else
1441 g_value_init (&val, G_TYPE_STRING);
1442 g_value_set_string (&val, utf8name);
1443 g_free (utf8name);
1445 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_TITLE, &val);
1446 g_value_unset (&val);
1448 /* genre */
1449 set_metadata_string_default_unknown (db, metadata, entry,
1450 RB_METADATA_FIELD_GENRE,
1451 RHYTHMDB_PROP_GENRE);
1453 /* artist */
1454 set_metadata_string_default_unknown (db, metadata, entry,
1455 RB_METADATA_FIELD_ARTIST,
1456 RHYTHMDB_PROP_ARTIST);
1457 /* album */
1458 set_metadata_string_default_unknown (db, metadata, entry,
1459 RB_METADATA_FIELD_ALBUM,
1460 RHYTHMDB_PROP_ALBUM);
1462 /* replaygain track gain */
1463 if (rb_metadata_get (metadata,
1464 RB_METADATA_FIELD_TRACK_GAIN,
1465 &val)) {
1466 rhythmdb_entry_set_internal (db, entry, TRUE,
1467 RHYTHMDB_PROP_TRACK_GAIN, &val);
1468 g_value_unset (&val);
1471 /* replaygain track peak */
1472 if (rb_metadata_get (metadata,
1473 RB_METADATA_FIELD_TRACK_PEAK,
1474 &val)) {
1475 rhythmdb_entry_set_internal (db, entry, TRUE,
1476 RHYTHMDB_PROP_TRACK_PEAK, &val);
1477 g_value_unset (&val);
1480 /* replaygain album gain */
1481 if (rb_metadata_get (metadata,
1482 RB_METADATA_FIELD_ALBUM_GAIN,
1483 &val)) {
1484 rhythmdb_entry_set_internal (db, entry, TRUE,
1485 RHYTHMDB_PROP_ALBUM_GAIN, &val);
1486 g_value_unset (&val);
1489 /* replaygain album peak */
1490 if (rb_metadata_get (metadata,
1491 RB_METADATA_FIELD_ALBUM_PEAK,
1492 &val)) {
1493 rhythmdb_entry_set_internal (db, entry, TRUE,
1494 RHYTHMDB_PROP_ALBUM_PEAK, &val);
1495 g_value_unset (&val);
1499 static gboolean
1500 is_ghost_entry (RhythmDBEntry *entry)
1502 GTimeVal time;
1503 gulong last_seen;
1504 gulong grace_period;
1505 GError *error;
1506 GConfClient *client;
1508 client = gconf_client_get_default ();
1509 if (client == NULL) {
1510 return FALSE;
1512 error = NULL;
1513 grace_period = gconf_client_get_int (client, CONF_GRACE_PERIOD,
1514 &error);
1515 g_object_unref (G_OBJECT (client));
1516 if (error != NULL) {
1517 g_error_free (error);
1518 return FALSE;
1521 /* This is a bit silly, but I prefer to make sure we won't
1522 * overflow in the following calculations
1524 if ((grace_period <= 0) || (grace_period > 20000)) {
1525 return FALSE;
1528 /* Convert from days to seconds */
1529 grace_period = grace_period * 60 * 60 * 24;
1530 g_get_current_time (&time);
1531 last_seen = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_LAST_SEEN);
1533 return (last_seen + grace_period < time.tv_sec);
1536 static void
1537 rhythmdb_process_stat_event (RhythmDB *db,
1538 RhythmDBEvent *event)
1540 RhythmDBEntry *entry;
1541 RhythmDBAction *action;
1543 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
1544 if (entry) {
1545 time_t mtime = (time_t) entry->mtime;
1547 if ((event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) && (entry->type != event->entry_type))
1548 g_warning ("attempt to use same location in multiple entry types");
1550 if (entry->type == RHYTHMDB_ENTRY_TYPE_IGNORE)
1551 rb_debug ("ignoring %p", entry);
1553 if (event->error) {
1554 if (!is_ghost_entry (entry)) {
1555 rhythmdb_entry_set_visibility (db, entry, FALSE);
1556 } else {
1557 rb_debug ("error accessing %s: %s", rb_refstring_get (event->real_uri),
1558 event->error->message);
1559 rhythmdb_entry_delete (db, entry);
1561 } else {
1562 GValue val = {0, };
1563 GTimeVal time;
1564 const char *mount_point;
1566 rhythmdb_entry_set_visibility (db, entry, TRUE);
1568 /* Update mount point if necessary (main reason is
1569 * that we want to set the mount point in legacy
1570 * rhythmdb that doesn't have it already
1572 mount_point = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
1573 if (mount_point == NULL) {
1574 rhythmdb_entry_set_mount_point (db, entry,
1575 rb_refstring_get (event->real_uri));
1578 /* Update last seen time. It will also be updated
1579 * upon saving and when a volume is unmounted
1581 g_get_current_time (&time);
1582 g_value_init (&val, G_TYPE_ULONG);
1583 g_value_set_ulong (&val, time.tv_sec);
1584 rhythmdb_entry_set_internal (db, entry, TRUE,
1585 RHYTHMDB_PROP_LAST_SEEN,
1586 &val);
1587 /* Old rhythmdb.xml files won't have a value for
1588 * FIRST_SEEN, so set it here
1590 if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_FIRST_SEEN) == 0) {
1591 rhythmdb_entry_set_internal (db, entry, TRUE,
1592 RHYTHMDB_PROP_FIRST_SEEN,
1593 &val);
1595 g_value_unset (&val);
1597 if (mtime == event->vfsinfo->mtime) {
1598 rb_debug ("not modified: %s", rb_refstring_get (event->real_uri));
1599 } else {
1600 RhythmDBEvent *new_event;
1602 rb_debug ("changed: %s", rb_refstring_get (event->real_uri));
1603 new_event = g_new0 (RhythmDBEvent, 1);
1604 new_event->db = db;
1605 new_event->uri = rb_refstring_ref (event->real_uri);
1606 new_event->type = RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED;
1607 g_async_queue_push (db->priv->event_queue,
1608 new_event);
1612 rhythmdb_commit (db);
1613 } else {
1614 action = g_new0 (RhythmDBAction, 1);
1615 action->type = RHYTHMDB_ACTION_LOAD;
1616 action->uri = rb_refstring_ref (event->real_uri);
1617 action->entry_type = event->entry_type;
1618 rb_debug ("queuing a RHYTHMDB_ACTION_LOAD: %s", rb_refstring_get (action->uri));
1619 g_async_queue_push (db->priv->action_queue, action);
1623 typedef struct
1625 RhythmDB *db;
1626 char *uri;
1627 char *msg;
1628 } RhythmDBLoadErrorData;
1630 static void
1631 rhythmdb_add_import_error_entry (RhythmDB *db,
1632 RhythmDBEvent *event)
1634 RhythmDBEntry *entry;
1635 GValue value = {0,};
1636 RhythmDBEntryType error_entry_type = RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR;
1638 if (g_error_matches (event->error, RB_METADATA_ERROR, RB_METADATA_ERROR_NOT_AUDIO_IGNORE)) {
1639 /* only add an ignore entry for the main library */
1640 if (event->entry_type != RHYTHMDB_ENTRY_TYPE_SONG &&
1641 event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID)
1642 return;
1644 error_entry_type = RHYTHMDB_ENTRY_TYPE_IGNORE;
1647 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
1648 if (entry) {
1649 RhythmDBEntryType entry_type = rhythmdb_entry_get_entry_type (entry);
1650 if (entry_type != RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR &&
1651 entry_type != RHYTHMDB_ENTRY_TYPE_IGNORE) {
1652 /* FIXME we've successfully read this file before.. so what should we do? */
1653 rb_debug ("%s already exists in the library.. ignoring import error?", rb_refstring_get (event->real_uri));
1654 return;
1657 if (entry_type != error_entry_type) {
1658 /* delete the existing entry, then create a new one below */
1659 rhythmdb_entry_delete (db, entry);
1660 entry = NULL;
1661 } else if (error_entry_type == RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR) {
1662 /* we've already got an error for this file, so just update it */
1663 g_value_init (&value, G_TYPE_STRING);
1664 g_value_set_string (&value, event->error->message);
1665 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
1666 g_value_unset (&value);
1667 } else {
1668 /* no need to update the ignored file entry */
1671 if (entry && event->vfsinfo) {
1672 /* mtime */
1673 g_value_init (&value, G_TYPE_ULONG);
1674 g_value_set_ulong (&value, event->vfsinfo->mtime);
1675 rhythmdb_entry_set(db, entry, RHYTHMDB_PROP_MTIME, &value);
1676 g_value_unset (&value);
1679 rhythmdb_add_timeout_commit (db, FALSE);
1682 if (entry == NULL) {
1683 /* create a new import error or ignore entry */
1684 entry = rhythmdb_entry_new (db, error_entry_type, rb_refstring_get (event->real_uri));
1685 if (entry == NULL)
1686 return;
1688 if (error_entry_type == RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR && event->error->message) {
1689 g_value_init (&value, G_TYPE_STRING);
1690 if (g_utf8_validate (event->error->message, -1, NULL))
1691 g_value_set_string (&value, event->error->message);
1692 else
1693 g_value_set_static_string (&value, _("invalid unicode in error message"));
1694 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
1695 g_value_unset (&value);
1698 /* mtime */
1699 if (event->vfsinfo) {
1700 g_value_init (&value, G_TYPE_ULONG);
1701 g_value_set_ulong (&value, event->vfsinfo->mtime);
1702 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MTIME, &value);
1703 g_value_unset (&value);
1706 /* record the mount point so we can delete entries for unmounted volumes */
1707 rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri));
1709 rhythmdb_entry_set_visibility (db, entry, TRUE);
1711 rhythmdb_add_timeout_commit (db, FALSE);
1715 static gboolean
1716 rhythmdb_process_metadata_load (RhythmDB *db,
1717 RhythmDBEvent *event)
1719 RhythmDBEntry *entry;
1720 GValue value = {0,};
1721 const char *mime;
1722 GTimeVal time;
1724 if (rhythmdb_get_readonly (db)) {
1725 rb_debug ("database is read-only right now, re-queuing event");
1726 g_async_queue_push (db->priv->event_queue, event);
1727 return FALSE;
1730 if (event->error) {
1731 rhythmdb_add_import_error_entry (db, event);
1732 return TRUE;
1735 /* do we really need to do this? */
1736 mime = rb_metadata_get_mime (event->metadata);
1737 if (!mime) {
1738 rb_debug ("unsupported file");
1739 return TRUE;
1742 g_get_current_time (&time);
1744 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
1746 if (entry != NULL) {
1747 if ((event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) &&
1748 (rhythmdb_entry_get_entry_type (entry) != event->entry_type)) {
1749 /* switching from IGNORE to SONG or vice versa, recreate the entry */
1750 rhythmdb_entry_delete (db, entry);
1751 rhythmdb_add_timeout_commit (db, FALSE);
1752 entry = NULL;
1756 if (entry == NULL) {
1757 if (event->entry_type == RHYTHMDB_ENTRY_TYPE_INVALID)
1758 event->entry_type = RHYTHMDB_ENTRY_TYPE_SONG;
1760 entry = rhythmdb_entry_new (db, event->entry_type, rb_refstring_get (event->real_uri));
1761 if (entry == NULL) {
1762 rb_debug ("entry already exists");
1763 return TRUE;
1766 /* initialize the last played date to 0=never */
1767 g_value_init (&value, G_TYPE_ULONG);
1768 g_value_set_ulong (&value, 0);
1769 rhythmdb_entry_set (db, entry,
1770 RHYTHMDB_PROP_LAST_PLAYED, &value);
1771 g_value_unset (&value);
1773 /* initialize the rating */
1774 g_value_init (&value, G_TYPE_DOUBLE);
1775 g_value_set_double (&value, 0);
1776 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &value);
1777 g_value_unset (&value);
1779 /* first seen */
1780 g_value_init (&value, G_TYPE_ULONG);
1781 g_value_set_ulong (&value, time.tv_sec);
1782 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &value);
1783 g_value_unset (&value);
1786 if ((event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) && (entry->type != event->entry_type))
1787 g_warning ("attempt to use same location in multiple entry types");
1789 /* mtime */
1790 if (event->vfsinfo) {
1791 g_value_init (&value, G_TYPE_ULONG);
1792 g_value_set_ulong (&value, event->vfsinfo->mtime);
1793 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_MTIME, &value);
1794 g_value_unset (&value);
1797 if (event->entry_type != RHYTHMDB_ENTRY_TYPE_IGNORE &&
1798 event->entry_type != RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR) {
1799 set_props_from_metadata (db, entry, event->vfsinfo, event->metadata);
1802 /* we've seen this entry */
1803 rhythmdb_entry_set_visibility (db, entry, TRUE);
1805 g_value_init (&value, G_TYPE_ULONG);
1806 g_value_set_ulong (&value, time.tv_sec);
1807 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_LAST_SEEN, &value);
1808 g_value_unset (&value);
1810 /* Remember the mount point of the volume the song is on */
1811 rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri));
1813 /* monitor the file for changes */
1814 /* FIXME: watch for errors */
1815 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY) && event->entry_type == RHYTHMDB_ENTRY_TYPE_SONG)
1816 rhythmdb_monitor_uri_path (db, rb_refstring_get (entry->location), NULL);
1818 rhythmdb_add_timeout_commit (db, FALSE);
1820 return TRUE;
1823 static void
1824 rhythmdb_process_queued_entry_set_event (RhythmDB *db,
1825 RhythmDBEvent *event)
1827 rhythmdb_entry_set_internal (db, event->entry,
1828 event->signal_change,
1829 event->change.prop,
1830 &event->change.new);
1831 /* Don't run rhythmdb_commit right now in case there
1832 * we can run a single commit for several queued
1833 * entry_set
1835 rhythmdb_add_timeout_commit (db, TRUE);
1838 static void
1839 rhythmdb_process_file_created_or_modified (RhythmDB *db,
1840 RhythmDBEvent *event)
1842 RhythmDBAction *action;
1844 action = g_new0 (RhythmDBAction, 1);
1845 action->type = RHYTHMDB_ACTION_LOAD;
1846 action->uri = rb_refstring_ref (event->uri);
1847 action->entry_type = RHYTHMDB_ENTRY_TYPE_INVALID;
1848 g_async_queue_push (db->priv->action_queue, action);
1851 static void
1852 rhythmdb_process_file_deleted (RhythmDB *db,
1853 RhythmDBEvent *event)
1855 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location_refstring (db, event->uri);
1857 g_hash_table_remove (db->priv->changed_files, event->uri);
1859 if (entry) {
1860 rb_debug ("deleting entry for %s", rb_refstring_get (event->uri));
1861 rhythmdb_entry_set_visibility (db, entry, FALSE);
1862 rhythmdb_commit (db);
1866 static gboolean
1867 rhythmdb_process_events (RhythmDB *db,
1868 GTimeVal *timeout)
1870 RhythmDBEvent *event;
1871 guint count = 0;
1873 while ((event = g_async_queue_try_pop (db->priv->event_queue)) != NULL) {
1874 gboolean free = TRUE;
1876 /* if the database is read-only, we can't process those events
1877 * since they call rhythmdb_entry_set. Doing it this way
1878 * is safe if we assume all calls to read_enter/read_leave
1879 * are done from the main thread (the thread this function
1880 * runs in).
1882 if (rhythmdb_get_readonly (db) &&
1883 ((event->type == RHYTHMDB_EVENT_STAT)
1884 || (event->type == RHYTHMDB_EVENT_METADATA_LOAD)
1885 || (event->type == RHYTHMDB_EVENT_ENTRY_SET))) {
1886 if (count >= g_async_queue_length (db->priv->event_queue)) {
1887 rb_debug ("Database is read-only, and we can't process any more events");
1888 /* give the running query some time to complete */
1889 return FALSE;
1891 rb_debug ("Database is read-only, delaying event processing\n");
1892 g_async_queue_push (db->priv->event_queue, event);
1893 goto next_event;
1896 switch (event->type) {
1897 case RHYTHMDB_EVENT_STAT:
1898 rb_debug ("processing RHYTHMDB_EVENT_STAT");
1899 rhythmdb_process_stat_event (db, event);
1900 break;
1901 case RHYTHMDB_EVENT_METADATA_LOAD:
1902 rb_debug ("processing RHYTHMDB_EVENT_METADATA_LOAD");
1903 free = rhythmdb_process_metadata_load (db, event);
1904 break;
1905 case RHYTHMDB_EVENT_ENTRY_SET:
1906 rb_debug ("processing RHYTHMDB_EVENT_ENTRY_SET");
1907 rhythmdb_process_queued_entry_set_event (db, event);
1908 break;
1909 case RHYTHMDB_EVENT_DB_LOAD:
1910 rb_debug ("processing RHYTHMDB_EVENT_DB_LOAD");
1911 g_signal_emit (G_OBJECT (db), rhythmdb_signals[LOAD_COMPLETE], 0);
1913 /* save the db every five minutes */
1914 if (db->priv->save_timeout_id > 0) {
1915 g_source_remove (db->priv->save_timeout_id);
1917 db->priv->save_timeout_id = g_timeout_add_full (G_PRIORITY_LOW,
1918 5 * 60 * 1000,
1919 (GSourceFunc) rhythmdb_idle_save,
1921 NULL);
1922 break;
1923 case RHYTHMDB_EVENT_THREAD_EXITED:
1924 rb_debug ("processing RHYTHMDB_EVENT_THREAD_EXITED");
1925 break;
1926 case RHYTHMDB_EVENT_DB_SAVED:
1927 rb_debug ("processing RHYTHMDB_EVENT_DB_SAVED");
1928 rhythmdb_read_leave (db);
1929 break;
1930 case RHYTHMDB_EVENT_QUERY_COMPLETE:
1931 rb_debug ("processing RHYTHMDB_EVENT_QUERY_COMPLETE");
1932 rhythmdb_read_leave (db);
1933 break;
1934 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED:
1935 rb_debug ("processing RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED");
1936 rhythmdb_process_file_created_or_modified (db, event);
1937 break;
1938 case RHYTHMDB_EVENT_FILE_DELETED:
1939 rb_debug ("processing RHYTHMDB_EVENT_FILE_DELETED");
1940 rhythmdb_process_file_deleted (db, event);
1941 break;
1943 if (free)
1944 rhythmdb_event_free (db, event);
1946 count++;
1947 next_event:
1948 if (timeout && (count % 8 == 0)) {
1949 GTimeVal now;
1950 g_get_current_time (&now);
1951 if (rb_compare_gtimeval (timeout,&now) < 0) {
1952 /* probably more work to do, so try to come back as soon as possible */
1953 return TRUE;
1958 /* queue is empty, so we can wait a while before checking it again */
1959 return FALSE;
1962 static gboolean
1963 rhythmdb_idle_poll_events (RhythmDB *db)
1965 gboolean poll_soon;
1966 GTimeVal timeout;
1968 g_get_current_time (&timeout);
1969 g_time_val_add (&timeout, G_USEC_PER_SEC*0.75);
1971 GDK_THREADS_ENTER ();
1973 poll_soon = rhythmdb_process_events (db, &timeout);
1975 if (poll_soon)
1976 db->priv->event_poll_id =
1977 g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) rhythmdb_idle_poll_events,
1978 db, NULL);
1979 else
1980 db->priv->event_poll_id =
1981 g_timeout_add (1000, (GSourceFunc) rhythmdb_idle_poll_events, db);
1983 GDK_THREADS_LEAVE ();
1985 return FALSE;
1988 #define READ_QUEUE_TIMEOUT G_USEC_PER_SEC / 10
1990 static gpointer
1991 read_queue (GAsyncQueue *queue, gboolean *cancel)
1993 GTimeVal timeout;
1994 gpointer ret;
1996 g_get_current_time (&timeout);
1997 g_time_val_add (&timeout, READ_QUEUE_TIMEOUT);
1999 if (G_UNLIKELY (*cancel))
2000 return NULL;
2001 while ((ret = g_async_queue_timed_pop (queue, &timeout)) == NULL) {
2002 if (G_UNLIKELY (*cancel))
2003 return NULL;
2004 g_get_current_time (&timeout);
2005 g_time_val_add (&timeout, G_USEC_PER_SEC);
2008 return ret;
2011 static void
2012 rhythmdb_execute_stat_info_cb (GnomeVFSAsyncHandle *handle,
2013 GList *results,
2014 /* GnomeVFSGetFileInfoResult* items */
2015 RhythmDBEvent *event)
2017 /* this is in the main thread, so we can't do any long operation here */
2018 GnomeVFSGetFileInfoResult *info_result = results->data;
2020 g_mutex_lock (event->db->priv->stat_mutex);
2021 event->db->priv->outstanding_stats = g_list_remove (event->db->priv->outstanding_stats, event);
2022 event->handle = NULL;
2023 g_mutex_unlock (event->db->priv->stat_mutex);
2025 if (info_result->result == GNOME_VFS_OK) {
2026 event->vfsinfo = gnome_vfs_file_info_dup (info_result->file_info);
2027 } else {
2028 event->error = make_access_failed_error (rb_refstring_get (event->real_uri),
2029 info_result->result);
2030 event->vfsinfo = NULL;
2032 g_async_queue_push (event->db->priv->event_queue, event);
2035 static void
2036 rhythmdb_execute_stat (RhythmDB *db,
2037 const char *uri,
2038 RhythmDBEvent *event)
2040 GnomeVFSURI *vfs_uri = gnome_vfs_uri_new (uri);
2042 GList *uri_list = g_list_append (NULL, vfs_uri);
2043 event->real_uri = rb_refstring_new (uri);
2045 g_mutex_lock (db->priv->stat_mutex);
2046 db->priv->outstanding_stats = g_list_prepend (db->priv->outstanding_stats, event);
2047 g_mutex_unlock (db->priv->stat_mutex);
2049 gnome_vfs_async_get_file_info (&event->handle, uri_list,
2050 GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
2051 GNOME_VFS_PRIORITY_MIN,
2052 (GnomeVFSAsyncGetFileInfoCallback) rhythmdb_execute_stat_info_cb,
2053 event);
2054 gnome_vfs_uri_unref (vfs_uri);
2055 g_list_free (uri_list);
2058 void
2059 queue_stat_uri (const char *uri,
2060 RhythmDB *db,
2061 RhythmDBEntryType type)
2063 RhythmDBEvent *result;
2065 rb_debug ("queueing stat for \"%s\"", uri);
2066 g_assert (uri && *uri);
2068 result = g_new0 (RhythmDBEvent, 1);
2069 result->db = db;
2070 result->type = RHYTHMDB_EVENT_STAT;
2071 result->entry_type = type;
2074 * before the action thread is started, we queue up stat events,
2075 * as we're still creating and running queries, as well as loading
2076 * the database. when we start the action thread, we'll kick off
2077 * a gnome-vfs job to run all the stat events too.
2079 * when the action thread is already running, we can start the
2080 * async_get_file_info job directly.
2082 g_mutex_lock (db->priv->stat_mutex);
2083 if (db->priv->action_thread_running) {
2084 g_mutex_unlock (db->priv->stat_mutex);
2085 rhythmdb_execute_stat (db, uri, result);
2086 } else {
2087 GnomeVFSURI *vfs_uri;
2089 vfs_uri = gnome_vfs_uri_new (uri);
2091 /* construct a list of URIs and a hash table containing
2092 * stat events to fill in and post on the event queue.
2094 if (g_hash_table_lookup (db->priv->stat_events, vfs_uri)) {
2095 g_free (result);
2096 gnome_vfs_uri_unref (vfs_uri);
2097 } else {
2098 result->real_uri = rb_refstring_new (uri);
2099 g_hash_table_insert (db->priv->stat_events, vfs_uri, result);
2100 db->priv->stat_list = g_list_prepend (db->priv->stat_list, vfs_uri);
2103 g_mutex_unlock (db->priv->stat_mutex);
2107 static void
2108 queue_stat_uri_tad (const char *uri,
2109 gboolean dir,
2110 RhythmDBAddThreadData *data)
2112 if (!dir)
2113 queue_stat_uri (uri, data->db, data->type);
2116 static gpointer
2117 add_thread_main (RhythmDBAddThreadData *data)
2119 RhythmDBEvent *result;
2121 rb_uri_handle_recursively (data->uri, (RBUriRecurseFunc) queue_stat_uri_tad,
2122 &data->db->priv->exiting, data);
2124 rb_debug ("exiting");
2125 result = g_new0 (RhythmDBEvent, 1);
2126 result->db = data->db;
2127 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2128 g_async_queue_push (data->db->priv->event_queue, result);
2129 g_free (data->uri);
2130 g_free (data);
2131 return NULL;
2134 static void
2135 rhythmdb_execute_load (RhythmDB *db,
2136 const char *uri,
2137 RhythmDBEvent *event)
2139 GnomeVFSURI *vfs_uri = gnome_vfs_uri_new (uri);
2140 GnomeVFSResult vfsresult;
2141 char *resolved;
2143 resolved = rb_uri_resolve_symlink (uri);
2144 if (resolved != NULL) {
2145 event->real_uri = rb_refstring_new (resolved);
2146 event->vfsinfo = gnome_vfs_file_info_new ();
2148 vfsresult = gnome_vfs_get_file_info (uri,
2149 event->vfsinfo,
2150 GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
2151 g_free (resolved);
2152 } else {
2153 event->real_uri = rb_refstring_new (uri);
2154 vfsresult = GNOME_VFS_ERROR_LOOP;
2157 if (vfsresult != GNOME_VFS_OK) {
2158 event->error = make_access_failed_error (uri, vfsresult);
2159 if (event->vfsinfo)
2160 gnome_vfs_file_info_unref (event->vfsinfo);
2161 event->vfsinfo = NULL;
2162 } else {
2163 if (event->type == RHYTHMDB_EVENT_METADATA_LOAD) {
2164 event->metadata = rb_metadata_new ();
2165 rb_metadata_load (event->metadata, rb_refstring_get (event->real_uri),
2166 &event->error);
2170 gnome_vfs_uri_unref (vfs_uri);
2171 g_async_queue_push (db->priv->event_queue, event);
2175 * rhythmdb_entry_get:
2176 * @entry: a #RhythmDBEntry.
2177 * @propid: the id of the property to get.
2178 * @val: return location for the property value.
2180 * Gets a property of an entry, storing it in the given #GValue.
2182 void
2183 rhythmdb_entry_get (RhythmDB *db,
2184 RhythmDBEntry *entry,
2185 RhythmDBPropType propid,
2186 GValue *val)
2188 g_return_if_fail (RHYTHMDB_IS (db));
2189 g_return_if_fail (entry != NULL);
2190 g_return_if_fail (entry->refcount > 0);
2192 rhythmdb_entry_sync_mirrored (entry, propid);
2194 g_assert (G_VALUE_TYPE (val) == rhythmdb_get_property_type (db, propid));
2195 switch (rhythmdb_property_type_map[propid]) {
2196 case G_TYPE_STRING:
2197 g_value_set_string (val, rhythmdb_entry_get_string (entry, propid));
2198 break;
2199 case G_TYPE_BOOLEAN:
2200 g_value_set_boolean (val, rhythmdb_entry_get_boolean (entry, propid));
2201 break;
2202 case G_TYPE_ULONG:
2203 g_value_set_ulong (val, rhythmdb_entry_get_ulong (entry, propid));
2204 break;
2205 case G_TYPE_UINT64:
2206 g_value_set_uint64 (val, rhythmdb_entry_get_uint64 (entry, propid));
2207 break;
2208 case G_TYPE_DOUBLE:
2209 g_value_set_double (val, rhythmdb_entry_get_double (entry, propid));
2210 break;
2211 case G_TYPE_POINTER:
2212 g_value_set_pointer (val, rhythmdb_entry_get_pointer (entry, propid));
2213 break;
2214 default:
2215 g_assert_not_reached ();
2216 break;
2220 static void
2221 entry_to_rb_metadata (RhythmDB *db,
2222 RhythmDBEntry *entry,
2223 RBMetaData *metadata)
2225 GValue val = {0, };
2226 int i;
2228 for (i = RHYTHMDB_PROP_TYPE; i != RHYTHMDB_NUM_PROPERTIES; i++) {
2229 RBMetaDataField field;
2231 if (metadata_field_from_prop (i, &field) == FALSE) {
2232 continue;
2235 g_value_init (&val, rhythmdb_property_type_map[i]);
2236 rhythmdb_entry_get (db, entry, i, &val);
2237 rb_metadata_set (metadata,
2238 field,
2239 &val);
2240 g_value_unset (&val);
2244 typedef struct
2246 RhythmDB *db;
2247 char *uri;
2248 GError *error;
2249 } RhythmDBSaveErrorData;
2251 static gboolean
2252 emit_save_error_idle (RhythmDBSaveErrorData *data)
2254 g_signal_emit (G_OBJECT (data->db), rhythmdb_signals[SAVE_ERROR], 0, data->uri, data->error);
2255 g_object_unref (G_OBJECT (data->db));
2256 g_free (data->uri);
2257 g_error_free (data->error);
2258 g_free (data);
2259 return FALSE;
2262 static gpointer
2263 action_thread_main (RhythmDB *db)
2265 RhythmDBEvent *result;
2267 while (TRUE) {
2268 RhythmDBAction *action;
2270 action = read_queue (db->priv->action_queue, &db->priv->exiting);
2272 if (action == NULL)
2273 break;
2275 switch (action->type) {
2276 case RHYTHMDB_ACTION_STAT:
2278 result = g_new0 (RhythmDBEvent, 1);
2279 result->db = db;
2280 result->type = RHYTHMDB_EVENT_STAT;
2281 result->entry_type = action->entry_type;
2283 rb_debug ("executing RHYTHMDB_ACTION_STAT for \"%s\"", rb_refstring_get (action->uri));
2285 rhythmdb_execute_stat (db, rb_refstring_get (action->uri), result);
2287 break;
2288 case RHYTHMDB_ACTION_LOAD:
2290 result = g_new0 (RhythmDBEvent, 1);
2291 result->db = db;
2292 result->type = RHYTHMDB_EVENT_METADATA_LOAD;
2293 result->entry_type = action->entry_type;
2295 rb_debug ("executing RHYTHMDB_ACTION_LOAD for \"%s\"", rb_refstring_get (action->uri));
2297 rhythmdb_execute_load (db, rb_refstring_get (action->uri), result);
2299 break;
2300 case RHYTHMDB_ACTION_SYNC:
2302 GError *error = NULL;
2303 RhythmDBEntry *entry;
2304 RhythmDBEntryType entry_type;
2306 if (db->priv->dry_run) {
2307 rb_debug ("dry run is enabled, not syncing metadata");
2308 break;
2311 entry = rhythmdb_entry_lookup_by_location_refstring (db, action->uri);
2312 if (!entry)
2313 break;
2315 entry_type = rhythmdb_entry_get_entry_type (entry);
2316 entry_type->sync_metadata (db, entry, &error, entry_type->sync_metadata_data);
2318 if (error != NULL) {
2319 RhythmDBSaveErrorData *data;
2321 data = g_new0 (RhythmDBSaveErrorData, 1);
2322 g_object_ref (db);
2323 data->db = db;
2324 data->uri = g_strdup (rb_refstring_get (action->uri));
2325 data->error = error;
2326 g_idle_add ((GSourceFunc)emit_save_error_idle, data);
2327 break;
2329 break;
2331 break;
2332 default:
2333 g_assert_not_reached ();
2334 break;
2336 rhythmdb_action_free (db, action);
2340 rb_debug ("exiting main thread");
2341 result = g_new0 (RhythmDBEvent, 1);
2342 result->db = db;
2343 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2344 g_async_queue_push (db->priv->event_queue, result);
2346 return NULL;
2350 * rhythmdb_add_uri:
2351 * @db: a #RhythmDB.
2352 * @uri: the URI to add an entry/entries for
2354 * Adds the file(s) pointed to by @uri to the database, as entries of type
2355 * RHYTHMDB_ENTRY_TYPE_SONG. If the URI is that of a file, they will be added.
2356 * If the URI is that of a directory, everything under it will be added recursively.
2358 void
2359 rhythmdb_add_uri (RhythmDB *db,
2360 const char *uri)
2362 rhythmdb_add_uri_with_type (db, uri, RHYTHMDB_ENTRY_TYPE_INVALID);
2365 void
2366 rhythmdb_add_uri_with_type (RhythmDB *db,
2367 const char *uri,
2368 RhythmDBEntryType type)
2370 char *realuri;
2371 char *canon_uri;
2373 canon_uri = rb_canonicalise_uri (uri);
2374 realuri = rb_uri_resolve_symlink (canon_uri);
2376 if (realuri == NULL) {
2377 /* create import error entry */
2378 RhythmDBEvent *event = g_new0 (RhythmDBEvent, 1);
2380 event->db = db;
2381 event->real_uri = rb_refstring_new (canon_uri);
2382 event->error = make_access_failed_error (canon_uri, GNOME_VFS_ERROR_LOOP);
2383 rhythmdb_add_import_error_entry (db, event);
2384 g_free (event);
2386 } else if (rb_uri_is_directory (realuri)) {
2387 RhythmDBAddThreadData *data = g_new0 (RhythmDBAddThreadData, 1);
2388 data->db = db;
2389 data->uri = g_strdup (realuri);
2390 data->type = type;
2392 rhythmdb_thread_create (db, db->priv->add_thread_pool, NULL, data);
2393 } else {
2394 queue_stat_uri (realuri, db, type);
2397 g_free (canon_uri);
2398 g_free (realuri);
2401 static gboolean
2402 rhythmdb_sync_library_idle (RhythmDB *db)
2404 rhythmdb_sync_library_location (db);
2405 g_object_unref (db);
2406 return FALSE;
2409 static gboolean
2410 rhythmdb_load_error_cb (GError *error)
2412 GDK_THREADS_ENTER ();
2413 rb_error_dialog (NULL,
2414 _("Could not load the music database"),
2415 error->message);
2416 g_error_free (error);
2418 GDK_THREADS_LEAVE ();
2419 return FALSE;
2422 static gpointer
2423 rhythmdb_load_thread_main (RhythmDB *db)
2425 RhythmDBEvent *result;
2426 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2427 GError *error = NULL;
2429 rb_profile_start ("loading db");
2430 g_mutex_lock (db->priv->saving_mutex);
2431 if (klass->impl_load (db, &db->priv->exiting, &error) == FALSE) {
2432 rb_debug ("db load failed: disabling saving");
2433 db->priv->can_save = FALSE;
2435 if (error) {
2436 g_idle_add ((GSourceFunc) rhythmdb_load_error_cb, error);
2439 g_mutex_unlock (db->priv->saving_mutex);
2441 g_object_ref (db);
2442 g_timeout_add (10000, (GSourceFunc) rhythmdb_sync_library_idle, db);
2444 rb_debug ("queuing db load complete signal");
2445 result = g_new0 (RhythmDBEvent, 1);
2446 result->type = RHYTHMDB_EVENT_DB_LOAD;
2447 g_async_queue_push (db->priv->event_queue, result);
2449 rb_debug ("exiting");
2450 result = g_new0 (RhythmDBEvent, 1);
2451 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2452 g_async_queue_push (db->priv->event_queue, result);
2454 rb_profile_end ("loading db");
2455 return NULL;
2459 * rhythmdb_load:
2460 * @db: a #RhythmDB.
2462 * Load the database from disk.
2464 void
2465 rhythmdb_load (RhythmDB *db)
2467 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_load_thread_main, db);
2470 static gpointer
2471 rhythmdb_save_thread_main (RhythmDB *db)
2473 RhythmDBClass *klass;
2474 RhythmDBEvent *result;
2476 rb_debug ("entering save thread");
2478 g_mutex_lock (db->priv->saving_mutex);
2480 if (!db->priv->dirty && !db->priv->can_save) {
2481 rb_debug ("no save needed, ignoring");
2482 g_mutex_unlock (db->priv->saving_mutex);
2483 goto out;
2486 while (db->priv->saving)
2487 g_cond_wait (db->priv->saving_condition, db->priv->saving_mutex);
2489 db->priv->saving = TRUE;
2491 rb_debug ("saving rhythmdb");
2493 klass = RHYTHMDB_GET_CLASS (db);
2494 klass->impl_save (db);
2496 db->priv->saving = FALSE;
2497 db->priv->dirty = FALSE;
2499 g_mutex_unlock (db->priv->saving_mutex);
2501 g_cond_broadcast (db->priv->saving_condition);
2503 out:
2504 result = g_new0 (RhythmDBEvent, 1);
2505 result->db = db;
2506 result->type = RHYTHMDB_EVENT_DB_SAVED;
2507 g_async_queue_push (db->priv->event_queue, result);
2509 result = g_new0 (RhythmDBEvent, 1);
2510 result->db = db;
2511 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2512 g_async_queue_push (db->priv->event_queue, result);
2513 return NULL;
2517 * rhythmdb_save_async:
2518 * @db: a #RhythmDB.
2520 * Save the database to disk, asynchronously.
2522 void
2523 rhythmdb_save_async (RhythmDB *db)
2525 rb_debug ("saving the rhythmdb in the background");
2527 rhythmdb_read_enter (db);
2529 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_save_thread_main, db);
2533 * rhythmdb_save:
2534 * @db: a #RhythmDB.
2536 * Save the database to disk, not returning until it has been saved.
2538 void
2539 rhythmdb_save (RhythmDB *db)
2541 rb_debug("saving the rhythmdb and blocking");
2543 rhythmdb_save_async (db);
2545 g_mutex_lock (db->priv->saving_mutex);
2547 while (db->priv->saving)
2548 g_cond_wait (db->priv->saving_condition, db->priv->saving_mutex);
2550 g_mutex_unlock (db->priv->saving_mutex);
2554 * rhythmdb_entry_set:
2555 * @db:# a RhythmDB.
2556 * @entry: a #RhythmDBEntry.
2557 * @propid: the id of the property to set.
2558 * @value: the property value.
2560 * This function can be called by any code which wishes to change a
2561 * song property and send a notification. It may be called when the
2562 * database is read-only; in this case the change will be queued for
2563 * an unspecified time in the future. The implication of this is that
2564 * rhythmdb_entry_get() may not reflect the changes immediately. However,
2565 * if this property is exposed in the user interface, you should still
2566 * make the change in the widget. Then when the database returns to a
2567 * writable state, your change will take effect in the database too,
2568 * and a notification will be sent at that point.
2570 * Note that you must call rhythmdb_commit() at some point after invoking
2571 * this function, and that even after the commit, your change may not
2572 * have taken effect.
2574 void
2575 rhythmdb_entry_set (RhythmDB *db,
2576 RhythmDBEntry *entry,
2577 guint propid,
2578 const GValue *value)
2580 g_return_if_fail (RHYTHMDB_IS (db));
2581 g_return_if_fail (entry != NULL);
2583 if ((entry->flags & RHYTHMDB_ENTRY_INSERTED) != 0) {
2584 if (!rhythmdb_get_readonly (db) && rb_is_main_thread ()) {
2585 rhythmdb_entry_set_internal (db, entry, TRUE, propid, value);
2586 } else {
2587 RhythmDBEvent *result;
2589 result = g_new0 (RhythmDBEvent, 1);
2590 result->db = db;
2591 result->type = RHYTHMDB_EVENT_ENTRY_SET;
2593 rb_debug ("queuing RHYTHMDB_ACTION_ENTRY_SET");
2595 result->entry = rhythmdb_entry_ref (entry);
2596 result->change.prop = propid;
2597 result->signal_change = TRUE;
2598 g_value_init (&result->change.new, G_VALUE_TYPE (value));
2599 g_value_copy (value, &result->change.new);
2600 g_async_queue_push (db->priv->event_queue, result);
2602 } else {
2603 rhythmdb_entry_set_internal (db, entry, FALSE, propid, value);
2607 static void
2608 record_entry_change (RhythmDB *db,
2609 RhythmDBEntry *entry,
2610 guint propid,
2611 const GValue *value)
2613 RhythmDBEntryChange *changedata;
2614 GSList *changelist;
2616 changedata = g_new0 (RhythmDBEntryChange, 1);
2617 changedata->prop = propid;
2619 /* Copy a temporary gvalue, since _entry_get uses
2620 * _set_static_string to avoid memory allocations. */
2622 GValue tem = {0,};
2623 g_value_init (&tem, G_VALUE_TYPE (value));
2624 rhythmdb_entry_get (db, entry, propid, &tem);
2625 g_value_init (&changedata->old, G_VALUE_TYPE (value));
2626 g_value_copy (&tem, &changedata->old);
2627 g_value_unset (&tem);
2629 g_value_init (&changedata->new, G_VALUE_TYPE (value));
2630 g_value_copy (value, &changedata->new);
2632 g_mutex_lock (db->priv->change_mutex);
2633 /* ref the entry before adding to hash, it is unreffed when removed */
2634 rhythmdb_entry_ref (entry);
2635 changelist = g_hash_table_lookup (db->priv->changed_entries, entry);
2636 changelist = g_slist_append (changelist, changedata);
2637 g_hash_table_insert (db->priv->changed_entries, entry, changelist);
2638 g_mutex_unlock (db->priv->change_mutex);
2641 void
2642 rhythmdb_entry_set_internal (RhythmDB *db,
2643 RhythmDBEntry *entry,
2644 gboolean notify_if_inserted,
2645 guint propid,
2646 const GValue *value)
2648 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2649 gboolean handled;
2650 RhythmDBPodcastFields *podcast = NULL;
2652 #ifndef G_DISABLE_ASSERT
2653 switch (G_VALUE_TYPE (value)) {
2654 case G_TYPE_STRING:
2655 /* the playback error is allowed to be NULL */
2656 if (propid != RHYTHMDB_PROP_PLAYBACK_ERROR || g_value_get_string (value))
2657 g_assert (g_utf8_validate (g_value_get_string (value), -1, NULL));
2658 break;
2659 case G_TYPE_BOOLEAN:
2660 case G_TYPE_ULONG:
2661 case G_TYPE_UINT64:
2662 case G_TYPE_DOUBLE:
2663 break;
2664 default:
2665 g_assert_not_reached ();
2666 break;
2668 #endif
2670 if ((entry->flags & RHYTHMDB_ENTRY_INSERTED) && notify_if_inserted) {
2671 record_entry_change (db, entry, propid, value);
2674 handled = klass->impl_entry_set (db, entry, propid, value);
2676 if (!handled) {
2677 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
2678 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
2679 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
2681 switch (propid) {
2682 case RHYTHMDB_PROP_TYPE:
2683 case RHYTHMDB_PROP_ENTRY_ID:
2684 g_assert_not_reached ();
2685 break;
2686 case RHYTHMDB_PROP_TITLE:
2687 if (entry->title != NULL) {
2688 rb_refstring_unref (entry->title);
2690 entry->title = rb_refstring_new (g_value_get_string (value));
2691 break;
2692 case RHYTHMDB_PROP_ALBUM:
2693 if (entry->album != NULL) {
2694 rb_refstring_unref (entry->album);
2696 entry->album = rb_refstring_new (g_value_get_string (value));
2697 break;
2698 case RHYTHMDB_PROP_ARTIST:
2699 if (entry->artist != NULL) {
2700 rb_refstring_unref (entry->artist);
2702 entry->artist = rb_refstring_new (g_value_get_string (value));
2703 break;
2704 case RHYTHMDB_PROP_GENRE:
2705 if (entry->genre != NULL) {
2706 rb_refstring_unref (entry->genre);
2708 entry->genre = rb_refstring_new (g_value_get_string (value));
2709 break;
2710 case RHYTHMDB_PROP_TRACK_NUMBER:
2711 entry->tracknum = g_value_get_ulong (value);
2712 break;
2713 case RHYTHMDB_PROP_DISC_NUMBER:
2714 entry->discnum = g_value_get_ulong (value);
2715 break;
2716 case RHYTHMDB_PROP_DURATION:
2717 entry->duration = g_value_get_ulong (value);
2718 break;
2719 case RHYTHMDB_PROP_BITRATE:
2720 entry->bitrate = g_value_get_ulong (value);
2721 break;
2722 case RHYTHMDB_PROP_DATE:
2724 gulong julian;
2725 julian = g_value_get_ulong (value);
2726 if (julian > 0)
2727 g_date_set_julian (&entry->date, julian);
2728 else
2729 g_date_clear (&entry->date, 1);
2730 break;
2732 case RHYTHMDB_PROP_TRACK_GAIN:
2733 entry->track_gain = g_value_get_double (value);
2734 break;
2735 case RHYTHMDB_PROP_TRACK_PEAK:
2736 entry->track_peak = g_value_get_double (value);
2737 break;
2738 case RHYTHMDB_PROP_ALBUM_GAIN:
2739 entry->album_gain = g_value_get_double (value);
2740 break;
2741 case RHYTHMDB_PROP_ALBUM_PEAK:
2742 entry->album_peak = g_value_get_double (value);
2743 break;
2744 case RHYTHMDB_PROP_LOCATION:
2745 rb_refstring_unref (entry->location);
2746 entry->location = rb_refstring_new (g_value_get_string (value));
2747 break;
2748 case RHYTHMDB_PROP_PLAYBACK_ERROR:
2749 rb_refstring_unref (entry->playback_error);
2750 if (g_value_get_string (value))
2751 entry->playback_error = rb_refstring_new (g_value_get_string (value));
2752 else
2753 entry->playback_error = NULL;
2754 break;
2755 case RHYTHMDB_PROP_MOUNTPOINT:
2756 if (entry->mountpoint != NULL) {
2757 rb_refstring_unref (entry->mountpoint);
2759 entry->mountpoint = rb_refstring_new (g_value_get_string (value));
2760 break;
2761 case RHYTHMDB_PROP_FILE_SIZE:
2762 entry->file_size = g_value_get_uint64 (value);
2763 break;
2764 case RHYTHMDB_PROP_MIMETYPE:
2765 if (entry->mimetype != NULL) {
2766 rb_refstring_unref (entry->mimetype);
2768 entry->mimetype = rb_refstring_new (g_value_get_string (value));
2769 break;
2770 case RHYTHMDB_PROP_MTIME:
2771 entry->mtime = g_value_get_ulong (value);
2772 break;
2773 case RHYTHMDB_PROP_FIRST_SEEN:
2774 entry->first_seen = g_value_get_ulong (value);
2775 entry->flags |= RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY;
2776 break;
2777 case RHYTHMDB_PROP_LAST_SEEN:
2778 entry->last_seen = g_value_get_ulong (value);
2779 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
2780 break;
2781 case RHYTHMDB_PROP_RATING:
2782 entry->rating = g_value_get_double (value);
2783 break;
2784 case RHYTHMDB_PROP_PLAY_COUNT:
2785 entry->play_count = g_value_get_ulong (value);
2786 break;
2787 case RHYTHMDB_PROP_LAST_PLAYED:
2788 entry->last_played = g_value_get_ulong (value);
2789 entry->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY;
2790 break;
2791 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
2792 rb_refstring_unref (entry->musicbrainz_trackid);
2793 entry->musicbrainz_trackid = rb_refstring_new (g_value_get_string (value));
2794 break;
2795 case RHYTHMDB_PROP_HIDDEN:
2796 if (g_value_get_boolean (value)) {
2797 entry->flags |= RHYTHMDB_ENTRY_HIDDEN;
2798 } else {
2799 entry->flags &= ~RHYTHMDB_ENTRY_HIDDEN;
2801 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
2802 break;
2803 case RHYTHMDB_PROP_STATUS:
2804 g_assert (podcast);
2805 podcast->status = g_value_get_ulong (value);
2806 break;
2807 case RHYTHMDB_PROP_DESCRIPTION:
2808 g_assert (podcast);
2809 rb_refstring_unref (podcast->description);
2810 podcast->description = rb_refstring_new (g_value_get_string (value));
2811 break;
2812 case RHYTHMDB_PROP_SUBTITLE:
2813 g_assert (podcast);
2814 rb_refstring_unref (podcast->subtitle);
2815 podcast->subtitle = rb_refstring_new (g_value_get_string (value));
2816 break;
2817 case RHYTHMDB_PROP_SUMMARY:
2818 g_assert (podcast);
2819 rb_refstring_unref (podcast->summary);
2820 podcast->summary = rb_refstring_new (g_value_get_string (value));
2821 break;
2822 case RHYTHMDB_PROP_LANG:
2823 g_assert (podcast);
2824 if (podcast->lang != NULL) {
2825 rb_refstring_unref (podcast->lang);
2827 podcast->lang = rb_refstring_new (g_value_get_string (value));
2828 break;
2829 case RHYTHMDB_PROP_COPYRIGHT:
2830 g_assert (podcast);
2831 if (podcast->copyright != NULL) {
2832 rb_refstring_unref (podcast->copyright);
2834 podcast->copyright = rb_refstring_new (g_value_get_string (value));
2835 break;
2836 case RHYTHMDB_PROP_IMAGE:
2837 g_assert (podcast);
2838 if (podcast->image != NULL) {
2839 rb_refstring_unref (podcast->image);
2841 podcast->image = rb_refstring_new (g_value_get_string (value));
2842 break;
2843 case RHYTHMDB_PROP_POST_TIME:
2844 g_assert (podcast);
2845 podcast->post_time = g_value_get_ulong (value);
2846 break;
2847 case RHYTHMDB_NUM_PROPERTIES:
2848 g_assert_not_reached ();
2849 break;
2853 /* set the dirty state */
2854 db->priv->dirty = TRUE;
2858 * rhythmdb_entry_sync_mirrored:
2859 * @db: a #RhythmDB.
2860 * @type: a #RhythmDBEntry.
2861 * @propid: the property to sync the mirrored version of.
2863 * Synchronise "mirrored" properties, such as the string version of the last-played
2864 * time. This should be called when a property is directly modified, passing the
2865 * original property.
2867 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
2870 static void
2871 rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
2872 guint propid)
2874 static const char *format;
2875 static const char *never;
2876 char *val;
2878 /* PERFORMANCE: doing these lookups every call creates a noticable slowdown */
2879 if (format == NULL)
2880 format = _("%Y-%m-%d %H:%M");
2881 if (never == NULL)
2882 never = _("Never");
2884 switch (propid) {
2885 case RHYTHMDB_PROP_LAST_PLAYED_STR:
2887 RBRefString *old, *new;
2889 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY))
2890 break;
2892 old = g_atomic_pointer_get (&entry->last_played_str);
2893 if (entry->last_played == 0) {
2894 new = rb_refstring_new (never);
2895 } else {
2896 val = eel_strdup_strftime (format, localtime ((glong*)&entry->last_played));
2897 new = rb_refstring_new (val);
2898 g_free (val);
2901 if (g_atomic_pointer_compare_and_exchange (&entry->last_played_str, old, new)) {
2902 if (old != NULL) {
2903 rb_refstring_unref (old);
2905 } else {
2906 rb_refstring_unref (new);
2909 break;
2911 case RHYTHMDB_PROP_FIRST_SEEN_STR:
2913 RBRefString *old, *new;
2915 if (!(entry->flags & RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY))
2916 break;
2918 old = g_atomic_pointer_get (&entry->first_seen_str);
2919 val = eel_strdup_strftime (format, localtime ((glong*)&entry->first_seen));
2920 new = rb_refstring_new (val);
2921 g_free (val);
2923 if (g_atomic_pointer_compare_and_exchange (&entry->first_seen_str, old, new)) {
2924 if (old != NULL) {
2925 rb_refstring_unref (old);
2927 } else {
2928 rb_refstring_unref (new);
2931 break;
2933 case RHYTHMDB_PROP_LAST_SEEN_STR:
2935 RBRefString *old, *new;
2937 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_SEEN_DIRTY))
2938 break;
2940 old = g_atomic_pointer_get (&entry->last_seen_str);
2941 /* only store last seen time as a string for hidden entries */
2942 if (entry->flags & RHYTHMDB_ENTRY_HIDDEN) {
2943 val = eel_strdup_strftime (format, localtime ((glong*)&entry->last_seen));
2944 new = rb_refstring_new (val);
2945 g_free (val);
2946 } else {
2947 new = NULL;
2950 if (g_atomic_pointer_compare_and_exchange (&entry->first_seen_str, old, new)) {
2951 if (old != NULL) {
2952 rb_refstring_unref (old);
2954 } else {
2955 rb_refstring_unref (new);
2958 break;
2960 default:
2961 break;
2966 * rhythmdb_entry_delete:
2967 * @db: a #RhythmDB.
2968 * @entry: a #RhythmDBEntry.
2970 * Delete entry @entry from the database, sending notification of it's deletion.
2971 * This is usually used by sources where entries can disappear randomly, such
2972 * as a network source.
2974 void
2975 rhythmdb_entry_delete (RhythmDB *db,
2976 RhythmDBEntry *entry)
2978 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2980 g_return_if_fail (RHYTHMDB_IS (db));
2981 g_return_if_fail (entry != NULL);
2983 /* ref the entry before adding to hash, it is unreffed when removed */
2984 rhythmdb_entry_ref (entry);
2986 klass->impl_entry_delete (db, entry);
2988 g_mutex_lock (db->priv->change_mutex);
2989 g_hash_table_insert (db->priv->deleted_entries, entry, g_thread_self ());
2990 g_mutex_unlock (db->priv->change_mutex);
2992 /* deleting an entry makes the db dirty */
2993 db->priv->dirty = TRUE;
2996 static gint
2997 rhythmdb_entry_move_to_trash_cb (GnomeVFSXferProgressInfo *info,
2998 gpointer data)
3000 /* Abort immediately if anything happens */
3001 if (info->status == GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR)
3002 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
3003 /* Don't overwrite files */
3004 if (info->status == GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE)
3005 return 0;
3006 return TRUE;
3009 static void
3010 rhythmdb_entry_move_to_trash_set_error (RhythmDB *db,
3011 RhythmDBEntry *entry,
3012 GnomeVFSResult res)
3014 GValue value = { 0, };
3016 if (res == -1)
3017 res = GNOME_VFS_ERROR_INTERNAL;
3019 g_value_init (&value, G_TYPE_STRING);
3020 g_value_set_string (&value, gnome_vfs_result_to_string (res));
3021 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
3022 g_value_unset (&value);
3024 rb_debug ("Deleting %s failed: %s", rb_refstring_get (entry->location),
3025 gnome_vfs_result_to_string (res));
3028 void
3029 rhythmdb_entry_move_to_trash (RhythmDB *db,
3030 RhythmDBEntry *entry)
3032 GnomeVFSResult res;
3033 GnomeVFSURI *uri, *trash, *dest;
3034 char *shortname;
3036 uri = gnome_vfs_uri_new (rb_refstring_get (entry->location));
3037 if (uri == NULL) {
3038 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3039 return;
3042 res = gnome_vfs_find_directory (uri,
3043 GNOME_VFS_DIRECTORY_KIND_TRASH,
3044 &trash,
3045 TRUE, TRUE,
3047 if (res != GNOME_VFS_OK || trash == NULL) {
3048 /* If the file doesn't exist, or trash isn't support,
3049 * remove it from the db */
3050 if (res == GNOME_VFS_ERROR_NOT_FOUND ||
3051 res == GNOME_VFS_ERROR_NOT_SUPPORTED) {
3052 rhythmdb_entry_set_visibility (db, entry, FALSE);
3053 } else {
3054 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3057 gnome_vfs_uri_unref (uri);
3058 return;
3061 /* Is the file already in the Trash? If so it should be hidden */
3062 if (gnome_vfs_uri_is_parent (trash, uri, TRUE)) {
3063 GValue value = { 0, };
3064 g_value_init (&value, G_TYPE_BOOLEAN);
3065 g_value_set_boolean (&value, TRUE);
3066 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_HIDDEN, &value);
3067 rhythmdb_commit (db);
3069 gnome_vfs_uri_unref (trash);
3070 gnome_vfs_uri_unref (uri);
3071 return;
3074 shortname = gnome_vfs_uri_extract_short_name (uri);
3075 if (shortname == NULL) {
3076 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3077 rhythmdb_commit (db);
3078 gnome_vfs_uri_unref (uri);
3079 gnome_vfs_uri_unref (trash);
3080 return;
3083 /* Compute the destination URI */
3084 dest = gnome_vfs_uri_append_path (trash, shortname);
3085 gnome_vfs_uri_unref (trash);
3086 g_free (shortname);
3087 if (dest == NULL) {
3088 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3089 rhythmdb_commit (db);
3090 gnome_vfs_uri_unref (uri);
3091 return;
3094 /* RB can't tell that a file's moved, so no unique names */
3095 res = gnome_vfs_xfer_uri (uri, dest,
3096 GNOME_VFS_XFER_REMOVESOURCE,
3097 GNOME_VFS_XFER_ERROR_MODE_ABORT,
3098 GNOME_VFS_XFER_OVERWRITE_MODE_SKIP,
3099 rhythmdb_entry_move_to_trash_cb,
3100 entry);
3102 if (res == GNOME_VFS_OK) {
3103 rhythmdb_entry_set_visibility (db, entry, FALSE);
3104 } else {
3105 rhythmdb_entry_move_to_trash_set_error (db, entry, res);
3107 rhythmdb_commit (db);
3109 gnome_vfs_uri_unref (dest);
3110 gnome_vfs_uri_unref (uri);
3114 * rhythmdb_entry_delete_by_type:
3115 * @db: a #RhythmDB.
3116 * @type: type of entried to delete.
3118 * Delete all entries from the database of the given type.
3119 * This is usually used by non-permanent sources when they disappear, such as
3120 * removable media being removed, or a network share becoming unavailable.
3122 void
3123 rhythmdb_entry_delete_by_type (RhythmDB *db,
3124 RhythmDBEntryType type)
3126 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3128 if (klass->impl_entry_delete_by_type) {
3129 klass->impl_entry_delete_by_type (db, type);
3130 } else {
3131 g_warning ("delete_by_type not implemented");
3135 const xmlChar *
3136 rhythmdb_nice_elt_name_from_propid (RhythmDB *db,
3137 RhythmDBPropType propid)
3139 return db->priv->column_xml_names[propid];
3143 rhythmdb_propid_from_nice_elt_name (RhythmDB *db,
3144 const xmlChar *name)
3146 gpointer ret, orig;
3147 if (g_hash_table_lookup_extended (db->priv->propname_map, name,
3148 &orig, &ret)) {
3149 return GPOINTER_TO_INT (ret);
3151 return -1;
3155 * rhythmdb_entry_lookup_by_location:
3156 * @db: a #RhythmDB.
3157 * @uri: the URI of the entry to lookup.
3159 * Looks up the entry with location @uri.
3161 * Returns: the entry with location @uri, or NULL if no such entry exists.
3163 RhythmDBEntry *
3164 rhythmdb_entry_lookup_by_location (RhythmDB *db,
3165 const char *uri)
3167 RBRefString *rs;
3169 rs = rb_refstring_find (uri);
3170 if (rs != NULL) {
3171 return rhythmdb_entry_lookup_by_location_refstring (db, rs);
3172 } else {
3173 return NULL;
3177 RhythmDBEntry *
3178 rhythmdb_entry_lookup_by_location_refstring (RhythmDB *db,
3179 RBRefString *uri)
3181 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3183 return klass->impl_lookup_by_location (db, uri);
3187 *rhythmdb_entry_lookup_by_id:
3188 * @db: a #RhythmDB.
3189 * @id: entry ID
3191 * Looks up the entry with id @id.
3193 * Returns: the entry with id @id, or NULL if no such entry exists.
3195 RhythmDBEntry *
3196 rhythmdb_entry_lookup_by_id (RhythmDB *db,
3197 gint id)
3199 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3201 return klass->impl_lookup_by_id (db, id);
3205 *rhythmdb_entry_lookup_from_string:
3206 * @db: a #RhythmDB.
3207 * @str: string
3208 * @is_id: whether the string is an entry ID or a location.
3210 * Locates an entry using a string containing either an entry ID
3211 * or a location.
3213 * Returns: the entry matching the string, or NULL if no such entry exists.
3215 RhythmDBEntry *
3216 rhythmdb_entry_lookup_from_string (RhythmDB *db,
3217 const char *str,
3218 gboolean is_id)
3220 if (is_id) {
3221 gint id;
3223 id = strtoul (str, NULL, 10);
3224 if (id == 0)
3225 return NULL;
3227 return rhythmdb_entry_lookup_by_id (db, id);
3228 } else {
3229 return rhythmdb_entry_lookup_by_location (db, str);
3234 *rhythmdb_entry_foreach:
3235 * @db: a #RhythmDB.
3236 * @func: the function to call with each entry.
3237 * @data: user data to pass to the function.
3239 * Calls the given function for each of the entries in the database.
3241 void
3242 rhythmdb_entry_foreach (RhythmDB *db,
3243 GFunc func,
3244 gpointer data)
3246 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3248 klass->impl_entry_foreach (db, func, data);
3252 *rhythmdb_entry_count:
3253 * @db: a #RhythmDB.
3255 * Returns: the number of entries in the database.
3257 gint64
3258 rhythmdb_entry_count (RhythmDB *db)
3260 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3262 return klass->impl_entry_count (db);
3266 *rhythmdb_entry_foreach_by_type:
3267 * @db: a #RhythmdB.
3268 * @entry_type: the type of entry to retrieve
3269 * @func: the function to call with each entry
3270 * @data: user data to pass to the function.
3272 * Calls the given function for each of the entries in the database
3273 * of a given type.
3275 void
3276 rhythmdb_entry_foreach_by_type (RhythmDB *db,
3277 RhythmDBEntryType entry_type,
3278 GFunc func,
3279 gpointer data)
3281 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3283 klass->impl_entry_foreach_by_type (db, entry_type, func, data);
3287 *rhythmdb_entry_count_by_type:
3288 * @db: a #RhythmDB.
3289 * @entry_type: a #RhythmDBEntryType.
3291 * Returns: the number of entries in the database of a particular type.
3293 gint64
3294 rhythmdb_entry_count_by_type (RhythmDB *db,
3295 RhythmDBEntryType entry_type)
3297 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3299 return klass->impl_entry_count_by_type (db, entry_type);
3304 * rhythmdb_evaluate_query:
3305 * @db: a #RhythmDB.
3306 * @query: a query.
3307 * @entry a @RhythmDBEntry.
3309 * Evaluates the given entry against the given query.
3311 * Returns: whether the given entry matches the criteria of the given query.
3313 gboolean
3314 rhythmdb_evaluate_query (RhythmDB *db,
3315 GPtrArray *query,
3316 RhythmDBEntry *entry)
3318 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3320 return klass->impl_evaluate_query (db, query, entry);
3323 static void
3324 rhythmdb_query_internal (RhythmDBQueryThreadData *data)
3326 RhythmDBEvent *result;
3327 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (data->db);
3329 rhythmdb_query_preprocess (data->db, data->query);
3331 rb_debug ("doing query");
3333 klass->impl_do_full_query (data->db, data->query,
3334 data->results,
3335 &data->cancel);
3337 rb_debug ("completed");
3338 rhythmdb_query_results_query_complete (data->results);
3340 result = g_new0 (RhythmDBEvent, 1);
3341 result->db = data->db;
3342 result->type = RHYTHMDB_EVENT_QUERY_COMPLETE;
3343 result->results = data->results;
3344 g_async_queue_push (data->db->priv->event_queue, result);
3346 rhythmdb_query_free (data->query);
3349 static gpointer
3350 query_thread_main (RhythmDBQueryThreadData *data)
3352 RhythmDBEvent *result;
3354 rb_debug ("entering query thread");
3356 rhythmdb_query_internal (data);
3358 result = g_new0 (RhythmDBEvent, 1);
3359 result->db = data->db;
3360 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
3361 g_async_queue_push (data->db->priv->event_queue, result);
3362 g_free (data);
3363 return NULL;
3366 void
3367 rhythmdb_do_full_query_async_parsed (RhythmDB *db,
3368 RhythmDBQueryResults *results,
3369 GPtrArray *query)
3371 RhythmDBQueryThreadData *data;
3373 data = g_new0 (RhythmDBQueryThreadData, 1);
3374 data->db = db;
3375 data->query = rhythmdb_query_copy (query);
3376 data->results = results;
3377 data->cancel = FALSE;
3379 rhythmdb_read_enter (db);
3381 rhythmdb_query_results_set_query (results, query);
3383 g_object_ref (results);
3384 g_object_ref (db);
3385 g_atomic_int_inc (&db->priv->outstanding_threads);
3386 g_async_queue_ref (db->priv->action_queue);
3387 g_async_queue_ref (db->priv->event_queue);
3388 g_thread_pool_push (db->priv->query_thread_pool, data, NULL);
3391 void
3392 rhythmdb_do_full_query_async (RhythmDB *db,
3393 RhythmDBQueryResults *results,
3394 ...)
3396 GPtrArray *query;
3397 va_list args;
3399 va_start (args, results);
3401 query = rhythmdb_query_parse_valist (db, args);
3403 rhythmdb_do_full_query_async_parsed (db, results, query);
3405 rhythmdb_query_free (query);
3407 va_end (args);
3410 static void
3411 rhythmdb_do_full_query_internal (RhythmDB *db,
3412 RhythmDBQueryResults *results,
3413 GPtrArray *query)
3415 RhythmDBQueryThreadData *data;
3417 data = g_new0 (RhythmDBQueryThreadData, 1);
3418 data->db = db;
3419 data->query = rhythmdb_query_copy (query);
3420 data->results = results;
3421 data->cancel = FALSE;
3423 rhythmdb_read_enter (db);
3425 rhythmdb_query_results_set_query (results, query);
3426 g_object_ref (results);
3428 rhythmdb_query_internal (data);
3429 g_free (data);
3432 void
3433 rhythmdb_do_full_query_parsed (RhythmDB *db,
3434 RhythmDBQueryResults *results,
3435 GPtrArray *query)
3437 rhythmdb_do_full_query_internal (db, results, query);
3440 void
3441 rhythmdb_do_full_query (RhythmDB *db,
3442 RhythmDBQueryResults *results,
3443 ...)
3445 GPtrArray *query;
3446 va_list args;
3448 va_start (args, results);
3450 query = rhythmdb_query_parse_valist (db, args);
3452 rhythmdb_do_full_query_internal (db, results, query);
3454 rhythmdb_query_free (query);
3456 va_end (args);
3459 /* This should really be standard. */
3460 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3462 GType
3463 rhythmdb_query_type_get_type (void)
3465 static GType etype = 0;
3467 if (etype == 0)
3469 static const GEnumValue values[] =
3472 ENUM_ENTRY (RHYTHMDB_QUERY_END, "Query end marker"),
3473 ENUM_ENTRY (RHYTHMDB_QUERY_DISJUNCTION, "Disjunctive marker"),
3474 ENUM_ENTRY (RHYTHMDB_QUERY_SUBQUERY, "Subquery"),
3475 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_EQUALS, "Property equivalence"),
3476 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LIKE, "Fuzzy property matching"),
3477 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_LIKE, "Inverted fuzzy property matching"),
3478 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_PREFIX, "Starts with"),
3479 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_SUFFIX, "Ends with"),
3480 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_GREATER, "True if property1 >= property2"),
3481 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LESS, "True if property1 <= property2"),
3482 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN, "True if property1 is within property2 of the current time"),
3483 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN, "True if property1 is not within property2 of the current time"),
3484 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_EQUALS, "Year equivalence: true if date within year"),
3485 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_GREATER, "True if date greater than year"),
3486 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_LESS, "True if date less than year"),
3487 { 0, 0, 0 }
3490 etype = g_enum_register_static ("RhythmDBQueryType", values);
3493 return etype;
3496 GType
3497 rhythmdb_prop_type_get_type (void)
3499 static GType etype = 0;
3501 if (etype == 0)
3503 static const GEnumValue values[] =
3505 /* We reuse the description to store extra data about
3506 * a property. The first part is just a generic
3507 * human-readable description. Next, there is
3508 * a string describing the GType of the property, in
3509 * parenthesis.
3510 * Finally, there is the XML element name in brackets.
3512 ENUM_ENTRY (RHYTHMDB_PROP_TYPE, "Type of entry (gpointer) [type]"),
3513 ENUM_ENTRY (RHYTHMDB_PROP_ENTRY_ID, "Numeric ID (guint) [entry-id]"),
3514 ENUM_ENTRY (RHYTHMDB_PROP_TITLE, "Title (gchararray) [title]"),
3515 ENUM_ENTRY (RHYTHMDB_PROP_GENRE, "Genre (gchararray) [genre]"),
3516 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST, "Artist (gchararray) [artist]"),
3517 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM, "Album (gchararray) [album]"),
3518 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_NUMBER, "Track Number (gulong) [track-number]"),
3519 ENUM_ENTRY (RHYTHMDB_PROP_DISC_NUMBER, "Disc Number (gulong) [disc-number]"),
3520 ENUM_ENTRY (RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, "Musicbrainz Track ID (gchararray) [mb-trackid]"),
3522 ENUM_ENTRY (RHYTHMDB_PROP_DURATION, "Duration (gulong) [duration]"),
3523 ENUM_ENTRY (RHYTHMDB_PROP_FILE_SIZE, "File Size (guint64) [file-size]"),
3524 ENUM_ENTRY (RHYTHMDB_PROP_LOCATION, "Location (gchararray) [location]"),
3525 ENUM_ENTRY (RHYTHMDB_PROP_MOUNTPOINT, "Mount point it's located in (gchararray) [mountpoint]"),
3526 ENUM_ENTRY (RHYTHMDB_PROP_MTIME, "Modification time (gulong) [mtime]"),
3527 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN, "Time the song was added to the library (gulong) [first-seen]"),
3528 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN, "Last time the song was available (gulong) [last-seen]"),
3529 ENUM_ENTRY (RHYTHMDB_PROP_RATING, "Rating (gdouble) [rating]"),
3530 ENUM_ENTRY (RHYTHMDB_PROP_PLAY_COUNT, "Play Count (gulong) [play-count]"),
3531 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED, "Last Played (gulong) [last-played]"),
3532 ENUM_ENTRY (RHYTHMDB_PROP_BITRATE, "Bitrate (gulong) [bitrate]"),
3533 ENUM_ENTRY (RHYTHMDB_PROP_DATE, "Date of release (gulong) [date]"),
3534 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_GAIN, "Replaygain track gain (gdouble) [replaygain-track-gain]"),
3535 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_PEAK, "Replaygain track peak (gdouble) [replaygain-track-peak]"),
3536 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_GAIN, "Replaygain album pain (gdouble) [replaygain-album-gain]"),
3537 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_PEAK, "Replaygain album peak (gdouble) [replaygain-album-peak]"),
3538 ENUM_ENTRY (RHYTHMDB_PROP_MIMETYPE, "Mime Type (gchararray) [mimetype]"),
3539 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_SORT_KEY, "Title sort key (gchararray) [title-sort-key]"),
3540 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_SORT_KEY, "Genre sort key (gchararray) [genre-sort-key]"),
3541 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_SORT_KEY, "Artist sort key (gchararray) [artist-sort-key]"),
3542 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_SORT_KEY, "Album sort key (gchararray) [album-sort-key]"),
3544 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_FOLDED, "Title folded (gchararray) [title-folded]"),
3545 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_FOLDED, "Genre folded (gchararray) [genre-folded]"),
3546 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_FOLDED, "Artist folded (gchararray) [artist-folded]"),
3547 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_FOLDED, "Album folded (gchararray) [album-folded]"),
3548 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED_STR, "Last Played (gchararray) [last-played-str]"),
3549 ENUM_ENTRY (RHYTHMDB_PROP_PLAYBACK_ERROR, "Playback error string (gchararray) [playback-error]"),
3550 ENUM_ENTRY (RHYTHMDB_PROP_HIDDEN, "Hidden (gboolean) [hidden]"),
3551 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN_STR, "Time Added to Library (gchararray) [first-seen-str]"),
3552 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN_STR, "Last time the song was available (gchararray) [last-seen-str]"),
3553 ENUM_ENTRY (RHYTHMDB_PROP_SEARCH_MATCH, "Search matching key (gchararray) [search-match]"),
3554 ENUM_ENTRY (RHYTHMDB_PROP_YEAR, "Year of date (gulong) [year]"),
3556 ENUM_ENTRY (RHYTHMDB_PROP_STATUS, "Status of file (gulong) [status]"),
3557 ENUM_ENTRY (RHYTHMDB_PROP_DESCRIPTION, "Podcast description(gchararray) [description]"),
3558 ENUM_ENTRY (RHYTHMDB_PROP_SUBTITLE, "Podcast subtitle (gchararray) [subtitle]"),
3559 ENUM_ENTRY (RHYTHMDB_PROP_SUMMARY, "Podcast summary (gchararray) [summary]"),
3560 ENUM_ENTRY (RHYTHMDB_PROP_LANG, "Podcast language (gchararray) [lang]"),
3561 ENUM_ENTRY (RHYTHMDB_PROP_COPYRIGHT, "Podcast copyright (gchararray) [copyright]"),
3562 ENUM_ENTRY (RHYTHMDB_PROP_IMAGE, "Podcast image(gchararray) [image]"),
3563 ENUM_ENTRY (RHYTHMDB_PROP_POST_TIME, "Podcast time of post (gulong) [post-time]"),
3564 { 0, 0, 0 }
3566 g_assert ((sizeof (values) / sizeof (values[0]) - 1) == RHYTHMDB_NUM_PROPERTIES);
3567 etype = g_enum_register_static ("RhythmDBPropType", values);
3570 return etype;
3573 void
3574 rhythmdb_emit_entry_deleted (RhythmDB *db,
3575 RhythmDBEntry *entry)
3577 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry);
3580 static gboolean
3581 rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
3582 GValue *return_accu,
3583 const GValue *handler_return,
3584 gpointer data)
3586 if (handler_return == NULL)
3587 return TRUE;
3589 g_value_copy (handler_return, return_accu);
3590 return (g_value_get_boxed (return_accu) == NULL);
3594 * rhythmdb_entry_request_extra_metadata:
3595 * @db: a #RhythmDB
3596 * @entry: a #RhythmDBEntry
3597 * @property_name: the metadata predicate
3599 * Emits a request for extra metadata for the @entry.
3600 * The @property_name argument is emitted as the ::detail part of the
3601 * "entry_extra_metadata_request" signal. It should be a namespaced RDF
3602 * predicate e.g. from Dublin Core, MusicBrainz, or internal to Rhythmbox
3603 * (namespace "rb:"). Suitable predicates would be those that are expensive to
3604 * acquire or only apply to a limited range of entries.
3605 * Handlers capable of providing a particular predicate may ensure they only
3606 * see appropriate requests by supplying an appropriate ::detail part when
3607 * connecting to the signal. Upon a handler returning a non-%NULL value,
3608 * emission will be stopped and the value returned to the caller; if no
3609 * handlers return a non-%NULL value, the caller will receive %NULL. Priority
3610 * is determined by signal connection order, with %G_CONNECT_AFTER providing a
3611 * second, lower rank of priority.
3612 * A handler returning a value should do so in a #GValue allocated on the heap;
3613 * the accumulator will take ownership. The caller should unset and free the
3614 * #GValue if non-%NULL when finished with it.
3616 * Returns: an allocated, initialised, set #GValue, or NULL
3618 GValue *
3619 rhythmdb_entry_request_extra_metadata (RhythmDB *db,
3620 RhythmDBEntry *entry,
3621 const gchar *property_name)
3623 GValue *value = NULL;
3625 g_signal_emit (G_OBJECT (db),
3626 rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST],
3627 g_quark_from_string (property_name),
3628 entry,
3629 &value);
3631 return value;
3635 * rhythmdb_emit_entry_extra_metadata_notify:
3636 * @db: a #RhythmDB
3637 * @entry: a #RhythmDBEntry
3638 * @property_name: the metadata predicate
3639 * @metadata: a #GValue
3641 * Emits a signal describing extra metadata for the @entry. The @property_name
3642 * argument is emitted as the ::detail part of the
3643 * "entry_extra_metadata_notify" signal and as the 'field' parameter. Handlers
3644 * can ensure they only get metadata they are interested in by supplying an
3645 * appropriate ::detail part when connecting to the signal. If handlers are
3646 * interested in the metadata they should ref or copy the contents of @metadata
3647 * and unref or free it when they are finished with it.
3649 void
3650 rhythmdb_emit_entry_extra_metadata_notify (RhythmDB *db,
3651 RhythmDBEntry *entry,
3652 const gchar *property_name,
3653 const GValue *metadata)
3655 g_signal_emit (G_OBJECT (db),
3656 rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY],
3657 g_quark_from_string (property_name),
3658 entry,
3659 property_name,
3660 metadata);
3663 static void
3664 unset_and_free_g_value (gpointer valpointer)
3666 GValue *value = valpointer;
3667 g_value_unset (value);
3668 g_free (value);
3672 * rhythmdb_entry_extra_gather:
3673 * @db: a #RhythmDB
3674 * @entry: a #RhythmDBEntry
3676 * Gathers all metadata for the @entry. The returned GHashTable maps property
3677 * names and extra metadata names (described under
3678 * @rhythmdb_entry_request_extra_metadata) to GValues. Anything wanting to
3679 * provide extra metadata should connect to the "entry_extra_metadata_gather"
3680 * signal.
3682 * Returns: a GHashTable containing metadata for the entry. This must be freed
3683 * using g_hash_table_destroy.
3685 GHashTable *
3686 rhythmdb_entry_gather_metadata (RhythmDB *db,
3687 RhythmDBEntry *entry)
3689 GHashTable *metadata;
3690 GEnumClass *klass;
3691 guint i;
3693 metadata = g_hash_table_new_full (g_str_hash,
3694 g_str_equal,
3695 g_free,
3696 unset_and_free_g_value);
3698 /* add core properties */
3699 klass = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
3700 for (i = 0; i < klass->n_values; i++) {
3701 GValue *value;
3702 gint prop;
3703 GType value_type;
3704 const char *name;
3706 prop = klass->values[i].value;
3708 /* only include easily marshallable types in the hash table */
3709 value_type = rhythmdb_get_property_type (db, prop);
3710 switch (value_type) {
3711 case G_TYPE_STRING:
3712 case G_TYPE_BOOLEAN:
3713 case G_TYPE_ULONG:
3714 case G_TYPE_UINT64:
3715 case G_TYPE_DOUBLE:
3716 break;
3717 default:
3718 continue;
3721 value = g_new0 (GValue, 1);
3722 g_value_init (value, value_type);
3723 rhythmdb_entry_get (db, entry, prop, value);
3724 name = (char *)rhythmdb_nice_elt_name_from_propid (db, prop);
3725 g_hash_table_insert (metadata,
3726 (gpointer) g_strdup (name),
3727 value);
3729 g_type_class_unref (klass);
3731 /* gather extra metadata */
3732 g_signal_emit (G_OBJECT (db),
3733 rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER], 0,
3734 entry,
3735 metadata);
3737 return metadata;
3740 static gboolean
3741 queue_is_empty (GAsyncQueue *queue)
3743 return g_async_queue_length (queue) <= 0;
3747 * rhythmdb_is_busy:
3748 * @db: a #RhythmDB.
3750 * Returns: whether the #RhythmDB has events to process.
3752 gboolean
3753 rhythmdb_is_busy (RhythmDB *db)
3755 return (!db->priv->action_thread_running ||
3756 !queue_is_empty (db->priv->event_queue) ||
3757 !queue_is_empty (db->priv->action_queue) ||
3758 (db->priv->stat_handle != NULL) ||
3759 (db->priv->outstanding_stats != NULL));
3763 * rhythmdb_compute_status_normal:
3764 * @n_songs: the number of tracks.
3765 * @duration: the total duration of the tracks.
3766 * @size: the total size of the tracks.
3767 * @singular: singular form of the format string to use for entries (eg "%d song")
3768 * @plural: plural form of the format string to use for entries (eg "%d songs")
3770 * Creates a string containing the "status" information about a list of tracks.
3771 * The singular and plural strings must be used in a direct ngettext call
3772 * elsewhere in order for them to be marked for translation correctly.
3774 * Returns: the string, which should be freed with g_free.
3776 char *
3777 rhythmdb_compute_status_normal (gint n_songs,
3778 glong duration,
3779 guint64 size,
3780 const char *singular,
3781 const char *plural)
3783 long days, hours, minutes, seconds;
3784 char *songcount = NULL;
3785 char *time = NULL;
3786 char *size_str = NULL;
3787 char *ret;
3788 const char *minutefmt;
3789 const char *hourfmt;
3790 const char *dayfmt;
3792 songcount = g_strdup_printf (ngettext (singular, plural, n_songs), n_songs);
3794 days = duration / (60 * 60 * 24);
3795 hours = (duration / (60 * 60)) - (days * 24);
3796 minutes = (duration / 60) - ((days * 24 * 60) + (hours * 60));
3797 seconds = duration % 60;
3799 minutefmt = ngettext ("%ld minute", "%ld minutes", minutes);
3800 hourfmt = ngettext ("%ld hour", "%ld hours", hours);
3801 dayfmt = ngettext ("%ld day", "%ld days", days);
3802 if (days > 0) {
3803 if (hours > 0)
3804 if (minutes > 0) {
3805 char *fmt;
3806 /* Translators: the format is "X days, X hours and X minutes" */
3807 fmt = g_strdup_printf (_("%s, %s and %s"), dayfmt, hourfmt, minutefmt);
3808 time = g_strdup_printf (fmt, days, hours, minutes);
3809 g_free (fmt);
3810 } else {
3811 char *fmt;
3812 /* Translators: the format is "X days and X hours" */
3813 fmt = g_strdup_printf (_("%s and %s"), dayfmt, hourfmt);
3814 time = g_strdup_printf (fmt, days, hours);
3815 g_free (fmt);
3817 else
3818 if (minutes > 0) {
3819 char *fmt;
3820 /* Translators: the format is "X days and X minutes" */
3821 fmt = g_strdup_printf (_("%s and %s"), dayfmt, minutefmt);
3822 time = g_strdup_printf (fmt, days, minutes);
3823 g_free (fmt);
3824 } else {
3825 time = g_strdup_printf (dayfmt, days);
3827 } else {
3828 if (hours > 0) {
3829 if (minutes > 0) {
3830 char *fmt;
3831 /* Translators: the format is "X hours and X minutes" */
3832 fmt = g_strdup_printf (_("%s and %s"), hourfmt, minutefmt);
3833 time = g_strdup_printf (fmt, hours, minutes);
3834 g_free (fmt);
3835 } else {
3836 time = g_strdup_printf (hourfmt, hours);
3839 } else {
3840 time = g_strdup_printf (minutefmt, minutes);
3844 size_str = gnome_vfs_format_file_size_for_display (size);
3846 if (size > 0 && duration > 0) {
3847 ret = g_strdup_printf ("%s, %s, %s", songcount, time, size_str);
3848 } else if (duration > 0) {
3849 ret = g_strdup_printf ("%s, %s", songcount, time);
3850 } else if (size > 0) {
3851 ret = g_strdup_printf ("%s, %s", songcount, size_str);
3852 } else {
3853 ret = g_strdup (songcount);
3856 g_free (songcount);
3857 g_free (time);
3858 g_free (size_str);
3860 return ret;
3863 static void
3864 default_sync_metadata (RhythmDB *db,
3865 RhythmDBEntry *entry,
3866 GError **error,
3867 gpointer data)
3869 const char *uri;
3870 GError *local_error = NULL;
3872 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3873 rb_metadata_load (db->priv->metadata,
3874 uri, &local_error);
3875 if (local_error != NULL) {
3876 g_propagate_error (error, local_error);
3877 return;
3880 entry_to_rb_metadata (db, entry, db->priv->metadata);
3882 rb_metadata_save (db->priv->metadata, &local_error);
3883 if (local_error != NULL) {
3884 RhythmDBAction *load_action;
3886 /* reload the metadata, to revert the db changes */
3887 load_action = g_new0 (RhythmDBAction, 1);
3888 load_action->type = RHYTHMDB_ACTION_LOAD;
3889 load_action->uri = rb_refstring_ref (entry->location);
3890 load_action->entry_type = RHYTHMDB_ENTRY_TYPE_INVALID;
3891 g_async_queue_push (db->priv->action_queue, load_action);
3893 g_propagate_error (error, local_error);
3898 * rhythmdb_entry_register_type:
3899 * @db: a #RhythmDB
3900 * @name: optional name for the entry type
3902 * Registers a new #RhythmDBEntryType. This should be called to create a new
3903 * entry type for non-permanent sources.
3905 * Returns: the new #RhythmDBEntryType.
3907 RhythmDBEntryType
3908 rhythmdb_entry_register_type (RhythmDB *db,
3909 const char *name)
3911 RhythmDBEntryType type;
3912 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3914 type = g_new0 (RhythmDBEntryType_, 1);
3915 type->can_sync_metadata = (RhythmDBEntryCanSyncFunc)rb_false_function;
3916 type->sync_metadata = default_sync_metadata;
3917 if (name) {
3918 type->name = g_strdup (name);
3919 g_mutex_lock (db->priv->entry_type_map_mutex);
3920 g_hash_table_insert (db->priv->entry_type_map, g_strdup (type->name), type);
3921 g_mutex_unlock (db->priv->entry_type_map_mutex);
3924 if (klass->impl_entry_type_registered)
3925 klass->impl_entry_type_registered (db, name, type);
3927 return type;
3930 static void
3931 rhythmdb_entry_register_type_alias (RhythmDB *db,
3932 RhythmDBEntryType type,
3933 const char *name)
3935 char *dn = g_strdup (name);
3937 g_mutex_lock (db->priv->entry_type_map_mutex);
3938 g_hash_table_insert (db->priv->entry_type_map, dn, type);
3939 g_mutex_unlock (db->priv->entry_type_map_mutex);
3942 typedef struct {
3943 GHFunc func;
3944 gpointer data;
3945 } RhythmDBEntryTypeForeachData;
3947 static void
3948 rhythmdb_entry_type_foreach_cb (const char *name,
3949 RhythmDBEntryType entry_type,
3950 RhythmDBEntryTypeForeachData *data)
3952 /* skip aliases */
3953 if (strcmp (entry_type->name, name))
3954 return;
3956 data->func ((gpointer) name, entry_type, data->data);
3960 * rhythmdb_entry_type_foreach:
3961 * @db: a #RhythmDB
3962 * @func: callback function to call for each registered entry type
3963 * @data: data to pass to the callback
3965 * Calls a function for each registered entry type.
3967 void
3968 rhythmdb_entry_type_foreach (RhythmDB *db,
3969 GHFunc func,
3970 gpointer data)
3972 RhythmDBEntryTypeForeachData d;
3974 d.func = func;
3975 d.data = data;
3977 g_mutex_lock (db->priv->entry_type_mutex);
3978 g_hash_table_foreach (db->priv->entry_type_map,
3979 (GHFunc) rhythmdb_entry_type_foreach_cb,
3980 &d);
3981 g_mutex_unlock (db->priv->entry_type_mutex);
3985 * rhythmdb_entry_type_get_by_name:
3986 * @db: a #RhythmDB
3987 * @name: name of the type to look for
3989 * Locates a #RhythmDBEntryType by name. Returns
3990 * RHYTHMDB_ENTRY_TYPE_INVALID if no entry type
3991 * is registered with the specified name.
3993 * Returns: the #RhythmDBEntryType
3995 RhythmDBEntryType
3996 rhythmdb_entry_type_get_by_name (RhythmDB *db,
3997 const char *name)
3999 gpointer t = NULL;
4001 g_mutex_lock (db->priv->entry_type_map_mutex);
4002 if (db->priv->entry_type_map) {
4003 t = g_hash_table_lookup (db->priv->entry_type_map, name);
4005 g_mutex_unlock (db->priv->entry_type_map_mutex);
4007 if (t)
4008 return (RhythmDBEntryType) t;
4010 return RHYTHMDB_ENTRY_TYPE_INVALID;
4013 static gboolean
4014 song_can_sync_metadata (RhythmDB *db,
4015 RhythmDBEntry *entry,
4016 gpointer data)
4018 const char *mimetype;
4020 mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
4021 return rb_metadata_can_save (db->priv->metadata, mimetype);
4024 static char *
4025 podcast_get_playback_uri (RhythmDBEntry *entry,
4026 gpointer data)
4028 return rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
4031 static void
4032 podcast_data_destroy (RhythmDBEntry *entry,
4033 gpointer something)
4035 RhythmDBPodcastFields *podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4036 rb_refstring_unref (podcast->description);
4037 rb_refstring_unref (podcast->subtitle);
4038 rb_refstring_unref (podcast->summary);
4039 rb_refstring_unref (podcast->lang);
4040 rb_refstring_unref (podcast->copyright);
4041 rb_refstring_unref (podcast->image);
4044 static RhythmDBEntryType song_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4045 static RhythmDBEntryType ignore_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4046 static RhythmDBEntryType import_error_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4048 /* to be evicted */
4049 static RhythmDBEntryType podcast_post_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4050 static RhythmDBEntryType podcast_feed_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4052 static void
4053 rhythmdb_register_core_entry_types (RhythmDB *db)
4055 /* regular songs */
4056 song_type = rhythmdb_entry_register_type (db, "song");
4057 rhythmdb_entry_register_type_alias (db, song_type, "0");
4058 song_type->save_to_disk = TRUE;
4059 song_type->category = RHYTHMDB_ENTRY_NORMAL;
4060 song_type->can_sync_metadata = song_can_sync_metadata;
4062 /* import errors */
4063 import_error_type = rhythmdb_entry_register_type (db, "import-error");
4064 import_error_type->get_playback_uri = (RhythmDBEntryStringFunc)rb_null_function;
4065 import_error_type->category = RHYTHMDB_ENTRY_VIRTUAL;
4067 /* ignored files */
4068 ignore_type = rhythmdb_entry_register_type (db, "ignore");
4069 ignore_type->save_to_disk = TRUE;
4070 ignore_type->category = RHYTHMDB_ENTRY_VIRTUAL;
4072 /* podcast posts */
4073 podcast_post_type = rhythmdb_entry_register_type (db, "podcast-post");
4074 podcast_post_type->entry_type_data_size = sizeof (RhythmDBPodcastFields);
4075 podcast_post_type->save_to_disk = TRUE;
4076 podcast_post_type->category = RHYTHMDB_ENTRY_NORMAL;
4077 podcast_post_type->pre_entry_destroy = (RhythmDBEntryActionFunc) podcast_data_destroy;
4078 podcast_post_type->get_playback_uri = podcast_get_playback_uri;
4080 /* podcast feeds */
4081 podcast_feed_type = rhythmdb_entry_register_type (db, "podcast-feed");
4082 podcast_feed_type->entry_type_data_size = sizeof (RhythmDBPodcastFields);
4083 podcast_feed_type->save_to_disk = TRUE;
4084 podcast_feed_type->category = RHYTHMDB_ENTRY_VIRTUAL;
4085 podcast_feed_type->pre_entry_destroy = (RhythmDBEntryActionFunc) podcast_data_destroy;
4088 RhythmDBEntryType
4089 rhythmdb_entry_song_get_type (void)
4091 return song_type;
4094 RhythmDBEntryType
4095 rhythmdb_entry_ignore_get_type (void)
4097 return ignore_type;
4100 RhythmDBEntryType
4101 rhythmdb_entry_import_error_get_type (void)
4103 return import_error_type;
4106 RhythmDBEntryType
4107 rhythmdb_entry_podcast_post_get_type (void)
4109 return podcast_post_type;
4112 RhythmDBEntryType
4113 rhythmdb_entry_podcast_feed_get_type (void)
4115 return podcast_feed_type;
4118 static void
4119 rhythmdb_entry_set_mount_point (RhythmDB *db,
4120 RhythmDBEntry *entry,
4121 const gchar *realuri)
4123 gchar *mount_point;
4124 GValue value = {0, };
4126 mount_point = rb_uri_get_mount_point (realuri);
4127 if (mount_point != NULL) {
4128 g_value_init (&value, G_TYPE_STRING);
4129 g_value_set_string_take_ownership (&value, mount_point);
4130 rhythmdb_entry_set_internal (db, entry, FALSE,
4131 RHYTHMDB_PROP_MOUNTPOINT,
4132 &value);
4133 g_value_unset (&value);
4137 void
4138 rhythmdb_entry_set_visibility (RhythmDB *db,
4139 RhythmDBEntry *entry,
4140 gboolean visible)
4142 GValue old_val = {0, };
4143 gboolean old_visible;
4145 g_return_if_fail (RHYTHMDB_IS (db));
4146 g_return_if_fail (entry != NULL);
4148 g_value_init (&old_val, G_TYPE_BOOLEAN);
4150 rhythmdb_entry_get (db, entry, RHYTHMDB_PROP_HIDDEN, &old_val);
4151 old_visible = !g_value_get_boolean (&old_val);
4153 if ((old_visible && !visible) || (!old_visible && visible)) {
4154 GValue new_val = {0, };
4156 g_value_init (&new_val, G_TYPE_BOOLEAN);
4157 g_value_set_boolean (&new_val, !visible);
4158 rhythmdb_entry_set_internal (db, entry, TRUE,
4159 RHYTHMDB_PROP_HIDDEN, &new_val);
4160 g_value_unset (&new_val);
4162 g_value_unset (&old_val);
4165 static gboolean
4166 rhythmdb_idle_save (RhythmDB *db)
4168 if (db->priv->dirty) {
4169 rb_debug ("database is dirty, doing regular save");
4170 rhythmdb_save_async (db);
4173 return TRUE;
4176 static void
4177 rhythmdb_sync_library_location (RhythmDB *db)
4179 gboolean reload = (db->priv->library_locations != NULL);
4181 if (db->priv->library_location_notify_id == 0) {
4182 db->priv->library_location_notify_id =
4183 eel_gconf_notification_add (CONF_LIBRARY_LOCATION,
4184 (GConfClientNotifyFunc) library_location_changed_cb,
4185 db);
4188 if (reload) {
4189 rb_debug ("ending monitor of old library directories");
4191 rhythmdb_stop_monitoring (db);
4193 g_slist_foreach (db->priv->library_locations, (GFunc) g_free, NULL);
4194 g_slist_free (db->priv->library_locations);
4195 db->priv->library_locations = NULL;
4198 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY)) {
4199 db->priv->library_locations = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
4201 rhythmdb_start_monitoring (db);
4205 static void
4206 library_location_changed_cb (GConfClient *client,
4207 guint cnxn_id,
4208 GConfEntry *entry,
4209 RhythmDB *db)
4211 rhythmdb_sync_library_location (db);
4214 char *
4215 rhythmdb_entry_dup_string (RhythmDBEntry *entry,
4216 RhythmDBPropType propid)
4218 const char *s;
4220 g_return_val_if_fail (entry != NULL, NULL);
4222 s = rhythmdb_entry_get_string (entry, propid);
4223 if (s != NULL) {
4224 return g_strdup (s);
4225 } else {
4226 return NULL;
4230 const char *
4231 rhythmdb_entry_get_string (RhythmDBEntry *entry,
4232 RhythmDBPropType propid)
4234 RhythmDBPodcastFields *podcast = NULL;
4236 g_return_val_if_fail (entry != NULL, NULL);
4237 g_return_val_if_fail (entry->refcount > 0, NULL);
4239 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
4240 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
4241 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4243 rhythmdb_entry_sync_mirrored (entry, propid);
4245 switch (propid) {
4246 case RHYTHMDB_PROP_TITLE:
4247 return rb_refstring_get (entry->title);
4248 case RHYTHMDB_PROP_ALBUM:
4249 return rb_refstring_get (entry->album);
4250 case RHYTHMDB_PROP_ARTIST:
4251 return rb_refstring_get (entry->artist);
4252 case RHYTHMDB_PROP_GENRE:
4253 return rb_refstring_get (entry->genre);
4254 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4255 return rb_refstring_get (entry->musicbrainz_trackid);
4256 case RHYTHMDB_PROP_MIMETYPE:
4257 return rb_refstring_get (entry->mimetype);
4258 case RHYTHMDB_PROP_TITLE_SORT_KEY:
4259 return rb_refstring_get_sort_key (entry->title);
4260 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
4261 return rb_refstring_get_sort_key (entry->album);
4262 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
4263 return rb_refstring_get_sort_key (entry->artist);
4264 case RHYTHMDB_PROP_GENRE_SORT_KEY:
4265 return rb_refstring_get_sort_key (entry->genre);
4266 case RHYTHMDB_PROP_TITLE_FOLDED:
4267 return rb_refstring_get_folded (entry->title);
4268 case RHYTHMDB_PROP_ALBUM_FOLDED:
4269 return rb_refstring_get_folded (entry->album);
4270 case RHYTHMDB_PROP_ARTIST_FOLDED:
4271 return rb_refstring_get_folded (entry->artist);
4272 case RHYTHMDB_PROP_GENRE_FOLDED:
4273 return rb_refstring_get_folded (entry->genre);
4274 case RHYTHMDB_PROP_LOCATION:
4275 return rb_refstring_get (entry->location);
4276 case RHYTHMDB_PROP_MOUNTPOINT:
4277 return rb_refstring_get (entry->mountpoint);
4278 case RHYTHMDB_PROP_LAST_PLAYED_STR:
4279 return rb_refstring_get (entry->last_played_str);
4280 case RHYTHMDB_PROP_PLAYBACK_ERROR:
4281 return rb_refstring_get (entry->playback_error);
4282 case RHYTHMDB_PROP_FIRST_SEEN_STR:
4283 return rb_refstring_get (entry->first_seen_str);
4284 case RHYTHMDB_PROP_LAST_SEEN_STR:
4285 return rb_refstring_get (entry->last_seen_str);
4286 case RHYTHMDB_PROP_SEARCH_MATCH:
4287 return NULL; /* synthetic property */
4288 /* Podcast properties */
4289 case RHYTHMDB_PROP_DESCRIPTION:
4290 if (podcast)
4291 return rb_refstring_get (podcast->description);
4292 else
4293 return NULL;
4294 case RHYTHMDB_PROP_SUBTITLE:
4295 if (podcast)
4296 return rb_refstring_get (podcast->subtitle);
4297 else
4298 return NULL;
4299 case RHYTHMDB_PROP_SUMMARY:
4300 if (podcast)
4301 return rb_refstring_get (podcast->summary);
4302 else
4303 return NULL;
4304 case RHYTHMDB_PROP_LANG:
4305 if (podcast)
4306 return rb_refstring_get (podcast->lang);
4307 else
4308 return NULL;
4309 case RHYTHMDB_PROP_COPYRIGHT:
4310 if (podcast)
4311 return rb_refstring_get (podcast->copyright);
4312 else
4313 return NULL;
4314 case RHYTHMDB_PROP_IMAGE:
4315 if (podcast)
4316 return rb_refstring_get (podcast->image);
4317 else
4318 return NULL;
4320 default:
4321 g_assert_not_reached ();
4322 return NULL;
4326 RBRefString *
4327 rhythmdb_entry_get_refstring (RhythmDBEntry *entry,
4328 RhythmDBPropType propid)
4330 g_return_val_if_fail (entry != NULL, NULL);
4331 g_return_val_if_fail (entry->refcount > 0, NULL);
4333 rhythmdb_entry_sync_mirrored (entry, propid);
4335 switch (propid) {
4336 case RHYTHMDB_PROP_TITLE:
4337 return rb_refstring_ref (entry->title);
4338 case RHYTHMDB_PROP_ALBUM:
4339 return rb_refstring_ref (entry->album);
4340 case RHYTHMDB_PROP_ARTIST:
4341 return rb_refstring_ref (entry->artist);
4342 case RHYTHMDB_PROP_GENRE:
4343 return rb_refstring_ref (entry->genre);
4344 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4345 return rb_refstring_ref (entry->musicbrainz_trackid);
4346 case RHYTHMDB_PROP_MIMETYPE:
4347 return rb_refstring_ref (entry->mimetype);
4348 case RHYTHMDB_PROP_MOUNTPOINT:
4349 return rb_refstring_ref (entry->mountpoint);
4350 case RHYTHMDB_PROP_LAST_PLAYED_STR:
4351 return rb_refstring_ref (entry->last_played_str);
4352 case RHYTHMDB_PROP_FIRST_SEEN_STR:
4353 return rb_refstring_ref (entry->first_seen_str);
4354 case RHYTHMDB_PROP_LAST_SEEN_STR:
4355 return rb_refstring_ref (entry->last_seen_str);
4356 case RHYTHMDB_PROP_LOCATION:
4357 return rb_refstring_ref (entry->location);
4358 case RHYTHMDB_PROP_PLAYBACK_ERROR:
4359 return rb_refstring_ref (entry->playback_error);
4360 default:
4361 g_assert_not_reached ();
4362 return NULL;
4366 gboolean
4367 rhythmdb_entry_get_boolean (RhythmDBEntry *entry,
4368 RhythmDBPropType propid)
4370 g_return_val_if_fail (entry != NULL, FALSE);
4372 switch (propid) {
4373 case RHYTHMDB_PROP_HIDDEN:
4374 return ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
4375 default:
4376 g_assert_not_reached ();
4377 return FALSE;
4381 guint64
4382 rhythmdb_entry_get_uint64 (RhythmDBEntry *entry,
4383 RhythmDBPropType propid)
4385 g_return_val_if_fail (entry != NULL, 0);
4387 switch (propid) {
4388 case RHYTHMDB_PROP_FILE_SIZE:
4389 return entry->file_size;
4390 default:
4391 g_assert_not_reached ();
4392 return 0;
4396 RhythmDBEntryType
4397 rhythmdb_entry_get_entry_type (RhythmDBEntry *entry)
4399 g_return_val_if_fail (entry != NULL, RHYTHMDB_ENTRY_TYPE_INVALID);
4401 return entry->type;
4404 gpointer
4405 rhythmdb_entry_get_pointer (RhythmDBEntry *entry,
4406 RhythmDBPropType propid)
4408 g_return_val_if_fail (entry != NULL, NULL);
4410 switch (propid) {
4411 case RHYTHMDB_PROP_TYPE:
4412 return entry->type;
4413 default:
4414 g_assert_not_reached ();
4415 return NULL;
4419 gulong
4420 rhythmdb_entry_get_ulong (RhythmDBEntry *entry,
4421 RhythmDBPropType propid)
4423 RhythmDBPodcastFields *podcast = NULL;
4425 g_return_val_if_fail (entry != NULL, 0);
4427 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
4428 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
4429 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4431 switch (propid) {
4432 case RHYTHMDB_PROP_ENTRY_ID:
4433 return entry->id;
4434 case RHYTHMDB_PROP_TRACK_NUMBER:
4435 return entry->tracknum;
4436 case RHYTHMDB_PROP_DISC_NUMBER:
4437 return entry->discnum;
4438 case RHYTHMDB_PROP_DURATION:
4439 return entry->duration;
4440 case RHYTHMDB_PROP_MTIME:
4441 return entry->mtime;
4442 case RHYTHMDB_PROP_FIRST_SEEN:
4443 return entry->first_seen;
4444 case RHYTHMDB_PROP_LAST_SEEN:
4445 return entry->last_seen;
4446 case RHYTHMDB_PROP_LAST_PLAYED:
4447 return entry->last_played;
4448 case RHYTHMDB_PROP_PLAY_COUNT:
4449 return entry->play_count;
4450 case RHYTHMDB_PROP_BITRATE:
4451 return entry->bitrate;
4452 case RHYTHMDB_PROP_DATE:
4453 if (g_date_valid (&entry->date))
4454 return g_date_get_julian (&entry->date);
4455 else
4456 return 0;
4457 case RHYTHMDB_PROP_YEAR:
4458 if (g_date_valid (&entry->date))
4459 return g_date_get_year (&entry->date);
4460 else
4461 return 0;
4462 case RHYTHMDB_PROP_POST_TIME:
4463 if (podcast)
4464 return podcast->post_time;
4465 else
4466 return 0;
4467 case RHYTHMDB_PROP_STATUS:
4468 if (podcast)
4469 return podcast->status;
4470 else
4471 return 0;
4472 default:
4473 g_assert_not_reached ();
4474 return 0;
4478 double
4479 rhythmdb_entry_get_double (RhythmDBEntry *entry,
4480 RhythmDBPropType propid)
4482 g_return_val_if_fail (entry != NULL, 0);
4484 switch (propid) {
4485 case RHYTHMDB_PROP_TRACK_GAIN:
4486 return entry->track_gain;
4487 case RHYTHMDB_PROP_TRACK_PEAK:
4488 return entry->track_peak;
4489 case RHYTHMDB_PROP_ALBUM_GAIN:
4490 return entry->album_gain;
4491 case RHYTHMDB_PROP_ALBUM_PEAK:
4492 return entry->album_peak;
4493 case RHYTHMDB_PROP_RATING:
4494 return entry->rating;
4495 default:
4496 g_assert_not_reached ();
4497 return 0.0;
4501 char *
4502 rhythmdb_entry_get_playback_uri (RhythmDBEntry *entry)
4504 RhythmDBEntryType type;
4506 g_return_val_if_fail (entry != NULL, NULL);
4508 type = rhythmdb_entry_get_entry_type (entry);
4509 if (type->get_playback_uri)
4510 return (type->get_playback_uri) (entry, type->get_playback_uri_data);
4511 else
4512 return rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_LOCATION);
4515 GType
4516 rhythmdb_get_property_type (RhythmDB *db,
4517 guint property_id)
4519 g_assert (property_id >= 0 && property_id < RHYTHMDB_NUM_PROPERTIES);
4520 return rhythmdb_property_type_map[property_id];
4523 GType
4524 rhythmdb_entry_get_type (void)
4526 static GType type = 0;
4528 if (G_UNLIKELY (type == 0)) {
4529 type = g_boxed_type_register_static ("RhythmDBEntry",
4530 (GBoxedCopyFunc)rhythmdb_entry_ref,
4531 (GBoxedFreeFunc)rhythmdb_entry_unref);
4534 return type;
4537 GType
4538 rhythmdb_entry_type_get_type (void)
4540 static GType type = 0;
4542 if (G_UNLIKELY (type == 0)) {
4543 type = g_boxed_type_register_static ("RhythmDBEntryType",
4544 (GBoxedCopyFunc)rb_copy_function,
4545 (GBoxedFreeFunc)rb_null_function);
4548 return type;