udapted vi.po
[rhythmbox.git] / rhythmdb / rhythmdb.c
blobef6ca89addd69fc7202d290f2e284de0d659cf4f
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 GList *uri_list;
2041 GnomeVFSURI *vfs_uri;
2043 event->real_uri = rb_refstring_new (uri);
2045 vfs_uri = gnome_vfs_uri_new (uri);
2046 if (vfs_uri == NULL) {
2047 event->error = make_access_failed_error (uri, GNOME_VFS_ERROR_INVALID_URI);
2048 g_async_queue_push (db->priv->event_queue, event);
2049 return;
2052 uri_list = g_list_append (NULL, vfs_uri);
2054 g_mutex_lock (db->priv->stat_mutex);
2055 db->priv->outstanding_stats = g_list_prepend (db->priv->outstanding_stats, event);
2056 g_mutex_unlock (db->priv->stat_mutex);
2058 gnome_vfs_async_get_file_info (&event->handle, uri_list,
2059 GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
2060 GNOME_VFS_PRIORITY_MIN,
2061 (GnomeVFSAsyncGetFileInfoCallback) rhythmdb_execute_stat_info_cb,
2062 event);
2063 gnome_vfs_uri_unref (vfs_uri);
2064 g_list_free (uri_list);
2067 void
2068 queue_stat_uri (const char *uri,
2069 RhythmDB *db,
2070 RhythmDBEntryType type)
2072 RhythmDBEvent *result;
2074 rb_debug ("queueing stat for \"%s\"", uri);
2075 g_assert (uri && *uri);
2077 result = g_new0 (RhythmDBEvent, 1);
2078 result->db = db;
2079 result->type = RHYTHMDB_EVENT_STAT;
2080 result->entry_type = type;
2083 * before the action thread is started, we queue up stat events,
2084 * as we're still creating and running queries, as well as loading
2085 * the database. when we start the action thread, we'll kick off
2086 * a gnome-vfs job to run all the stat events too.
2088 * when the action thread is already running, we can start the
2089 * async_get_file_info job directly.
2091 g_mutex_lock (db->priv->stat_mutex);
2092 if (db->priv->action_thread_running) {
2093 g_mutex_unlock (db->priv->stat_mutex);
2094 rhythmdb_execute_stat (db, uri, result);
2095 } else {
2096 GnomeVFSURI *vfs_uri;
2098 vfs_uri = gnome_vfs_uri_new (uri);
2099 if (vfs_uri == NULL) {
2100 result->real_uri = rb_refstring_new (uri);
2101 result->error = make_access_failed_error (uri, GNOME_VFS_ERROR_INVALID_URI);
2102 g_async_queue_push (db->priv->event_queue, result);
2103 } else {
2104 /* construct a list of URIs and a hash table containing
2105 * stat events to fill in and post on the event queue.
2107 if (g_hash_table_lookup (db->priv->stat_events, vfs_uri)) {
2108 g_free (result);
2109 gnome_vfs_uri_unref (vfs_uri);
2110 } else {
2111 result->real_uri = rb_refstring_new (uri);
2112 g_hash_table_insert (db->priv->stat_events, vfs_uri, result);
2113 db->priv->stat_list = g_list_prepend (db->priv->stat_list, vfs_uri);
2117 g_mutex_unlock (db->priv->stat_mutex);
2121 static void
2122 queue_stat_uri_tad (const char *uri,
2123 gboolean dir,
2124 RhythmDBAddThreadData *data)
2126 if (!dir)
2127 queue_stat_uri (uri, data->db, data->type);
2130 static gpointer
2131 add_thread_main (RhythmDBAddThreadData *data)
2133 RhythmDBEvent *result;
2135 rb_uri_handle_recursively (data->uri, (RBUriRecurseFunc) queue_stat_uri_tad,
2136 &data->db->priv->exiting, data);
2138 rb_debug ("exiting");
2139 result = g_new0 (RhythmDBEvent, 1);
2140 result->db = data->db;
2141 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2142 g_async_queue_push (data->db->priv->event_queue, result);
2143 g_free (data->uri);
2144 g_free (data);
2145 return NULL;
2148 static void
2149 rhythmdb_execute_load (RhythmDB *db,
2150 const char *uri,
2151 RhythmDBEvent *event)
2153 GnomeVFSResult vfsresult;
2154 char *resolved;
2156 resolved = rb_uri_resolve_symlink (uri);
2157 if (resolved != NULL) {
2158 event->real_uri = rb_refstring_new (resolved);
2159 event->vfsinfo = gnome_vfs_file_info_new ();
2161 vfsresult = gnome_vfs_get_file_info (uri,
2162 event->vfsinfo,
2163 GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
2164 g_free (resolved);
2165 } else {
2166 event->real_uri = rb_refstring_new (uri);
2167 vfsresult = GNOME_VFS_ERROR_LOOP;
2170 if (vfsresult != GNOME_VFS_OK) {
2171 event->error = make_access_failed_error (uri, vfsresult);
2172 if (event->vfsinfo)
2173 gnome_vfs_file_info_unref (event->vfsinfo);
2174 event->vfsinfo = NULL;
2175 } else {
2176 if (event->type == RHYTHMDB_EVENT_METADATA_LOAD) {
2177 event->metadata = rb_metadata_new ();
2178 rb_metadata_load (event->metadata, rb_refstring_get (event->real_uri),
2179 &event->error);
2183 g_async_queue_push (db->priv->event_queue, event);
2187 * rhythmdb_entry_get:
2188 * @entry: a #RhythmDBEntry.
2189 * @propid: the id of the property to get.
2190 * @val: return location for the property value.
2192 * Gets a property of an entry, storing it in the given #GValue.
2194 void
2195 rhythmdb_entry_get (RhythmDB *db,
2196 RhythmDBEntry *entry,
2197 RhythmDBPropType propid,
2198 GValue *val)
2200 g_return_if_fail (RHYTHMDB_IS (db));
2201 g_return_if_fail (entry != NULL);
2202 g_return_if_fail (entry->refcount > 0);
2204 rhythmdb_entry_sync_mirrored (entry, propid);
2206 g_assert (G_VALUE_TYPE (val) == rhythmdb_get_property_type (db, propid));
2207 switch (rhythmdb_property_type_map[propid]) {
2208 case G_TYPE_STRING:
2209 g_value_set_string (val, rhythmdb_entry_get_string (entry, propid));
2210 break;
2211 case G_TYPE_BOOLEAN:
2212 g_value_set_boolean (val, rhythmdb_entry_get_boolean (entry, propid));
2213 break;
2214 case G_TYPE_ULONG:
2215 g_value_set_ulong (val, rhythmdb_entry_get_ulong (entry, propid));
2216 break;
2217 case G_TYPE_UINT64:
2218 g_value_set_uint64 (val, rhythmdb_entry_get_uint64 (entry, propid));
2219 break;
2220 case G_TYPE_DOUBLE:
2221 g_value_set_double (val, rhythmdb_entry_get_double (entry, propid));
2222 break;
2223 case G_TYPE_POINTER:
2224 g_value_set_pointer (val, rhythmdb_entry_get_pointer (entry, propid));
2225 break;
2226 default:
2227 g_assert_not_reached ();
2228 break;
2232 static void
2233 entry_to_rb_metadata (RhythmDB *db,
2234 RhythmDBEntry *entry,
2235 RBMetaData *metadata)
2237 GValue val = {0, };
2238 int i;
2240 for (i = RHYTHMDB_PROP_TYPE; i != RHYTHMDB_NUM_PROPERTIES; i++) {
2241 RBMetaDataField field;
2243 if (metadata_field_from_prop (i, &field) == FALSE) {
2244 continue;
2247 g_value_init (&val, rhythmdb_property_type_map[i]);
2248 rhythmdb_entry_get (db, entry, i, &val);
2249 rb_metadata_set (metadata,
2250 field,
2251 &val);
2252 g_value_unset (&val);
2256 typedef struct
2258 RhythmDB *db;
2259 char *uri;
2260 GError *error;
2261 } RhythmDBSaveErrorData;
2263 static gboolean
2264 emit_save_error_idle (RhythmDBSaveErrorData *data)
2266 g_signal_emit (G_OBJECT (data->db), rhythmdb_signals[SAVE_ERROR], 0, data->uri, data->error);
2267 g_object_unref (G_OBJECT (data->db));
2268 g_free (data->uri);
2269 g_error_free (data->error);
2270 g_free (data);
2271 return FALSE;
2274 static gpointer
2275 action_thread_main (RhythmDB *db)
2277 RhythmDBEvent *result;
2279 while (TRUE) {
2280 RhythmDBAction *action;
2282 action = read_queue (db->priv->action_queue, &db->priv->exiting);
2284 if (action == NULL)
2285 break;
2287 switch (action->type) {
2288 case RHYTHMDB_ACTION_STAT:
2290 result = g_new0 (RhythmDBEvent, 1);
2291 result->db = db;
2292 result->type = RHYTHMDB_EVENT_STAT;
2293 result->entry_type = action->entry_type;
2295 rb_debug ("executing RHYTHMDB_ACTION_STAT for \"%s\"", rb_refstring_get (action->uri));
2297 rhythmdb_execute_stat (db, rb_refstring_get (action->uri), result);
2299 break;
2300 case RHYTHMDB_ACTION_LOAD:
2302 result = g_new0 (RhythmDBEvent, 1);
2303 result->db = db;
2304 result->type = RHYTHMDB_EVENT_METADATA_LOAD;
2305 result->entry_type = action->entry_type;
2307 rb_debug ("executing RHYTHMDB_ACTION_LOAD for \"%s\"", rb_refstring_get (action->uri));
2309 rhythmdb_execute_load (db, rb_refstring_get (action->uri), result);
2311 break;
2312 case RHYTHMDB_ACTION_SYNC:
2314 GError *error = NULL;
2315 RhythmDBEntry *entry;
2316 RhythmDBEntryType entry_type;
2318 if (db->priv->dry_run) {
2319 rb_debug ("dry run is enabled, not syncing metadata");
2320 break;
2323 entry = rhythmdb_entry_lookup_by_location_refstring (db, action->uri);
2324 if (!entry)
2325 break;
2327 entry_type = rhythmdb_entry_get_entry_type (entry);
2328 entry_type->sync_metadata (db, entry, &error, entry_type->sync_metadata_data);
2330 if (error != NULL) {
2331 RhythmDBSaveErrorData *data;
2333 data = g_new0 (RhythmDBSaveErrorData, 1);
2334 g_object_ref (db);
2335 data->db = db;
2336 data->uri = g_strdup (rb_refstring_get (action->uri));
2337 data->error = error;
2338 g_idle_add ((GSourceFunc)emit_save_error_idle, data);
2339 break;
2341 break;
2343 break;
2344 default:
2345 g_assert_not_reached ();
2346 break;
2348 rhythmdb_action_free (db, action);
2352 rb_debug ("exiting main thread");
2353 result = g_new0 (RhythmDBEvent, 1);
2354 result->db = db;
2355 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2356 g_async_queue_push (db->priv->event_queue, result);
2358 return NULL;
2362 * rhythmdb_add_uri:
2363 * @db: a #RhythmDB.
2364 * @uri: the URI to add an entry/entries for
2366 * Adds the file(s) pointed to by @uri to the database, as entries of type
2367 * RHYTHMDB_ENTRY_TYPE_SONG. If the URI is that of a file, they will be added.
2368 * If the URI is that of a directory, everything under it will be added recursively.
2370 void
2371 rhythmdb_add_uri (RhythmDB *db,
2372 const char *uri)
2374 rhythmdb_add_uri_with_type (db, uri, RHYTHMDB_ENTRY_TYPE_INVALID);
2377 void
2378 rhythmdb_add_uri_with_type (RhythmDB *db,
2379 const char *uri,
2380 RhythmDBEntryType type)
2382 char *realuri;
2383 char *canon_uri;
2385 canon_uri = rb_canonicalise_uri (uri);
2386 realuri = rb_uri_resolve_symlink (canon_uri);
2388 if (realuri == NULL) {
2389 /* create import error entry */
2390 RhythmDBEvent *event = g_new0 (RhythmDBEvent, 1);
2392 event->db = db;
2393 event->real_uri = rb_refstring_new (canon_uri);
2394 event->error = make_access_failed_error (canon_uri, GNOME_VFS_ERROR_LOOP);
2395 rhythmdb_add_import_error_entry (db, event);
2396 g_free (event);
2398 } else if (rb_uri_is_directory (realuri)) {
2399 RhythmDBAddThreadData *data = g_new0 (RhythmDBAddThreadData, 1);
2400 data->db = db;
2401 data->uri = g_strdup (realuri);
2402 data->type = type;
2404 rhythmdb_thread_create (db, db->priv->add_thread_pool, NULL, data);
2405 } else {
2406 queue_stat_uri (realuri, db, type);
2409 g_free (canon_uri);
2410 g_free (realuri);
2413 static gboolean
2414 rhythmdb_sync_library_idle (RhythmDB *db)
2416 rhythmdb_sync_library_location (db);
2417 g_object_unref (db);
2418 return FALSE;
2421 static gboolean
2422 rhythmdb_load_error_cb (GError *error)
2424 GDK_THREADS_ENTER ();
2425 rb_error_dialog (NULL,
2426 _("Could not load the music database"),
2427 error->message);
2428 g_error_free (error);
2430 GDK_THREADS_LEAVE ();
2431 return FALSE;
2434 static gpointer
2435 rhythmdb_load_thread_main (RhythmDB *db)
2437 RhythmDBEvent *result;
2438 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2439 GError *error = NULL;
2441 rb_profile_start ("loading db");
2442 g_mutex_lock (db->priv->saving_mutex);
2443 if (klass->impl_load (db, &db->priv->exiting, &error) == FALSE) {
2444 rb_debug ("db load failed: disabling saving");
2445 db->priv->can_save = FALSE;
2447 if (error) {
2448 g_idle_add ((GSourceFunc) rhythmdb_load_error_cb, error);
2451 g_mutex_unlock (db->priv->saving_mutex);
2453 g_object_ref (db);
2454 g_timeout_add (10000, (GSourceFunc) rhythmdb_sync_library_idle, db);
2456 rb_debug ("queuing db load complete signal");
2457 result = g_new0 (RhythmDBEvent, 1);
2458 result->type = RHYTHMDB_EVENT_DB_LOAD;
2459 g_async_queue_push (db->priv->event_queue, result);
2461 rb_debug ("exiting");
2462 result = g_new0 (RhythmDBEvent, 1);
2463 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2464 g_async_queue_push (db->priv->event_queue, result);
2466 rb_profile_end ("loading db");
2467 return NULL;
2471 * rhythmdb_load:
2472 * @db: a #RhythmDB.
2474 * Load the database from disk.
2476 void
2477 rhythmdb_load (RhythmDB *db)
2479 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_load_thread_main, db);
2482 static gpointer
2483 rhythmdb_save_thread_main (RhythmDB *db)
2485 RhythmDBClass *klass;
2486 RhythmDBEvent *result;
2488 rb_debug ("entering save thread");
2490 g_mutex_lock (db->priv->saving_mutex);
2492 if (!db->priv->dirty && !db->priv->can_save) {
2493 rb_debug ("no save needed, ignoring");
2494 g_mutex_unlock (db->priv->saving_mutex);
2495 goto out;
2498 while (db->priv->saving)
2499 g_cond_wait (db->priv->saving_condition, db->priv->saving_mutex);
2501 db->priv->saving = TRUE;
2503 rb_debug ("saving rhythmdb");
2505 klass = RHYTHMDB_GET_CLASS (db);
2506 klass->impl_save (db);
2508 db->priv->saving = FALSE;
2509 db->priv->dirty = FALSE;
2511 g_mutex_unlock (db->priv->saving_mutex);
2513 g_cond_broadcast (db->priv->saving_condition);
2515 out:
2516 result = g_new0 (RhythmDBEvent, 1);
2517 result->db = db;
2518 result->type = RHYTHMDB_EVENT_DB_SAVED;
2519 g_async_queue_push (db->priv->event_queue, result);
2521 result = g_new0 (RhythmDBEvent, 1);
2522 result->db = db;
2523 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2524 g_async_queue_push (db->priv->event_queue, result);
2525 return NULL;
2529 * rhythmdb_save_async:
2530 * @db: a #RhythmDB.
2532 * Save the database to disk, asynchronously.
2534 void
2535 rhythmdb_save_async (RhythmDB *db)
2537 rb_debug ("saving the rhythmdb in the background");
2539 rhythmdb_read_enter (db);
2541 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_save_thread_main, db);
2545 * rhythmdb_save:
2546 * @db: a #RhythmDB.
2548 * Save the database to disk, not returning until it has been saved.
2550 void
2551 rhythmdb_save (RhythmDB *db)
2553 rb_debug("saving the rhythmdb and blocking");
2555 rhythmdb_save_async (db);
2557 g_mutex_lock (db->priv->saving_mutex);
2559 while (db->priv->saving)
2560 g_cond_wait (db->priv->saving_condition, db->priv->saving_mutex);
2562 g_mutex_unlock (db->priv->saving_mutex);
2566 * rhythmdb_entry_set:
2567 * @db:# a RhythmDB.
2568 * @entry: a #RhythmDBEntry.
2569 * @propid: the id of the property to set.
2570 * @value: the property value.
2572 * This function can be called by any code which wishes to change a
2573 * song property and send a notification. It may be called when the
2574 * database is read-only; in this case the change will be queued for
2575 * an unspecified time in the future. The implication of this is that
2576 * rhythmdb_entry_get() may not reflect the changes immediately. However,
2577 * if this property is exposed in the user interface, you should still
2578 * make the change in the widget. Then when the database returns to a
2579 * writable state, your change will take effect in the database too,
2580 * and a notification will be sent at that point.
2582 * Note that you must call rhythmdb_commit() at some point after invoking
2583 * this function, and that even after the commit, your change may not
2584 * have taken effect.
2586 void
2587 rhythmdb_entry_set (RhythmDB *db,
2588 RhythmDBEntry *entry,
2589 guint propid,
2590 const GValue *value)
2592 g_return_if_fail (RHYTHMDB_IS (db));
2593 g_return_if_fail (entry != NULL);
2595 if ((entry->flags & RHYTHMDB_ENTRY_INSERTED) != 0) {
2596 if (!rhythmdb_get_readonly (db) && rb_is_main_thread ()) {
2597 rhythmdb_entry_set_internal (db, entry, TRUE, propid, value);
2598 } else {
2599 RhythmDBEvent *result;
2601 result = g_new0 (RhythmDBEvent, 1);
2602 result->db = db;
2603 result->type = RHYTHMDB_EVENT_ENTRY_SET;
2605 rb_debug ("queuing RHYTHMDB_ACTION_ENTRY_SET");
2607 result->entry = rhythmdb_entry_ref (entry);
2608 result->change.prop = propid;
2609 result->signal_change = TRUE;
2610 g_value_init (&result->change.new, G_VALUE_TYPE (value));
2611 g_value_copy (value, &result->change.new);
2612 g_async_queue_push (db->priv->event_queue, result);
2614 } else {
2615 rhythmdb_entry_set_internal (db, entry, FALSE, propid, value);
2619 static void
2620 record_entry_change (RhythmDB *db,
2621 RhythmDBEntry *entry,
2622 guint propid,
2623 const GValue *value)
2625 RhythmDBEntryChange *changedata;
2626 GSList *changelist;
2628 changedata = g_new0 (RhythmDBEntryChange, 1);
2629 changedata->prop = propid;
2631 /* Copy a temporary gvalue, since _entry_get uses
2632 * _set_static_string to avoid memory allocations. */
2634 GValue tem = {0,};
2635 g_value_init (&tem, G_VALUE_TYPE (value));
2636 rhythmdb_entry_get (db, entry, propid, &tem);
2637 g_value_init (&changedata->old, G_VALUE_TYPE (value));
2638 g_value_copy (&tem, &changedata->old);
2639 g_value_unset (&tem);
2641 g_value_init (&changedata->new, G_VALUE_TYPE (value));
2642 g_value_copy (value, &changedata->new);
2644 g_mutex_lock (db->priv->change_mutex);
2645 /* ref the entry before adding to hash, it is unreffed when removed */
2646 rhythmdb_entry_ref (entry);
2647 changelist = g_hash_table_lookup (db->priv->changed_entries, entry);
2648 changelist = g_slist_append (changelist, changedata);
2649 g_hash_table_insert (db->priv->changed_entries, entry, changelist);
2650 g_mutex_unlock (db->priv->change_mutex);
2653 void
2654 rhythmdb_entry_set_internal (RhythmDB *db,
2655 RhythmDBEntry *entry,
2656 gboolean notify_if_inserted,
2657 guint propid,
2658 const GValue *value)
2660 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2661 gboolean handled;
2662 RhythmDBPodcastFields *podcast = NULL;
2664 #ifndef G_DISABLE_ASSERT
2665 switch (G_VALUE_TYPE (value)) {
2666 case G_TYPE_STRING:
2667 /* the playback error is allowed to be NULL */
2668 if (propid != RHYTHMDB_PROP_PLAYBACK_ERROR || g_value_get_string (value))
2669 g_assert (g_utf8_validate (g_value_get_string (value), -1, NULL));
2670 break;
2671 case G_TYPE_BOOLEAN:
2672 case G_TYPE_ULONG:
2673 case G_TYPE_UINT64:
2674 case G_TYPE_DOUBLE:
2675 break;
2676 default:
2677 g_assert_not_reached ();
2678 break;
2680 #endif
2682 if ((entry->flags & RHYTHMDB_ENTRY_INSERTED) && notify_if_inserted) {
2683 record_entry_change (db, entry, propid, value);
2686 handled = klass->impl_entry_set (db, entry, propid, value);
2688 if (!handled) {
2689 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
2690 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
2691 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
2693 switch (propid) {
2694 case RHYTHMDB_PROP_TYPE:
2695 case RHYTHMDB_PROP_ENTRY_ID:
2696 g_assert_not_reached ();
2697 break;
2698 case RHYTHMDB_PROP_TITLE:
2699 if (entry->title != NULL) {
2700 rb_refstring_unref (entry->title);
2702 entry->title = rb_refstring_new (g_value_get_string (value));
2703 break;
2704 case RHYTHMDB_PROP_ALBUM:
2705 if (entry->album != NULL) {
2706 rb_refstring_unref (entry->album);
2708 entry->album = rb_refstring_new (g_value_get_string (value));
2709 break;
2710 case RHYTHMDB_PROP_ARTIST:
2711 if (entry->artist != NULL) {
2712 rb_refstring_unref (entry->artist);
2714 entry->artist = rb_refstring_new (g_value_get_string (value));
2715 break;
2716 case RHYTHMDB_PROP_GENRE:
2717 if (entry->genre != NULL) {
2718 rb_refstring_unref (entry->genre);
2720 entry->genre = rb_refstring_new (g_value_get_string (value));
2721 break;
2722 case RHYTHMDB_PROP_TRACK_NUMBER:
2723 entry->tracknum = g_value_get_ulong (value);
2724 break;
2725 case RHYTHMDB_PROP_DISC_NUMBER:
2726 entry->discnum = g_value_get_ulong (value);
2727 break;
2728 case RHYTHMDB_PROP_DURATION:
2729 entry->duration = g_value_get_ulong (value);
2730 break;
2731 case RHYTHMDB_PROP_BITRATE:
2732 entry->bitrate = g_value_get_ulong (value);
2733 break;
2734 case RHYTHMDB_PROP_DATE:
2736 gulong julian;
2737 julian = g_value_get_ulong (value);
2738 if (julian > 0)
2739 g_date_set_julian (&entry->date, julian);
2740 else
2741 g_date_clear (&entry->date, 1);
2742 break;
2744 case RHYTHMDB_PROP_TRACK_GAIN:
2745 entry->track_gain = g_value_get_double (value);
2746 break;
2747 case RHYTHMDB_PROP_TRACK_PEAK:
2748 entry->track_peak = g_value_get_double (value);
2749 break;
2750 case RHYTHMDB_PROP_ALBUM_GAIN:
2751 entry->album_gain = g_value_get_double (value);
2752 break;
2753 case RHYTHMDB_PROP_ALBUM_PEAK:
2754 entry->album_peak = g_value_get_double (value);
2755 break;
2756 case RHYTHMDB_PROP_LOCATION:
2757 rb_refstring_unref (entry->location);
2758 entry->location = rb_refstring_new (g_value_get_string (value));
2759 break;
2760 case RHYTHMDB_PROP_PLAYBACK_ERROR:
2761 rb_refstring_unref (entry->playback_error);
2762 if (g_value_get_string (value))
2763 entry->playback_error = rb_refstring_new (g_value_get_string (value));
2764 else
2765 entry->playback_error = NULL;
2766 break;
2767 case RHYTHMDB_PROP_MOUNTPOINT:
2768 if (entry->mountpoint != NULL) {
2769 rb_refstring_unref (entry->mountpoint);
2771 entry->mountpoint = rb_refstring_new (g_value_get_string (value));
2772 break;
2773 case RHYTHMDB_PROP_FILE_SIZE:
2774 entry->file_size = g_value_get_uint64 (value);
2775 break;
2776 case RHYTHMDB_PROP_MIMETYPE:
2777 if (entry->mimetype != NULL) {
2778 rb_refstring_unref (entry->mimetype);
2780 entry->mimetype = rb_refstring_new (g_value_get_string (value));
2781 break;
2782 case RHYTHMDB_PROP_MTIME:
2783 entry->mtime = g_value_get_ulong (value);
2784 break;
2785 case RHYTHMDB_PROP_FIRST_SEEN:
2786 entry->first_seen = g_value_get_ulong (value);
2787 entry->flags |= RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY;
2788 break;
2789 case RHYTHMDB_PROP_LAST_SEEN:
2790 entry->last_seen = g_value_get_ulong (value);
2791 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
2792 break;
2793 case RHYTHMDB_PROP_RATING:
2794 entry->rating = g_value_get_double (value);
2795 break;
2796 case RHYTHMDB_PROP_PLAY_COUNT:
2797 entry->play_count = g_value_get_ulong (value);
2798 break;
2799 case RHYTHMDB_PROP_LAST_PLAYED:
2800 entry->last_played = g_value_get_ulong (value);
2801 entry->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY;
2802 break;
2803 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
2804 rb_refstring_unref (entry->musicbrainz_trackid);
2805 entry->musicbrainz_trackid = rb_refstring_new (g_value_get_string (value));
2806 break;
2807 case RHYTHMDB_PROP_HIDDEN:
2808 if (g_value_get_boolean (value)) {
2809 entry->flags |= RHYTHMDB_ENTRY_HIDDEN;
2810 } else {
2811 entry->flags &= ~RHYTHMDB_ENTRY_HIDDEN;
2813 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
2814 break;
2815 case RHYTHMDB_PROP_STATUS:
2816 g_assert (podcast);
2817 podcast->status = g_value_get_ulong (value);
2818 break;
2819 case RHYTHMDB_PROP_DESCRIPTION:
2820 g_assert (podcast);
2821 rb_refstring_unref (podcast->description);
2822 podcast->description = rb_refstring_new (g_value_get_string (value));
2823 break;
2824 case RHYTHMDB_PROP_SUBTITLE:
2825 g_assert (podcast);
2826 rb_refstring_unref (podcast->subtitle);
2827 podcast->subtitle = rb_refstring_new (g_value_get_string (value));
2828 break;
2829 case RHYTHMDB_PROP_SUMMARY:
2830 g_assert (podcast);
2831 rb_refstring_unref (podcast->summary);
2832 podcast->summary = rb_refstring_new (g_value_get_string (value));
2833 break;
2834 case RHYTHMDB_PROP_LANG:
2835 g_assert (podcast);
2836 if (podcast->lang != NULL) {
2837 rb_refstring_unref (podcast->lang);
2839 podcast->lang = rb_refstring_new (g_value_get_string (value));
2840 break;
2841 case RHYTHMDB_PROP_COPYRIGHT:
2842 g_assert (podcast);
2843 if (podcast->copyright != NULL) {
2844 rb_refstring_unref (podcast->copyright);
2846 podcast->copyright = rb_refstring_new (g_value_get_string (value));
2847 break;
2848 case RHYTHMDB_PROP_IMAGE:
2849 g_assert (podcast);
2850 if (podcast->image != NULL) {
2851 rb_refstring_unref (podcast->image);
2853 podcast->image = rb_refstring_new (g_value_get_string (value));
2854 break;
2855 case RHYTHMDB_PROP_POST_TIME:
2856 g_assert (podcast);
2857 podcast->post_time = g_value_get_ulong (value);
2858 break;
2859 case RHYTHMDB_NUM_PROPERTIES:
2860 g_assert_not_reached ();
2861 break;
2865 /* set the dirty state */
2866 db->priv->dirty = TRUE;
2870 * rhythmdb_entry_sync_mirrored:
2871 * @db: a #RhythmDB.
2872 * @type: a #RhythmDBEntry.
2873 * @propid: the property to sync the mirrored version of.
2875 * Synchronise "mirrored" properties, such as the string version of the last-played
2876 * time. This should be called when a property is directly modified, passing the
2877 * original property.
2879 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
2882 static void
2883 rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
2884 guint propid)
2886 static const char *format;
2887 static const char *never;
2888 char *val;
2890 /* PERFORMANCE: doing these lookups every call creates a noticable slowdown */
2891 if (format == NULL)
2892 format = _("%Y-%m-%d %H:%M");
2893 if (never == NULL)
2894 never = _("Never");
2896 switch (propid) {
2897 case RHYTHMDB_PROP_LAST_PLAYED_STR:
2899 RBRefString *old, *new;
2901 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY))
2902 break;
2904 old = g_atomic_pointer_get (&entry->last_played_str);
2905 if (entry->last_played == 0) {
2906 new = rb_refstring_new (never);
2907 } else {
2908 val = eel_strdup_strftime (format, localtime ((glong*)&entry->last_played));
2909 new = rb_refstring_new (val);
2910 g_free (val);
2913 if (g_atomic_pointer_compare_and_exchange (&entry->last_played_str, old, new)) {
2914 if (old != NULL) {
2915 rb_refstring_unref (old);
2917 } else {
2918 rb_refstring_unref (new);
2921 break;
2923 case RHYTHMDB_PROP_FIRST_SEEN_STR:
2925 RBRefString *old, *new;
2927 if (!(entry->flags & RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY))
2928 break;
2930 old = g_atomic_pointer_get (&entry->first_seen_str);
2931 val = eel_strdup_strftime (format, localtime ((glong*)&entry->first_seen));
2932 new = rb_refstring_new (val);
2933 g_free (val);
2935 if (g_atomic_pointer_compare_and_exchange (&entry->first_seen_str, old, new)) {
2936 if (old != NULL) {
2937 rb_refstring_unref (old);
2939 } else {
2940 rb_refstring_unref (new);
2943 break;
2945 case RHYTHMDB_PROP_LAST_SEEN_STR:
2947 RBRefString *old, *new;
2949 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_SEEN_DIRTY))
2950 break;
2952 old = g_atomic_pointer_get (&entry->last_seen_str);
2953 /* only store last seen time as a string for hidden entries */
2954 if (entry->flags & RHYTHMDB_ENTRY_HIDDEN) {
2955 val = eel_strdup_strftime (format, localtime ((glong*)&entry->last_seen));
2956 new = rb_refstring_new (val);
2957 g_free (val);
2958 } else {
2959 new = NULL;
2962 if (g_atomic_pointer_compare_and_exchange (&entry->first_seen_str, old, new)) {
2963 if (old != NULL) {
2964 rb_refstring_unref (old);
2966 } else {
2967 rb_refstring_unref (new);
2970 break;
2972 default:
2973 break;
2978 * rhythmdb_entry_delete:
2979 * @db: a #RhythmDB.
2980 * @entry: a #RhythmDBEntry.
2982 * Delete entry @entry from the database, sending notification of it's deletion.
2983 * This is usually used by sources where entries can disappear randomly, such
2984 * as a network source.
2986 void
2987 rhythmdb_entry_delete (RhythmDB *db,
2988 RhythmDBEntry *entry)
2990 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2992 g_return_if_fail (RHYTHMDB_IS (db));
2993 g_return_if_fail (entry != NULL);
2995 /* ref the entry before adding to hash, it is unreffed when removed */
2996 rhythmdb_entry_ref (entry);
2998 klass->impl_entry_delete (db, entry);
3000 g_mutex_lock (db->priv->change_mutex);
3001 g_hash_table_insert (db->priv->deleted_entries, entry, g_thread_self ());
3002 g_mutex_unlock (db->priv->change_mutex);
3004 /* deleting an entry makes the db dirty */
3005 db->priv->dirty = TRUE;
3008 static gint
3009 rhythmdb_entry_move_to_trash_cb (GnomeVFSXferProgressInfo *info,
3010 gpointer data)
3012 /* Abort immediately if anything happens */
3013 if (info->status == GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR)
3014 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
3015 /* Don't overwrite files */
3016 if (info->status == GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE)
3017 return 0;
3018 return TRUE;
3021 static void
3022 rhythmdb_entry_move_to_trash_set_error (RhythmDB *db,
3023 RhythmDBEntry *entry,
3024 GnomeVFSResult res)
3026 GValue value = { 0, };
3028 if (res == -1)
3029 res = GNOME_VFS_ERROR_INTERNAL;
3031 g_value_init (&value, G_TYPE_STRING);
3032 g_value_set_string (&value, gnome_vfs_result_to_string (res));
3033 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
3034 g_value_unset (&value);
3036 rb_debug ("Deleting %s failed: %s", rb_refstring_get (entry->location),
3037 gnome_vfs_result_to_string (res));
3040 void
3041 rhythmdb_entry_move_to_trash (RhythmDB *db,
3042 RhythmDBEntry *entry)
3044 GnomeVFSResult res;
3045 GnomeVFSURI *uri, *trash, *dest;
3046 char *shortname;
3048 uri = gnome_vfs_uri_new (rb_refstring_get (entry->location));
3049 if (uri == NULL) {
3050 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3051 return;
3054 res = gnome_vfs_find_directory (uri,
3055 GNOME_VFS_DIRECTORY_KIND_TRASH,
3056 &trash,
3057 TRUE, TRUE,
3059 if (res != GNOME_VFS_OK || trash == NULL) {
3060 /* If the file doesn't exist, or trash isn't support,
3061 * remove it from the db */
3062 if (res == GNOME_VFS_ERROR_NOT_FOUND ||
3063 res == GNOME_VFS_ERROR_NOT_SUPPORTED) {
3064 rhythmdb_entry_set_visibility (db, entry, FALSE);
3065 } else {
3066 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3069 gnome_vfs_uri_unref (uri);
3070 return;
3073 /* Is the file already in the Trash? If so it should be hidden */
3074 if (gnome_vfs_uri_is_parent (trash, uri, TRUE)) {
3075 GValue value = { 0, };
3076 g_value_init (&value, G_TYPE_BOOLEAN);
3077 g_value_set_boolean (&value, TRUE);
3078 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_HIDDEN, &value);
3079 rhythmdb_commit (db);
3081 gnome_vfs_uri_unref (trash);
3082 gnome_vfs_uri_unref (uri);
3083 return;
3086 shortname = gnome_vfs_uri_extract_short_name (uri);
3087 if (shortname == NULL) {
3088 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3089 rhythmdb_commit (db);
3090 gnome_vfs_uri_unref (uri);
3091 gnome_vfs_uri_unref (trash);
3092 return;
3095 /* Compute the destination URI */
3096 dest = gnome_vfs_uri_append_path (trash, shortname);
3097 gnome_vfs_uri_unref (trash);
3098 g_free (shortname);
3099 if (dest == NULL) {
3100 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3101 rhythmdb_commit (db);
3102 gnome_vfs_uri_unref (uri);
3103 return;
3106 /* RB can't tell that a file's moved, so no unique names */
3107 res = gnome_vfs_xfer_uri (uri, dest,
3108 GNOME_VFS_XFER_REMOVESOURCE,
3109 GNOME_VFS_XFER_ERROR_MODE_ABORT,
3110 GNOME_VFS_XFER_OVERWRITE_MODE_SKIP,
3111 rhythmdb_entry_move_to_trash_cb,
3112 entry);
3114 if (res == GNOME_VFS_OK) {
3115 rhythmdb_entry_set_visibility (db, entry, FALSE);
3116 } else {
3117 rhythmdb_entry_move_to_trash_set_error (db, entry, res);
3119 rhythmdb_commit (db);
3121 gnome_vfs_uri_unref (dest);
3122 gnome_vfs_uri_unref (uri);
3126 * rhythmdb_entry_delete_by_type:
3127 * @db: a #RhythmDB.
3128 * @type: type of entried to delete.
3130 * Delete all entries from the database of the given type.
3131 * This is usually used by non-permanent sources when they disappear, such as
3132 * removable media being removed, or a network share becoming unavailable.
3134 void
3135 rhythmdb_entry_delete_by_type (RhythmDB *db,
3136 RhythmDBEntryType type)
3138 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3140 if (klass->impl_entry_delete_by_type) {
3141 klass->impl_entry_delete_by_type (db, type);
3142 } else {
3143 g_warning ("delete_by_type not implemented");
3147 const xmlChar *
3148 rhythmdb_nice_elt_name_from_propid (RhythmDB *db,
3149 RhythmDBPropType propid)
3151 return db->priv->column_xml_names[propid];
3155 rhythmdb_propid_from_nice_elt_name (RhythmDB *db,
3156 const xmlChar *name)
3158 gpointer ret, orig;
3159 if (g_hash_table_lookup_extended (db->priv->propname_map, name,
3160 &orig, &ret)) {
3161 return GPOINTER_TO_INT (ret);
3163 return -1;
3167 * rhythmdb_entry_lookup_by_location:
3168 * @db: a #RhythmDB.
3169 * @uri: the URI of the entry to lookup.
3171 * Looks up the entry with location @uri.
3173 * Returns: the entry with location @uri, or NULL if no such entry exists.
3175 RhythmDBEntry *
3176 rhythmdb_entry_lookup_by_location (RhythmDB *db,
3177 const char *uri)
3179 RBRefString *rs;
3181 rs = rb_refstring_find (uri);
3182 if (rs != NULL) {
3183 return rhythmdb_entry_lookup_by_location_refstring (db, rs);
3184 } else {
3185 return NULL;
3189 RhythmDBEntry *
3190 rhythmdb_entry_lookup_by_location_refstring (RhythmDB *db,
3191 RBRefString *uri)
3193 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3195 return klass->impl_lookup_by_location (db, uri);
3199 *rhythmdb_entry_lookup_by_id:
3200 * @db: a #RhythmDB.
3201 * @id: entry ID
3203 * Looks up the entry with id @id.
3205 * Returns: the entry with id @id, or NULL if no such entry exists.
3207 RhythmDBEntry *
3208 rhythmdb_entry_lookup_by_id (RhythmDB *db,
3209 gint id)
3211 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3213 return klass->impl_lookup_by_id (db, id);
3217 *rhythmdb_entry_lookup_from_string:
3218 * @db: a #RhythmDB.
3219 * @str: string
3220 * @is_id: whether the string is an entry ID or a location.
3222 * Locates an entry using a string containing either an entry ID
3223 * or a location.
3225 * Returns: the entry matching the string, or NULL if no such entry exists.
3227 RhythmDBEntry *
3228 rhythmdb_entry_lookup_from_string (RhythmDB *db,
3229 const char *str,
3230 gboolean is_id)
3232 if (is_id) {
3233 gint id;
3235 id = strtoul (str, NULL, 10);
3236 if (id == 0)
3237 return NULL;
3239 return rhythmdb_entry_lookup_by_id (db, id);
3240 } else {
3241 return rhythmdb_entry_lookup_by_location (db, str);
3246 *rhythmdb_entry_foreach:
3247 * @db: a #RhythmDB.
3248 * @func: the function to call with each entry.
3249 * @data: user data to pass to the function.
3251 * Calls the given function for each of the entries in the database.
3253 void
3254 rhythmdb_entry_foreach (RhythmDB *db,
3255 GFunc func,
3256 gpointer data)
3258 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3260 klass->impl_entry_foreach (db, func, data);
3264 *rhythmdb_entry_count:
3265 * @db: a #RhythmDB.
3267 * Returns: the number of entries in the database.
3269 gint64
3270 rhythmdb_entry_count (RhythmDB *db)
3272 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3274 return klass->impl_entry_count (db);
3278 *rhythmdb_entry_foreach_by_type:
3279 * @db: a #RhythmdB.
3280 * @entry_type: the type of entry to retrieve
3281 * @func: the function to call with each entry
3282 * @data: user data to pass to the function.
3284 * Calls the given function for each of the entries in the database
3285 * of a given type.
3287 void
3288 rhythmdb_entry_foreach_by_type (RhythmDB *db,
3289 RhythmDBEntryType entry_type,
3290 GFunc func,
3291 gpointer data)
3293 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3295 klass->impl_entry_foreach_by_type (db, entry_type, func, data);
3299 *rhythmdb_entry_count_by_type:
3300 * @db: a #RhythmDB.
3301 * @entry_type: a #RhythmDBEntryType.
3303 * Returns: the number of entries in the database of a particular type.
3305 gint64
3306 rhythmdb_entry_count_by_type (RhythmDB *db,
3307 RhythmDBEntryType entry_type)
3309 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3311 return klass->impl_entry_count_by_type (db, entry_type);
3316 * rhythmdb_evaluate_query:
3317 * @db: a #RhythmDB.
3318 * @query: a query.
3319 * @entry a @RhythmDBEntry.
3321 * Evaluates the given entry against the given query.
3323 * Returns: whether the given entry matches the criteria of the given query.
3325 gboolean
3326 rhythmdb_evaluate_query (RhythmDB *db,
3327 GPtrArray *query,
3328 RhythmDBEntry *entry)
3330 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3332 return klass->impl_evaluate_query (db, query, entry);
3335 static void
3336 rhythmdb_query_internal (RhythmDBQueryThreadData *data)
3338 RhythmDBEvent *result;
3339 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (data->db);
3341 rhythmdb_query_preprocess (data->db, data->query);
3343 rb_debug ("doing query");
3345 klass->impl_do_full_query (data->db, data->query,
3346 data->results,
3347 &data->cancel);
3349 rb_debug ("completed");
3350 rhythmdb_query_results_query_complete (data->results);
3352 result = g_new0 (RhythmDBEvent, 1);
3353 result->db = data->db;
3354 result->type = RHYTHMDB_EVENT_QUERY_COMPLETE;
3355 result->results = data->results;
3356 g_async_queue_push (data->db->priv->event_queue, result);
3358 rhythmdb_query_free (data->query);
3361 static gpointer
3362 query_thread_main (RhythmDBQueryThreadData *data)
3364 RhythmDBEvent *result;
3366 rb_debug ("entering query thread");
3368 rhythmdb_query_internal (data);
3370 result = g_new0 (RhythmDBEvent, 1);
3371 result->db = data->db;
3372 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
3373 g_async_queue_push (data->db->priv->event_queue, result);
3374 g_free (data);
3375 return NULL;
3378 void
3379 rhythmdb_do_full_query_async_parsed (RhythmDB *db,
3380 RhythmDBQueryResults *results,
3381 GPtrArray *query)
3383 RhythmDBQueryThreadData *data;
3385 data = g_new0 (RhythmDBQueryThreadData, 1);
3386 data->db = db;
3387 data->query = rhythmdb_query_copy (query);
3388 data->results = results;
3389 data->cancel = FALSE;
3391 rhythmdb_read_enter (db);
3393 rhythmdb_query_results_set_query (results, query);
3395 g_object_ref (results);
3396 g_object_ref (db);
3397 g_atomic_int_inc (&db->priv->outstanding_threads);
3398 g_async_queue_ref (db->priv->action_queue);
3399 g_async_queue_ref (db->priv->event_queue);
3400 g_thread_pool_push (db->priv->query_thread_pool, data, NULL);
3403 void
3404 rhythmdb_do_full_query_async (RhythmDB *db,
3405 RhythmDBQueryResults *results,
3406 ...)
3408 GPtrArray *query;
3409 va_list args;
3411 va_start (args, results);
3413 query = rhythmdb_query_parse_valist (db, args);
3415 rhythmdb_do_full_query_async_parsed (db, results, query);
3417 rhythmdb_query_free (query);
3419 va_end (args);
3422 static void
3423 rhythmdb_do_full_query_internal (RhythmDB *db,
3424 RhythmDBQueryResults *results,
3425 GPtrArray *query)
3427 RhythmDBQueryThreadData *data;
3429 data = g_new0 (RhythmDBQueryThreadData, 1);
3430 data->db = db;
3431 data->query = rhythmdb_query_copy (query);
3432 data->results = results;
3433 data->cancel = FALSE;
3435 rhythmdb_read_enter (db);
3437 rhythmdb_query_results_set_query (results, query);
3438 g_object_ref (results);
3440 rhythmdb_query_internal (data);
3441 g_free (data);
3444 void
3445 rhythmdb_do_full_query_parsed (RhythmDB *db,
3446 RhythmDBQueryResults *results,
3447 GPtrArray *query)
3449 rhythmdb_do_full_query_internal (db, results, query);
3452 void
3453 rhythmdb_do_full_query (RhythmDB *db,
3454 RhythmDBQueryResults *results,
3455 ...)
3457 GPtrArray *query;
3458 va_list args;
3460 va_start (args, results);
3462 query = rhythmdb_query_parse_valist (db, args);
3464 rhythmdb_do_full_query_internal (db, results, query);
3466 rhythmdb_query_free (query);
3468 va_end (args);
3471 /* This should really be standard. */
3472 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3474 GType
3475 rhythmdb_query_type_get_type (void)
3477 static GType etype = 0;
3479 if (etype == 0)
3481 static const GEnumValue values[] =
3484 ENUM_ENTRY (RHYTHMDB_QUERY_END, "Query end marker"),
3485 ENUM_ENTRY (RHYTHMDB_QUERY_DISJUNCTION, "Disjunctive marker"),
3486 ENUM_ENTRY (RHYTHMDB_QUERY_SUBQUERY, "Subquery"),
3487 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_EQUALS, "Property equivalence"),
3488 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LIKE, "Fuzzy property matching"),
3489 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_LIKE, "Inverted fuzzy property matching"),
3490 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_PREFIX, "Starts with"),
3491 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_SUFFIX, "Ends with"),
3492 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_GREATER, "True if property1 >= property2"),
3493 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LESS, "True if property1 <= property2"),
3494 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN, "True if property1 is within property2 of the current time"),
3495 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN, "True if property1 is not within property2 of the current time"),
3496 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_EQUALS, "Year equivalence: true if date within year"),
3497 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_GREATER, "True if date greater than year"),
3498 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_LESS, "True if date less than year"),
3499 { 0, 0, 0 }
3502 etype = g_enum_register_static ("RhythmDBQueryType", values);
3505 return etype;
3508 GType
3509 rhythmdb_prop_type_get_type (void)
3511 static GType etype = 0;
3513 if (etype == 0)
3515 static const GEnumValue values[] =
3517 /* We reuse the description to store extra data about
3518 * a property. The first part is just a generic
3519 * human-readable description. Next, there is
3520 * a string describing the GType of the property, in
3521 * parenthesis.
3522 * Finally, there is the XML element name in brackets.
3524 ENUM_ENTRY (RHYTHMDB_PROP_TYPE, "Type of entry (gpointer) [type]"),
3525 ENUM_ENTRY (RHYTHMDB_PROP_ENTRY_ID, "Numeric ID (guint) [entry-id]"),
3526 ENUM_ENTRY (RHYTHMDB_PROP_TITLE, "Title (gchararray) [title]"),
3527 ENUM_ENTRY (RHYTHMDB_PROP_GENRE, "Genre (gchararray) [genre]"),
3528 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST, "Artist (gchararray) [artist]"),
3529 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM, "Album (gchararray) [album]"),
3530 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_NUMBER, "Track Number (gulong) [track-number]"),
3531 ENUM_ENTRY (RHYTHMDB_PROP_DISC_NUMBER, "Disc Number (gulong) [disc-number]"),
3532 ENUM_ENTRY (RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, "Musicbrainz Track ID (gchararray) [mb-trackid]"),
3534 ENUM_ENTRY (RHYTHMDB_PROP_DURATION, "Duration (gulong) [duration]"),
3535 ENUM_ENTRY (RHYTHMDB_PROP_FILE_SIZE, "File Size (guint64) [file-size]"),
3536 ENUM_ENTRY (RHYTHMDB_PROP_LOCATION, "Location (gchararray) [location]"),
3537 ENUM_ENTRY (RHYTHMDB_PROP_MOUNTPOINT, "Mount point it's located in (gchararray) [mountpoint]"),
3538 ENUM_ENTRY (RHYTHMDB_PROP_MTIME, "Modification time (gulong) [mtime]"),
3539 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN, "Time the song was added to the library (gulong) [first-seen]"),
3540 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN, "Last time the song was available (gulong) [last-seen]"),
3541 ENUM_ENTRY (RHYTHMDB_PROP_RATING, "Rating (gdouble) [rating]"),
3542 ENUM_ENTRY (RHYTHMDB_PROP_PLAY_COUNT, "Play Count (gulong) [play-count]"),
3543 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED, "Last Played (gulong) [last-played]"),
3544 ENUM_ENTRY (RHYTHMDB_PROP_BITRATE, "Bitrate (gulong) [bitrate]"),
3545 ENUM_ENTRY (RHYTHMDB_PROP_DATE, "Date of release (gulong) [date]"),
3546 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_GAIN, "Replaygain track gain (gdouble) [replaygain-track-gain]"),
3547 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_PEAK, "Replaygain track peak (gdouble) [replaygain-track-peak]"),
3548 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_GAIN, "Replaygain album pain (gdouble) [replaygain-album-gain]"),
3549 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_PEAK, "Replaygain album peak (gdouble) [replaygain-album-peak]"),
3550 ENUM_ENTRY (RHYTHMDB_PROP_MIMETYPE, "Mime Type (gchararray) [mimetype]"),
3551 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_SORT_KEY, "Title sort key (gchararray) [title-sort-key]"),
3552 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_SORT_KEY, "Genre sort key (gchararray) [genre-sort-key]"),
3553 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_SORT_KEY, "Artist sort key (gchararray) [artist-sort-key]"),
3554 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_SORT_KEY, "Album sort key (gchararray) [album-sort-key]"),
3556 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_FOLDED, "Title folded (gchararray) [title-folded]"),
3557 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_FOLDED, "Genre folded (gchararray) [genre-folded]"),
3558 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_FOLDED, "Artist folded (gchararray) [artist-folded]"),
3559 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_FOLDED, "Album folded (gchararray) [album-folded]"),
3560 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED_STR, "Last Played (gchararray) [last-played-str]"),
3561 ENUM_ENTRY (RHYTHMDB_PROP_PLAYBACK_ERROR, "Playback error string (gchararray) [playback-error]"),
3562 ENUM_ENTRY (RHYTHMDB_PROP_HIDDEN, "Hidden (gboolean) [hidden]"),
3563 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN_STR, "Time Added to Library (gchararray) [first-seen-str]"),
3564 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN_STR, "Last time the song was available (gchararray) [last-seen-str]"),
3565 ENUM_ENTRY (RHYTHMDB_PROP_SEARCH_MATCH, "Search matching key (gchararray) [search-match]"),
3566 ENUM_ENTRY (RHYTHMDB_PROP_YEAR, "Year of date (gulong) [year]"),
3568 ENUM_ENTRY (RHYTHMDB_PROP_STATUS, "Status of file (gulong) [status]"),
3569 ENUM_ENTRY (RHYTHMDB_PROP_DESCRIPTION, "Podcast description(gchararray) [description]"),
3570 ENUM_ENTRY (RHYTHMDB_PROP_SUBTITLE, "Podcast subtitle (gchararray) [subtitle]"),
3571 ENUM_ENTRY (RHYTHMDB_PROP_SUMMARY, "Podcast summary (gchararray) [summary]"),
3572 ENUM_ENTRY (RHYTHMDB_PROP_LANG, "Podcast language (gchararray) [lang]"),
3573 ENUM_ENTRY (RHYTHMDB_PROP_COPYRIGHT, "Podcast copyright (gchararray) [copyright]"),
3574 ENUM_ENTRY (RHYTHMDB_PROP_IMAGE, "Podcast image(gchararray) [image]"),
3575 ENUM_ENTRY (RHYTHMDB_PROP_POST_TIME, "Podcast time of post (gulong) [post-time]"),
3576 { 0, 0, 0 }
3578 g_assert ((sizeof (values) / sizeof (values[0]) - 1) == RHYTHMDB_NUM_PROPERTIES);
3579 etype = g_enum_register_static ("RhythmDBPropType", values);
3582 return etype;
3585 void
3586 rhythmdb_emit_entry_deleted (RhythmDB *db,
3587 RhythmDBEntry *entry)
3589 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry);
3592 static gboolean
3593 rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
3594 GValue *return_accu,
3595 const GValue *handler_return,
3596 gpointer data)
3598 if (handler_return == NULL)
3599 return TRUE;
3601 g_value_copy (handler_return, return_accu);
3602 return (g_value_get_boxed (return_accu) == NULL);
3606 * rhythmdb_entry_request_extra_metadata:
3607 * @db: a #RhythmDB
3608 * @entry: a #RhythmDBEntry
3609 * @property_name: the metadata predicate
3611 * Emits a request for extra metadata for the @entry.
3612 * The @property_name argument is emitted as the ::detail part of the
3613 * "entry_extra_metadata_request" signal. It should be a namespaced RDF
3614 * predicate e.g. from Dublin Core, MusicBrainz, or internal to Rhythmbox
3615 * (namespace "rb:"). Suitable predicates would be those that are expensive to
3616 * acquire or only apply to a limited range of entries.
3617 * Handlers capable of providing a particular predicate may ensure they only
3618 * see appropriate requests by supplying an appropriate ::detail part when
3619 * connecting to the signal. Upon a handler returning a non-%NULL value,
3620 * emission will be stopped and the value returned to the caller; if no
3621 * handlers return a non-%NULL value, the caller will receive %NULL. Priority
3622 * is determined by signal connection order, with %G_CONNECT_AFTER providing a
3623 * second, lower rank of priority.
3624 * A handler returning a value should do so in a #GValue allocated on the heap;
3625 * the accumulator will take ownership. The caller should unset and free the
3626 * #GValue if non-%NULL when finished with it.
3628 * Returns: an allocated, initialised, set #GValue, or NULL
3630 GValue *
3631 rhythmdb_entry_request_extra_metadata (RhythmDB *db,
3632 RhythmDBEntry *entry,
3633 const gchar *property_name)
3635 GValue *value = NULL;
3637 g_signal_emit (G_OBJECT (db),
3638 rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST],
3639 g_quark_from_string (property_name),
3640 entry,
3641 &value);
3643 return value;
3647 * rhythmdb_emit_entry_extra_metadata_notify:
3648 * @db: a #RhythmDB
3649 * @entry: a #RhythmDBEntry
3650 * @property_name: the metadata predicate
3651 * @metadata: a #GValue
3653 * Emits a signal describing extra metadata for the @entry. The @property_name
3654 * argument is emitted as the ::detail part of the
3655 * "entry_extra_metadata_notify" signal and as the 'field' parameter. Handlers
3656 * can ensure they only get metadata they are interested in by supplying an
3657 * appropriate ::detail part when connecting to the signal. If handlers are
3658 * interested in the metadata they should ref or copy the contents of @metadata
3659 * and unref or free it when they are finished with it.
3661 void
3662 rhythmdb_emit_entry_extra_metadata_notify (RhythmDB *db,
3663 RhythmDBEntry *entry,
3664 const gchar *property_name,
3665 const GValue *metadata)
3667 g_signal_emit (G_OBJECT (db),
3668 rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY],
3669 g_quark_from_string (property_name),
3670 entry,
3671 property_name,
3672 metadata);
3675 static void
3676 unset_and_free_g_value (gpointer valpointer)
3678 GValue *value = valpointer;
3679 g_value_unset (value);
3680 g_free (value);
3684 * rhythmdb_entry_extra_gather:
3685 * @db: a #RhythmDB
3686 * @entry: a #RhythmDBEntry
3688 * Gathers all metadata for the @entry. The returned GHashTable maps property
3689 * names and extra metadata names (described under
3690 * @rhythmdb_entry_request_extra_metadata) to GValues. Anything wanting to
3691 * provide extra metadata should connect to the "entry_extra_metadata_gather"
3692 * signal.
3694 * Returns: a GHashTable containing metadata for the entry. This must be freed
3695 * using g_hash_table_destroy.
3697 GHashTable *
3698 rhythmdb_entry_gather_metadata (RhythmDB *db,
3699 RhythmDBEntry *entry)
3701 GHashTable *metadata;
3702 GEnumClass *klass;
3703 guint i;
3705 metadata = g_hash_table_new_full (g_str_hash,
3706 g_str_equal,
3707 g_free,
3708 unset_and_free_g_value);
3710 /* add core properties */
3711 klass = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
3712 for (i = 0; i < klass->n_values; i++) {
3713 GValue *value;
3714 gint prop;
3715 GType value_type;
3716 const char *name;
3718 prop = klass->values[i].value;
3720 /* only include easily marshallable types in the hash table */
3721 value_type = rhythmdb_get_property_type (db, prop);
3722 switch (value_type) {
3723 case G_TYPE_STRING:
3724 case G_TYPE_BOOLEAN:
3725 case G_TYPE_ULONG:
3726 case G_TYPE_UINT64:
3727 case G_TYPE_DOUBLE:
3728 break;
3729 default:
3730 continue;
3733 value = g_new0 (GValue, 1);
3734 g_value_init (value, value_type);
3735 rhythmdb_entry_get (db, entry, prop, value);
3736 name = (char *)rhythmdb_nice_elt_name_from_propid (db, prop);
3737 g_hash_table_insert (metadata,
3738 (gpointer) g_strdup (name),
3739 value);
3741 g_type_class_unref (klass);
3743 /* gather extra metadata */
3744 g_signal_emit (G_OBJECT (db),
3745 rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER], 0,
3746 entry,
3747 metadata);
3749 return metadata;
3752 static gboolean
3753 queue_is_empty (GAsyncQueue *queue)
3755 return g_async_queue_length (queue) <= 0;
3759 * rhythmdb_is_busy:
3760 * @db: a #RhythmDB.
3762 * Returns: whether the #RhythmDB has events to process.
3764 gboolean
3765 rhythmdb_is_busy (RhythmDB *db)
3767 return (!db->priv->action_thread_running ||
3768 !queue_is_empty (db->priv->event_queue) ||
3769 !queue_is_empty (db->priv->action_queue) ||
3770 (db->priv->stat_handle != NULL) ||
3771 (db->priv->outstanding_stats != NULL));
3775 * rhythmdb_compute_status_normal:
3776 * @n_songs: the number of tracks.
3777 * @duration: the total duration of the tracks.
3778 * @size: the total size of the tracks.
3779 * @singular: singular form of the format string to use for entries (eg "%d song")
3780 * @plural: plural form of the format string to use for entries (eg "%d songs")
3782 * Creates a string containing the "status" information about a list of tracks.
3783 * The singular and plural strings must be used in a direct ngettext call
3784 * elsewhere in order for them to be marked for translation correctly.
3786 * Returns: the string, which should be freed with g_free.
3788 char *
3789 rhythmdb_compute_status_normal (gint n_songs,
3790 glong duration,
3791 guint64 size,
3792 const char *singular,
3793 const char *plural)
3795 long days, hours, minutes, seconds;
3796 char *songcount = NULL;
3797 char *time = NULL;
3798 char *size_str = NULL;
3799 char *ret;
3800 const char *minutefmt;
3801 const char *hourfmt;
3802 const char *dayfmt;
3804 songcount = g_strdup_printf (ngettext (singular, plural, n_songs), n_songs);
3806 days = duration / (60 * 60 * 24);
3807 hours = (duration / (60 * 60)) - (days * 24);
3808 minutes = (duration / 60) - ((days * 24 * 60) + (hours * 60));
3809 seconds = duration % 60;
3811 minutefmt = ngettext ("%ld minute", "%ld minutes", minutes);
3812 hourfmt = ngettext ("%ld hour", "%ld hours", hours);
3813 dayfmt = ngettext ("%ld day", "%ld days", days);
3814 if (days > 0) {
3815 if (hours > 0)
3816 if (minutes > 0) {
3817 char *fmt;
3818 /* Translators: the format is "X days, X hours and X minutes" */
3819 fmt = g_strdup_printf (_("%s, %s and %s"), dayfmt, hourfmt, minutefmt);
3820 time = g_strdup_printf (fmt, days, hours, minutes);
3821 g_free (fmt);
3822 } else {
3823 char *fmt;
3824 /* Translators: the format is "X days and X hours" */
3825 fmt = g_strdup_printf (_("%s and %s"), dayfmt, hourfmt);
3826 time = g_strdup_printf (fmt, days, hours);
3827 g_free (fmt);
3829 else
3830 if (minutes > 0) {
3831 char *fmt;
3832 /* Translators: the format is "X days and X minutes" */
3833 fmt = g_strdup_printf (_("%s and %s"), dayfmt, minutefmt);
3834 time = g_strdup_printf (fmt, days, minutes);
3835 g_free (fmt);
3836 } else {
3837 time = g_strdup_printf (dayfmt, days);
3839 } else {
3840 if (hours > 0) {
3841 if (minutes > 0) {
3842 char *fmt;
3843 /* Translators: the format is "X hours and X minutes" */
3844 fmt = g_strdup_printf (_("%s and %s"), hourfmt, minutefmt);
3845 time = g_strdup_printf (fmt, hours, minutes);
3846 g_free (fmt);
3847 } else {
3848 time = g_strdup_printf (hourfmt, hours);
3851 } else {
3852 time = g_strdup_printf (minutefmt, minutes);
3856 size_str = gnome_vfs_format_file_size_for_display (size);
3858 if (size > 0 && duration > 0) {
3859 ret = g_strdup_printf ("%s, %s, %s", songcount, time, size_str);
3860 } else if (duration > 0) {
3861 ret = g_strdup_printf ("%s, %s", songcount, time);
3862 } else if (size > 0) {
3863 ret = g_strdup_printf ("%s, %s", songcount, size_str);
3864 } else {
3865 ret = g_strdup (songcount);
3868 g_free (songcount);
3869 g_free (time);
3870 g_free (size_str);
3872 return ret;
3875 static void
3876 default_sync_metadata (RhythmDB *db,
3877 RhythmDBEntry *entry,
3878 GError **error,
3879 gpointer data)
3881 const char *uri;
3882 GError *local_error = NULL;
3884 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3885 rb_metadata_load (db->priv->metadata,
3886 uri, &local_error);
3887 if (local_error != NULL) {
3888 g_propagate_error (error, local_error);
3889 return;
3892 entry_to_rb_metadata (db, entry, db->priv->metadata);
3894 rb_metadata_save (db->priv->metadata, &local_error);
3895 if (local_error != NULL) {
3896 RhythmDBAction *load_action;
3898 /* reload the metadata, to revert the db changes */
3899 load_action = g_new0 (RhythmDBAction, 1);
3900 load_action->type = RHYTHMDB_ACTION_LOAD;
3901 load_action->uri = rb_refstring_ref (entry->location);
3902 load_action->entry_type = RHYTHMDB_ENTRY_TYPE_INVALID;
3903 g_async_queue_push (db->priv->action_queue, load_action);
3905 g_propagate_error (error, local_error);
3910 * rhythmdb_entry_register_type:
3911 * @db: a #RhythmDB
3912 * @name: optional name for the entry type
3914 * Registers a new #RhythmDBEntryType. This should be called to create a new
3915 * entry type for non-permanent sources.
3917 * Returns: the new #RhythmDBEntryType.
3919 RhythmDBEntryType
3920 rhythmdb_entry_register_type (RhythmDB *db,
3921 const char *name)
3923 RhythmDBEntryType type;
3924 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3926 type = g_new0 (RhythmDBEntryType_, 1);
3927 type->can_sync_metadata = (RhythmDBEntryCanSyncFunc)rb_false_function;
3928 type->sync_metadata = default_sync_metadata;
3929 if (name) {
3930 type->name = g_strdup (name);
3931 g_mutex_lock (db->priv->entry_type_map_mutex);
3932 g_hash_table_insert (db->priv->entry_type_map, g_strdup (type->name), type);
3933 g_mutex_unlock (db->priv->entry_type_map_mutex);
3936 if (klass->impl_entry_type_registered)
3937 klass->impl_entry_type_registered (db, name, type);
3939 return type;
3942 static void
3943 rhythmdb_entry_register_type_alias (RhythmDB *db,
3944 RhythmDBEntryType type,
3945 const char *name)
3947 char *dn = g_strdup (name);
3949 g_mutex_lock (db->priv->entry_type_map_mutex);
3950 g_hash_table_insert (db->priv->entry_type_map, dn, type);
3951 g_mutex_unlock (db->priv->entry_type_map_mutex);
3954 typedef struct {
3955 GHFunc func;
3956 gpointer data;
3957 } RhythmDBEntryTypeForeachData;
3959 static void
3960 rhythmdb_entry_type_foreach_cb (const char *name,
3961 RhythmDBEntryType entry_type,
3962 RhythmDBEntryTypeForeachData *data)
3964 /* skip aliases */
3965 if (strcmp (entry_type->name, name))
3966 return;
3968 data->func ((gpointer) name, entry_type, data->data);
3972 * rhythmdb_entry_type_foreach:
3973 * @db: a #RhythmDB
3974 * @func: callback function to call for each registered entry type
3975 * @data: data to pass to the callback
3977 * Calls a function for each registered entry type.
3979 void
3980 rhythmdb_entry_type_foreach (RhythmDB *db,
3981 GHFunc func,
3982 gpointer data)
3984 RhythmDBEntryTypeForeachData d;
3986 d.func = func;
3987 d.data = data;
3989 g_mutex_lock (db->priv->entry_type_mutex);
3990 g_hash_table_foreach (db->priv->entry_type_map,
3991 (GHFunc) rhythmdb_entry_type_foreach_cb,
3992 &d);
3993 g_mutex_unlock (db->priv->entry_type_mutex);
3997 * rhythmdb_entry_type_get_by_name:
3998 * @db: a #RhythmDB
3999 * @name: name of the type to look for
4001 * Locates a #RhythmDBEntryType by name. Returns
4002 * RHYTHMDB_ENTRY_TYPE_INVALID if no entry type
4003 * is registered with the specified name.
4005 * Returns: the #RhythmDBEntryType
4007 RhythmDBEntryType
4008 rhythmdb_entry_type_get_by_name (RhythmDB *db,
4009 const char *name)
4011 gpointer t = NULL;
4013 g_mutex_lock (db->priv->entry_type_map_mutex);
4014 if (db->priv->entry_type_map) {
4015 t = g_hash_table_lookup (db->priv->entry_type_map, name);
4017 g_mutex_unlock (db->priv->entry_type_map_mutex);
4019 if (t)
4020 return (RhythmDBEntryType) t;
4022 return RHYTHMDB_ENTRY_TYPE_INVALID;
4025 static gboolean
4026 song_can_sync_metadata (RhythmDB *db,
4027 RhythmDBEntry *entry,
4028 gpointer data)
4030 const char *mimetype;
4032 mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
4033 return rb_metadata_can_save (db->priv->metadata, mimetype);
4036 static char *
4037 podcast_get_playback_uri (RhythmDBEntry *entry,
4038 gpointer data)
4040 return rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
4043 static void
4044 podcast_data_destroy (RhythmDBEntry *entry,
4045 gpointer something)
4047 RhythmDBPodcastFields *podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4048 rb_refstring_unref (podcast->description);
4049 rb_refstring_unref (podcast->subtitle);
4050 rb_refstring_unref (podcast->summary);
4051 rb_refstring_unref (podcast->lang);
4052 rb_refstring_unref (podcast->copyright);
4053 rb_refstring_unref (podcast->image);
4056 static RhythmDBEntryType song_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4057 static RhythmDBEntryType ignore_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4058 static RhythmDBEntryType import_error_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4060 /* to be evicted */
4061 static RhythmDBEntryType podcast_post_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4062 static RhythmDBEntryType podcast_feed_type = RHYTHMDB_ENTRY_TYPE_INVALID;
4064 static void
4065 rhythmdb_register_core_entry_types (RhythmDB *db)
4067 /* regular songs */
4068 song_type = rhythmdb_entry_register_type (db, "song");
4069 rhythmdb_entry_register_type_alias (db, song_type, "0");
4070 song_type->save_to_disk = TRUE;
4071 song_type->category = RHYTHMDB_ENTRY_NORMAL;
4072 song_type->can_sync_metadata = song_can_sync_metadata;
4074 /* import errors */
4075 import_error_type = rhythmdb_entry_register_type (db, "import-error");
4076 import_error_type->get_playback_uri = (RhythmDBEntryStringFunc)rb_null_function;
4077 import_error_type->category = RHYTHMDB_ENTRY_VIRTUAL;
4079 /* ignored files */
4080 ignore_type = rhythmdb_entry_register_type (db, "ignore");
4081 ignore_type->save_to_disk = TRUE;
4082 ignore_type->category = RHYTHMDB_ENTRY_VIRTUAL;
4084 /* podcast posts */
4085 podcast_post_type = rhythmdb_entry_register_type (db, "podcast-post");
4086 podcast_post_type->entry_type_data_size = sizeof (RhythmDBPodcastFields);
4087 podcast_post_type->save_to_disk = TRUE;
4088 podcast_post_type->category = RHYTHMDB_ENTRY_NORMAL;
4089 podcast_post_type->pre_entry_destroy = (RhythmDBEntryActionFunc) podcast_data_destroy;
4090 podcast_post_type->get_playback_uri = podcast_get_playback_uri;
4092 /* podcast feeds */
4093 podcast_feed_type = rhythmdb_entry_register_type (db, "podcast-feed");
4094 podcast_feed_type->entry_type_data_size = sizeof (RhythmDBPodcastFields);
4095 podcast_feed_type->save_to_disk = TRUE;
4096 podcast_feed_type->category = RHYTHMDB_ENTRY_VIRTUAL;
4097 podcast_feed_type->pre_entry_destroy = (RhythmDBEntryActionFunc) podcast_data_destroy;
4100 RhythmDBEntryType
4101 rhythmdb_entry_song_get_type (void)
4103 return song_type;
4106 RhythmDBEntryType
4107 rhythmdb_entry_ignore_get_type (void)
4109 return ignore_type;
4112 RhythmDBEntryType
4113 rhythmdb_entry_import_error_get_type (void)
4115 return import_error_type;
4118 RhythmDBEntryType
4119 rhythmdb_entry_podcast_post_get_type (void)
4121 return podcast_post_type;
4124 RhythmDBEntryType
4125 rhythmdb_entry_podcast_feed_get_type (void)
4127 return podcast_feed_type;
4130 static void
4131 rhythmdb_entry_set_mount_point (RhythmDB *db,
4132 RhythmDBEntry *entry,
4133 const gchar *realuri)
4135 gchar *mount_point;
4136 GValue value = {0, };
4138 mount_point = rb_uri_get_mount_point (realuri);
4139 if (mount_point != NULL) {
4140 g_value_init (&value, G_TYPE_STRING);
4141 g_value_set_string_take_ownership (&value, mount_point);
4142 rhythmdb_entry_set_internal (db, entry, FALSE,
4143 RHYTHMDB_PROP_MOUNTPOINT,
4144 &value);
4145 g_value_unset (&value);
4149 void
4150 rhythmdb_entry_set_visibility (RhythmDB *db,
4151 RhythmDBEntry *entry,
4152 gboolean visible)
4154 GValue old_val = {0, };
4155 gboolean old_visible;
4157 g_return_if_fail (RHYTHMDB_IS (db));
4158 g_return_if_fail (entry != NULL);
4160 g_value_init (&old_val, G_TYPE_BOOLEAN);
4162 rhythmdb_entry_get (db, entry, RHYTHMDB_PROP_HIDDEN, &old_val);
4163 old_visible = !g_value_get_boolean (&old_val);
4165 if ((old_visible && !visible) || (!old_visible && visible)) {
4166 GValue new_val = {0, };
4168 g_value_init (&new_val, G_TYPE_BOOLEAN);
4169 g_value_set_boolean (&new_val, !visible);
4170 rhythmdb_entry_set_internal (db, entry, TRUE,
4171 RHYTHMDB_PROP_HIDDEN, &new_val);
4172 g_value_unset (&new_val);
4174 g_value_unset (&old_val);
4177 static gboolean
4178 rhythmdb_idle_save (RhythmDB *db)
4180 if (db->priv->dirty) {
4181 rb_debug ("database is dirty, doing regular save");
4182 rhythmdb_save_async (db);
4185 return TRUE;
4188 static void
4189 rhythmdb_sync_library_location (RhythmDB *db)
4191 gboolean reload = (db->priv->library_locations != NULL);
4193 if (db->priv->library_location_notify_id == 0) {
4194 db->priv->library_location_notify_id =
4195 eel_gconf_notification_add (CONF_LIBRARY_LOCATION,
4196 (GConfClientNotifyFunc) library_location_changed_cb,
4197 db);
4200 if (reload) {
4201 rb_debug ("ending monitor of old library directories");
4203 rhythmdb_stop_monitoring (db);
4205 g_slist_foreach (db->priv->library_locations, (GFunc) g_free, NULL);
4206 g_slist_free (db->priv->library_locations);
4207 db->priv->library_locations = NULL;
4210 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY)) {
4211 db->priv->library_locations = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
4213 rhythmdb_start_monitoring (db);
4217 static void
4218 library_location_changed_cb (GConfClient *client,
4219 guint cnxn_id,
4220 GConfEntry *entry,
4221 RhythmDB *db)
4223 rhythmdb_sync_library_location (db);
4226 char *
4227 rhythmdb_entry_dup_string (RhythmDBEntry *entry,
4228 RhythmDBPropType propid)
4230 const char *s;
4232 g_return_val_if_fail (entry != NULL, NULL);
4234 s = rhythmdb_entry_get_string (entry, propid);
4235 if (s != NULL) {
4236 return g_strdup (s);
4237 } else {
4238 return NULL;
4242 const char *
4243 rhythmdb_entry_get_string (RhythmDBEntry *entry,
4244 RhythmDBPropType propid)
4246 RhythmDBPodcastFields *podcast = NULL;
4248 g_return_val_if_fail (entry != NULL, NULL);
4249 g_return_val_if_fail (entry->refcount > 0, NULL);
4251 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
4252 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
4253 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4255 rhythmdb_entry_sync_mirrored (entry, propid);
4257 switch (propid) {
4258 case RHYTHMDB_PROP_TITLE:
4259 return rb_refstring_get (entry->title);
4260 case RHYTHMDB_PROP_ALBUM:
4261 return rb_refstring_get (entry->album);
4262 case RHYTHMDB_PROP_ARTIST:
4263 return rb_refstring_get (entry->artist);
4264 case RHYTHMDB_PROP_GENRE:
4265 return rb_refstring_get (entry->genre);
4266 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4267 return rb_refstring_get (entry->musicbrainz_trackid);
4268 case RHYTHMDB_PROP_MIMETYPE:
4269 return rb_refstring_get (entry->mimetype);
4270 case RHYTHMDB_PROP_TITLE_SORT_KEY:
4271 return rb_refstring_get_sort_key (entry->title);
4272 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
4273 return rb_refstring_get_sort_key (entry->album);
4274 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
4275 return rb_refstring_get_sort_key (entry->artist);
4276 case RHYTHMDB_PROP_GENRE_SORT_KEY:
4277 return rb_refstring_get_sort_key (entry->genre);
4278 case RHYTHMDB_PROP_TITLE_FOLDED:
4279 return rb_refstring_get_folded (entry->title);
4280 case RHYTHMDB_PROP_ALBUM_FOLDED:
4281 return rb_refstring_get_folded (entry->album);
4282 case RHYTHMDB_PROP_ARTIST_FOLDED:
4283 return rb_refstring_get_folded (entry->artist);
4284 case RHYTHMDB_PROP_GENRE_FOLDED:
4285 return rb_refstring_get_folded (entry->genre);
4286 case RHYTHMDB_PROP_LOCATION:
4287 return rb_refstring_get (entry->location);
4288 case RHYTHMDB_PROP_MOUNTPOINT:
4289 return rb_refstring_get (entry->mountpoint);
4290 case RHYTHMDB_PROP_LAST_PLAYED_STR:
4291 return rb_refstring_get (entry->last_played_str);
4292 case RHYTHMDB_PROP_PLAYBACK_ERROR:
4293 return rb_refstring_get (entry->playback_error);
4294 case RHYTHMDB_PROP_FIRST_SEEN_STR:
4295 return rb_refstring_get (entry->first_seen_str);
4296 case RHYTHMDB_PROP_LAST_SEEN_STR:
4297 return rb_refstring_get (entry->last_seen_str);
4298 case RHYTHMDB_PROP_SEARCH_MATCH:
4299 return NULL; /* synthetic property */
4300 /* Podcast properties */
4301 case RHYTHMDB_PROP_DESCRIPTION:
4302 if (podcast)
4303 return rb_refstring_get (podcast->description);
4304 else
4305 return NULL;
4306 case RHYTHMDB_PROP_SUBTITLE:
4307 if (podcast)
4308 return rb_refstring_get (podcast->subtitle);
4309 else
4310 return NULL;
4311 case RHYTHMDB_PROP_SUMMARY:
4312 if (podcast)
4313 return rb_refstring_get (podcast->summary);
4314 else
4315 return NULL;
4316 case RHYTHMDB_PROP_LANG:
4317 if (podcast)
4318 return rb_refstring_get (podcast->lang);
4319 else
4320 return NULL;
4321 case RHYTHMDB_PROP_COPYRIGHT:
4322 if (podcast)
4323 return rb_refstring_get (podcast->copyright);
4324 else
4325 return NULL;
4326 case RHYTHMDB_PROP_IMAGE:
4327 if (podcast)
4328 return rb_refstring_get (podcast->image);
4329 else
4330 return NULL;
4332 default:
4333 g_assert_not_reached ();
4334 return NULL;
4338 RBRefString *
4339 rhythmdb_entry_get_refstring (RhythmDBEntry *entry,
4340 RhythmDBPropType propid)
4342 g_return_val_if_fail (entry != NULL, NULL);
4343 g_return_val_if_fail (entry->refcount > 0, NULL);
4345 rhythmdb_entry_sync_mirrored (entry, propid);
4347 switch (propid) {
4348 case RHYTHMDB_PROP_TITLE:
4349 return rb_refstring_ref (entry->title);
4350 case RHYTHMDB_PROP_ALBUM:
4351 return rb_refstring_ref (entry->album);
4352 case RHYTHMDB_PROP_ARTIST:
4353 return rb_refstring_ref (entry->artist);
4354 case RHYTHMDB_PROP_GENRE:
4355 return rb_refstring_ref (entry->genre);
4356 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4357 return rb_refstring_ref (entry->musicbrainz_trackid);
4358 case RHYTHMDB_PROP_MIMETYPE:
4359 return rb_refstring_ref (entry->mimetype);
4360 case RHYTHMDB_PROP_MOUNTPOINT:
4361 return rb_refstring_ref (entry->mountpoint);
4362 case RHYTHMDB_PROP_LAST_PLAYED_STR:
4363 return rb_refstring_ref (entry->last_played_str);
4364 case RHYTHMDB_PROP_FIRST_SEEN_STR:
4365 return rb_refstring_ref (entry->first_seen_str);
4366 case RHYTHMDB_PROP_LAST_SEEN_STR:
4367 return rb_refstring_ref (entry->last_seen_str);
4368 case RHYTHMDB_PROP_LOCATION:
4369 return rb_refstring_ref (entry->location);
4370 case RHYTHMDB_PROP_PLAYBACK_ERROR:
4371 return rb_refstring_ref (entry->playback_error);
4372 default:
4373 g_assert_not_reached ();
4374 return NULL;
4378 gboolean
4379 rhythmdb_entry_get_boolean (RhythmDBEntry *entry,
4380 RhythmDBPropType propid)
4382 g_return_val_if_fail (entry != NULL, FALSE);
4384 switch (propid) {
4385 case RHYTHMDB_PROP_HIDDEN:
4386 return ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
4387 default:
4388 g_assert_not_reached ();
4389 return FALSE;
4393 guint64
4394 rhythmdb_entry_get_uint64 (RhythmDBEntry *entry,
4395 RhythmDBPropType propid)
4397 g_return_val_if_fail (entry != NULL, 0);
4399 switch (propid) {
4400 case RHYTHMDB_PROP_FILE_SIZE:
4401 return entry->file_size;
4402 default:
4403 g_assert_not_reached ();
4404 return 0;
4408 RhythmDBEntryType
4409 rhythmdb_entry_get_entry_type (RhythmDBEntry *entry)
4411 g_return_val_if_fail (entry != NULL, RHYTHMDB_ENTRY_TYPE_INVALID);
4413 return entry->type;
4416 gpointer
4417 rhythmdb_entry_get_pointer (RhythmDBEntry *entry,
4418 RhythmDBPropType propid)
4420 g_return_val_if_fail (entry != NULL, NULL);
4422 switch (propid) {
4423 case RHYTHMDB_PROP_TYPE:
4424 return entry->type;
4425 default:
4426 g_assert_not_reached ();
4427 return NULL;
4431 gulong
4432 rhythmdb_entry_get_ulong (RhythmDBEntry *entry,
4433 RhythmDBPropType propid)
4435 RhythmDBPodcastFields *podcast = NULL;
4437 g_return_val_if_fail (entry != NULL, 0);
4439 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
4440 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
4441 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4443 switch (propid) {
4444 case RHYTHMDB_PROP_ENTRY_ID:
4445 return entry->id;
4446 case RHYTHMDB_PROP_TRACK_NUMBER:
4447 return entry->tracknum;
4448 case RHYTHMDB_PROP_DISC_NUMBER:
4449 return entry->discnum;
4450 case RHYTHMDB_PROP_DURATION:
4451 return entry->duration;
4452 case RHYTHMDB_PROP_MTIME:
4453 return entry->mtime;
4454 case RHYTHMDB_PROP_FIRST_SEEN:
4455 return entry->first_seen;
4456 case RHYTHMDB_PROP_LAST_SEEN:
4457 return entry->last_seen;
4458 case RHYTHMDB_PROP_LAST_PLAYED:
4459 return entry->last_played;
4460 case RHYTHMDB_PROP_PLAY_COUNT:
4461 return entry->play_count;
4462 case RHYTHMDB_PROP_BITRATE:
4463 return entry->bitrate;
4464 case RHYTHMDB_PROP_DATE:
4465 if (g_date_valid (&entry->date))
4466 return g_date_get_julian (&entry->date);
4467 else
4468 return 0;
4469 case RHYTHMDB_PROP_YEAR:
4470 if (g_date_valid (&entry->date))
4471 return g_date_get_year (&entry->date);
4472 else
4473 return 0;
4474 case RHYTHMDB_PROP_POST_TIME:
4475 if (podcast)
4476 return podcast->post_time;
4477 else
4478 return 0;
4479 case RHYTHMDB_PROP_STATUS:
4480 if (podcast)
4481 return podcast->status;
4482 else
4483 return 0;
4484 default:
4485 g_assert_not_reached ();
4486 return 0;
4490 double
4491 rhythmdb_entry_get_double (RhythmDBEntry *entry,
4492 RhythmDBPropType propid)
4494 g_return_val_if_fail (entry != NULL, 0);
4496 switch (propid) {
4497 case RHYTHMDB_PROP_TRACK_GAIN:
4498 return entry->track_gain;
4499 case RHYTHMDB_PROP_TRACK_PEAK:
4500 return entry->track_peak;
4501 case RHYTHMDB_PROP_ALBUM_GAIN:
4502 return entry->album_gain;
4503 case RHYTHMDB_PROP_ALBUM_PEAK:
4504 return entry->album_peak;
4505 case RHYTHMDB_PROP_RATING:
4506 return entry->rating;
4507 default:
4508 g_assert_not_reached ();
4509 return 0.0;
4513 char *
4514 rhythmdb_entry_get_playback_uri (RhythmDBEntry *entry)
4516 RhythmDBEntryType type;
4518 g_return_val_if_fail (entry != NULL, NULL);
4520 type = rhythmdb_entry_get_entry_type (entry);
4521 if (type->get_playback_uri)
4522 return (type->get_playback_uri) (entry, type->get_playback_uri_data);
4523 else
4524 return rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_LOCATION);
4527 GType
4528 rhythmdb_get_property_type (RhythmDB *db,
4529 guint property_id)
4531 g_assert (property_id >= 0 && property_id < RHYTHMDB_NUM_PROPERTIES);
4532 return rhythmdb_property_type_map[property_id];
4535 GType
4536 rhythmdb_entry_get_type (void)
4538 static GType type = 0;
4540 if (G_UNLIKELY (type == 0)) {
4541 type = g_boxed_type_register_static ("RhythmDBEntry",
4542 (GBoxedCopyFunc)rhythmdb_entry_ref,
4543 (GBoxedFreeFunc)rhythmdb_entry_unref);
4546 return type;
4549 GType
4550 rhythmdb_entry_type_get_type (void)
4552 static GType type = 0;
4554 if (G_UNLIKELY (type == 0)) {
4555 type = g_boxed_type_register_static ("RhythmDBEntryType",
4556 (GBoxedCopyFunc)rb_copy_function,
4557 (GBoxedFreeFunc)rb_null_function);
4560 return type;