Updated Finnish translation
[rhythmbox.git] / rhythmdb / rhythmdb.c
bloba9e9a068e293fcd6eaf32b189e231085fc3cf131
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"
50 #define RB_PARSE_NICK_START (xmlChar *) "["
51 #define RB_PARSE_NICK_END (xmlChar *) "]"
53 GType rhythmdb_property_type_map[RHYTHMDB_NUM_PROPERTIES];
55 #define RHYTHMDB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE, RhythmDBPrivate))
56 G_DEFINE_ABSTRACT_TYPE(RhythmDB, rhythmdb, G_TYPE_OBJECT)
58 typedef struct
60 RhythmDB *db;
61 GPtrArray *query;
62 guint propid;
63 RhythmDBQueryResults *results;
64 gboolean cancel;
65 } RhythmDBQueryThreadData;
67 typedef struct
69 RhythmDB *db;
70 char *uri;
71 RhythmDBEntryType type;
72 } RhythmDBAddThreadData;
74 typedef struct
76 enum {
77 RHYTHMDB_ACTION_STAT,
78 RHYTHMDB_ACTION_LOAD,
79 RHYTHMDB_ACTION_SYNC
80 } type;
81 RBRefString *uri;
82 RhythmDBEntryType entry_type;
83 } RhythmDBAction;
85 static void rhythmdb_finalize (GObject *object);
86 static void rhythmdb_set_property (GObject *object,
87 guint prop_id,
88 const GValue *value,
89 GParamSpec *pspec);
90 static void rhythmdb_get_property (GObject *object,
91 guint prop_id,
92 GValue *value,
93 GParamSpec *pspec);
94 static void rhythmdb_thread_create (RhythmDB *db,
95 GThreadPool *pool,
96 GThreadFunc func,
97 gpointer data);
98 static void rhythmdb_read_enter (RhythmDB *db);
99 static void rhythmdb_read_leave (RhythmDB *db);
100 static gboolean rhythmdb_idle_poll_events (RhythmDB *db);
101 static gpointer add_thread_main (RhythmDBAddThreadData *data);
102 static gpointer action_thread_main (RhythmDB *db);
103 static gpointer query_thread_main (RhythmDBQueryThreadData *data);
104 static void rhythmdb_entry_set_mount_point (RhythmDB *db,
105 RhythmDBEntry *entry,
106 const gchar *realuri);
108 static gboolean free_entry_changes (RhythmDBEntry *entry,
109 GSList *changes,
110 RhythmDB *db);
111 static gboolean rhythmdb_idle_save (RhythmDB *db);
112 static void library_location_changed_cb (GConfClient *client,
113 guint cnxn_id,
114 GConfEntry *entry,
115 RhythmDB *db);
116 static void rhythmdb_sync_library_location (RhythmDB *db);
117 static void rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
118 guint propid);
119 static void rhythmdb_register_core_entry_types (RhythmDB *db);
120 static gboolean rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
121 GValue *return_accu,
122 const GValue *handler_return,
123 gpointer data);
125 enum
127 PROP_0,
128 PROP_NAME,
129 PROP_DRY_RUN,
130 PROP_NO_UPDATE,
133 enum
135 ENTRY_ADDED,
136 ENTRY_CHANGED,
137 ENTRY_DELETED,
138 ENTRY_EXTRA_METADATA_REQUEST,
139 ENTRY_EXTRA_METADATA_NOTIFY,
140 ENTRY_EXTRA_METADATA_GATHER,
141 LOAD_COMPLETE,
142 SAVE_COMPLETE,
143 SAVE_ERROR,
144 READ_ONLY,
145 LAST_SIGNAL
148 static guint rhythmdb_signals[LAST_SIGNAL] = { 0 };
150 static void
151 rhythmdb_class_init (RhythmDBClass *klass)
153 GObjectClass *object_class = G_OBJECT_CLASS (klass);
155 object_class->finalize = rhythmdb_finalize;
157 object_class->set_property = rhythmdb_set_property;
158 object_class->get_property = rhythmdb_get_property;
160 g_object_class_install_property (object_class,
161 PROP_NAME,
162 g_param_spec_string ("name",
163 "name",
164 "name",
165 NULL,
166 G_PARAM_READWRITE));
168 g_object_class_install_property (object_class,
169 PROP_DRY_RUN,
170 g_param_spec_boolean ("dry-run",
171 "dry run",
172 "Whether or not changes should be saved",
173 FALSE,
174 G_PARAM_READWRITE));
175 g_object_class_install_property (object_class,
176 PROP_NO_UPDATE,
177 g_param_spec_boolean ("no-update",
178 "no update",
179 "Whether or not to update the database",
180 FALSE,
181 G_PARAM_READWRITE));
182 rhythmdb_signals[ENTRY_ADDED] =
183 g_signal_new ("entry_added",
184 RHYTHMDB_TYPE,
185 G_SIGNAL_RUN_LAST,
186 G_STRUCT_OFFSET (RhythmDBClass, entry_added),
187 NULL, NULL,
188 g_cclosure_marshal_VOID__BOXED,
189 G_TYPE_NONE,
190 1, RHYTHMDB_TYPE_ENTRY);
192 rhythmdb_signals[ENTRY_DELETED] =
193 g_signal_new ("entry_deleted",
194 RHYTHMDB_TYPE,
195 G_SIGNAL_RUN_LAST,
196 G_STRUCT_OFFSET (RhythmDBClass, entry_deleted),
197 NULL, NULL,
198 g_cclosure_marshal_VOID__BOXED,
199 G_TYPE_NONE,
200 1, RHYTHMDB_TYPE_ENTRY);
202 rhythmdb_signals[ENTRY_CHANGED] =
203 g_signal_new ("entry_changed",
204 RHYTHMDB_TYPE,
205 G_SIGNAL_RUN_LAST,
206 G_STRUCT_OFFSET (RhythmDBClass, entry_changed),
207 NULL, NULL,
208 rb_marshal_VOID__BOXED_POINTER,
209 G_TYPE_NONE, 2,
210 RHYTHMDB_TYPE_ENTRY, G_TYPE_POINTER);
212 rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST] =
213 g_signal_new ("entry_extra_metadata_request",
214 G_OBJECT_CLASS_TYPE (object_class),
215 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
216 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_request),
217 rhythmdb_entry_extra_metadata_accumulator, NULL,
218 rb_marshal_BOXED__BOXED,
219 G_TYPE_VALUE, 1,
220 RHYTHMDB_TYPE_ENTRY);
222 rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY] =
223 g_signal_new ("entry_extra_metadata_notify",
224 G_OBJECT_CLASS_TYPE (object_class),
225 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
226 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_notify),
227 NULL, NULL,
228 rb_marshal_VOID__BOXED_STRING_BOXED,
229 G_TYPE_NONE, 3,
230 RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_VALUE);
232 rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER] =
233 g_signal_new ("entry_extra_metadata_gather",
234 G_OBJECT_CLASS_TYPE (object_class),
235 G_SIGNAL_RUN_LAST,
236 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_gather),
237 NULL, NULL,
238 rb_marshal_VOID__BOXED_BOXED,
239 G_TYPE_NONE, 2,
240 RHYTHMDB_TYPE_ENTRY, G_TYPE_HASH_TABLE);
242 rhythmdb_signals[LOAD_COMPLETE] =
243 g_signal_new ("load_complete",
244 RHYTHMDB_TYPE,
245 G_SIGNAL_RUN_LAST,
246 G_STRUCT_OFFSET (RhythmDBClass, load_complete),
247 NULL, NULL,
248 g_cclosure_marshal_VOID__VOID,
249 G_TYPE_NONE,
252 rhythmdb_signals[SAVE_COMPLETE] =
253 g_signal_new ("save_complete",
254 RHYTHMDB_TYPE,
255 G_SIGNAL_RUN_LAST,
256 G_STRUCT_OFFSET (RhythmDBClass, save_complete),
257 NULL, NULL,
258 g_cclosure_marshal_VOID__VOID,
259 G_TYPE_NONE,
262 rhythmdb_signals[SAVE_ERROR] =
263 g_signal_new ("save-error",
264 G_OBJECT_CLASS_TYPE (object_class),
265 G_SIGNAL_RUN_LAST,
266 G_STRUCT_OFFSET (RhythmDBClass, save_error),
267 NULL, NULL,
268 rb_marshal_VOID__STRING_POINTER,
269 G_TYPE_NONE,
271 G_TYPE_STRING,
272 G_TYPE_POINTER);
274 rhythmdb_signals[READ_ONLY] =
275 g_signal_new ("read-only",
276 G_OBJECT_CLASS_TYPE (object_class),
277 G_SIGNAL_RUN_LAST,
278 G_STRUCT_OFFSET (RhythmDBClass, read_only),
279 NULL, NULL,
280 g_cclosure_marshal_VOID__BOOLEAN,
281 G_TYPE_NONE,
283 G_TYPE_BOOLEAN);
285 g_type_class_add_private (klass, sizeof (RhythmDBPrivate));
288 static gboolean
289 metadata_field_from_prop (RhythmDBPropType prop,
290 RBMetaDataField *field)
292 switch (prop) {
293 case RHYTHMDB_PROP_TITLE:
294 *field = RB_METADATA_FIELD_TITLE;
295 return TRUE;
296 case RHYTHMDB_PROP_ARTIST:
297 *field = RB_METADATA_FIELD_ARTIST;
298 return TRUE;
299 case RHYTHMDB_PROP_ALBUM:
300 *field = RB_METADATA_FIELD_ALBUM;
301 return TRUE;
302 case RHYTHMDB_PROP_GENRE:
303 *field = RB_METADATA_FIELD_GENRE;
304 return TRUE;
305 case RHYTHMDB_PROP_TRACK_NUMBER:
306 *field = RB_METADATA_FIELD_TRACK_NUMBER;
307 return TRUE;
308 case RHYTHMDB_PROP_DISC_NUMBER:
309 *field = RB_METADATA_FIELD_DISC_NUMBER;
310 return TRUE;
311 case RHYTHMDB_PROP_DATE:
312 *field = RB_METADATA_FIELD_DATE;
313 return TRUE;
314 case RHYTHMDB_PROP_TRACK_GAIN:
315 *field = RB_METADATA_FIELD_TRACK_GAIN;
316 return TRUE;
317 case RHYTHMDB_PROP_TRACK_PEAK:
318 *field = RB_METADATA_FIELD_TRACK_PEAK;
319 return TRUE;
320 case RHYTHMDB_PROP_ALBUM_GAIN:
321 *field = RB_METADATA_FIELD_ALBUM_GAIN;
322 return TRUE;
323 case RHYTHMDB_PROP_ALBUM_PEAK:
324 *field = RB_METADATA_FIELD_ALBUM_PEAK;
325 return TRUE;
326 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
327 *field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
328 return TRUE;
329 default:
330 return FALSE;
334 static GType
335 extract_gtype_from_enum_entry (RhythmDB *db,
336 GEnumClass *klass,
337 guint i)
339 GType ret;
340 GEnumValue *value;
341 RBMetaDataField field;
342 char *typename;
343 char *typename_end;
345 value = g_enum_get_value (klass, i);
347 typename = strstr (value->value_nick, "(");
348 g_assert (typename != NULL);
350 typename_end = strstr (typename, ")");
351 g_assert (typename_end);
353 typename++;
354 typename = g_strndup (typename, typename_end-typename);
355 ret = g_type_from_name (typename);
356 g_free (typename);
358 /* Check to see whether this is a property that maps to
359 a RBMetaData property. */
360 if (metadata_field_from_prop (value->value, &field))
361 g_assert (ret == rb_metadata_get_field_type (field));
362 return ret;
365 static xmlChar *
366 extract_nice_name_from_enum_entry (RhythmDB *db,
367 GEnumClass *klass,
368 guint i)
370 GEnumValue *value;
371 xmlChar *nick;
372 const xmlChar *name;
373 const xmlChar *name_end;
375 value = g_enum_get_value (klass, i);
376 nick = BAD_CAST value->value_nick;
378 name = xmlStrstr (nick, RB_PARSE_NICK_START);
379 g_return_val_if_fail (name != NULL, NULL);
380 name_end = xmlStrstr (name, RB_PARSE_NICK_END);
381 name++;
383 return xmlStrndup (name, name_end - name);
386 static void
387 rhythmdb_init (RhythmDB *db)
389 guint i;
390 GEnumClass *prop_class;
392 db->priv = RHYTHMDB_GET_PRIVATE (db);
394 db->priv->action_queue = g_async_queue_new ();
395 db->priv->event_queue = g_async_queue_new ();
396 db->priv->restored_queue = g_async_queue_new ();
398 db->priv->query_thread_pool = g_thread_pool_new ((GFunc)query_thread_main,
399 NULL,
400 -1, FALSE, NULL);
401 /* Limit this pool to 3 threads. They'll each be thrashing the disk,
402 * so parallelism is limited.
404 db->priv->add_thread_pool = g_thread_pool_new ((GFunc)add_thread_main,
405 NULL,
406 3, FALSE, NULL);
408 db->priv->metadata = rb_metadata_new ();
410 prop_class = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
412 g_assert (prop_class->n_values == RHYTHMDB_NUM_PROPERTIES);
413 db->priv->column_xml_names = g_new0 (xmlChar *, RHYTHMDB_NUM_PROPERTIES);
415 /* Now, extract the GType and XML tag of each column from the
416 * enum descriptions, and cache that for later use. */
417 for (i = 0; i < prop_class->n_values; i++) {
418 rhythmdb_property_type_map[i] = extract_gtype_from_enum_entry (db, prop_class, i);
419 g_assert (rhythmdb_property_type_map[i] != G_TYPE_INVALID);
421 db->priv->column_xml_names[i] = extract_nice_name_from_enum_entry (db, prop_class, i);
422 g_assert (db->priv->column_xml_names[i]);
425 g_type_class_unref (prop_class);
427 db->priv->propname_map = g_hash_table_new (g_str_hash, g_str_equal);
429 for (i = 0; i < RHYTHMDB_NUM_PROPERTIES; i++) {
430 const xmlChar *name = rhythmdb_nice_elt_name_from_propid (db, i);
431 g_hash_table_insert (db->priv->propname_map, (gpointer) name, GINT_TO_POINTER (i));
434 db->priv->entry_type_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
435 db->priv->entry_type_map_mutex = g_mutex_new ();
436 db->priv->entry_type_mutex = g_mutex_new ();
437 rhythmdb_register_core_entry_types (db);
439 db->priv->stat_events = g_hash_table_new_full (gnome_vfs_uri_hash,
440 (GEqualFunc) gnome_vfs_uri_equal,
441 (GDestroyNotify) gnome_vfs_uri_unref,
442 NULL);
443 db->priv->stat_mutex = g_mutex_new ();
445 db->priv->change_mutex = g_mutex_new ();
447 db->priv->changed_entries = g_hash_table_new_full (NULL,
448 NULL,
449 (GDestroyNotify) rhythmdb_entry_unref,
450 NULL);
451 db->priv->added_entries = g_hash_table_new_full (NULL,
452 NULL,
453 (GDestroyNotify) rhythmdb_entry_unref,
454 NULL);
455 db->priv->deleted_entries = g_hash_table_new_full (NULL,
456 NULL,
457 (GDestroyNotify) rhythmdb_entry_unref,
458 NULL);
460 db->priv->event_poll_id = g_idle_add ((GSourceFunc) rhythmdb_idle_poll_events, db);
462 db->priv->saving_condition = g_cond_new ();
463 db->priv->saving_mutex = g_mutex_new ();
465 db->priv->can_save = TRUE;
466 db->priv->exiting = FALSE;
467 db->priv->saving = FALSE;
468 db->priv->dirty = FALSE;
470 db->priv->empty_string = rb_refstring_new ("");
471 db->priv->octet_stream_str = rb_refstring_new ("application/octet-stream");
473 db->priv->next_entry_id = 1;
475 rhythmdb_init_monitoring (db);
478 static GError *
479 make_access_failed_error (const char *uri, GnomeVFSResult result)
481 char *unescaped;
482 char *utf8ised;
483 GError *error;
485 /* make sure the URI we put in the error message is valid utf8 */
486 unescaped = gnome_vfs_unescape_string_for_display (uri);
487 utf8ised = rb_make_valid_utf8 (unescaped, '?');
489 error = g_error_new (RHYTHMDB_ERROR,
490 RHYTHMDB_ERROR_ACCESS_FAILED,
491 _("Couldn't access %s: %s"),
492 utf8ised,
493 gnome_vfs_result_to_string (result));
494 rb_debug ("got error on %s: %s", utf8ised, error->message);
495 g_free (unescaped);
496 g_free (utf8ised);
497 return error;
500 static void
501 rhythmdb_execute_multi_stat_info_cb (GnomeVFSAsyncHandle *handle,
502 GList *results,
503 /* GnomeVFSGetFileInfoResult* items */
504 RhythmDB *db)
506 g_mutex_lock (db->priv->stat_mutex);
507 while (results != NULL) {
508 GnomeVFSGetFileInfoResult *info_result = results->data;
509 RhythmDBEvent *event;
511 event = g_hash_table_lookup (db->priv->stat_events, info_result->uri);
512 if (event == NULL) {
513 char *uri_string;
514 uri_string = gnome_vfs_uri_to_string (info_result->uri, GNOME_VFS_URI_HIDE_NONE);
515 rb_debug ("ignoring unexpected uri in gnome_vfs_async_get_file_info response: %s",
516 uri_string);
517 g_free (uri_string);
518 results = results->next;
519 continue;
521 g_hash_table_remove (db->priv->stat_events, info_result->uri);
523 if (info_result->result == GNOME_VFS_OK) {
524 event->vfsinfo = gnome_vfs_file_info_dup (info_result->file_info);
525 } else {
526 event->error = make_access_failed_error (rb_refstring_get (event->real_uri),
527 info_result->result);
528 event->vfsinfo = NULL;
530 g_async_queue_push (db->priv->event_queue, event);
532 results = results->next;
534 db->priv->stat_handle = NULL;
535 g_mutex_unlock (db->priv->stat_mutex);
538 void
539 rhythmdb_start_action_thread (RhythmDB *db)
541 g_mutex_lock (db->priv->stat_mutex);
542 db->priv->action_thread_running = TRUE;
543 rhythmdb_thread_create (db, NULL, (GThreadFunc) action_thread_main, db);
545 if (db->priv->stat_list != NULL) {
546 gnome_vfs_async_get_file_info (&db->priv->stat_handle, db->priv->stat_list,
547 GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
548 GNOME_VFS_PRIORITY_MIN,
549 (GnomeVFSAsyncGetFileInfoCallback) rhythmdb_execute_multi_stat_info_cb,
550 db);
551 g_list_free (db->priv->stat_list);
552 db->priv->stat_list = NULL;
555 g_mutex_unlock (db->priv->stat_mutex);
558 static void
559 rhythmdb_action_free (RhythmDB *db,
560 RhythmDBAction *action)
562 rb_refstring_unref (action->uri);
563 g_free (action);
566 static void
567 rhythmdb_event_free (RhythmDB *db,
568 RhythmDBEvent *result)
570 switch (result->type) {
571 case RHYTHMDB_EVENT_THREAD_EXITED:
572 g_object_unref (db);
573 g_assert (g_atomic_int_dec_and_test (&db->priv->outstanding_threads) >= 0);
574 g_async_queue_unref (db->priv->action_queue);
575 g_async_queue_unref (db->priv->event_queue);
576 break;
577 case RHYTHMDB_EVENT_STAT:
578 case RHYTHMDB_EVENT_METADATA_LOAD:
579 case RHYTHMDB_EVENT_DB_LOAD:
580 case RHYTHMDB_EVENT_DB_SAVED:
581 case RHYTHMDB_EVENT_QUERY_COMPLETE:
582 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED:
583 case RHYTHMDB_EVENT_FILE_DELETED:
584 break;
585 case RHYTHMDB_EVENT_ENTRY_SET:
586 g_value_unset (&result->change.new);
587 break;
589 if (result->error)
590 g_error_free (result->error);
591 rb_refstring_unref (result->uri);
592 rb_refstring_unref (result->real_uri);
593 if (result->vfsinfo)
594 gnome_vfs_file_info_unref (result->vfsinfo);
595 if (result->metadata)
596 g_object_unref (result->metadata);
597 if (result->results)
598 g_object_unref (result->results);
599 if (result->handle)
600 gnome_vfs_async_cancel (result->handle);
601 if (result->entry != NULL) {
602 rhythmdb_entry_unref (result->entry);
604 g_free (result);
608 * rhythmdb_shutdown:
610 * Ceases all #RhythmDB operations, including stopping all directory monitoring, and
611 * removing all actions and events currently queued.
613 static void
614 _shutdown_foreach_swapped (RhythmDBEvent *event, RhythmDB *db)
616 rhythmdb_event_free (db, event);
619 void
620 rhythmdb_shutdown (RhythmDB *db)
622 RhythmDBEvent *result;
623 RhythmDBAction *action;
625 g_return_if_fail (RHYTHMDB_IS (db));
627 db->priv->exiting = TRUE;
629 eel_gconf_notification_remove (db->priv->library_location_notify_id);
630 g_slist_foreach (db->priv->library_locations, (GFunc) g_free, NULL);
631 g_slist_free (db->priv->library_locations);
632 db->priv->library_locations = NULL;
634 /* abort all async vfs operations */
635 g_mutex_lock (db->priv->stat_mutex);
636 if (db->priv->stat_handle) {
637 gnome_vfs_async_cancel (db->priv->stat_handle);
638 db->priv->stat_handle = NULL;
640 g_list_foreach (db->priv->outstanding_stats, (GFunc)_shutdown_foreach_swapped, db);
641 g_list_free (db->priv->outstanding_stats);
642 db->priv->outstanding_stats = NULL;
643 g_mutex_unlock (db->priv->stat_mutex);
645 while ((action = g_async_queue_try_pop (db->priv->action_queue)) != NULL) {
646 rhythmdb_action_free (db, action);
649 rb_debug ("%d outstanding threads", g_atomic_int_get (&db->priv->outstanding_threads));
650 while (g_atomic_int_get (&db->priv->outstanding_threads) > 0) {
651 result = g_async_queue_pop (db->priv->event_queue);
652 rhythmdb_event_free (db, result);
655 /* FIXME */
656 while ((result = g_async_queue_try_pop (db->priv->event_queue)) != NULL)
657 rhythmdb_event_free (db, result);
660 static void
661 rhythmdb_finalize (GObject *object)
663 RhythmDB *db;
664 int i;
666 g_return_if_fail (object != NULL);
667 g_return_if_fail (RHYTHMDB_IS (object));
669 db = RHYTHMDB (object);
671 g_return_if_fail (db->priv != NULL);
673 rhythmdb_finalize_monitoring (db);
675 g_source_remove (db->priv->event_poll_id);
676 if (db->priv->save_timeout_id > 0)
677 g_source_remove (db->priv->save_timeout_id);
678 if (db->priv->emit_entry_signals_id > 0) {
679 g_source_remove (db->priv->emit_entry_signals_id);
680 g_list_foreach (db->priv->added_entries_to_emit, (GFunc)rhythmdb_entry_unref, NULL);
681 g_list_foreach (db->priv->deleted_entries_to_emit, (GFunc)rhythmdb_entry_unref, NULL);
684 g_thread_pool_free (db->priv->query_thread_pool, FALSE, TRUE);
685 g_thread_pool_free (db->priv->add_thread_pool, FALSE, TRUE);
686 g_async_queue_unref (db->priv->action_queue);
687 g_async_queue_unref (db->priv->event_queue);
688 g_async_queue_unref (db->priv->restored_queue);
690 g_mutex_free (db->priv->saving_mutex);
691 g_cond_free (db->priv->saving_condition);
693 g_hash_table_destroy (db->priv->stat_events);
694 g_mutex_free (db->priv->stat_mutex);
696 g_mutex_free (db->priv->change_mutex);
698 g_hash_table_destroy (db->priv->propname_map);
700 g_hash_table_destroy (db->priv->added_entries);
701 g_hash_table_destroy (db->priv->deleted_entries);
702 g_hash_table_destroy (db->priv->changed_entries);
704 rb_refstring_unref (db->priv->empty_string);
705 rb_refstring_unref (db->priv->octet_stream_str);
707 g_hash_table_destroy (db->priv->entry_type_map);
708 g_mutex_free (db->priv->entry_type_map_mutex);
709 g_mutex_free (db->priv->entry_type_mutex);
711 g_object_unref (db->priv->metadata);
713 for (i = 0; i < RHYTHMDB_NUM_PROPERTIES; i++) {
714 xmlFree (db->priv->column_xml_names[i]);
716 g_free (db->priv->column_xml_names);
718 g_free (db->priv->name);
720 G_OBJECT_CLASS (rhythmdb_parent_class)->finalize (object);
723 static void
724 rhythmdb_set_property (GObject *object,
725 guint prop_id,
726 const GValue *value,
727 GParamSpec *pspec)
729 RhythmDB *source = RHYTHMDB (object);
731 switch (prop_id) {
732 case PROP_NAME:
733 source->priv->name = g_strdup (g_value_get_string (value));
734 break;
735 case PROP_DRY_RUN:
736 source->priv->dry_run = g_value_get_boolean (value);
737 break;
738 case PROP_NO_UPDATE:
739 source->priv->no_update = g_value_get_boolean (value);
740 break;
741 default:
742 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
743 break;
747 static void
748 rhythmdb_get_property (GObject *object,
749 guint prop_id,
750 GValue *value,
751 GParamSpec *pspec)
753 RhythmDB *source = RHYTHMDB (object);
755 switch (prop_id) {
756 case PROP_NAME:
757 g_value_set_string (value, source->priv->name);
758 break;
759 case PROP_DRY_RUN:
760 g_value_set_boolean (value, source->priv->dry_run);
761 break;
762 case PROP_NO_UPDATE:
763 g_value_set_boolean (value, source->priv->no_update);
764 break;
765 default:
766 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
767 break;
771 static void
772 rhythmdb_thread_create (RhythmDB *db,
773 GThreadPool *pool,
774 GThreadFunc func,
775 gpointer data)
777 g_object_ref (db);
778 g_atomic_int_inc (&db->priv->outstanding_threads);
779 g_async_queue_ref (db->priv->action_queue);
780 g_async_queue_ref (db->priv->event_queue);
782 if (pool)
783 g_thread_pool_push (pool, data, NULL);
784 else
785 g_thread_create ((GThreadFunc) func, data, FALSE, NULL);
788 static gboolean
789 rhythmdb_get_readonly (RhythmDB *db)
791 return (g_atomic_int_get (&db->priv->read_counter) > 0);
794 static void
795 rhythmdb_read_enter (RhythmDB *db)
797 gint count;
798 g_return_if_fail (g_atomic_int_get (&db->priv->read_counter) >= 0);
799 g_assert (rb_is_main_thread ());
801 count = g_atomic_int_exchange_and_add (&db->priv->read_counter, 1);
802 rb_debug ("counter: %d", count+1);
803 if (count == 0)
804 g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY],
805 0, TRUE);
808 static void
809 rhythmdb_read_leave (RhythmDB *db)
811 gint count;
812 g_return_if_fail (rhythmdb_get_readonly (db));
813 g_assert (rb_is_main_thread ());
815 count = g_atomic_int_exchange_and_add (&db->priv->read_counter, -1);
816 rb_debug ("counter: %d", count-1);
817 if (count == 1)
818 g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY],
819 0, FALSE);
822 static gboolean
823 free_entry_changes (RhythmDBEntry *entry,
824 GSList *changes,
825 RhythmDB *db)
827 GSList *t;
828 for (t = changes; t; t = t->next) {
829 RhythmDBEntryChange *change = t->data;
830 g_value_unset (&change->old);
831 g_value_unset (&change->new);
832 g_free (change);
834 g_slist_free (changes);
836 return TRUE;
839 static void
840 emit_entry_changed (RhythmDBEntry *entry,
841 GSList *changes,
842 RhythmDB *db)
844 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_CHANGED], 0, entry, changes);
847 static void
848 sync_entry_changed (RhythmDBEntry *entry,
849 GSList *changes,
850 RhythmDB *db)
852 GSList *t;
854 for (t = changes; t; t = t->next) {
855 RBMetaDataField field;
856 RhythmDBEntryChange *change = t->data;
858 if (metadata_field_from_prop (change->prop, &field)) {
859 RhythmDBAction *action;
861 if (!rhythmdb_entry_is_editable (db, entry)) {
862 g_warning ("trying to sync properties of non-editable file");
863 break;
866 action = g_new0 (RhythmDBAction, 1);
867 action->type = RHYTHMDB_ACTION_SYNC;
868 action->uri = rb_refstring_ref (entry->location);
869 g_async_queue_push (db->priv->action_queue, action);
870 break;
875 static gboolean
876 rhythmdb_emit_entry_signals_idle (RhythmDB *db)
878 GList *added_entries;
879 GList *deleted_entries;
880 GList *l;
882 /* get lists of entries to emit, reset source id value */
883 g_mutex_lock (db->priv->change_mutex);
885 added_entries = db->priv->added_entries_to_emit;
886 db->priv->added_entries_to_emit = NULL;
888 deleted_entries = db->priv->deleted_entries_to_emit;
889 db->priv->deleted_entries_to_emit = NULL;
891 db->priv->emit_entry_signals_id = 0;
893 g_mutex_unlock (db->priv->change_mutex);
895 /* emit added entries */
896 for (l = added_entries; l; l = g_list_next (l)) {
897 RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
898 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_ADDED], 0, entry);
899 rhythmdb_entry_unref (entry);
902 /* emit deleted entries */
903 for (l = deleted_entries; l; l = g_list_next (l)) {
904 RhythmDBEntry *entry = (RhythmDBEntry *)l->data;
905 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry);
906 rhythmdb_entry_unref (entry);
909 g_list_free (added_entries);
910 g_list_free (deleted_entries);
911 return FALSE;
914 static gboolean
915 process_added_entries_cb (RhythmDBEntry *entry,
916 GThread *thread,
917 RhythmDB *db)
919 if (thread != g_thread_self ())
920 return FALSE;
922 if (entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
923 const gchar *uri;
925 uri = rhythmdb_entry_get_string (entry,
926 RHYTHMDB_PROP_LOCATION);
928 #ifdef HAVE_GSTREAMER_0_8
929 /* always start remote files hidden*/
930 if (!g_str_has_prefix (uri, "file://")) {
931 entry->flags |= RHYTHMDB_ENTRY_HIDDEN;
933 #endif
935 queue_stat_uri (uri, db, RHYTHMDB_ENTRY_TYPE_INVALID);
938 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) == 0);
939 entry->flags |= RHYTHMDB_ENTRY_INSERTED;
941 rhythmdb_entry_ref (entry);
942 db->priv->added_entries_to_emit = g_list_prepend (db->priv->added_entries_to_emit, entry);
944 return TRUE;
947 static gboolean
948 process_deleted_entries_cb (RhythmDBEntry *entry,
949 GThread *thread,
950 RhythmDB *db)
952 if (thread != g_thread_self ())
953 return FALSE;
955 rhythmdb_entry_ref (entry);
956 db->priv->deleted_entries_to_emit = g_list_prepend (db->priv->deleted_entries_to_emit, entry);
958 return TRUE;
961 static void
962 rhythmdb_commit_internal (RhythmDB *db,
963 gboolean sync_changes,
964 GThread *thread)
966 g_mutex_lock (db->priv->change_mutex);
968 g_hash_table_foreach (db->priv->changed_entries, (GHFunc) emit_entry_changed, db);
969 if (sync_changes)
970 g_hash_table_foreach (db->priv->changed_entries, (GHFunc) sync_entry_changed, db);
971 g_hash_table_foreach_remove (db->priv->changed_entries, (GHRFunc) free_entry_changes, db);
973 /* update the lists of entry added/deleted signals to emit */
974 g_hash_table_foreach_remove (db->priv->added_entries, (GHRFunc) process_added_entries_cb, db);
975 g_hash_table_foreach_remove (db->priv->deleted_entries, (GHRFunc) process_deleted_entries_cb, db);
977 /* if there are some signals to emit, add a new idle callback if required */
978 if (db->priv->added_entries_to_emit || db->priv->deleted_entries_to_emit) {
979 if (db->priv->emit_entry_signals_id == 0)
980 db->priv->emit_entry_signals_id = g_idle_add ((GSourceFunc) rhythmdb_emit_entry_signals_idle, db);
983 g_mutex_unlock (db->priv->change_mutex);
986 typedef struct {
987 RhythmDB *db;
988 gboolean sync;
989 GThread *thread;
990 } RhythmDBTimeoutCommitData;
992 static gboolean
993 timeout_rhythmdb_commit (RhythmDBTimeoutCommitData *data)
995 rhythmdb_commit_internal (data->db, data->sync, data->thread);
996 g_object_unref (data->db);
997 g_free (data);
998 return FALSE;
1001 static void
1002 rhythmdb_add_timeout_commit (RhythmDB *db,
1003 gboolean sync)
1005 RhythmDBTimeoutCommitData *data;
1007 g_assert (rb_is_main_thread ());
1009 data = g_new0 (RhythmDBTimeoutCommitData, 1);
1010 data->db = g_object_ref (db);
1011 data->sync = sync;
1012 data->thread = g_thread_self ();
1013 g_timeout_add (100, (GSourceFunc)timeout_rhythmdb_commit, data);
1017 * rhythmdb_commit:
1018 * @db: a #RhythmDB.
1020 * Apply all database changes, and send notification of changes and new entries.
1021 * This needs to be called after any changes have been made, such as a group of
1022 * rhythmdb_entry_set() calls, or a new entry has been added.
1024 void
1025 rhythmdb_commit (RhythmDB *db)
1027 rhythmdb_commit_internal (db, TRUE, g_thread_self ());
1030 GQuark
1031 rhythmdb_error_quark (void)
1033 static GQuark quark;
1034 if (!quark)
1035 quark = g_quark_from_static_string ("rhythmdb_error");
1037 return quark;
1040 /* structure alignment magic, stolen from glib */
1041 #define STRUCT_ALIGNMENT (2 * sizeof (gsize))
1042 #define ALIGN_STRUCT(offset) \
1043 ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
1046 * rhythmdb_entry_allocate:
1047 * @db: a #RhythmDB.
1048 * @type: type of entry to allocate
1050 * Allocate and initialise memory for a new #RhythmDBEntry of the type @type.
1051 * The entry's initial properties needs to be set with rhythmdb_entry_set (),
1052 * the entry added to the database with rhythmdb_entry_insert(), and committed with
1053 * rhythmdb_commit().
1055 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
1057 * Returns: the newly allocated #RhythmDBEntry
1059 RhythmDBEntry *
1060 rhythmdb_entry_allocate (RhythmDB *db,
1061 RhythmDBEntryType type)
1063 RhythmDBEntry *ret;
1064 gsize size = sizeof (RhythmDBEntry);
1066 if (type->entry_type_data_size) {
1067 size = ALIGN_STRUCT (sizeof (RhythmDBEntry)) + type->entry_type_data_size;
1069 ret = g_malloc0 (size);
1070 ret->id = (guint) g_atomic_int_exchange_and_add (&db->priv->next_entry_id, 1);
1072 ret->type = type;
1073 ret->title = rb_refstring_ref (db->priv->empty_string);
1074 ret->genre = rb_refstring_ref (db->priv->empty_string);
1075 ret->artist = rb_refstring_ref (db->priv->empty_string);
1076 ret->album = rb_refstring_ref (db->priv->empty_string);
1077 ret->musicbrainz_trackid = rb_refstring_ref (db->priv->empty_string);
1078 ret->mimetype = rb_refstring_ref (db->priv->octet_stream_str);
1080 ret->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY |
1081 RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY |
1082 RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
1084 /* The refcount is initially 0, we want to set it to 1 */
1085 ret->refcount = 1;
1087 if (type->post_entry_create)
1088 (type->post_entry_create)(ret, type->post_entry_create_data);
1090 return ret;
1094 * rhythmdb_entry_get_type_data:
1095 * @entry: a #RhythmDBEntry
1096 * @expected_size: expected size of the type-specific data.
1098 * Returns a pointer to the entry's type-specific data, checking that
1099 * the size of the data structure matches what is expected.
1100 * Callers should use the RHYTHMDB_ENTRY_GET_TYPE_DATA macro for
1101 * a slightly more friendly interface to this functionality.
1103 gpointer
1104 rhythmdb_entry_get_type_data (RhythmDBEntry *entry,
1105 guint expected_size)
1107 g_return_val_if_fail (entry != NULL, NULL);
1109 g_assert (expected_size == entry->type->entry_type_data_size);
1110 gsize offset = ALIGN_STRUCT (sizeof (RhythmDBEntry));
1112 return (gpointer) (((guint8 *)entry) + offset);
1116 * rhythmdb_entry_insert:
1117 * @db: a #RhythmDB.
1118 * @entry: the entry to insert.
1120 * Inserts a newly-created entry into the database.
1122 * Note that you must call rhythmdb_commit() at some point after invoking
1123 * this function.
1125 void
1126 rhythmdb_entry_insert (RhythmDB *db,
1127 RhythmDBEntry *entry)
1129 g_return_if_fail (RHYTHMDB_IS (db));
1130 g_return_if_fail (entry != NULL);
1132 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) == 0);
1133 g_return_if_fail (entry->location != NULL);
1135 /* ref the entry before adding to hash, it is unreffed when removed */
1136 rhythmdb_entry_ref (entry);
1137 g_mutex_lock (db->priv->change_mutex);
1138 g_hash_table_insert (db->priv->added_entries, entry, g_thread_self ());
1139 g_mutex_unlock (db->priv->change_mutex);
1143 * rhythmdb_entry_new:
1144 * @db: a #RhythmDB.
1145 * @type: type of entry to create
1146 * @uri: the location of the entry, this be unique amongst all entries.
1148 * Creates a new entry of type @type and location @uri, and inserts
1149 * it into the database. You must call rhythmdb_commit() at some point
1150 * after invoking this function.
1152 * This may return NULL if entry creation fails. This can occur if there is
1153 * already an entry with the given uri.
1155 * Returns: the newly created #RhythmDBEntry
1157 RhythmDBEntry *
1158 rhythmdb_entry_new (RhythmDB *db,
1159 RhythmDBEntryType type,
1160 const char *uri)
1162 RhythmDBEntry *ret;
1163 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
1165 ret = rhythmdb_entry_lookup_by_location (db, uri);
1166 if (ret) {
1167 g_warning ("attempting to create entry that already exists: %s", uri);
1168 return NULL;
1171 ret = rhythmdb_entry_allocate (db, type);
1172 ret->location = rb_refstring_new (uri);
1173 klass->impl_entry_new (db, ret);
1174 rb_debug ("emitting entry added");
1175 rhythmdb_entry_insert (db, ret);
1177 return ret;
1181 * rhythmdb_entry_example_new:
1182 * @db: a #RhythmDB.
1183 * @type: type of entry to create
1184 * @uri: the location of the entry, this be unique amongst all entries.
1186 * Creates a new sample entry of type @type and location @uri, it does not insert
1187 * it into the database. This is indended for use as a example entry.
1189 * This may return NULL if entry creation fails.
1191 * Returns: the newly created #RhythmDBEntry
1193 RhythmDBEntry *
1194 rhythmdb_entry_example_new (RhythmDB *db,
1195 RhythmDBEntryType type,
1196 const char *uri)
1198 RhythmDBEntry *ret;
1200 ret = rhythmdb_entry_allocate (db, type);
1201 if (uri)
1202 ret->location = rb_refstring_new (uri);
1204 if (type == RHYTHMDB_ENTRY_TYPE_SONG) {
1205 rb_refstring_unref (ret->artist);
1206 ret->artist = rb_refstring_new ("The Beatles");
1207 rb_refstring_unref (ret->album);
1208 ret->album = rb_refstring_new ("Help!");
1209 rb_refstring_unref (ret->title);
1210 ret->title = rb_refstring_new ("Ticket To Ride");
1211 ret->tracknum = 7;
1212 } else {
1215 return ret;
1219 * rhythmdb_entry_ref:
1220 * @db: a #RhythmDB.
1221 * @entry: a #RhythmDBEntry.
1223 * Increase the reference count of the entry.
1225 RhythmDBEntry *
1226 rhythmdb_entry_ref (RhythmDBEntry *entry)
1228 g_return_val_if_fail (entry != NULL, NULL);
1229 g_return_val_if_fail (entry->refcount > 0, NULL);
1231 g_atomic_int_inc (&entry->refcount);
1233 return entry;
1236 static void
1237 rhythmdb_entry_finalize (RhythmDBEntry *entry)
1239 RhythmDBEntryType type;
1241 type = rhythmdb_entry_get_entry_type (entry);
1243 if (type->pre_entry_destroy)
1244 (type->pre_entry_destroy)(entry, type->pre_entry_destroy_data);
1246 rb_refstring_unref (entry->location);
1247 rb_refstring_unref (entry->playback_error);
1248 rb_refstring_unref (entry->title);
1249 rb_refstring_unref (entry->genre);
1250 rb_refstring_unref (entry->artist);
1251 rb_refstring_unref (entry->album);
1252 rb_refstring_unref (entry->musicbrainz_trackid);
1253 rb_refstring_unref (entry->mimetype);
1255 g_free (entry);
1259 * rhythmdb_entry_unref:
1260 * @db: a #RhythmDB.
1261 * @entry: a #RhythmDBEntry.
1263 * Decrease the reference count of the entry, and destroy it if there are
1264 * no references left.
1266 void
1267 rhythmdb_entry_unref (RhythmDBEntry *entry)
1269 gboolean is_zero;
1271 g_return_if_fail (entry != NULL);
1272 g_return_if_fail (entry->refcount > 0);
1274 is_zero = g_atomic_int_dec_and_test (&entry->refcount);
1275 if (G_UNLIKELY (is_zero)) {
1276 rhythmdb_entry_finalize (entry);
1281 * rhythmdb_entry_is_editable:
1282 * @db: a #RhythmDB.
1283 * @entry: a #RhythmDBEntry.
1285 * This determines whether any changes to the entries metadata can be saved.
1286 * Usually this is only true for entries backed by files, where tag-writing is
1287 * enabled, and the appropriate tag-writing facilities are available.
1289 * Returns: whether the entries metadata can be changed.
1292 gboolean
1293 rhythmdb_entry_is_editable (RhythmDB *db,
1294 RhythmDBEntry *entry)
1296 RhythmDBEntryType entry_type;
1298 g_return_val_if_fail (RHYTHMDB_IS (db), FALSE);
1299 g_return_val_if_fail (entry != NULL, FALSE);
1301 entry_type = rhythmdb_entry_get_entry_type (entry);
1302 return entry_type->can_sync_metadata (db, entry, entry_type->can_sync_metadata_data);
1305 static void
1306 set_metadata_string_default_unknown (RhythmDB *db,
1307 RBMetaData *metadata,
1308 RhythmDBEntry *entry,
1309 RBMetaDataField field,
1310 RhythmDBPropType prop)
1312 const char *unknown = _("Unknown");
1313 GValue val = {0, };
1315 if (!(rb_metadata_get (metadata,
1316 field,
1317 &val))) {
1318 g_value_init (&val, G_TYPE_STRING);
1319 g_value_set_static_string (&val, unknown);
1320 } else {
1321 const gchar *str = g_value_get_string (&val);
1322 if (str == NULL || str[0] == '\0')
1323 g_value_set_static_string (&val, unknown);
1325 rhythmdb_entry_set_internal (db, entry, TRUE, prop, &val);
1326 g_value_unset (&val);
1329 static void
1330 set_props_from_metadata (RhythmDB *db,
1331 RhythmDBEntry *entry,
1332 GnomeVFSFileInfo *vfsinfo,
1333 RBMetaData *metadata)
1335 const char *mime;
1336 GValue val = {0,};
1338 g_value_init (&val, G_TYPE_STRING);
1339 mime = rb_metadata_get_mime (metadata);
1340 if (mime) {
1341 g_value_set_string (&val, mime);
1342 rhythmdb_entry_set_internal (db, entry, TRUE,
1343 RHYTHMDB_PROP_MIMETYPE, &val);
1345 g_value_unset (&val);
1347 /* track number */
1348 if (!rb_metadata_get (metadata,
1349 RB_METADATA_FIELD_TRACK_NUMBER,
1350 &val)) {
1351 g_value_init (&val, G_TYPE_ULONG);
1352 g_value_set_ulong (&val, 0);
1354 rhythmdb_entry_set_internal (db, entry, TRUE,
1355 RHYTHMDB_PROP_TRACK_NUMBER, &val);
1356 g_value_unset (&val);
1358 /* disc number */
1359 if (!rb_metadata_get (metadata,
1360 RB_METADATA_FIELD_DISC_NUMBER,
1361 &val)) {
1362 g_value_init (&val, G_TYPE_ULONG);
1363 g_value_set_ulong (&val, 0);
1365 rhythmdb_entry_set_internal (db, entry, TRUE,
1366 RHYTHMDB_PROP_DISC_NUMBER, &val);
1367 g_value_unset (&val);
1369 /* duration */
1370 if (rb_metadata_get (metadata,
1371 RB_METADATA_FIELD_DURATION,
1372 &val)) {
1373 rhythmdb_entry_set_internal (db, entry, TRUE,
1374 RHYTHMDB_PROP_DURATION, &val);
1375 g_value_unset (&val);
1378 /* bitrate */
1379 if (rb_metadata_get (metadata,
1380 RB_METADATA_FIELD_BITRATE,
1381 &val)) {
1382 rhythmdb_entry_set_internal (db, entry, TRUE,
1383 RHYTHMDB_PROP_BITRATE, &val);
1384 g_value_unset (&val);
1387 /* date */
1388 if (rb_metadata_get (metadata,
1389 RB_METADATA_FIELD_DATE,
1390 &val)) {
1391 rhythmdb_entry_set_internal (db, entry, TRUE,
1392 RHYTHMDB_PROP_DATE, &val);
1393 g_value_unset (&val);
1396 /* musicbrainz trackid */
1397 if (rb_metadata_get (metadata,
1398 RB_METADATA_FIELD_MUSICBRAINZ_TRACKID,
1399 &val)) {
1400 rhythmdb_entry_set_internal (db, entry, TRUE,
1401 RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, &val);
1402 g_value_unset (&val);
1405 /* filesize */
1406 g_value_init (&val, G_TYPE_UINT64);
1407 g_value_set_uint64 (&val, vfsinfo->size);
1408 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_FILE_SIZE, &val);
1409 g_value_unset (&val);
1411 /* title */
1412 if (!rb_metadata_get (metadata,
1413 RB_METADATA_FIELD_TITLE,
1414 &val) || g_value_get_string (&val)[0] == '\0') {
1415 char *utf8name;
1416 utf8name = g_filename_to_utf8 (vfsinfo->name, -1, NULL, NULL, NULL);
1417 if (!utf8name) {
1418 utf8name = g_strdup (_("<invalid filename>"));
1420 if (G_VALUE_HOLDS_STRING (&val))
1421 g_value_reset (&val);
1422 else
1423 g_value_init (&val, G_TYPE_STRING);
1424 g_value_set_string (&val, utf8name);
1425 g_free (utf8name);
1427 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_TITLE, &val);
1428 g_value_unset (&val);
1430 /* genre */
1431 set_metadata_string_default_unknown (db, metadata, entry,
1432 RB_METADATA_FIELD_GENRE,
1433 RHYTHMDB_PROP_GENRE);
1435 /* artist */
1436 set_metadata_string_default_unknown (db, metadata, entry,
1437 RB_METADATA_FIELD_ARTIST,
1438 RHYTHMDB_PROP_ARTIST);
1439 /* album */
1440 set_metadata_string_default_unknown (db, metadata, entry,
1441 RB_METADATA_FIELD_ALBUM,
1442 RHYTHMDB_PROP_ALBUM);
1444 /* replaygain track gain */
1445 if (rb_metadata_get (metadata,
1446 RB_METADATA_FIELD_TRACK_GAIN,
1447 &val)) {
1448 rhythmdb_entry_set_internal (db, entry, TRUE,
1449 RHYTHMDB_PROP_TRACK_GAIN, &val);
1450 g_value_unset (&val);
1453 /* replaygain track peak */
1454 if (rb_metadata_get (metadata,
1455 RB_METADATA_FIELD_TRACK_PEAK,
1456 &val)) {
1457 rhythmdb_entry_set_internal (db, entry, TRUE,
1458 RHYTHMDB_PROP_TRACK_PEAK, &val);
1459 g_value_unset (&val);
1462 /* replaygain album gain */
1463 if (rb_metadata_get (metadata,
1464 RB_METADATA_FIELD_ALBUM_GAIN,
1465 &val)) {
1466 rhythmdb_entry_set_internal (db, entry, TRUE,
1467 RHYTHMDB_PROP_ALBUM_GAIN, &val);
1468 g_value_unset (&val);
1471 /* replaygain album peak */
1472 if (rb_metadata_get (metadata,
1473 RB_METADATA_FIELD_ALBUM_PEAK,
1474 &val)) {
1475 rhythmdb_entry_set_internal (db, entry, TRUE,
1476 RHYTHMDB_PROP_ALBUM_PEAK, &val);
1477 g_value_unset (&val);
1481 static gboolean
1482 is_ghost_entry (RhythmDBEntry *entry)
1484 GTimeVal time;
1485 gulong last_seen;
1486 gulong grace_period;
1487 GError *error;
1488 GConfClient *client;
1490 client = gconf_client_get_default ();
1491 if (client == NULL) {
1492 return FALSE;
1494 error = NULL;
1495 grace_period = gconf_client_get_int (client, CONF_GRACE_PERIOD,
1496 &error);
1497 g_object_unref (G_OBJECT (client));
1498 if (error != NULL) {
1499 g_error_free (error);
1500 return FALSE;
1503 /* This is a bit silly, but I prefer to make sure we won't
1504 * overflow in the following calculations
1506 if ((grace_period < 0) || (grace_period > 20000)) {
1507 return FALSE;
1510 /* Convert from days to seconds */
1511 grace_period = grace_period * 60 * 60 * 24;
1512 g_get_current_time (&time);
1513 last_seen = rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_LAST_SEEN);
1515 return (last_seen + grace_period < time.tv_sec);
1518 static void
1519 rhythmdb_process_stat_event (RhythmDB *db,
1520 RhythmDBEvent *event)
1522 RhythmDBEntry *entry;
1523 RhythmDBAction *action;
1525 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
1526 if (entry) {
1527 time_t mtime = (time_t) entry->mtime;
1529 if ((event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) && (entry->type != event->entry_type))
1530 g_warning ("attempt to use same location in multiple entry types");
1532 if (entry->type == RHYTHMDB_ENTRY_TYPE_IGNORE)
1533 rb_debug ("ignoring %p", entry);
1535 if (event->error) {
1536 if (!is_ghost_entry (entry)) {
1537 rhythmdb_entry_set_visibility (db, entry, FALSE);
1538 } else {
1539 rb_debug ("error accessing %s: %s", rb_refstring_get (event->real_uri),
1540 event->error->message);
1541 rhythmdb_entry_delete (db, entry);
1543 } else {
1544 GValue val = {0, };
1545 GTimeVal time;
1546 const char *mount_point;
1548 rhythmdb_entry_set_visibility (db, entry, TRUE);
1550 /* Update mount point if necessary (main reason is
1551 * that we want to set the mount point in legacy
1552 * rhythmdb that doesn't have it already
1554 mount_point = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
1555 if (mount_point == NULL) {
1556 rhythmdb_entry_set_mount_point (db, entry,
1557 rb_refstring_get (event->real_uri));
1560 /* Update last seen time. It will also be updated
1561 * upon saving and when a volume is unmounted
1563 g_get_current_time (&time);
1564 g_value_init (&val, G_TYPE_ULONG);
1565 g_value_set_ulong (&val, time.tv_sec);
1566 rhythmdb_entry_set_internal (db, entry, TRUE,
1567 RHYTHMDB_PROP_LAST_SEEN,
1568 &val);
1569 /* Old rhythmdb.xml files won't have a value for
1570 * FIRST_SEEN, so set it here
1572 if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_FIRST_SEEN) == 0) {
1573 rhythmdb_entry_set_internal (db, entry, TRUE,
1574 RHYTHMDB_PROP_FIRST_SEEN,
1575 &val);
1577 g_value_unset (&val);
1579 if (mtime == event->vfsinfo->mtime) {
1580 rb_debug ("not modified: %s", rb_refstring_get (event->real_uri));
1581 /* monitor the file for changes */
1582 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY))
1583 rhythmdb_monitor_uri_path (db, rb_refstring_get (entry->location), NULL /* FIXME */);
1584 } else {
1585 RhythmDBEvent *new_event;
1587 rb_debug ("changed: %s", rb_refstring_get (event->real_uri));
1588 new_event = g_new0 (RhythmDBEvent, 1);
1589 new_event->db = db;
1590 new_event->uri = rb_refstring_ref (event->real_uri);
1591 new_event->type = RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED;
1592 g_async_queue_push (db->priv->event_queue,
1593 new_event);
1597 rhythmdb_commit (db);
1598 } else {
1599 action = g_new0 (RhythmDBAction, 1);
1600 action->type = RHYTHMDB_ACTION_LOAD;
1601 action->uri = rb_refstring_ref (event->real_uri);
1602 action->entry_type = event->entry_type;
1603 rb_debug ("queuing a RHYTHMDB_ACTION_LOAD: %s", rb_refstring_get (action->uri));
1604 g_async_queue_push (db->priv->action_queue, action);
1608 typedef struct
1610 RhythmDB *db;
1611 char *uri;
1612 char *msg;
1613 } RhythmDBLoadErrorData;
1615 static void
1616 rhythmdb_add_import_error_entry (RhythmDB *db,
1617 RhythmDBEvent *event)
1619 RhythmDBEntry *entry;
1620 GValue value = {0,};
1621 RhythmDBEntryType error_entry_type = RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR;
1623 if (g_error_matches (event->error, RB_METADATA_ERROR, RB_METADATA_ERROR_NOT_AUDIO_IGNORE)) {
1624 /* only add an ignore entry for the main library */
1625 if (event->entry_type != RHYTHMDB_ENTRY_TYPE_SONG &&
1626 event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID)
1627 return;
1629 error_entry_type = RHYTHMDB_ENTRY_TYPE_IGNORE;
1632 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
1633 if (entry) {
1634 RhythmDBEntryType entry_type = rhythmdb_entry_get_entry_type (entry);
1635 if (entry_type != RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR &&
1636 entry_type != RHYTHMDB_ENTRY_TYPE_IGNORE) {
1637 /* FIXME we've successfully read this file before.. so what should we do? */
1638 rb_debug ("%s already exists in the library.. ignoring import error?", rb_refstring_get (event->real_uri));
1639 return;
1642 if (entry_type != error_entry_type) {
1643 /* delete the existing entry, then create a new one below */
1644 rhythmdb_entry_delete (db, entry);
1645 entry = NULL;
1646 } else if (error_entry_type == RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR) {
1647 /* we've already got an error for this file, so just update it */
1648 g_value_init (&value, G_TYPE_STRING);
1649 g_value_set_string (&value, event->error->message);
1650 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
1651 g_value_unset (&value);
1652 } else {
1653 /* no need to update the ignored file entry */
1656 if (entry && event->vfsinfo) {
1657 /* mtime */
1658 g_value_init (&value, G_TYPE_ULONG);
1659 g_value_set_ulong (&value, event->vfsinfo->mtime);
1660 rhythmdb_entry_set(db, entry, RHYTHMDB_PROP_MTIME, &value);
1661 g_value_unset (&value);
1664 rhythmdb_add_timeout_commit (db, FALSE);
1667 if (entry == NULL) {
1668 /* create a new import error or ignore entry */
1669 entry = rhythmdb_entry_new (db, error_entry_type, rb_refstring_get (event->real_uri));
1670 if (entry == NULL)
1671 return;
1673 if (error_entry_type == RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR && event->error->message) {
1674 g_value_init (&value, G_TYPE_STRING);
1675 if (g_utf8_validate (event->error->message, -1, NULL))
1676 g_value_set_string (&value, event->error->message);
1677 else
1678 g_value_set_static_string (&value, _("invalid unicode in error message"));
1679 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
1680 g_value_unset (&value);
1683 /* mtime */
1684 if (event->vfsinfo) {
1685 g_value_init (&value, G_TYPE_ULONG);
1686 g_value_set_ulong (&value, event->vfsinfo->mtime);
1687 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_MTIME, &value);
1688 g_value_unset (&value);
1691 /* record the mount point so we can delete entries for unmounted volumes */
1692 rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri));
1694 rhythmdb_entry_set_visibility (db, entry, TRUE);
1696 rhythmdb_add_timeout_commit (db, FALSE);
1700 static gboolean
1701 rhythmdb_process_metadata_load (RhythmDB *db,
1702 RhythmDBEvent *event)
1704 RhythmDBEntry *entry;
1705 GValue value = {0,};
1706 const char *mime;
1707 GTimeVal time;
1709 if (rhythmdb_get_readonly (db)) {
1710 rb_debug ("database is read-only right now, re-queuing event");
1711 g_async_queue_push (db->priv->event_queue, event);
1712 return FALSE;
1715 if (event->error) {
1716 rhythmdb_add_import_error_entry (db, event);
1717 return TRUE;
1720 /* do we really need to do this? */
1721 mime = rb_metadata_get_mime (event->metadata);
1722 if (!mime) {
1723 rb_debug ("unsupported file");
1724 return TRUE;
1727 g_get_current_time (&time);
1729 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
1731 if (entry != NULL) {
1732 if ((event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) &&
1733 (rhythmdb_entry_get_entry_type (entry) != event->entry_type)) {
1734 /* switching from IGNORE to SONG or vice versa, recreate the entry */
1735 rhythmdb_entry_delete (db, entry);
1736 rhythmdb_add_timeout_commit (db, FALSE);
1737 entry = NULL;
1741 if (entry == NULL) {
1742 if (event->entry_type == RHYTHMDB_ENTRY_TYPE_INVALID)
1743 event->entry_type = RHYTHMDB_ENTRY_TYPE_SONG;
1745 entry = rhythmdb_entry_new (db, event->entry_type, rb_refstring_get (event->real_uri));
1746 if (entry == NULL) {
1747 rb_debug ("entry already exists");
1748 return TRUE;
1751 /* initialize the last played date to 0=never */
1752 g_value_init (&value, G_TYPE_ULONG);
1753 g_value_set_ulong (&value, 0);
1754 rhythmdb_entry_set (db, entry,
1755 RHYTHMDB_PROP_LAST_PLAYED, &value);
1756 g_value_unset (&value);
1758 /* initialize the rating */
1759 g_value_init (&value, G_TYPE_DOUBLE);
1760 g_value_set_double (&value, 0);
1761 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &value);
1762 g_value_unset (&value);
1764 /* first seen */
1765 g_value_init (&value, G_TYPE_ULONG);
1766 g_value_set_ulong (&value, time.tv_sec);
1767 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &value);
1768 g_value_unset (&value);
1771 if ((event->entry_type != RHYTHMDB_ENTRY_TYPE_INVALID) && (entry->type != event->entry_type))
1772 g_warning ("attempt to use same location in multiple entry types");
1774 /* mtime */
1775 if (event->vfsinfo) {
1776 g_value_init (&value, G_TYPE_ULONG);
1777 g_value_set_ulong (&value, event->vfsinfo->mtime);
1778 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_MTIME, &value);
1779 g_value_unset (&value);
1782 if (event->entry_type != RHYTHMDB_ENTRY_TYPE_IGNORE &&
1783 event->entry_type != RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR) {
1784 set_props_from_metadata (db, entry, event->vfsinfo, event->metadata);
1787 /* we've seen this entry */
1788 rhythmdb_entry_set_visibility (db, entry, TRUE);
1790 g_value_init (&value, G_TYPE_ULONG);
1791 g_value_set_ulong (&value, time.tv_sec);
1792 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_LAST_SEEN, &value);
1793 g_value_unset (&value);
1795 /* Remember the mount point of the volume the song is on */
1796 rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri));
1798 /* monitor the file for changes */
1799 /* FIXME: watch for errors */
1800 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY) && event->entry_type == RHYTHMDB_ENTRY_TYPE_SONG)
1801 rhythmdb_monitor_uri_path (db, rb_refstring_get (entry->location), NULL);
1803 rhythmdb_add_timeout_commit (db, FALSE);
1805 return TRUE;
1808 static void
1809 rhythmdb_process_queued_entry_set_event (RhythmDB *db,
1810 RhythmDBEvent *event)
1812 rhythmdb_entry_set_internal (db, event->entry,
1813 event->signal_change,
1814 event->change.prop,
1815 &event->change.new);
1816 /* Don't run rhythmdb_commit right now in case there
1817 * we can run a single commit for several queued
1818 * entry_set
1820 rhythmdb_add_timeout_commit (db, TRUE);
1823 static void
1824 rhythmdb_process_file_created_or_modified (RhythmDB *db,
1825 RhythmDBEvent *event)
1827 RhythmDBAction *action;
1829 action = g_new0 (RhythmDBAction, 1);
1830 action->type = RHYTHMDB_ACTION_LOAD;
1831 action->uri = rb_refstring_ref (event->uri);
1832 action->entry_type = RHYTHMDB_ENTRY_TYPE_INVALID;
1833 g_async_queue_push (db->priv->action_queue, action);
1836 static void
1837 rhythmdb_process_file_deleted (RhythmDB *db,
1838 RhythmDBEvent *event)
1840 RhythmDBEntry *entry = rhythmdb_entry_lookup_by_location_refstring (db, event->uri);
1842 g_hash_table_remove (db->priv->changed_files, event->uri);
1844 if (entry) {
1845 rb_debug ("deleting entry for %s", rb_refstring_get (event->uri));
1846 rhythmdb_entry_set_visibility (db, entry, FALSE);
1847 rhythmdb_commit (db);
1851 static gboolean
1852 rhythmdb_process_events (RhythmDB *db,
1853 GTimeVal *timeout)
1855 RhythmDBEvent *event;
1856 guint count = 0;
1858 while ((event = g_async_queue_try_pop (db->priv->event_queue)) != NULL) {
1859 gboolean free = TRUE;
1861 /* if the database is read-only, we can't process those events
1862 * since they call rhythmdb_entry_set. Doing it this way
1863 * is safe if we assume all calls to read_enter/read_leave
1864 * are done from the main thread (the thread this function
1865 * runs in).
1867 if (rhythmdb_get_readonly (db) &&
1868 ((event->type == RHYTHMDB_EVENT_STAT)
1869 || (event->type == RHYTHMDB_EVENT_METADATA_LOAD)
1870 || (event->type == RHYTHMDB_EVENT_ENTRY_SET))) {
1871 if (count >= g_async_queue_length (db->priv->event_queue)) {
1872 rb_debug ("Database is read-only, and we can't process any more events");
1873 /* give the running query some time to complete */
1874 return FALSE;
1876 rb_debug ("Database is read-only, delaying event processing\n");
1877 g_async_queue_push (db->priv->event_queue, event);
1878 goto next_event;
1881 switch (event->type) {
1882 case RHYTHMDB_EVENT_STAT:
1883 rb_debug ("processing RHYTHMDB_EVENT_STAT");
1884 rhythmdb_process_stat_event (db, event);
1885 break;
1886 case RHYTHMDB_EVENT_METADATA_LOAD:
1887 rb_debug ("processing RHYTHMDB_EVENT_METADATA_LOAD");
1888 free = rhythmdb_process_metadata_load (db, event);
1889 break;
1890 case RHYTHMDB_EVENT_ENTRY_SET:
1891 rb_debug ("processing RHYTHMDB_EVENT_ENTRY_SET");
1892 rhythmdb_process_queued_entry_set_event (db, event);
1893 break;
1894 case RHYTHMDB_EVENT_DB_LOAD:
1895 rb_debug ("processing RHYTHMDB_EVENT_DB_LOAD");
1896 g_signal_emit (G_OBJECT (db), rhythmdb_signals[LOAD_COMPLETE], 0);
1898 /* save the db every five minutes */
1899 if (db->priv->save_timeout_id > 0) {
1900 g_source_remove (db->priv->save_timeout_id);
1902 db->priv->save_timeout_id = g_timeout_add_full (G_PRIORITY_LOW,
1903 5 * 60 * 1000,
1904 (GSourceFunc) rhythmdb_idle_save,
1906 NULL);
1907 break;
1908 case RHYTHMDB_EVENT_THREAD_EXITED:
1909 rb_debug ("processing RHYTHMDB_EVENT_THREAD_EXITED");
1910 break;
1911 case RHYTHMDB_EVENT_DB_SAVED:
1912 rb_debug ("processing RHYTHMDB_EVENT_DB_SAVED");
1913 rhythmdb_read_leave (db);
1914 break;
1915 case RHYTHMDB_EVENT_QUERY_COMPLETE:
1916 rb_debug ("processing RHYTHMDB_EVENT_QUERY_COMPLETE");
1917 rhythmdb_read_leave (db);
1918 break;
1919 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED:
1920 rb_debug ("processing RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED");
1921 rhythmdb_process_file_created_or_modified (db, event);
1922 break;
1923 case RHYTHMDB_EVENT_FILE_DELETED:
1924 rb_debug ("processing RHYTHMDB_EVENT_FILE_DELETED");
1925 rhythmdb_process_file_deleted (db, event);
1926 break;
1928 if (free)
1929 rhythmdb_event_free (db, event);
1931 count++;
1932 next_event:
1933 if (timeout && (count % 8 == 0)) {
1934 GTimeVal now;
1935 g_get_current_time (&now);
1936 if (rb_compare_gtimeval (timeout,&now) < 0) {
1937 /* probably more work to do, so try to come back as soon as possible */
1938 return TRUE;
1943 /* queue is empty, so we can wait a while before checking it again */
1944 return FALSE;
1947 static gboolean
1948 rhythmdb_idle_poll_events (RhythmDB *db)
1950 gboolean poll_soon;
1951 GTimeVal timeout;
1953 g_get_current_time (&timeout);
1954 g_time_val_add (&timeout, G_USEC_PER_SEC*0.75);
1956 GDK_THREADS_ENTER ();
1958 poll_soon = rhythmdb_process_events (db, &timeout);
1960 if (poll_soon)
1961 db->priv->event_poll_id =
1962 g_idle_add_full (G_PRIORITY_LOW, (GSourceFunc) rhythmdb_idle_poll_events,
1963 db, NULL);
1964 else
1965 db->priv->event_poll_id =
1966 g_timeout_add (1000, (GSourceFunc) rhythmdb_idle_poll_events, db);
1968 GDK_THREADS_LEAVE ();
1970 return FALSE;
1973 #define READ_QUEUE_TIMEOUT G_USEC_PER_SEC / 10
1975 static gpointer
1976 read_queue (GAsyncQueue *queue, gboolean *cancel)
1978 GTimeVal timeout;
1979 gpointer ret;
1981 g_get_current_time (&timeout);
1982 g_time_val_add (&timeout, READ_QUEUE_TIMEOUT);
1984 if (G_UNLIKELY (*cancel))
1985 return NULL;
1986 while ((ret = g_async_queue_timed_pop (queue, &timeout)) == NULL) {
1987 if (G_UNLIKELY (*cancel))
1988 return NULL;
1989 g_get_current_time (&timeout);
1990 g_time_val_add (&timeout, G_USEC_PER_SEC);
1993 return ret;
1996 static void
1997 rhythmdb_execute_stat_info_cb (GnomeVFSAsyncHandle *handle,
1998 GList *results,
1999 /* GnomeVFSGetFileInfoResult* items */
2000 RhythmDBEvent *event)
2002 /* this is in the main thread, so we can't do any long operation here */
2003 GnomeVFSGetFileInfoResult *info_result = results->data;
2005 g_mutex_lock (event->db->priv->stat_mutex);
2006 event->db->priv->outstanding_stats = g_list_remove (event->db->priv->outstanding_stats, event);
2007 event->handle = NULL;
2008 g_mutex_unlock (event->db->priv->stat_mutex);
2010 if (info_result->result == GNOME_VFS_OK) {
2011 event->vfsinfo = gnome_vfs_file_info_dup (info_result->file_info);
2012 } else {
2013 event->error = make_access_failed_error (rb_refstring_get (event->real_uri),
2014 info_result->result);
2015 event->vfsinfo = NULL;
2017 g_async_queue_push (event->db->priv->event_queue, event);
2020 static void
2021 rhythmdb_execute_stat (RhythmDB *db,
2022 const char *uri,
2023 RhythmDBEvent *event)
2025 GnomeVFSURI *vfs_uri = gnome_vfs_uri_new (uri);
2027 GList *uri_list = g_list_append (NULL, vfs_uri);
2028 event->real_uri = rb_refstring_new (uri);
2030 g_mutex_lock (db->priv->stat_mutex);
2031 db->priv->outstanding_stats = g_list_prepend (db->priv->outstanding_stats, event);
2032 g_mutex_unlock (db->priv->stat_mutex);
2034 gnome_vfs_async_get_file_info (&event->handle, uri_list,
2035 GNOME_VFS_FILE_INFO_FOLLOW_LINKS,
2036 GNOME_VFS_PRIORITY_MIN,
2037 (GnomeVFSAsyncGetFileInfoCallback) rhythmdb_execute_stat_info_cb,
2038 event);
2039 gnome_vfs_uri_unref (vfs_uri);
2040 g_list_free (uri_list);
2043 void
2044 queue_stat_uri (const char *uri,
2045 RhythmDB *db,
2046 RhythmDBEntryType type)
2048 RhythmDBEvent *result;
2050 rb_debug ("queueing stat for \"%s\"", uri);
2051 g_assert (uri && *uri);
2053 result = g_new0 (RhythmDBEvent, 1);
2054 result->db = db;
2055 result->type = RHYTHMDB_EVENT_STAT;
2056 result->entry_type = type;
2059 * before the action thread is started, we queue up stat events,
2060 * as we're still creating and running queries, as well as loading
2061 * the database. when we start the action thread, we'll kick off
2062 * a gnome-vfs job to run all the stat events too.
2064 * when the action thread is already running, we can start the
2065 * async_get_file_info job directly.
2067 g_mutex_lock (db->priv->stat_mutex);
2068 if (db->priv->action_thread_running) {
2069 g_mutex_unlock (db->priv->stat_mutex);
2070 rhythmdb_execute_stat (db, uri, result);
2071 } else {
2072 GnomeVFSURI *vfs_uri;
2074 vfs_uri = gnome_vfs_uri_new (uri);
2076 /* construct a list of URIs and a hash table containing
2077 * stat events to fill in and post on the event queue.
2079 if (g_hash_table_lookup (db->priv->stat_events, vfs_uri)) {
2080 g_free (result);
2081 gnome_vfs_uri_unref (vfs_uri);
2082 } else {
2083 result->real_uri = rb_refstring_new (uri);
2084 g_hash_table_insert (db->priv->stat_events, vfs_uri, result);
2085 db->priv->stat_list = g_list_prepend (db->priv->stat_list, vfs_uri);
2088 g_mutex_unlock (db->priv->stat_mutex);
2092 static void
2093 queue_stat_uri_tad (const char *uri,
2094 RhythmDBAddThreadData *data)
2096 queue_stat_uri (uri, data->db, data->type);
2099 static gpointer
2100 add_thread_main (RhythmDBAddThreadData *data)
2102 RhythmDBEvent *result;
2104 rb_uri_handle_recursively (data->uri, (GFunc) queue_stat_uri_tad,
2105 &data->db->priv->exiting, data);
2107 rb_debug ("exiting");
2108 result = g_new0 (RhythmDBEvent, 1);
2109 result->db = data->db;
2110 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2111 g_async_queue_push (data->db->priv->event_queue, result);
2112 g_free (data->uri);
2113 g_free (data);
2114 return NULL;
2117 static void
2118 rhythmdb_execute_load (RhythmDB *db,
2119 const char *uri,
2120 RhythmDBEvent *event)
2122 GnomeVFSURI *vfs_uri = gnome_vfs_uri_new (uri);
2123 GnomeVFSResult vfsresult;
2125 event->real_uri = rb_refstring_new (rb_uri_resolve_symlink (uri));
2126 event->vfsinfo = gnome_vfs_file_info_new ();
2128 vfsresult = gnome_vfs_get_file_info (uri,
2129 event->vfsinfo,
2130 GNOME_VFS_FILE_INFO_FOLLOW_LINKS);
2131 if (vfsresult != GNOME_VFS_OK) {
2132 event->error = make_access_failed_error (uri, vfsresult);
2133 if (event->vfsinfo)
2134 gnome_vfs_file_info_unref (event->vfsinfo);
2135 event->vfsinfo = NULL;
2136 } else {
2137 if (event->type == RHYTHMDB_EVENT_METADATA_LOAD) {
2138 event->metadata = rb_metadata_new ();
2139 rb_metadata_load (event->metadata, rb_refstring_get (event->real_uri),
2140 &event->error);
2144 gnome_vfs_uri_unref (vfs_uri);
2145 g_async_queue_push (db->priv->event_queue, event);
2149 * rhythmdb_entry_get:
2150 * @entry: a #RhythmDBEntry.
2151 * @propid: the id of the property to get.
2152 * @val: return location for the property value.
2154 * Gets a property of an entry, storing it in the given #GValue.
2156 void
2157 rhythmdb_entry_get (RhythmDB *db,
2158 RhythmDBEntry *entry,
2159 RhythmDBPropType propid,
2160 GValue *val)
2162 g_return_if_fail (RHYTHMDB_IS (db));
2163 g_return_if_fail (entry != NULL);
2164 g_return_if_fail (entry->refcount > 0);
2166 rhythmdb_entry_sync_mirrored (entry, propid);
2168 g_assert (G_VALUE_TYPE (val) == rhythmdb_get_property_type (db, propid));
2169 switch (rhythmdb_property_type_map[propid]) {
2170 case G_TYPE_STRING:
2171 g_value_set_string (val, rhythmdb_entry_get_string (entry, propid));
2172 break;
2173 case G_TYPE_BOOLEAN:
2174 g_value_set_boolean (val, rhythmdb_entry_get_boolean (entry, propid));
2175 break;
2176 case G_TYPE_ULONG:
2177 g_value_set_ulong (val, rhythmdb_entry_get_ulong (entry, propid));
2178 break;
2179 case G_TYPE_UINT64:
2180 g_value_set_uint64 (val, rhythmdb_entry_get_uint64 (entry, propid));
2181 break;
2182 case G_TYPE_DOUBLE:
2183 g_value_set_double (val, rhythmdb_entry_get_double (entry, propid));
2184 break;
2185 case G_TYPE_POINTER:
2186 g_value_set_pointer (val, rhythmdb_entry_get_pointer (entry, propid));
2187 break;
2188 default:
2189 g_assert_not_reached ();
2190 break;
2194 static void
2195 entry_to_rb_metadata (RhythmDB *db,
2196 RhythmDBEntry *entry,
2197 RBMetaData *metadata)
2199 GValue val = {0, };
2200 int i;
2202 for (i = RHYTHMDB_PROP_TYPE; i != RHYTHMDB_NUM_PROPERTIES; i++) {
2203 RBMetaDataField field;
2205 if (metadata_field_from_prop (i, &field) == FALSE) {
2206 continue;
2209 g_value_init (&val, rhythmdb_property_type_map[i]);
2210 rhythmdb_entry_get (db, entry, i, &val);
2211 rb_metadata_set (metadata,
2212 field,
2213 &val);
2214 g_value_unset (&val);
2218 typedef struct
2220 RhythmDB *db;
2221 char *uri;
2222 GError *error;
2223 } RhythmDBSaveErrorData;
2225 static gboolean
2226 emit_save_error_idle (RhythmDBSaveErrorData *data)
2228 g_signal_emit (G_OBJECT (data->db), rhythmdb_signals[SAVE_ERROR], 0, data->uri, data->error);
2229 g_object_unref (G_OBJECT (data->db));
2230 g_free (data->uri);
2231 g_error_free (data->error);
2232 g_free (data);
2233 return FALSE;
2236 static gpointer
2237 action_thread_main (RhythmDB *db)
2239 RhythmDBEvent *result;
2241 while (TRUE) {
2242 RhythmDBAction *action;
2244 action = read_queue (db->priv->action_queue, &db->priv->exiting);
2246 if (action == NULL)
2247 break;
2249 switch (action->type) {
2250 case RHYTHMDB_ACTION_STAT:
2252 result = g_new0 (RhythmDBEvent, 1);
2253 result->db = db;
2254 result->type = RHYTHMDB_EVENT_STAT;
2255 result->entry_type = action->entry_type;
2257 rb_debug ("executing RHYTHMDB_ACTION_STAT for \"%s\"", rb_refstring_get (action->uri));
2259 rhythmdb_execute_stat (db, rb_refstring_get (action->uri), result);
2261 break;
2262 case RHYTHMDB_ACTION_LOAD:
2264 result = g_new0 (RhythmDBEvent, 1);
2265 result->db = db;
2266 result->type = RHYTHMDB_EVENT_METADATA_LOAD;
2267 result->entry_type = action->entry_type;
2269 rb_debug ("executing RHYTHMDB_ACTION_LOAD for \"%s\"", rb_refstring_get (action->uri));
2271 rhythmdb_execute_load (db, rb_refstring_get (action->uri), result);
2273 break;
2274 case RHYTHMDB_ACTION_SYNC:
2276 GError *error = NULL;
2277 RhythmDBEntry *entry;
2278 RhythmDBEntryType entry_type;
2280 if (db->priv->dry_run) {
2281 rb_debug ("dry run is enabled, not syncing metadata");
2282 break;
2285 entry = rhythmdb_entry_lookup_by_location_refstring (db, action->uri);
2286 if (!entry)
2287 break;
2289 entry_type = rhythmdb_entry_get_entry_type (entry);
2290 entry_type->sync_metadata (db, entry, &error, entry_type->sync_metadata_data);
2292 if (error != NULL) {
2293 RhythmDBSaveErrorData *data;
2295 data = g_new0 (RhythmDBSaveErrorData, 1);
2296 g_object_ref (db);
2297 data->db = db;
2298 data->uri = g_strdup (rb_refstring_get (action->uri));
2299 data->error = error;
2300 g_idle_add ((GSourceFunc)emit_save_error_idle, data);
2301 break;
2303 break;
2305 break;
2306 default:
2307 g_assert_not_reached ();
2308 break;
2310 rhythmdb_action_free (db, action);
2314 rb_debug ("exiting main thread");
2315 result = g_new0 (RhythmDBEvent, 1);
2316 result->db = db;
2317 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2318 g_async_queue_push (db->priv->event_queue, result);
2320 return NULL;
2324 * rhythmdb_add_uri:
2325 * @db: a #RhythmDB.
2326 * @uri: the URI to add an entry/entries for
2328 * Adds the file(s) pointed to by @uri to the database, as entries of type
2329 * RHYTHMDB_ENTRY_TYPE_SONG. If the URI is that of a file, they will be added.
2330 * If the URI is that of a directory, everything under it will be added recursively.
2332 void
2333 rhythmdb_add_uri (RhythmDB *db,
2334 const char *uri)
2336 rhythmdb_add_uri_with_type (db, uri, RHYTHMDB_ENTRY_TYPE_INVALID);
2339 void
2340 rhythmdb_add_uri_with_type (RhythmDB *db,
2341 const char *uri,
2342 RhythmDBEntryType type)
2344 char *realuri;
2345 char *canon_uri;
2347 canon_uri = rb_canonicalise_uri (uri);
2348 realuri = rb_uri_resolve_symlink (canon_uri);
2349 g_free (canon_uri);
2351 if (rb_uri_is_directory (realuri)) {
2352 RhythmDBAddThreadData *data = g_new0 (RhythmDBAddThreadData, 1);
2353 data->db = db;
2354 data->uri = g_strdup (realuri);
2355 data->type = type;
2357 rhythmdb_thread_create (db, db->priv->add_thread_pool, NULL, data);
2358 } else {
2359 queue_stat_uri (realuri, db, type);
2362 g_free (realuri);
2365 static gboolean
2366 rhythmdb_sync_library_idle (RhythmDB *db)
2368 rhythmdb_sync_library_location (db);
2369 g_object_unref (db);
2370 return FALSE;
2373 static gboolean
2374 rhythmdb_load_error_cb (GError *error)
2376 GDK_THREADS_ENTER ();
2377 rb_error_dialog (NULL,
2378 _("Could not load the music database"),
2379 error->message);
2380 g_error_free (error);
2382 GDK_THREADS_LEAVE ();
2383 return FALSE;
2386 static gpointer
2387 rhythmdb_load_thread_main (RhythmDB *db)
2389 RhythmDBEvent *result;
2390 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2391 GError *error = NULL;
2393 rb_profile_start ("loading db");
2394 g_mutex_lock (db->priv->saving_mutex);
2395 if (klass->impl_load (db, &db->priv->exiting, &error) == FALSE) {
2396 rb_debug ("db load failed: disabling saving");
2397 db->priv->can_save = FALSE;
2399 if (error) {
2400 g_idle_add ((GSourceFunc) rhythmdb_load_error_cb, error);
2403 g_mutex_unlock (db->priv->saving_mutex);
2405 g_object_ref (db);
2406 g_timeout_add (10000, (GSourceFunc) rhythmdb_sync_library_idle, db);
2408 rb_debug ("queuing db load complete signal");
2409 result = g_new0 (RhythmDBEvent, 1);
2410 result->type = RHYTHMDB_EVENT_DB_LOAD;
2411 g_async_queue_push (db->priv->event_queue, result);
2413 rb_debug ("exiting");
2414 result = g_new0 (RhythmDBEvent, 1);
2415 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2416 g_async_queue_push (db->priv->event_queue, result);
2418 rb_profile_end ("loading db");
2419 return NULL;
2423 * rhythmdb_load:
2424 * @db: a #RhythmDB.
2426 * Load the database from disk.
2428 void
2429 rhythmdb_load (RhythmDB *db)
2431 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_load_thread_main, db);
2434 static gpointer
2435 rhythmdb_save_thread_main (RhythmDB *db)
2437 RhythmDBClass *klass;
2438 RhythmDBEvent *result;
2440 rb_debug ("entering save thread");
2442 g_mutex_lock (db->priv->saving_mutex);
2444 if (!db->priv->dirty && !db->priv->can_save) {
2445 rb_debug ("no save needed, ignoring");
2446 g_mutex_unlock (db->priv->saving_mutex);
2447 goto out;
2450 while (db->priv->saving)
2451 g_cond_wait (db->priv->saving_condition, db->priv->saving_mutex);
2453 db->priv->saving = TRUE;
2455 rb_debug ("saving rhythmdb");
2457 klass = RHYTHMDB_GET_CLASS (db);
2458 klass->impl_save (db);
2460 db->priv->saving = FALSE;
2461 db->priv->dirty = FALSE;
2463 g_mutex_unlock (db->priv->saving_mutex);
2465 g_cond_broadcast (db->priv->saving_condition);
2467 out:
2468 result = g_new0 (RhythmDBEvent, 1);
2469 result->db = db;
2470 result->type = RHYTHMDB_EVENT_DB_SAVED;
2471 g_async_queue_push (db->priv->event_queue, result);
2473 result = g_new0 (RhythmDBEvent, 1);
2474 result->db = db;
2475 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2476 g_async_queue_push (db->priv->event_queue, result);
2477 return NULL;
2481 * rhythmdb_save_async:
2482 * @db: a #RhythmDB.
2484 * Save the database to disk, asynchronously.
2486 void
2487 rhythmdb_save_async (RhythmDB *db)
2489 rb_debug ("saving the rhythmdb in the background");
2491 rhythmdb_read_enter (db);
2493 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_save_thread_main, db);
2497 * rhythmdb_save:
2498 * @db: a #RhythmDB.
2500 * Save the database to disk, not returning until it has been saved.
2502 void
2503 rhythmdb_save (RhythmDB *db)
2505 rb_debug("saving the rhythmdb and blocking");
2507 rhythmdb_save_async (db);
2509 g_mutex_lock (db->priv->saving_mutex);
2511 while (db->priv->saving)
2512 g_cond_wait (db->priv->saving_condition, db->priv->saving_mutex);
2514 g_mutex_unlock (db->priv->saving_mutex);
2518 * rhythmdb_entry_set:
2519 * @db:# a RhythmDB.
2520 * @entry: a #RhythmDBEntry.
2521 * @propid: the id of the property to set.
2522 * @value: the property value.
2524 * This function can be called by any code which wishes to change a
2525 * song property and send a notification. It may be called when the
2526 * database is read-only; in this case the change will be queued for
2527 * an unspecified time in the future. The implication of this is that
2528 * rhythmdb_entry_get() may not reflect the changes immediately. However,
2529 * if this property is exposed in the user interface, you should still
2530 * make the change in the widget. Then when the database returns to a
2531 * writable state, your change will take effect in the database too,
2532 * and a notification will be sent at that point.
2534 * Note that you must call rhythmdb_commit() at some point after invoking
2535 * this function, and that even after the commit, your change may not
2536 * have taken effect.
2538 void
2539 rhythmdb_entry_set (RhythmDB *db,
2540 RhythmDBEntry *entry,
2541 guint propid,
2542 const GValue *value)
2544 g_return_if_fail (RHYTHMDB_IS (db));
2545 g_return_if_fail (entry != NULL);
2547 if ((entry->flags & RHYTHMDB_ENTRY_INSERTED) != 0) {
2548 if (!rhythmdb_get_readonly (db) && rb_is_main_thread ()) {
2549 rhythmdb_entry_set_internal (db, entry, TRUE, propid, value);
2550 } else {
2551 RhythmDBEvent *result;
2553 result = g_new0 (RhythmDBEvent, 1);
2554 result->db = db;
2555 result->type = RHYTHMDB_EVENT_ENTRY_SET;
2557 rb_debug ("queuing RHYTHMDB_ACTION_ENTRY_SET");
2559 result->entry = rhythmdb_entry_ref (entry);
2560 result->change.prop = propid;
2561 result->signal_change = TRUE;
2562 g_value_init (&result->change.new, G_VALUE_TYPE (value));
2563 g_value_copy (value, &result->change.new);
2564 g_async_queue_push (db->priv->event_queue, result);
2566 } else {
2567 rhythmdb_entry_set_internal (db, entry, FALSE, propid, value);
2571 static void
2572 record_entry_change (RhythmDB *db,
2573 RhythmDBEntry *entry,
2574 guint propid,
2575 const GValue *value)
2577 RhythmDBEntryChange *changedata;
2578 GSList *changelist;
2580 changedata = g_new0 (RhythmDBEntryChange, 1);
2581 changedata->prop = propid;
2583 /* Copy a temporary gvalue, since _entry_get uses
2584 * _set_static_string to avoid memory allocations. */
2586 GValue tem = {0,};
2587 g_value_init (&tem, G_VALUE_TYPE (value));
2588 rhythmdb_entry_get (db, entry, propid, &tem);
2589 g_value_init (&changedata->old, G_VALUE_TYPE (value));
2590 g_value_copy (&tem, &changedata->old);
2591 g_value_unset (&tem);
2593 g_value_init (&changedata->new, G_VALUE_TYPE (value));
2594 g_value_copy (value, &changedata->new);
2596 g_mutex_lock (db->priv->change_mutex);
2597 /* ref the entry before adding to hash, it is unreffed when removed */
2598 rhythmdb_entry_ref (entry);
2599 changelist = g_hash_table_lookup (db->priv->changed_entries, entry);
2600 changelist = g_slist_append (changelist, changedata);
2601 g_hash_table_insert (db->priv->changed_entries, entry, changelist);
2602 g_mutex_unlock (db->priv->change_mutex);
2605 void
2606 rhythmdb_entry_set_internal (RhythmDB *db,
2607 RhythmDBEntry *entry,
2608 gboolean notify_if_inserted,
2609 guint propid,
2610 const GValue *value)
2612 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2613 gboolean handled;
2614 RhythmDBPodcastFields *podcast = NULL;
2616 #ifndef G_DISABLE_ASSERT
2617 switch (G_VALUE_TYPE (value)) {
2618 case G_TYPE_STRING:
2619 /* the playback error is allowed to be NULL */
2620 if (propid != RHYTHMDB_PROP_PLAYBACK_ERROR || g_value_get_string (value))
2621 g_assert (g_utf8_validate (g_value_get_string (value), -1, NULL));
2622 break;
2623 case G_TYPE_BOOLEAN:
2624 case G_TYPE_ULONG:
2625 case G_TYPE_UINT64:
2626 case G_TYPE_DOUBLE:
2627 break;
2628 default:
2629 g_assert_not_reached ();
2630 break;
2632 #endif
2634 if ((entry->flags & RHYTHMDB_ENTRY_INSERTED) && notify_if_inserted) {
2635 record_entry_change (db, entry, propid, value);
2638 handled = klass->impl_entry_set (db, entry, propid, value);
2640 if (!handled) {
2641 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
2642 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
2643 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
2645 switch (propid) {
2646 case RHYTHMDB_PROP_TYPE:
2647 case RHYTHMDB_PROP_ENTRY_ID:
2648 g_assert_not_reached ();
2649 break;
2650 case RHYTHMDB_PROP_TITLE:
2651 if (entry->title != NULL) {
2652 rb_refstring_unref (entry->title);
2654 entry->title = rb_refstring_new (g_value_get_string (value));
2655 break;
2656 case RHYTHMDB_PROP_ALBUM:
2657 if (entry->album != NULL) {
2658 rb_refstring_unref (entry->album);
2660 entry->album = rb_refstring_new (g_value_get_string (value));
2661 break;
2662 case RHYTHMDB_PROP_ARTIST:
2663 if (entry->artist != NULL) {
2664 rb_refstring_unref (entry->artist);
2666 entry->artist = rb_refstring_new (g_value_get_string (value));
2667 break;
2668 case RHYTHMDB_PROP_GENRE:
2669 if (entry->genre != NULL) {
2670 rb_refstring_unref (entry->genre);
2672 entry->genre = rb_refstring_new (g_value_get_string (value));
2673 break;
2674 case RHYTHMDB_PROP_TRACK_NUMBER:
2675 entry->tracknum = g_value_get_ulong (value);
2676 break;
2677 case RHYTHMDB_PROP_DISC_NUMBER:
2678 entry->discnum = g_value_get_ulong (value);
2679 break;
2680 case RHYTHMDB_PROP_DURATION:
2681 entry->duration = g_value_get_ulong (value);
2682 break;
2683 case RHYTHMDB_PROP_BITRATE:
2684 entry->bitrate = g_value_get_ulong (value);
2685 break;
2686 case RHYTHMDB_PROP_DATE:
2688 gulong julian;
2689 julian = g_value_get_ulong (value);
2690 if (julian > 0)
2691 g_date_set_julian (&entry->date, julian);
2692 else
2693 g_date_clear (&entry->date, 1);
2694 break;
2696 case RHYTHMDB_PROP_TRACK_GAIN:
2697 entry->track_gain = g_value_get_double (value);
2698 break;
2699 case RHYTHMDB_PROP_TRACK_PEAK:
2700 entry->track_peak = g_value_get_double (value);
2701 break;
2702 case RHYTHMDB_PROP_ALBUM_GAIN:
2703 entry->album_gain = g_value_get_double (value);
2704 break;
2705 case RHYTHMDB_PROP_ALBUM_PEAK:
2706 entry->album_peak = g_value_get_double (value);
2707 break;
2708 case RHYTHMDB_PROP_LOCATION:
2709 rb_refstring_unref (entry->location);
2710 entry->location = rb_refstring_new (g_value_get_string (value));
2711 break;
2712 case RHYTHMDB_PROP_PLAYBACK_ERROR:
2713 rb_refstring_unref (entry->playback_error);
2714 if (g_value_get_string (value))
2715 entry->playback_error = rb_refstring_new (g_value_get_string (value));
2716 else
2717 entry->playback_error = NULL;
2718 break;
2719 case RHYTHMDB_PROP_MOUNTPOINT:
2720 if (entry->mountpoint != NULL) {
2721 rb_refstring_unref (entry->mountpoint);
2723 entry->mountpoint = rb_refstring_new (g_value_get_string (value));
2724 break;
2725 case RHYTHMDB_PROP_FILE_SIZE:
2726 entry->file_size = g_value_get_uint64 (value);
2727 break;
2728 case RHYTHMDB_PROP_MIMETYPE:
2729 if (entry->mimetype != NULL) {
2730 rb_refstring_unref (entry->mimetype);
2732 entry->mimetype = rb_refstring_new (g_value_get_string (value));
2733 break;
2734 case RHYTHMDB_PROP_MTIME:
2735 entry->mtime = g_value_get_ulong (value);
2736 break;
2737 case RHYTHMDB_PROP_FIRST_SEEN:
2738 entry->first_seen = g_value_get_ulong (value);
2739 entry->flags |= RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY;
2740 break;
2741 case RHYTHMDB_PROP_LAST_SEEN:
2742 entry->last_seen = g_value_get_ulong (value);
2743 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
2744 break;
2745 case RHYTHMDB_PROP_RATING:
2746 entry->rating = g_value_get_double (value);
2747 break;
2748 case RHYTHMDB_PROP_PLAY_COUNT:
2749 entry->play_count = g_value_get_ulong (value);
2750 break;
2751 case RHYTHMDB_PROP_LAST_PLAYED:
2752 entry->last_played = g_value_get_ulong (value);
2753 entry->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY;
2754 break;
2755 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
2756 rb_refstring_unref (entry->musicbrainz_trackid);
2757 entry->musicbrainz_trackid = rb_refstring_new (g_value_get_string (value));
2758 break;
2759 case RHYTHMDB_PROP_HIDDEN:
2760 if (g_value_get_boolean (value)) {
2761 entry->flags |= RHYTHMDB_ENTRY_HIDDEN;
2762 } else {
2763 entry->flags &= ~RHYTHMDB_ENTRY_HIDDEN;
2765 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
2766 break;
2767 case RHYTHMDB_PROP_STATUS:
2768 g_assert (podcast);
2769 podcast->status = g_value_get_ulong (value);
2770 break;
2771 case RHYTHMDB_PROP_DESCRIPTION:
2772 g_assert (podcast);
2773 rb_refstring_unref (podcast->description);
2774 podcast->description = rb_refstring_new (g_value_get_string (value));
2775 break;
2776 case RHYTHMDB_PROP_SUBTITLE:
2777 g_assert (podcast);
2778 rb_refstring_unref (podcast->subtitle);
2779 podcast->subtitle = rb_refstring_new (g_value_get_string (value));
2780 break;
2781 case RHYTHMDB_PROP_SUMMARY:
2782 g_assert (podcast);
2783 rb_refstring_unref (podcast->summary);
2784 podcast->summary = rb_refstring_new (g_value_get_string (value));
2785 break;
2786 case RHYTHMDB_PROP_LANG:
2787 g_assert (podcast);
2788 if (podcast->lang != NULL) {
2789 rb_refstring_unref (podcast->lang);
2791 podcast->lang = rb_refstring_new (g_value_get_string (value));
2792 break;
2793 case RHYTHMDB_PROP_COPYRIGHT:
2794 g_assert (podcast);
2795 if (podcast->copyright != NULL) {
2796 rb_refstring_unref (podcast->copyright);
2798 podcast->copyright = rb_refstring_new (g_value_get_string (value));
2799 break;
2800 case RHYTHMDB_PROP_IMAGE:
2801 g_assert (podcast);
2802 if (podcast->image != NULL) {
2803 rb_refstring_unref (podcast->image);
2805 podcast->image = rb_refstring_new (g_value_get_string (value));
2806 break;
2807 case RHYTHMDB_PROP_POST_TIME:
2808 g_assert (podcast);
2809 podcast->post_time = g_value_get_ulong (value);
2810 break;
2811 case RHYTHMDB_NUM_PROPERTIES:
2812 g_assert_not_reached ();
2813 break;
2817 /* set the dirty state */
2818 db->priv->dirty = TRUE;
2822 * rhythmdb_entry_sync_mirrored:
2823 * @db: a #RhythmDB.
2824 * @type: a #RhythmDBEntry.
2825 * @propid: the property to sync the mirrored version of.
2827 * Synchronise "mirrored" properties, such as the string version of the last-played
2828 * time. This should be called when a property is directly modified, passing the
2829 * original property.
2831 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
2834 static void
2835 rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
2836 guint propid)
2838 static const char *format;
2839 static const char *never;
2840 char *val;
2842 /* PERFORMANCE: doing these lookups every call creates a noticable slowdown */
2843 if (format == NULL)
2844 format = _("%Y-%m-%d %H:%M");
2845 if (never == NULL)
2846 never = _("Never");
2848 switch (propid) {
2849 case RHYTHMDB_PROP_LAST_PLAYED_STR:
2851 RBRefString *old, *new;
2853 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY))
2854 break;
2856 old = g_atomic_pointer_get (&entry->last_played_str);
2857 if (entry->last_played == 0) {
2858 new = rb_refstring_new (never);
2859 } else {
2860 val = eel_strdup_strftime (format, localtime ((glong*)&entry->last_played));
2861 new = rb_refstring_new (val);
2862 g_free (val);
2865 if (g_atomic_pointer_compare_and_exchange (&entry->last_played_str, old, new)) {
2866 if (old != NULL) {
2867 rb_refstring_unref (old);
2869 } else {
2870 rb_refstring_unref (new);
2873 break;
2875 case RHYTHMDB_PROP_FIRST_SEEN_STR:
2877 RBRefString *old, *new;
2879 if (!(entry->flags & RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY))
2880 break;
2882 old = g_atomic_pointer_get (&entry->first_seen_str);
2883 val = eel_strdup_strftime (format, localtime ((glong*)&entry->first_seen));
2884 new = rb_refstring_new (val);
2885 g_free (val);
2887 if (g_atomic_pointer_compare_and_exchange (&entry->first_seen_str, old, new)) {
2888 if (old != NULL) {
2889 rb_refstring_unref (old);
2891 } else {
2892 rb_refstring_unref (new);
2895 break;
2897 case RHYTHMDB_PROP_LAST_SEEN_STR:
2899 RBRefString *old, *new;
2901 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_SEEN_DIRTY))
2902 break;
2904 old = g_atomic_pointer_get (&entry->last_seen_str);
2905 /* only store last seen time as a string for hidden entries */
2906 if (entry->flags & RHYTHMDB_ENTRY_HIDDEN) {
2907 val = eel_strdup_strftime (format, localtime ((glong*)&entry->last_seen));
2908 new = rb_refstring_new (val);
2909 g_free (val);
2910 } else {
2911 new = NULL;
2914 if (g_atomic_pointer_compare_and_exchange (&entry->first_seen_str, old, new)) {
2915 if (old != NULL) {
2916 rb_refstring_unref (old);
2918 } else {
2919 rb_refstring_unref (new);
2922 break;
2924 default:
2925 break;
2930 * rhythmdb_entry_delete:
2931 * @db: a #RhythmDB.
2932 * @entry: a #RhythmDBEntry.
2934 * Delete entry @entry from the database, sending notification of it's deletion.
2935 * This is usually used by sources where entries can disappear randomly, such
2936 * as a network source.
2938 void
2939 rhythmdb_entry_delete (RhythmDB *db,
2940 RhythmDBEntry *entry)
2942 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
2944 g_return_if_fail (RHYTHMDB_IS (db));
2945 g_return_if_fail (entry != NULL);
2947 klass->impl_entry_delete (db, entry);
2949 /* ref the entry before adding to hash, it is unreffed when removed */
2950 rhythmdb_entry_ref (entry);
2951 g_mutex_lock (db->priv->change_mutex);
2952 g_hash_table_insert (db->priv->deleted_entries, entry, g_thread_self ());
2953 g_mutex_unlock (db->priv->change_mutex);
2955 /* deleting an entry makes the db dirty */
2956 db->priv->dirty = TRUE;
2959 static gint
2960 rhythmdb_entry_move_to_trash_cb (GnomeVFSXferProgressInfo *info,
2961 gpointer data)
2963 /* Abort immediately if anything happens */
2964 if (info->status == GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR)
2965 return GNOME_VFS_XFER_ERROR_ACTION_ABORT;
2966 /* Don't overwrite files */
2967 if (info->status == GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE)
2968 return 0;
2969 return TRUE;
2972 static void
2973 rhythmdb_entry_move_to_trash_set_error (RhythmDB *db,
2974 RhythmDBEntry *entry,
2975 GnomeVFSResult res)
2977 GValue value = { 0, };
2979 if (res == -1)
2980 res = GNOME_VFS_ERROR_INTERNAL;
2982 g_value_init (&value, G_TYPE_STRING);
2983 g_value_set_string (&value, gnome_vfs_result_to_string (res));
2984 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
2985 g_value_unset (&value);
2987 rb_debug ("Deleting %s failed: %s", rb_refstring_get (entry->location),
2988 gnome_vfs_result_to_string (res));
2991 void
2992 rhythmdb_entry_move_to_trash (RhythmDB *db,
2993 RhythmDBEntry *entry)
2995 GnomeVFSResult res;
2996 GnomeVFSURI *uri, *trash, *dest;
2997 char *shortname;
2999 uri = gnome_vfs_uri_new (rb_refstring_get (entry->location));
3000 if (uri == NULL) {
3001 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3002 return;
3005 res = gnome_vfs_find_directory (uri,
3006 GNOME_VFS_DIRECTORY_KIND_TRASH,
3007 &trash,
3008 TRUE, TRUE,
3010 if (res != GNOME_VFS_OK || trash == NULL) {
3011 /* If the file doesn't exist, or trash isn't support,
3012 * remove it from the db */
3013 if (res == GNOME_VFS_ERROR_NOT_FOUND ||
3014 res == GNOME_VFS_ERROR_NOT_SUPPORTED) {
3015 rhythmdb_entry_set_visibility (db, entry, FALSE);
3016 } else {
3017 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3020 gnome_vfs_uri_unref (uri);
3021 return;
3024 /* Is the file already in the Trash? If so it should be hidden */
3025 if (gnome_vfs_uri_is_parent (trash, uri, TRUE)) {
3026 GValue value = { 0, };
3027 g_value_init (&value, G_TYPE_BOOLEAN);
3028 g_value_set_boolean (&value, TRUE);
3029 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_HIDDEN, &value);
3030 rhythmdb_commit (db);
3032 gnome_vfs_uri_unref (trash);
3033 gnome_vfs_uri_unref (uri);
3034 return;
3037 shortname = gnome_vfs_uri_extract_short_name (uri);
3038 if (shortname == NULL) {
3039 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3040 rhythmdb_commit (db);
3041 gnome_vfs_uri_unref (uri);
3042 gnome_vfs_uri_unref (trash);
3043 return;
3046 /* Compute the destination URI */
3047 dest = gnome_vfs_uri_append_path (trash, shortname);
3048 gnome_vfs_uri_unref (trash);
3049 g_free (shortname);
3050 if (dest == NULL) {
3051 rhythmdb_entry_move_to_trash_set_error (db, entry, -1);
3052 rhythmdb_commit (db);
3053 gnome_vfs_uri_unref (uri);
3054 return;
3057 /* RB can't tell that a file's moved, so no unique names */
3058 res = gnome_vfs_xfer_uri (uri, dest,
3059 GNOME_VFS_XFER_REMOVESOURCE,
3060 GNOME_VFS_XFER_ERROR_MODE_ABORT,
3061 GNOME_VFS_XFER_OVERWRITE_MODE_SKIP,
3062 rhythmdb_entry_move_to_trash_cb,
3063 entry);
3065 if (res == GNOME_VFS_OK) {
3066 rhythmdb_entry_set_visibility (db, entry, FALSE);
3067 } else {
3068 rhythmdb_entry_move_to_trash_set_error (db, entry, res);
3070 rhythmdb_commit (db);
3072 gnome_vfs_uri_unref (dest);
3073 gnome_vfs_uri_unref (uri);
3077 * rhythmdb_entry_delete_by_type:
3078 * @db: a #RhythmDB.
3079 * @type: type of entried to delete.
3081 * Delete all entries from the database of the given type.
3082 * This is usually used by non-permanent sources when they disappear, such as
3083 * removable media being removed, or a network share becoming unavailable.
3085 void
3086 rhythmdb_entry_delete_by_type (RhythmDB *db,
3087 RhythmDBEntryType type)
3089 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3091 if (klass->impl_entry_delete_by_type) {
3092 klass->impl_entry_delete_by_type (db, type);
3093 } else {
3094 g_warning ("delete_by_type not implemented");
3098 const xmlChar *
3099 rhythmdb_nice_elt_name_from_propid (RhythmDB *db,
3100 RhythmDBPropType propid)
3102 return db->priv->column_xml_names[propid];
3106 rhythmdb_propid_from_nice_elt_name (RhythmDB *db,
3107 const xmlChar *name)
3109 gpointer ret, orig;
3110 if (g_hash_table_lookup_extended (db->priv->propname_map, name,
3111 &orig, &ret)) {
3112 return GPOINTER_TO_INT (ret);
3114 return -1;
3118 * rhythmdb_entry_lookup_by_location:
3119 * @db: a #RhythmDB.
3120 * @uri: the URI of the entry to lookup.
3122 * Looks up the entry with location @uri.
3124 * Returns: the entry with location @uri, or NULL if no such entry exists.
3126 RhythmDBEntry *
3127 rhythmdb_entry_lookup_by_location (RhythmDB *db,
3128 const char *uri)
3130 RBRefString *rs;
3132 rs = rb_refstring_find (uri);
3133 if (rs != NULL) {
3134 return rhythmdb_entry_lookup_by_location_refstring (db, rs);
3135 } else {
3136 return NULL;
3140 RhythmDBEntry *
3141 rhythmdb_entry_lookup_by_location_refstring (RhythmDB *db,
3142 RBRefString *uri)
3144 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3146 return klass->impl_lookup_by_location (db, uri);
3150 *rhythmdb_entry_foreach:
3151 * @db: a #RhythmDB.
3152 * @func: the function to call with each entry.
3153 * @data: user data to pass to the function.
3155 * Calls the given function for each of the entries in the database.
3157 void
3158 rhythmdb_entry_foreach (RhythmDB *db,
3159 GFunc func,
3160 gpointer data)
3162 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3164 klass->impl_entry_foreach (db, func, data);
3168 * rhythmdb_evaluate_query:
3169 * @db: a #RhythmDB.
3170 * @query: a query.
3171 * @entry a @RhythmDBEntry.
3173 * Evaluates the given entry against the given query.
3175 * Returns: whether the given entry matches the criteria of the given query.
3177 gboolean
3178 rhythmdb_evaluate_query (RhythmDB *db,
3179 GPtrArray *query,
3180 RhythmDBEntry *entry)
3182 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3184 return klass->impl_evaluate_query (db, query, entry);
3187 static void
3188 rhythmdb_query_internal (RhythmDBQueryThreadData *data)
3190 RhythmDBEvent *result;
3191 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (data->db);
3193 rhythmdb_query_preprocess (data->db, data->query);
3195 rb_debug ("doing query");
3197 klass->impl_do_full_query (data->db, data->query,
3198 data->results,
3199 &data->cancel);
3201 rb_debug ("completed");
3202 rhythmdb_query_results_query_complete (data->results);
3204 result = g_new0 (RhythmDBEvent, 1);
3205 result->db = data->db;
3206 result->type = RHYTHMDB_EVENT_QUERY_COMPLETE;
3207 result->results = data->results;
3208 g_async_queue_push (data->db->priv->event_queue, result);
3210 rhythmdb_query_free (data->query);
3213 static gpointer
3214 query_thread_main (RhythmDBQueryThreadData *data)
3216 RhythmDBEvent *result;
3218 rb_debug ("entering query thread");
3220 rhythmdb_query_internal (data);
3222 result = g_new0 (RhythmDBEvent, 1);
3223 result->db = data->db;
3224 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
3225 g_async_queue_push (data->db->priv->event_queue, result);
3226 g_free (data);
3227 return NULL;
3230 void
3231 rhythmdb_do_full_query_async_parsed (RhythmDB *db,
3232 RhythmDBQueryResults *results,
3233 GPtrArray *query)
3235 RhythmDBQueryThreadData *data;
3237 data = g_new0 (RhythmDBQueryThreadData, 1);
3238 data->db = db;
3239 data->query = rhythmdb_query_copy (query);
3240 data->results = results;
3241 data->cancel = FALSE;
3243 rhythmdb_read_enter (db);
3245 rhythmdb_query_results_set_query (results, query);
3247 g_object_ref (results);
3248 g_object_ref (db);
3249 g_atomic_int_inc (&db->priv->outstanding_threads);
3250 g_async_queue_ref (db->priv->action_queue);
3251 g_async_queue_ref (db->priv->event_queue);
3252 g_thread_pool_push (db->priv->query_thread_pool, data, NULL);
3255 void
3256 rhythmdb_do_full_query_async (RhythmDB *db,
3257 RhythmDBQueryResults *results,
3258 ...)
3260 GPtrArray *query;
3261 va_list args;
3263 va_start (args, results);
3265 query = rhythmdb_query_parse_valist (db, args);
3267 rhythmdb_do_full_query_async_parsed (db, results, query);
3269 rhythmdb_query_free (query);
3271 va_end (args);
3274 static void
3275 rhythmdb_do_full_query_internal (RhythmDB *db,
3276 RhythmDBQueryResults *results,
3277 GPtrArray *query)
3279 RhythmDBQueryThreadData *data;
3281 data = g_new0 (RhythmDBQueryThreadData, 1);
3282 data->db = db;
3283 data->query = rhythmdb_query_copy (query);
3284 data->results = results;
3285 data->cancel = FALSE;
3287 rhythmdb_read_enter (db);
3289 rhythmdb_query_results_set_query (results, query);
3290 g_object_ref (results);
3292 rhythmdb_query_internal (data);
3293 g_free (data);
3296 void
3297 rhythmdb_do_full_query_parsed (RhythmDB *db,
3298 RhythmDBQueryResults *results,
3299 GPtrArray *query)
3301 rhythmdb_do_full_query_internal (db, results, query);
3304 void
3305 rhythmdb_do_full_query (RhythmDB *db,
3306 RhythmDBQueryResults *results,
3307 ...)
3309 GPtrArray *query;
3310 va_list args;
3312 va_start (args, results);
3314 query = rhythmdb_query_parse_valist (db, args);
3316 rhythmdb_do_full_query_internal (db, results, query);
3318 rhythmdb_query_free (query);
3320 va_end (args);
3323 /* This should really be standard. */
3324 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3326 GType
3327 rhythmdb_query_type_get_type (void)
3329 static GType etype = 0;
3331 if (etype == 0)
3333 static const GEnumValue values[] =
3336 ENUM_ENTRY (RHYTHMDB_QUERY_END, "Query end marker"),
3337 ENUM_ENTRY (RHYTHMDB_QUERY_DISJUNCTION, "Disjunctive marker"),
3338 ENUM_ENTRY (RHYTHMDB_QUERY_SUBQUERY, "Subquery"),
3339 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_EQUALS, "Property equivalence"),
3340 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LIKE, "Fuzzy property matching"),
3341 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_LIKE, "Inverted fuzzy property matching"),
3342 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_PREFIX, "Starts with"),
3343 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_SUFFIX, "Ends with"),
3344 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_GREATER, "True if property1 >= property2"),
3345 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LESS, "True if property1 <= property2"),
3346 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN, "True if property1 is within property2 of the current time"),
3347 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN, "True if property1 is not within property2 of the current time"),
3348 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_EQUALS, "Year equivalence: true if date within year"),
3349 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_GREATER, "True if date greater than year"),
3350 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_LESS, "True if date less than year"),
3351 { 0, 0, 0 }
3354 etype = g_enum_register_static ("RhythmDBQueryType", values);
3357 return etype;
3360 GType
3361 rhythmdb_prop_type_get_type (void)
3363 static GType etype = 0;
3365 if (etype == 0)
3367 static const GEnumValue values[] =
3369 /* We reuse the description to store extra data about
3370 * a property. The first part is just a generic
3371 * human-readable description. Next, there is
3372 * a string describing the GType of the property, in
3373 * parenthesis.
3374 * Finally, there is the XML element name in brackets.
3376 ENUM_ENTRY (RHYTHMDB_PROP_TYPE, "Type of entry (gpointer) [type]"),
3377 ENUM_ENTRY (RHYTHMDB_PROP_ENTRY_ID, "Numeric ID (guint) [entry-id]"),
3378 ENUM_ENTRY (RHYTHMDB_PROP_TITLE, "Title (gchararray) [title]"),
3379 ENUM_ENTRY (RHYTHMDB_PROP_GENRE, "Genre (gchararray) [genre]"),
3380 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST, "Artist (gchararray) [artist]"),
3381 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM, "Album (gchararray) [album]"),
3382 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_NUMBER, "Track Number (gulong) [track-number]"),
3383 ENUM_ENTRY (RHYTHMDB_PROP_DISC_NUMBER, "Disc Number (gulong) [disc-number]"),
3384 ENUM_ENTRY (RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, "Musicbrainz Track ID (gchararray) [mb-trackid]"),
3386 ENUM_ENTRY (RHYTHMDB_PROP_DURATION, "Duration (gulong) [duration]"),
3387 ENUM_ENTRY (RHYTHMDB_PROP_FILE_SIZE, "File Size (guint64) [file-size]"),
3388 ENUM_ENTRY (RHYTHMDB_PROP_LOCATION, "Location (gchararray) [location]"),
3389 ENUM_ENTRY (RHYTHMDB_PROP_MOUNTPOINT, "Mount point it's located in (gchararray) [mountpoint]"),
3390 ENUM_ENTRY (RHYTHMDB_PROP_MTIME, "Modification time (gulong) [mtime]"),
3391 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN, "Time the song was added to the library (gulong) [first-seen]"),
3392 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN, "Last time the song was available (gulong) [last-seen]"),
3393 ENUM_ENTRY (RHYTHMDB_PROP_RATING, "Rating (gdouble) [rating]"),
3394 ENUM_ENTRY (RHYTHMDB_PROP_PLAY_COUNT, "Play Count (gulong) [play-count]"),
3395 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED, "Last Played (gulong) [last-played]"),
3396 ENUM_ENTRY (RHYTHMDB_PROP_BITRATE, "Bitrate (gulong) [bitrate]"),
3397 ENUM_ENTRY (RHYTHMDB_PROP_DATE, "Date of release (gulong) [date]"),
3398 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_GAIN, "Replaygain track gain (gdouble) [replaygain-track-gain]"),
3399 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_PEAK, "Replaygain track peak (gdouble) [replaygain-track-peak]"),
3400 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_GAIN, "Replaygain album pain (gdouble) [replaygain-album-gain]"),
3401 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_PEAK, "Replaygain album peak (gdouble) [replaygain-album-peak]"),
3402 ENUM_ENTRY (RHYTHMDB_PROP_MIMETYPE, "Mime Type (gchararray) [mimetype]"),
3403 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_SORT_KEY, "Title sort key (gchararray) [title-sort-key]"),
3404 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_SORT_KEY, "Genre sort key (gchararray) [genre-sort-key]"),
3405 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_SORT_KEY, "Artist sort key (gchararray) [artist-sort-key]"),
3406 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_SORT_KEY, "Album sort key (gchararray) [album-sort-key]"),
3408 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_FOLDED, "Title folded (gchararray) [title-folded]"),
3409 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_FOLDED, "Genre folded (gchararray) [genre-folded]"),
3410 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_FOLDED, "Artist folded (gchararray) [artist-folded]"),
3411 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_FOLDED, "Album folded (gchararray) [album-folded]"),
3412 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED_STR, "Last Played (gchararray) [last-played-str]"),
3413 ENUM_ENTRY (RHYTHMDB_PROP_PLAYBACK_ERROR, "Playback error string (gchararray) [playback-error]"),
3414 ENUM_ENTRY (RHYTHMDB_PROP_HIDDEN, "Hidden (gboolean) [hidden]"),
3415 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN_STR, "Time Added to Library (gchararray) [first-seen-str]"),
3416 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN_STR, "Last time the song was available (gchararray) [last-seen-str]"),
3417 ENUM_ENTRY (RHYTHMDB_PROP_SEARCH_MATCH, "Search matching key (gchararray) [search-match]"),
3418 ENUM_ENTRY (RHYTHMDB_PROP_YEAR, "Year of date (gulong) [year]"),
3420 ENUM_ENTRY (RHYTHMDB_PROP_STATUS, "Status of file (gulong) [status]"),
3421 ENUM_ENTRY (RHYTHMDB_PROP_DESCRIPTION, "Podcast description(gchararray) [description]"),
3422 ENUM_ENTRY (RHYTHMDB_PROP_SUBTITLE, "Podcast subtitle (gchararray) [subtitle]"),
3423 ENUM_ENTRY (RHYTHMDB_PROP_SUMMARY, "Podcast summary (gchararray) [summary]"),
3424 ENUM_ENTRY (RHYTHMDB_PROP_LANG, "Podcast language (gchararray) [lang]"),
3425 ENUM_ENTRY (RHYTHMDB_PROP_COPYRIGHT, "Podcast copyright (gchararray) [copyright]"),
3426 ENUM_ENTRY (RHYTHMDB_PROP_IMAGE, "Podcast image(gchararray) [image]"),
3427 ENUM_ENTRY (RHYTHMDB_PROP_POST_TIME, "Podcast time of post (gulong) [post-time]"),
3428 { 0, 0, 0 }
3430 g_assert ((sizeof (values) / sizeof (values[0]) - 1) == RHYTHMDB_NUM_PROPERTIES);
3431 etype = g_enum_register_static ("RhythmDBPropType", values);
3434 return etype;
3437 void
3438 rhythmdb_emit_entry_deleted (RhythmDB *db,
3439 RhythmDBEntry *entry)
3441 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry);
3444 static gboolean
3445 rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
3446 GValue *return_accu,
3447 const GValue *handler_return,
3448 gpointer data)
3450 if (handler_return == NULL)
3451 return TRUE;
3453 g_value_copy (handler_return, return_accu);
3454 return (g_value_get_boxed (return_accu) == NULL);
3458 * rhythmdb_entry_request_extra_metadata:
3459 * @db: a #RhythmDB
3460 * @entry: a #RhythmDBEntry
3461 * @property_name: the metadata predicate
3463 * Emits a request for extra metadata for the @entry.
3464 * The @property_name argument is emitted as the ::detail part of the
3465 * "entry_extra_metadata_request" signal. It should be a namespaced RDF
3466 * predicate e.g. from Dublin Core, MusicBrainz, or internal to Rhythmbox
3467 * (namespace "rb:"). Suitable predicates would be those that are expensive to
3468 * acquire or only apply to a limited range of entries.
3469 * Handlers capable of providing a particular predicate may ensure they only
3470 * see appropriate requests by supplying an appropriate ::detail part when
3471 * connecting to the signal. Upon a handler returning a non-%NULL value,
3472 * emission will be stopped and the value returned to the caller; if no
3473 * handlers return a non-%NULL value, the caller will receive %NULL. Priority
3474 * is determined by signal connection order, with %G_CONNECT_AFTER providing a
3475 * second, lower rank of priority.
3476 * A handler returning a value should do so in a #GValue allocated on the heap;
3477 * the accumulator will take ownership. The caller should unset and free the
3478 * #GValue if non-%NULL when finished with it.
3480 * Returns: an allocated, initialised, set #GValue, or NULL
3482 GValue *
3483 rhythmdb_entry_request_extra_metadata (RhythmDB *db,
3484 RhythmDBEntry *entry,
3485 const gchar *property_name)
3487 GValue *value = NULL;
3489 g_signal_emit (G_OBJECT (db),
3490 rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST],
3491 g_quark_from_string (property_name),
3492 entry,
3493 &value);
3495 return value;
3499 * rhythmdb_emit_entry_extra_metadata_notify:
3500 * @db: a #RhythmDB
3501 * @entry: a #RhythmDBEntry
3502 * @property_name: the metadata predicate
3503 * @metadata: a #GValue
3505 * Emits a signal describing extra metadata for the @entry. The @property_name
3506 * argument is emitted as the ::detail part of the
3507 * "entry_extra_metadata_notify" signal and as the 'field' parameter. Handlers
3508 * can ensure they only get metadata they are interested in by supplying an
3509 * appropriate ::detail part when connecting to the signal. If handlers are
3510 * interested in the metadata they should ref or copy the contents of @metadata
3511 * and unref or free it when they are finished with it.
3513 void
3514 rhythmdb_emit_entry_extra_metadata_notify (RhythmDB *db,
3515 RhythmDBEntry *entry,
3516 const gchar *property_name,
3517 const GValue *metadata)
3519 g_signal_emit (G_OBJECT (db),
3520 rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY],
3521 g_quark_from_string (property_name),
3522 entry,
3523 property_name,
3524 metadata);
3527 static void
3528 unset_and_free_g_value (gpointer valpointer)
3530 GValue *value = valpointer;
3531 g_value_unset (value);
3532 g_free (value);
3536 * rhythmdb_entry_extra_gather:
3537 * @db: a #RhythmDB
3538 * @entry: a #RhythmDBEntry
3540 * Gathers all metadata for the @entry. The returned GHashTable maps property
3541 * names and extra metadata names (described under
3542 * @rhythmdb_entry_request_extra_metadata) to GValues. Anything wanting to
3543 * provide extra metadata should connect to the "entry_extra_metadata_gather"
3544 * signal.
3546 * Returns: a GHashTable containing metadata for the entry. This must be freed
3547 * using g_hash_table_destroy.
3549 GHashTable *
3550 rhythmdb_entry_gather_metadata (RhythmDB *db,
3551 RhythmDBEntry *entry)
3553 GHashTable *metadata;
3554 GEnumClass *klass;
3555 guint i;
3557 metadata = g_hash_table_new_full (g_str_hash,
3558 g_str_equal,
3559 g_free,
3560 unset_and_free_g_value);
3562 /* add core properties */
3563 klass = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
3564 for (i = 0; i < klass->n_values; i++) {
3565 GValue *value;
3566 gint prop;
3567 GType value_type;
3568 const char *name;
3570 prop = klass->values[i].value;
3572 /* only include easily marshallable types in the hash table */
3573 value_type = rhythmdb_get_property_type (db, prop);
3574 switch (value_type) {
3575 case G_TYPE_STRING:
3576 case G_TYPE_BOOLEAN:
3577 case G_TYPE_ULONG:
3578 case G_TYPE_UINT64:
3579 case G_TYPE_DOUBLE:
3580 break;
3581 default:
3582 continue;
3585 value = g_new0 (GValue, 1);
3586 g_value_init (value, value_type);
3587 rhythmdb_entry_get (db, entry, prop, value);
3588 name = (char *)rhythmdb_nice_elt_name_from_propid (db, prop);
3589 g_hash_table_insert (metadata,
3590 (gpointer) g_strdup (name),
3591 value);
3593 g_type_class_unref (klass);
3595 /* gather extra metadata */
3596 g_signal_emit (G_OBJECT (db),
3597 rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER], 0,
3598 entry,
3599 metadata);
3601 return metadata;
3604 static gboolean
3605 queue_is_empty (GAsyncQueue *queue)
3607 return g_async_queue_length (queue) <= 0;
3611 * rhythmdb_is_busy:
3612 * @db: a #RhythmDB.
3614 * Returns: whether the #RhythmDB has events to process.
3616 gboolean
3617 rhythmdb_is_busy (RhythmDB *db)
3619 return (!db->priv->action_thread_running || !queue_is_empty (db->priv->event_queue));
3623 * rhythmdb_compute_status_normal:
3624 * @n_songs: the number of tracks.
3625 * @duration: the total duration of the tracks.
3626 * @size: the total size of the tracks.
3627 * @singular: singular form of the format string to use for entries (eg "%d song")
3628 * @plural: plural form of the format string to use for entries (eg "%d songs")
3630 * Creates a string containing the "status" information about a list of tracks.
3631 * The singular and plural strings must be used in a direct ngettext call
3632 * elsewhere in order for them to be marked for translation correctly.
3634 * Returns: the string, which should be freed with g_free.
3636 char *
3637 rhythmdb_compute_status_normal (gint n_songs,
3638 glong duration,
3639 guint64 size,
3640 const char *singular,
3641 const char *plural)
3643 long days, hours, minutes, seconds;
3644 char *songcount = NULL;
3645 char *time = NULL;
3646 char *size_str = NULL;
3647 char *ret;
3648 const char *minutefmt;
3649 const char *hourfmt;
3650 const char *dayfmt;
3652 songcount = g_strdup_printf (ngettext (singular, plural, n_songs), n_songs);
3654 days = duration / (60 * 60 * 24);
3655 hours = (duration / (60 * 60)) - (days * 24);
3656 minutes = (duration / 60) - ((days * 24 * 60) + (hours * 60));
3657 seconds = duration % 60;
3659 minutefmt = ngettext ("%ld minute", "%ld minutes", minutes);
3660 hourfmt = ngettext ("%ld hour", "%ld hours", hours);
3661 dayfmt = ngettext ("%ld day", "%ld days", days);
3662 if (days > 0) {
3663 if (hours > 0)
3664 if (minutes > 0) {
3665 char *fmt;
3666 /* Translators: the format is "X days, X hours and X minutes" */
3667 fmt = g_strdup_printf (_("%s, %s and %s"), dayfmt, hourfmt, minutefmt);
3668 time = g_strdup_printf (fmt, days, hours, minutes);
3669 g_free (fmt);
3670 } else {
3671 char *fmt;
3672 /* Translators: the format is "X days and X hours" */
3673 fmt = g_strdup_printf (_("%s and %s"), dayfmt, hourfmt);
3674 time = g_strdup_printf (fmt, days, hours);
3675 g_free (fmt);
3677 else
3678 if (minutes > 0) {
3679 char *fmt;
3680 /* Translators: the format is "X days and X minutes" */
3681 fmt = g_strdup_printf (_("%s and %s"), dayfmt, minutefmt);
3682 time = g_strdup_printf (fmt, days, minutes);
3683 g_free (fmt);
3684 } else {
3685 time = g_strdup_printf (dayfmt, days);
3687 } else {
3688 if (hours > 0) {
3689 if (minutes > 0) {
3690 char *fmt;
3691 /* Translators: the format is "X hours and X minutes" */
3692 fmt = g_strdup_printf (_("%s and %s"), hourfmt, minutefmt);
3693 time = g_strdup_printf (fmt, hours, minutes);
3694 g_free (fmt);
3695 } else {
3696 time = g_strdup_printf (hourfmt, hours);
3699 } else {
3700 time = g_strdup_printf (minutefmt, minutes);
3704 size_str = gnome_vfs_format_file_size_for_display (size);
3706 if (size > 0 && duration > 0) {
3707 ret = g_strdup_printf ("%s, %s, %s", songcount, time, size_str);
3708 } else if (duration > 0) {
3709 ret = g_strdup_printf ("%s, %s", songcount, time);
3710 } else if (size > 0) {
3711 ret = g_strdup_printf ("%s, %s", songcount, size_str);
3712 } else {
3713 ret = g_strdup (songcount);
3716 g_free (songcount);
3717 g_free (time);
3718 g_free (size_str);
3720 return ret;
3723 static void
3724 default_sync_metadata (RhythmDB *db,
3725 RhythmDBEntry *entry,
3726 GError **error,
3727 gpointer data)
3729 const char *uri;
3730 GError *local_error = NULL;
3732 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
3733 rb_metadata_load (db->priv->metadata,
3734 uri, &local_error);
3735 if (local_error != NULL) {
3736 g_propagate_error (error, local_error);
3737 return;
3740 entry_to_rb_metadata (db, entry, db->priv->metadata);
3742 rb_metadata_save (db->priv->metadata, &local_error);
3743 if (local_error != NULL) {
3744 RhythmDBAction *load_action;
3746 /* reload the metadata, to revert the db changes */
3747 load_action = g_new0 (RhythmDBAction, 1);
3748 load_action->type = RHYTHMDB_ACTION_LOAD;
3749 load_action->uri = rb_refstring_ref (entry->location);
3750 load_action->entry_type = RHYTHMDB_ENTRY_TYPE_INVALID;
3751 g_async_queue_push (db->priv->action_queue, load_action);
3753 g_propagate_error (error, local_error);
3758 * rhythmdb_entry_register_type:
3759 * @db: a #RhythmDB
3760 * @name: optional name for the entry type
3762 * Registers a new #RhythmDBEntryType. This should be called to create a new
3763 * entry type for non-permanent sources.
3765 * Returns: the new #RhythmDBEntryType.
3767 RhythmDBEntryType
3768 rhythmdb_entry_register_type (RhythmDB *db,
3769 const char *name)
3771 RhythmDBEntryType type;
3772 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3774 type = g_new0 (RhythmDBEntryType_, 1);
3775 type->can_sync_metadata = (RhythmDBEntryCanSyncFunc)rb_false_function;
3776 type->sync_metadata = default_sync_metadata;
3777 if (name) {
3778 type->name = g_strdup (name);
3779 g_mutex_lock (db->priv->entry_type_map_mutex);
3780 g_hash_table_insert (db->priv->entry_type_map, g_strdup (type->name), type);
3781 g_mutex_unlock (db->priv->entry_type_map_mutex);
3784 if (klass->impl_entry_type_registered)
3785 klass->impl_entry_type_registered (db, name, type);
3787 return type;
3790 static void
3791 rhythmdb_entry_register_type_alias (RhythmDB *db,
3792 RhythmDBEntryType type,
3793 const char *name)
3795 char *dn = g_strdup (name);
3797 g_mutex_lock (db->priv->entry_type_map_mutex);
3798 g_hash_table_insert (db->priv->entry_type_map, dn, type);
3799 g_mutex_unlock (db->priv->entry_type_map_mutex);
3802 typedef struct {
3803 GHFunc func;
3804 gpointer data;
3805 } RhythmDBEntryTypeForeachData;
3807 static void
3808 rhythmdb_entry_type_foreach_cb (const char *name,
3809 RhythmDBEntryType entry_type,
3810 RhythmDBEntryTypeForeachData *data)
3812 /* skip aliases */
3813 if (strcmp (entry_type->name, name))
3814 return;
3816 data->func ((gpointer) name, entry_type, data->data);
3820 * rhythmdb_entry_type_foreach:
3821 * @db: a #RhythmDB
3822 * @func: callback function to call for each registered entry type
3823 * @data: data to pass to the callback
3825 * Calls a function for each registered entry type.
3827 void
3828 rhythmdb_entry_type_foreach (RhythmDB *db,
3829 GHFunc func,
3830 gpointer data)
3832 RhythmDBEntryTypeForeachData d;
3834 d.func = func;
3835 d.data = data;
3837 g_mutex_lock (db->priv->entry_type_mutex);
3838 g_hash_table_foreach (db->priv->entry_type_map,
3839 (GHFunc) rhythmdb_entry_type_foreach_cb,
3840 &d);
3841 g_mutex_unlock (db->priv->entry_type_mutex);
3845 * rhythmdb_entry_type_get_by_name:
3846 * @db: a #RhythmDB
3847 * @name: name of the type to look for
3849 * Locates a #RhythmDBEntryType by name. Returns
3850 * RHYTHMDB_ENTRY_TYPE_INVALID if no entry type
3851 * is registered with the specified name.
3853 * Returns: the #RhythmDBEntryType
3855 RhythmDBEntryType
3856 rhythmdb_entry_type_get_by_name (RhythmDB *db,
3857 const char *name)
3859 gpointer t = NULL;
3861 g_mutex_lock (db->priv->entry_type_map_mutex);
3862 if (db->priv->entry_type_map) {
3863 t = g_hash_table_lookup (db->priv->entry_type_map, name);
3865 g_mutex_unlock (db->priv->entry_type_map_mutex);
3867 if (t)
3868 return (RhythmDBEntryType) t;
3870 return RHYTHMDB_ENTRY_TYPE_INVALID;
3873 static gboolean
3874 song_can_sync_metadata (RhythmDB *db,
3875 RhythmDBEntry *entry,
3876 gpointer data)
3878 const char *mimetype;
3880 mimetype = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MIMETYPE);
3881 return rb_metadata_can_save (db->priv->metadata, mimetype);
3884 static char *
3885 podcast_get_playback_uri (RhythmDBEntry *entry,
3886 gpointer data)
3888 return rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
3891 static void
3892 podcast_data_destroy (RhythmDBEntry *entry,
3893 gpointer something)
3895 RhythmDBPodcastFields *podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
3896 rb_refstring_unref (podcast->description);
3897 rb_refstring_unref (podcast->subtitle);
3898 rb_refstring_unref (podcast->summary);
3899 rb_refstring_unref (podcast->lang);
3900 rb_refstring_unref (podcast->copyright);
3901 rb_refstring_unref (podcast->image);
3904 static RhythmDBEntryType song_type = RHYTHMDB_ENTRY_TYPE_INVALID;
3905 static RhythmDBEntryType ignore_type = RHYTHMDB_ENTRY_TYPE_INVALID;
3906 static RhythmDBEntryType import_error_type = RHYTHMDB_ENTRY_TYPE_INVALID;
3908 /* to be evicted */
3909 static RhythmDBEntryType podcast_post_type = RHYTHMDB_ENTRY_TYPE_INVALID;
3910 static RhythmDBEntryType podcast_feed_type = RHYTHMDB_ENTRY_TYPE_INVALID;
3912 static void
3913 rhythmdb_register_core_entry_types (RhythmDB *db)
3915 /* regular songs */
3916 song_type = rhythmdb_entry_register_type (db, "song");
3917 rhythmdb_entry_register_type_alias (db, song_type, "0");
3918 song_type->save_to_disk = TRUE;
3919 song_type->category = RHYTHMDB_ENTRY_NORMAL;
3920 song_type->can_sync_metadata = song_can_sync_metadata;
3922 /* import errors */
3923 import_error_type = rhythmdb_entry_register_type (db, "import-error");
3924 import_error_type->get_playback_uri = (RhythmDBEntryStringFunc)rb_null_function;
3925 import_error_type->category = RHYTHMDB_ENTRY_VIRTUAL;
3927 /* ignored files */
3928 ignore_type = rhythmdb_entry_register_type (db, "ignore");
3929 ignore_type->save_to_disk = TRUE;
3930 ignore_type->category = RHYTHMDB_ENTRY_VIRTUAL;
3932 /* podcast posts */
3933 podcast_post_type = rhythmdb_entry_register_type (db, "podcast-post");
3934 podcast_post_type->entry_type_data_size = sizeof (RhythmDBPodcastFields);
3935 podcast_post_type->save_to_disk = TRUE;
3936 podcast_post_type->category = RHYTHMDB_ENTRY_NORMAL;
3937 podcast_post_type->pre_entry_destroy = (RhythmDBEntryActionFunc) podcast_data_destroy;
3938 podcast_post_type->get_playback_uri = podcast_get_playback_uri;
3940 /* podcast feeds */
3941 podcast_feed_type = rhythmdb_entry_register_type (db, "podcast-feed");
3942 podcast_feed_type->entry_type_data_size = sizeof (RhythmDBPodcastFields);
3943 podcast_feed_type->save_to_disk = TRUE;
3944 podcast_feed_type->category = RHYTHMDB_ENTRY_VIRTUAL;
3945 podcast_feed_type->pre_entry_destroy = (RhythmDBEntryActionFunc) podcast_data_destroy;
3948 RhythmDBEntryType
3949 rhythmdb_entry_song_get_type (void)
3951 return song_type;
3954 RhythmDBEntryType
3955 rhythmdb_entry_ignore_get_type (void)
3957 return ignore_type;
3960 RhythmDBEntryType
3961 rhythmdb_entry_import_error_get_type (void)
3963 return import_error_type;
3966 RhythmDBEntryType
3967 rhythmdb_entry_podcast_post_get_type (void)
3969 return podcast_post_type;
3972 RhythmDBEntryType
3973 rhythmdb_entry_podcast_feed_get_type (void)
3975 return podcast_feed_type;
3978 static void
3979 rhythmdb_entry_set_mount_point (RhythmDB *db,
3980 RhythmDBEntry *entry,
3981 const gchar *realuri)
3983 gchar *mount_point;
3984 GValue value = {0, };
3986 mount_point = rb_uri_get_mount_point (realuri);
3987 if (mount_point != NULL) {
3988 g_value_init (&value, G_TYPE_STRING);
3989 g_value_set_string_take_ownership (&value, mount_point);
3990 rhythmdb_entry_set_internal (db, entry, FALSE,
3991 RHYTHMDB_PROP_MOUNTPOINT,
3992 &value);
3993 g_value_unset (&value);
3997 void
3998 rhythmdb_entry_set_visibility (RhythmDB *db,
3999 RhythmDBEntry *entry,
4000 gboolean visible)
4002 GValue old_val = {0, };
4003 gboolean old_visible;
4005 g_return_if_fail (RHYTHMDB_IS (db));
4006 g_return_if_fail (entry != NULL);
4008 g_value_init (&old_val, G_TYPE_BOOLEAN);
4010 rhythmdb_entry_get (db, entry, RHYTHMDB_PROP_HIDDEN, &old_val);
4011 old_visible = !g_value_get_boolean (&old_val);
4013 if ((old_visible && !visible) || (!old_visible && visible)) {
4014 GValue new_val = {0, };
4016 g_value_init (&new_val, G_TYPE_BOOLEAN);
4017 g_value_set_boolean (&new_val, !visible);
4018 rhythmdb_entry_set_internal (db, entry, TRUE,
4019 RHYTHMDB_PROP_HIDDEN, &new_val);
4020 g_value_unset (&new_val);
4022 g_value_unset (&old_val);
4025 static gboolean
4026 rhythmdb_idle_save (RhythmDB *db)
4028 if (db->priv->dirty) {
4029 rb_debug ("database is dirty, doing regular save");
4030 rhythmdb_save_async (db);
4033 return TRUE;
4036 static void
4037 rhythmdb_sync_library_location (RhythmDB *db)
4039 gboolean reload = (db->priv->library_locations != NULL);
4041 if (db->priv->library_location_notify_id == 0) {
4042 db->priv->library_location_notify_id =
4043 eel_gconf_notification_add (CONF_LIBRARY_LOCATION,
4044 (GConfClientNotifyFunc) library_location_changed_cb,
4045 db);
4048 if (reload) {
4049 rb_debug ("ending monitor of old library directories");
4051 rhythmdb_stop_monitoring (db);
4053 g_slist_foreach (db->priv->library_locations, (GFunc) g_free, NULL);
4054 g_slist_free (db->priv->library_locations);
4055 db->priv->library_locations = NULL;
4058 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY)) {
4059 db->priv->library_locations = eel_gconf_get_string_list (CONF_LIBRARY_LOCATION);
4061 rhythmdb_start_monitoring (db);
4065 static void
4066 library_location_changed_cb (GConfClient *client,
4067 guint cnxn_id,
4068 GConfEntry *entry,
4069 RhythmDB *db)
4071 rhythmdb_sync_library_location (db);
4074 char *
4075 rhythmdb_entry_dup_string (RhythmDBEntry *entry,
4076 RhythmDBPropType propid)
4078 const char *s;
4080 g_return_val_if_fail (entry != NULL, NULL);
4082 s = rhythmdb_entry_get_string (entry, propid);
4083 if (s != NULL) {
4084 return g_strdup (s);
4085 } else {
4086 return NULL;
4090 const char *
4091 rhythmdb_entry_get_string (RhythmDBEntry *entry,
4092 RhythmDBPropType propid)
4094 RhythmDBPodcastFields *podcast = NULL;
4096 g_return_val_if_fail (entry != NULL, NULL);
4097 g_return_val_if_fail (entry->refcount > 0, NULL);
4099 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
4100 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
4101 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4103 rhythmdb_entry_sync_mirrored (entry, propid);
4105 switch (propid) {
4106 case RHYTHMDB_PROP_TITLE:
4107 return rb_refstring_get (entry->title);
4108 case RHYTHMDB_PROP_ALBUM:
4109 return rb_refstring_get (entry->album);
4110 case RHYTHMDB_PROP_ARTIST:
4111 return rb_refstring_get (entry->artist);
4112 case RHYTHMDB_PROP_GENRE:
4113 return rb_refstring_get (entry->genre);
4114 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4115 return rb_refstring_get (entry->musicbrainz_trackid);
4116 case RHYTHMDB_PROP_MIMETYPE:
4117 return rb_refstring_get (entry->mimetype);
4118 case RHYTHMDB_PROP_TITLE_SORT_KEY:
4119 return rb_refstring_get_sort_key (entry->title);
4120 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
4121 return rb_refstring_get_sort_key (entry->album);
4122 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
4123 return rb_refstring_get_sort_key (entry->artist);
4124 case RHYTHMDB_PROP_GENRE_SORT_KEY:
4125 return rb_refstring_get_sort_key (entry->genre);
4126 case RHYTHMDB_PROP_TITLE_FOLDED:
4127 return rb_refstring_get_folded (entry->title);
4128 case RHYTHMDB_PROP_ALBUM_FOLDED:
4129 return rb_refstring_get_folded (entry->album);
4130 case RHYTHMDB_PROP_ARTIST_FOLDED:
4131 return rb_refstring_get_folded (entry->artist);
4132 case RHYTHMDB_PROP_GENRE_FOLDED:
4133 return rb_refstring_get_folded (entry->genre);
4134 case RHYTHMDB_PROP_LOCATION:
4135 return rb_refstring_get (entry->location);
4136 case RHYTHMDB_PROP_MOUNTPOINT:
4137 return rb_refstring_get (entry->mountpoint);
4138 case RHYTHMDB_PROP_LAST_PLAYED_STR:
4139 return rb_refstring_get (entry->last_played_str);
4140 case RHYTHMDB_PROP_PLAYBACK_ERROR:
4141 return rb_refstring_get (entry->playback_error);
4142 case RHYTHMDB_PROP_FIRST_SEEN_STR:
4143 return rb_refstring_get (entry->first_seen_str);
4144 case RHYTHMDB_PROP_LAST_SEEN_STR:
4145 return rb_refstring_get (entry->last_seen_str);
4146 case RHYTHMDB_PROP_SEARCH_MATCH:
4147 return NULL; /* synthetic property */
4148 /* Podcast properties */
4149 case RHYTHMDB_PROP_DESCRIPTION:
4150 if (podcast)
4151 return rb_refstring_get (podcast->description);
4152 else
4153 return NULL;
4154 case RHYTHMDB_PROP_SUBTITLE:
4155 if (podcast)
4156 return rb_refstring_get (podcast->subtitle);
4157 else
4158 return NULL;
4159 case RHYTHMDB_PROP_SUMMARY:
4160 if (podcast)
4161 return rb_refstring_get (podcast->summary);
4162 else
4163 return NULL;
4164 case RHYTHMDB_PROP_LANG:
4165 if (podcast)
4166 return rb_refstring_get (podcast->lang);
4167 else
4168 return NULL;
4169 case RHYTHMDB_PROP_COPYRIGHT:
4170 if (podcast)
4171 return rb_refstring_get (podcast->copyright);
4172 else
4173 return NULL;
4174 case RHYTHMDB_PROP_IMAGE:
4175 if (podcast)
4176 return rb_refstring_get (podcast->image);
4177 else
4178 return NULL;
4180 default:
4181 g_assert_not_reached ();
4182 return NULL;
4186 RBRefString *
4187 rhythmdb_entry_get_refstring (RhythmDBEntry *entry,
4188 RhythmDBPropType propid)
4190 g_return_val_if_fail (entry != NULL, NULL);
4191 g_return_val_if_fail (entry->refcount > 0, NULL);
4193 rhythmdb_entry_sync_mirrored (entry, propid);
4195 switch (propid) {
4196 case RHYTHMDB_PROP_TITLE:
4197 return rb_refstring_ref (entry->title);
4198 case RHYTHMDB_PROP_ALBUM:
4199 return rb_refstring_ref (entry->album);
4200 case RHYTHMDB_PROP_ARTIST:
4201 return rb_refstring_ref (entry->artist);
4202 case RHYTHMDB_PROP_GENRE:
4203 return rb_refstring_ref (entry->genre);
4204 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4205 return rb_refstring_ref (entry->musicbrainz_trackid);
4206 case RHYTHMDB_PROP_MIMETYPE:
4207 return rb_refstring_ref (entry->mimetype);
4208 case RHYTHMDB_PROP_MOUNTPOINT:
4209 return rb_refstring_ref (entry->mountpoint);
4210 case RHYTHMDB_PROP_LAST_PLAYED_STR:
4211 return rb_refstring_ref (entry->last_played_str);
4212 case RHYTHMDB_PROP_FIRST_SEEN_STR:
4213 return rb_refstring_ref (entry->first_seen_str);
4214 case RHYTHMDB_PROP_LAST_SEEN_STR:
4215 return rb_refstring_ref (entry->last_seen_str);
4216 case RHYTHMDB_PROP_LOCATION:
4217 return rb_refstring_ref (entry->location);
4218 case RHYTHMDB_PROP_PLAYBACK_ERROR:
4219 return rb_refstring_ref (entry->playback_error);
4220 default:
4221 g_assert_not_reached ();
4222 return NULL;
4226 gboolean
4227 rhythmdb_entry_get_boolean (RhythmDBEntry *entry,
4228 RhythmDBPropType propid)
4230 g_return_val_if_fail (entry != NULL, FALSE);
4232 switch (propid) {
4233 case RHYTHMDB_PROP_HIDDEN:
4234 return ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
4235 default:
4236 g_assert_not_reached ();
4237 return FALSE;
4241 guint64
4242 rhythmdb_entry_get_uint64 (RhythmDBEntry *entry,
4243 RhythmDBPropType propid)
4245 g_return_val_if_fail (entry != NULL, 0);
4247 switch (propid) {
4248 case RHYTHMDB_PROP_FILE_SIZE:
4249 return entry->file_size;
4250 default:
4251 g_assert_not_reached ();
4252 return 0;
4256 RhythmDBEntryType
4257 rhythmdb_entry_get_entry_type (RhythmDBEntry *entry)
4259 g_return_val_if_fail (entry != NULL, RHYTHMDB_ENTRY_TYPE_INVALID);
4261 return entry->type;
4264 gpointer
4265 rhythmdb_entry_get_pointer (RhythmDBEntry *entry,
4266 RhythmDBPropType propid)
4268 g_return_val_if_fail (entry != NULL, NULL);
4270 switch (propid) {
4271 case RHYTHMDB_PROP_TYPE:
4272 return entry->type;
4273 default:
4274 g_assert_not_reached ();
4275 return NULL;
4279 gulong
4280 rhythmdb_entry_get_ulong (RhythmDBEntry *entry,
4281 RhythmDBPropType propid)
4283 RhythmDBPodcastFields *podcast = NULL;
4285 g_return_val_if_fail (entry != NULL, 0);
4287 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
4288 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST)
4289 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4291 switch (propid) {
4292 case RHYTHMDB_PROP_ENTRY_ID:
4293 return entry->id;
4294 case RHYTHMDB_PROP_TRACK_NUMBER:
4295 return entry->tracknum;
4296 case RHYTHMDB_PROP_DISC_NUMBER:
4297 return entry->discnum;
4298 case RHYTHMDB_PROP_DURATION:
4299 return entry->duration;
4300 case RHYTHMDB_PROP_MTIME:
4301 return entry->mtime;
4302 case RHYTHMDB_PROP_FIRST_SEEN:
4303 return entry->first_seen;
4304 case RHYTHMDB_PROP_LAST_SEEN:
4305 return entry->last_seen;
4306 case RHYTHMDB_PROP_LAST_PLAYED:
4307 return entry->last_played;
4308 case RHYTHMDB_PROP_PLAY_COUNT:
4309 return entry->play_count;
4310 case RHYTHMDB_PROP_BITRATE:
4311 return entry->bitrate;
4312 case RHYTHMDB_PROP_DATE:
4313 if (g_date_valid (&entry->date))
4314 return g_date_get_julian (&entry->date);
4315 else
4316 return 0;
4317 case RHYTHMDB_PROP_YEAR:
4318 if (g_date_valid (&entry->date))
4319 return g_date_get_year (&entry->date);
4320 else
4321 return 0;
4322 case RHYTHMDB_PROP_POST_TIME:
4323 if (podcast)
4324 return podcast->post_time;
4325 else
4326 return 0;
4327 case RHYTHMDB_PROP_STATUS:
4328 if (podcast)
4329 return podcast->status;
4330 else
4331 return 0;
4332 default:
4333 g_assert_not_reached ();
4334 return 0;
4338 double
4339 rhythmdb_entry_get_double (RhythmDBEntry *entry,
4340 RhythmDBPropType propid)
4342 g_return_val_if_fail (entry != NULL, 0);
4344 switch (propid) {
4345 case RHYTHMDB_PROP_TRACK_GAIN:
4346 return entry->track_gain;
4347 case RHYTHMDB_PROP_TRACK_PEAK:
4348 return entry->track_peak;
4349 case RHYTHMDB_PROP_ALBUM_GAIN:
4350 return entry->album_gain;
4351 case RHYTHMDB_PROP_ALBUM_PEAK:
4352 return entry->album_peak;
4353 case RHYTHMDB_PROP_RATING:
4354 return entry->rating;
4355 default:
4356 g_assert_not_reached ();
4357 return 0.0;
4361 char *
4362 rhythmdb_entry_get_playback_uri (RhythmDBEntry *entry)
4364 RhythmDBEntryType type;
4366 g_return_val_if_fail (entry != NULL, NULL);
4368 type = rhythmdb_entry_get_entry_type (entry);
4369 if (type->get_playback_uri)
4370 return (type->get_playback_uri) (entry, type->get_playback_uri_data);
4371 else
4372 return rhythmdb_entry_dup_string (entry, RHYTHMDB_PROP_LOCATION);
4375 GType
4376 rhythmdb_get_property_type (RhythmDB *db,
4377 guint property_id)
4379 g_assert (property_id >= 0 && property_id < RHYTHMDB_NUM_PROPERTIES);
4380 return rhythmdb_property_type_map[property_id];
4383 GType
4384 rhythmdb_entry_get_type (void)
4386 static GType type = 0;
4388 if (G_UNLIKELY (type == 0)) {
4389 type = g_boxed_type_register_static ("RhythmDBEntry",
4390 (GBoxedCopyFunc)rhythmdb_entry_ref,
4391 (GBoxedFreeFunc)rhythmdb_entry_unref);
4394 return type;
4397 GType
4398 rhythmdb_entry_type_get_type (void)
4400 static GType type = 0;
4402 if (G_UNLIKELY (type == 0)) {
4403 type = g_boxed_type_register_static ("RhythmDBEntryType",
4404 (GBoxedCopyFunc)rb_copy_function,
4405 (GBoxedFreeFunc)rb_null_function);
4408 return type;