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.
25 #define G_IMPLEMENT_INLINES 1
26 #define __RHYTHMDB_C__
28 #undef G_IMPLEMENT_INLINES
31 #include <libxml/tree.h>
33 #include <glib-object.h>
34 #include <glib/gi18n.h>
35 #include <gobject/gvaluecollector.h>
37 #include <gconf/gconf-client.h>
39 #include "rb-marshal.h"
40 #include "rb-file-helpers.h"
43 #include "rb-cut-and-paste-code.h"
44 #include "rb-preferences.h"
45 #include "eel-gconf-extensions.h"
46 #include "rhythmdb-private.h"
47 #include "rhythmdb-property-model.h"
48 #include "rb-dialog.h"
51 #define RB_PARSE_NICK_START (xmlChar *) "["
52 #define RB_PARSE_NICK_END (xmlChar *) "]"
54 GType rhythmdb_property_type_map
[RHYTHMDB_NUM_PROPERTIES
];
56 #define RHYTHMDB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE, RhythmDBPrivate))
57 G_DEFINE_ABSTRACT_TYPE(RhythmDB
, rhythmdb
, G_TYPE_OBJECT
)
64 RhythmDBQueryResults
*results
;
66 } RhythmDBQueryThreadData
;
72 RhythmDBEntryType type
;
73 } RhythmDBAddThreadData
;
83 RhythmDBEntryType entry_type
;
86 static void rhythmdb_finalize (GObject
*object
);
87 static void rhythmdb_set_property (GObject
*object
,
91 static void rhythmdb_get_property (GObject
*object
,
95 static void rhythmdb_thread_create (RhythmDB
*db
,
99 static void rhythmdb_read_enter (RhythmDB
*db
);
100 static void rhythmdb_read_leave (RhythmDB
*db
);
101 static gboolean
rhythmdb_idle_poll_events (RhythmDB
*db
);
102 static gpointer
add_thread_main (RhythmDBAddThreadData
*data
);
103 static gpointer
action_thread_main (RhythmDB
*db
);
104 static gpointer
query_thread_main (RhythmDBQueryThreadData
*data
);
105 static void rhythmdb_entry_set_mount_point (RhythmDB
*db
,
106 RhythmDBEntry
*entry
,
107 const gchar
*realuri
);
109 static gboolean
free_entry_changes (RhythmDBEntry
*entry
,
112 static gboolean
rhythmdb_idle_save (RhythmDB
*db
);
113 static void library_location_changed_cb (GConfClient
*client
,
117 static void rhythmdb_sync_library_location (RhythmDB
*db
);
118 static void rhythmdb_entry_sync_mirrored (RhythmDBEntry
*entry
,
120 static void rhythmdb_register_core_entry_types (RhythmDB
*db
);
121 static gboolean
rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint
*ihint
,
123 const GValue
*handler_return
,
139 ENTRY_EXTRA_METADATA_REQUEST
,
140 ENTRY_EXTRA_METADATA_NOTIFY
,
141 ENTRY_EXTRA_METADATA_GATHER
,
149 static guint rhythmdb_signals
[LAST_SIGNAL
] = { 0 };
152 rhythmdb_class_init (RhythmDBClass
*klass
)
154 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
156 object_class
->finalize
= rhythmdb_finalize
;
158 object_class
->set_property
= rhythmdb_set_property
;
159 object_class
->get_property
= rhythmdb_get_property
;
161 g_object_class_install_property (object_class
,
163 g_param_spec_string ("name",
169 g_object_class_install_property (object_class
,
171 g_param_spec_boolean ("dry-run",
173 "Whether or not changes should be saved",
176 g_object_class_install_property (object_class
,
178 g_param_spec_boolean ("no-update",
180 "Whether or not to update the database",
183 rhythmdb_signals
[ENTRY_ADDED
] =
184 g_signal_new ("entry_added",
187 G_STRUCT_OFFSET (RhythmDBClass
, entry_added
),
189 g_cclosure_marshal_VOID__BOXED
,
191 1, RHYTHMDB_TYPE_ENTRY
);
193 rhythmdb_signals
[ENTRY_DELETED
] =
194 g_signal_new ("entry_deleted",
197 G_STRUCT_OFFSET (RhythmDBClass
, entry_deleted
),
199 g_cclosure_marshal_VOID__BOXED
,
201 1, RHYTHMDB_TYPE_ENTRY
);
203 rhythmdb_signals
[ENTRY_CHANGED
] =
204 g_signal_new ("entry_changed",
207 G_STRUCT_OFFSET (RhythmDBClass
, entry_changed
),
209 rb_marshal_VOID__BOXED_POINTER
,
211 RHYTHMDB_TYPE_ENTRY
, G_TYPE_POINTER
);
213 rhythmdb_signals
[ENTRY_EXTRA_METADATA_REQUEST
] =
214 g_signal_new ("entry_extra_metadata_request",
215 G_OBJECT_CLASS_TYPE (object_class
),
216 G_SIGNAL_RUN_LAST
| G_SIGNAL_DETAILED
,
217 G_STRUCT_OFFSET (RhythmDBClass
, entry_extra_metadata_request
),
218 rhythmdb_entry_extra_metadata_accumulator
, NULL
,
219 rb_marshal_BOXED__BOXED
,
221 RHYTHMDB_TYPE_ENTRY
);
223 rhythmdb_signals
[ENTRY_EXTRA_METADATA_NOTIFY
] =
224 g_signal_new ("entry_extra_metadata_notify",
225 G_OBJECT_CLASS_TYPE (object_class
),
226 G_SIGNAL_RUN_LAST
| G_SIGNAL_DETAILED
,
227 G_STRUCT_OFFSET (RhythmDBClass
, entry_extra_metadata_notify
),
229 rb_marshal_VOID__BOXED_STRING_BOXED
,
231 RHYTHMDB_TYPE_ENTRY
, G_TYPE_STRING
, G_TYPE_VALUE
);
233 rhythmdb_signals
[ENTRY_EXTRA_METADATA_GATHER
] =
234 g_signal_new ("entry_extra_metadata_gather",
235 G_OBJECT_CLASS_TYPE (object_class
),
237 G_STRUCT_OFFSET (RhythmDBClass
, entry_extra_metadata_gather
),
239 #if (GLIB_MAJOR_VERSION >= 2 && GLIB_MINOR_VERSION >= 10)
240 rb_marshal_VOID__BOXED_BOXED
,
242 RHYTHMDB_TYPE_ENTRY
, G_TYPE_HASH_TABLE
);
244 /* work with glib < 2.10 */
245 rb_marshal_VOID__BOXED_POINTER
,
247 RHYTHMDB_TYPE_ENTRY
, G_TYPE_POINTER
);
250 rhythmdb_signals
[LOAD_COMPLETE
] =
251 g_signal_new ("load_complete",
254 G_STRUCT_OFFSET (RhythmDBClass
, load_complete
),
256 g_cclosure_marshal_VOID__VOID
,
260 rhythmdb_signals
[SAVE_COMPLETE
] =
261 g_signal_new ("save_complete",
264 G_STRUCT_OFFSET (RhythmDBClass
, save_complete
),
266 g_cclosure_marshal_VOID__VOID
,
270 rhythmdb_signals
[SAVE_ERROR
] =
271 g_signal_new ("save-error",
272 G_OBJECT_CLASS_TYPE (object_class
),
274 G_STRUCT_OFFSET (RhythmDBClass
, save_error
),
276 rb_marshal_VOID__STRING_POINTER
,
282 rhythmdb_signals
[READ_ONLY
] =
283 g_signal_new ("read-only",
284 G_OBJECT_CLASS_TYPE (object_class
),
286 G_STRUCT_OFFSET (RhythmDBClass
, read_only
),
288 g_cclosure_marshal_VOID__BOOLEAN
,
293 g_type_class_add_private (klass
, sizeof (RhythmDBPrivate
));
297 metadata_field_from_prop (RhythmDBPropType prop
,
298 RBMetaDataField
*field
)
301 case RHYTHMDB_PROP_TITLE
:
302 *field
= RB_METADATA_FIELD_TITLE
;
304 case RHYTHMDB_PROP_ARTIST
:
305 *field
= RB_METADATA_FIELD_ARTIST
;
307 case RHYTHMDB_PROP_ALBUM
:
308 *field
= RB_METADATA_FIELD_ALBUM
;
310 case RHYTHMDB_PROP_GENRE
:
311 *field
= RB_METADATA_FIELD_GENRE
;
313 case RHYTHMDB_PROP_TRACK_NUMBER
:
314 *field
= RB_METADATA_FIELD_TRACK_NUMBER
;
316 case RHYTHMDB_PROP_DISC_NUMBER
:
317 *field
= RB_METADATA_FIELD_DISC_NUMBER
;
319 case RHYTHMDB_PROP_DATE
:
320 *field
= RB_METADATA_FIELD_DATE
;
322 case RHYTHMDB_PROP_TRACK_GAIN
:
323 *field
= RB_METADATA_FIELD_TRACK_GAIN
;
325 case RHYTHMDB_PROP_TRACK_PEAK
:
326 *field
= RB_METADATA_FIELD_TRACK_PEAK
;
328 case RHYTHMDB_PROP_ALBUM_GAIN
:
329 *field
= RB_METADATA_FIELD_ALBUM_GAIN
;
331 case RHYTHMDB_PROP_ALBUM_PEAK
:
332 *field
= RB_METADATA_FIELD_ALBUM_PEAK
;
334 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
335 *field
= RB_METADATA_FIELD_MUSICBRAINZ_TRACKID
;
343 extract_gtype_from_enum_entry (RhythmDB
*db
,
349 RBMetaDataField field
;
353 value
= g_enum_get_value (klass
, i
);
355 typename
= strstr (value
->value_nick
, "(");
356 g_assert (typename
!= NULL
);
358 typename_end
= strstr (typename
, ")");
359 g_assert (typename_end
);
362 typename
= g_strndup (typename
, typename_end
-typename
);
363 ret
= g_type_from_name (typename
);
366 /* Check to see whether this is a property that maps to
367 a RBMetaData property. */
368 if (metadata_field_from_prop (value
->value
, &field
))
369 g_assert (ret
== rb_metadata_get_field_type (field
));
374 extract_nice_name_from_enum_entry (RhythmDB
*db
,
381 const xmlChar
*name_end
;
383 value
= g_enum_get_value (klass
, i
);
384 nick
= BAD_CAST value
->value_nick
;
386 name
= xmlStrstr (nick
, RB_PARSE_NICK_START
);
387 g_return_val_if_fail (name
!= NULL
, NULL
);
388 name_end
= xmlStrstr (name
, RB_PARSE_NICK_END
);
391 return xmlStrndup (name
, name_end
- name
);
395 rhythmdb_init (RhythmDB
*db
)
398 GEnumClass
*prop_class
;
400 db
->priv
= RHYTHMDB_GET_PRIVATE (db
);
402 db
->priv
->action_queue
= g_async_queue_new ();
403 db
->priv
->event_queue
= g_async_queue_new ();
404 db
->priv
->restored_queue
= g_async_queue_new ();
406 db
->priv
->query_thread_pool
= g_thread_pool_new ((GFunc
)query_thread_main
,
409 /* Limit this pool to 3 threads. They'll each be thrashing the disk,
410 * so parallelism is limited.
412 db
->priv
->add_thread_pool
= g_thread_pool_new ((GFunc
)add_thread_main
,
416 db
->priv
->metadata
= rb_metadata_new ();
418 prop_class
= g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE
);
420 g_assert (prop_class
->n_values
== RHYTHMDB_NUM_PROPERTIES
);
421 db
->priv
->column_xml_names
= g_new0 (xmlChar
*, RHYTHMDB_NUM_PROPERTIES
);
423 /* Now, extract the GType and XML tag of each column from the
424 * enum descriptions, and cache that for later use. */
425 for (i
= 0; i
< prop_class
->n_values
; i
++) {
426 rhythmdb_property_type_map
[i
] = extract_gtype_from_enum_entry (db
, prop_class
, i
);
427 g_assert (rhythmdb_property_type_map
[i
] != G_TYPE_INVALID
);
429 db
->priv
->column_xml_names
[i
] = extract_nice_name_from_enum_entry (db
, prop_class
, i
);
430 g_assert (db
->priv
->column_xml_names
[i
]);
433 g_type_class_unref (prop_class
);
435 db
->priv
->propname_map
= g_hash_table_new (g_str_hash
, g_str_equal
);
437 for (i
= 0; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
438 const xmlChar
*name
= rhythmdb_nice_elt_name_from_propid (db
, i
);
439 g_hash_table_insert (db
->priv
->propname_map
, (gpointer
) name
, GINT_TO_POINTER (i
));
442 db
->priv
->entry_type_map
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, NULL
);
443 db
->priv
->entry_type_map_mutex
= g_mutex_new ();
444 db
->priv
->entry_type_mutex
= g_mutex_new ();
445 rhythmdb_register_core_entry_types (db
);
447 db
->priv
->stat_events
= g_hash_table_new_full (gnome_vfs_uri_hash
,
448 (GEqualFunc
) gnome_vfs_uri_equal
,
449 (GDestroyNotify
) gnome_vfs_uri_unref
,
451 db
->priv
->stat_mutex
= g_mutex_new ();
453 db
->priv
->change_mutex
= g_mutex_new ();
455 db
->priv
->changed_entries
= g_hash_table_new_full (NULL
,
457 (GDestroyNotify
) rhythmdb_entry_unref
,
459 db
->priv
->added_entries
= g_hash_table_new_full (NULL
,
461 (GDestroyNotify
) rhythmdb_entry_unref
,
463 db
->priv
->deleted_entries
= g_hash_table_new_full (NULL
,
465 (GDestroyNotify
) rhythmdb_entry_unref
,
468 db
->priv
->event_poll_id
= g_idle_add ((GSourceFunc
) rhythmdb_idle_poll_events
, db
);
470 db
->priv
->saving_condition
= g_cond_new ();
471 db
->priv
->saving_mutex
= g_mutex_new ();
473 db
->priv
->can_save
= TRUE
;
474 db
->priv
->exiting
= FALSE
;
475 db
->priv
->saving
= FALSE
;
476 db
->priv
->dirty
= FALSE
;
478 db
->priv
->empty_string
= rb_refstring_new ("");
479 db
->priv
->octet_stream_str
= rb_refstring_new ("application/octet-stream");
481 db
->priv
->next_entry_id
= 1;
483 rhythmdb_init_monitoring (db
);
487 make_access_failed_error (const char *uri
, GnomeVFSResult result
)
493 /* make sure the URI we put in the error message is valid utf8 */
494 unescaped
= gnome_vfs_unescape_string_for_display (uri
);
495 utf8ised
= rb_make_valid_utf8 (unescaped
, '?');
497 error
= g_error_new (RHYTHMDB_ERROR
,
498 RHYTHMDB_ERROR_ACCESS_FAILED
,
499 _("Couldn't access %s: %s"),
501 gnome_vfs_result_to_string (result
));
502 rb_debug ("got error on %s: %s", utf8ised
, error
->message
);
509 rhythmdb_execute_multi_stat_info_cb (GnomeVFSAsyncHandle
*handle
,
511 /* GnomeVFSGetFileInfoResult* items */
514 g_mutex_lock (db
->priv
->stat_mutex
);
515 while (results
!= NULL
) {
516 GnomeVFSGetFileInfoResult
*info_result
= results
->data
;
517 RhythmDBEvent
*event
;
519 event
= g_hash_table_lookup (db
->priv
->stat_events
, info_result
->uri
);
522 uri_string
= gnome_vfs_uri_to_string (info_result
->uri
, GNOME_VFS_URI_HIDE_NONE
);
523 rb_debug ("ignoring unexpected uri in gnome_vfs_async_get_file_info response: %s",
526 results
= results
->next
;
529 g_hash_table_remove (db
->priv
->stat_events
, info_result
->uri
);
531 if (info_result
->result
== GNOME_VFS_OK
) {
532 event
->vfsinfo
= gnome_vfs_file_info_dup (info_result
->file_info
);
534 event
->error
= make_access_failed_error (rb_refstring_get (event
->real_uri
),
535 info_result
->result
);
536 event
->vfsinfo
= NULL
;
538 g_async_queue_push (db
->priv
->event_queue
, event
);
540 results
= results
->next
;
542 db
->priv
->stat_handle
= NULL
;
543 g_mutex_unlock (db
->priv
->stat_mutex
);
547 rhythmdb_start_action_thread (RhythmDB
*db
)
549 g_mutex_lock (db
->priv
->stat_mutex
);
550 db
->priv
->action_thread_running
= TRUE
;
551 rhythmdb_thread_create (db
, NULL
, (GThreadFunc
) action_thread_main
, db
);
553 if (db
->priv
->stat_list
!= NULL
) {
554 gnome_vfs_async_get_file_info (&db
->priv
->stat_handle
, db
->priv
->stat_list
,
555 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
,
556 GNOME_VFS_PRIORITY_MIN
,
557 (GnomeVFSAsyncGetFileInfoCallback
) rhythmdb_execute_multi_stat_info_cb
,
559 g_list_free (db
->priv
->stat_list
);
560 db
->priv
->stat_list
= NULL
;
563 g_mutex_unlock (db
->priv
->stat_mutex
);
567 rhythmdb_action_free (RhythmDB
*db
,
568 RhythmDBAction
*action
)
570 rb_refstring_unref (action
->uri
);
575 rhythmdb_event_free (RhythmDB
*db
,
576 RhythmDBEvent
*result
)
578 switch (result
->type
) {
579 case RHYTHMDB_EVENT_THREAD_EXITED
:
581 g_assert (g_atomic_int_dec_and_test (&db
->priv
->outstanding_threads
) >= 0);
582 g_async_queue_unref (db
->priv
->action_queue
);
583 g_async_queue_unref (db
->priv
->event_queue
);
585 case RHYTHMDB_EVENT_STAT
:
586 case RHYTHMDB_EVENT_METADATA_LOAD
:
587 case RHYTHMDB_EVENT_DB_LOAD
:
588 case RHYTHMDB_EVENT_DB_SAVED
:
589 case RHYTHMDB_EVENT_QUERY_COMPLETE
:
590 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED
:
591 case RHYTHMDB_EVENT_FILE_DELETED
:
593 case RHYTHMDB_EVENT_ENTRY_SET
:
594 g_value_unset (&result
->change
.new);
598 g_error_free (result
->error
);
599 rb_refstring_unref (result
->uri
);
600 rb_refstring_unref (result
->real_uri
);
602 gnome_vfs_file_info_unref (result
->vfsinfo
);
603 if (result
->metadata
)
604 g_object_unref (result
->metadata
);
606 g_object_unref (result
->results
);
608 gnome_vfs_async_cancel (result
->handle
);
609 if (result
->entry
!= NULL
) {
610 rhythmdb_entry_unref (result
->entry
);
618 * Ceases all #RhythmDB operations, including stopping all directory monitoring, and
619 * removing all actions and events currently queued.
622 _shutdown_foreach_swapped (RhythmDBEvent
*event
, RhythmDB
*db
)
624 rhythmdb_event_free (db
, event
);
628 rhythmdb_shutdown (RhythmDB
*db
)
630 RhythmDBEvent
*result
;
631 RhythmDBAction
*action
;
633 g_return_if_fail (RHYTHMDB_IS (db
));
635 db
->priv
->exiting
= TRUE
;
637 eel_gconf_notification_remove (db
->priv
->library_location_notify_id
);
638 g_slist_foreach (db
->priv
->library_locations
, (GFunc
) g_free
, NULL
);
639 g_slist_free (db
->priv
->library_locations
);
640 db
->priv
->library_locations
= NULL
;
642 /* abort all async vfs operations */
643 g_mutex_lock (db
->priv
->stat_mutex
);
644 if (db
->priv
->stat_handle
) {
645 gnome_vfs_async_cancel (db
->priv
->stat_handle
);
646 db
->priv
->stat_handle
= NULL
;
648 g_list_foreach (db
->priv
->outstanding_stats
, (GFunc
)_shutdown_foreach_swapped
, db
);
649 g_list_free (db
->priv
->outstanding_stats
);
650 db
->priv
->outstanding_stats
= NULL
;
651 g_mutex_unlock (db
->priv
->stat_mutex
);
653 while ((action
= g_async_queue_try_pop (db
->priv
->action_queue
)) != NULL
) {
654 rhythmdb_action_free (db
, action
);
657 rb_debug ("%d outstanding threads", g_atomic_int_get (&db
->priv
->outstanding_threads
));
658 while (g_atomic_int_get (&db
->priv
->outstanding_threads
) > 0) {
659 result
= g_async_queue_pop (db
->priv
->event_queue
);
660 rhythmdb_event_free (db
, result
);
664 while ((result
= g_async_queue_try_pop (db
->priv
->event_queue
)) != NULL
)
665 rhythmdb_event_free (db
, result
);
669 _shutdown_foreach_hash (gpointer uri
, RhythmDBEvent
*event
, RhythmDB
*db
)
671 rhythmdb_event_free (db
, event
);
675 rhythmdb_finalize (GObject
*object
)
680 g_return_if_fail (object
!= NULL
);
681 g_return_if_fail (RHYTHMDB_IS (object
));
683 rb_debug ("finalizing rhythmdb");
684 db
= RHYTHMDB (object
);
686 g_return_if_fail (db
->priv
!= NULL
);
688 rhythmdb_finalize_monitoring (db
);
690 g_source_remove (db
->priv
->event_poll_id
);
691 if (db
->priv
->save_timeout_id
> 0)
692 g_source_remove (db
->priv
->save_timeout_id
);
693 if (db
->priv
->emit_entry_signals_id
> 0) {
694 g_source_remove (db
->priv
->emit_entry_signals_id
);
695 g_list_foreach (db
->priv
->added_entries_to_emit
, (GFunc
)rhythmdb_entry_unref
, NULL
);
696 g_list_foreach (db
->priv
->deleted_entries_to_emit
, (GFunc
)rhythmdb_entry_unref
, NULL
);
699 g_thread_pool_free (db
->priv
->query_thread_pool
, FALSE
, TRUE
);
700 g_thread_pool_free (db
->priv
->add_thread_pool
, FALSE
, TRUE
);
701 g_async_queue_unref (db
->priv
->action_queue
);
702 g_async_queue_unref (db
->priv
->event_queue
);
703 g_async_queue_unref (db
->priv
->restored_queue
);
705 g_mutex_free (db
->priv
->saving_mutex
);
706 g_cond_free (db
->priv
->saving_condition
);
708 g_list_free (db
->priv
->stat_list
);
709 g_hash_table_foreach (db
->priv
->stat_events
, (GHFunc
)_shutdown_foreach_hash
, db
);
710 g_hash_table_destroy (db
->priv
->stat_events
);
711 g_mutex_free (db
->priv
->stat_mutex
);
713 g_mutex_free (db
->priv
->change_mutex
);
715 g_hash_table_destroy (db
->priv
->propname_map
);
717 g_hash_table_destroy (db
->priv
->added_entries
);
718 g_hash_table_destroy (db
->priv
->deleted_entries
);
719 g_hash_table_destroy (db
->priv
->changed_entries
);
721 rb_refstring_unref (db
->priv
->empty_string
);
722 rb_refstring_unref (db
->priv
->octet_stream_str
);
724 g_hash_table_destroy (db
->priv
->entry_type_map
);
725 g_mutex_free (db
->priv
->entry_type_map_mutex
);
726 g_mutex_free (db
->priv
->entry_type_mutex
);
728 g_object_unref (db
->priv
->metadata
);
730 for (i
= 0; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
731 xmlFree (db
->priv
->column_xml_names
[i
]);
733 g_free (db
->priv
->column_xml_names
);
735 g_free (db
->priv
->name
);
737 G_OBJECT_CLASS (rhythmdb_parent_class
)->finalize (object
);
741 rhythmdb_set_property (GObject
*object
,
746 RhythmDB
*source
= RHYTHMDB (object
);
750 source
->priv
->name
= g_strdup (g_value_get_string (value
));
753 source
->priv
->dry_run
= g_value_get_boolean (value
);
756 source
->priv
->no_update
= g_value_get_boolean (value
);
759 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
765 rhythmdb_get_property (GObject
*object
,
770 RhythmDB
*source
= RHYTHMDB (object
);
774 g_value_set_string (value
, source
->priv
->name
);
777 g_value_set_boolean (value
, source
->priv
->dry_run
);
780 g_value_set_boolean (value
, source
->priv
->no_update
);
783 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
789 rhythmdb_thread_create (RhythmDB
*db
,
795 g_atomic_int_inc (&db
->priv
->outstanding_threads
);
796 g_async_queue_ref (db
->priv
->action_queue
);
797 g_async_queue_ref (db
->priv
->event_queue
);
800 g_thread_pool_push (pool
, data
, NULL
);
802 g_thread_create ((GThreadFunc
) func
, data
, FALSE
, NULL
);
806 rhythmdb_get_readonly (RhythmDB
*db
)
808 return (g_atomic_int_get (&db
->priv
->read_counter
) > 0);
812 rhythmdb_read_enter (RhythmDB
*db
)
815 g_return_if_fail (g_atomic_int_get (&db
->priv
->read_counter
) >= 0);
816 g_assert (rb_is_main_thread ());
818 count
= g_atomic_int_exchange_and_add (&db
->priv
->read_counter
, 1);
819 rb_debug ("counter: %d", count
+1);
821 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[READ_ONLY
],
826 rhythmdb_read_leave (RhythmDB
*db
)
829 g_return_if_fail (rhythmdb_get_readonly (db
));
830 g_assert (rb_is_main_thread ());
832 count
= g_atomic_int_exchange_and_add (&db
->priv
->read_counter
, -1);
833 rb_debug ("counter: %d", count
-1);
835 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[READ_ONLY
],
840 free_entry_changes (RhythmDBEntry
*entry
,
845 for (t
= changes
; t
; t
= t
->next
) {
846 RhythmDBEntryChange
*change
= t
->data
;
847 g_value_unset (&change
->old
);
848 g_value_unset (&change
->new);
851 g_slist_free (changes
);
857 emit_entry_changed (RhythmDBEntry
*entry
,
861 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_CHANGED
], 0, entry
, changes
);
865 sync_entry_changed (RhythmDBEntry
*entry
,
871 for (t
= changes
; t
; t
= t
->next
) {
872 RBMetaDataField field
;
873 RhythmDBEntryChange
*change
= t
->data
;
875 if (metadata_field_from_prop (change
->prop
, &field
)) {
876 RhythmDBAction
*action
;
878 if (!rhythmdb_entry_is_editable (db
, entry
)) {
879 g_warning ("trying to sync properties of non-editable file");
883 action
= g_new0 (RhythmDBAction
, 1);
884 action
->type
= RHYTHMDB_ACTION_SYNC
;
885 action
->uri
= rb_refstring_ref (entry
->location
);
886 g_async_queue_push (db
->priv
->action_queue
, action
);
893 rhythmdb_emit_entry_signals_idle (RhythmDB
*db
)
895 GList
*added_entries
;
896 GList
*deleted_entries
;
899 /* get lists of entries to emit, reset source id value */
900 g_mutex_lock (db
->priv
->change_mutex
);
902 added_entries
= db
->priv
->added_entries_to_emit
;
903 db
->priv
->added_entries_to_emit
= NULL
;
905 deleted_entries
= db
->priv
->deleted_entries_to_emit
;
906 db
->priv
->deleted_entries_to_emit
= NULL
;
908 db
->priv
->emit_entry_signals_id
= 0;
910 g_mutex_unlock (db
->priv
->change_mutex
);
912 /* emit added entries */
913 for (l
= added_entries
; l
; l
= g_list_next (l
)) {
914 RhythmDBEntry
*entry
= (RhythmDBEntry
*)l
->data
;
915 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_ADDED
], 0, entry
);
916 rhythmdb_entry_unref (entry
);
919 /* emit deleted entries */
920 for (l
= deleted_entries
; l
; l
= g_list_next (l
)) {
921 RhythmDBEntry
*entry
= (RhythmDBEntry
*)l
->data
;
922 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_DELETED
], 0, entry
);
923 rhythmdb_entry_unref (entry
);
926 g_list_free (added_entries
);
927 g_list_free (deleted_entries
);
932 process_added_entries_cb (RhythmDBEntry
*entry
,
936 if (thread
!= g_thread_self ())
939 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_SONG
) {
942 uri
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
946 #ifdef HAVE_GSTREAMER_0_8
947 /* always start remote files hidden*/
948 if (!g_str_has_prefix (uri
, "file://")) {
949 entry
->flags
|= RHYTHMDB_ENTRY_HIDDEN
;
953 queue_stat_uri (uri
, db
, RHYTHMDB_ENTRY_TYPE_INVALID
);
956 g_assert ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) == 0);
957 entry
->flags
|= RHYTHMDB_ENTRY_INSERTED
;
959 rhythmdb_entry_ref (entry
);
960 db
->priv
->added_entries_to_emit
= g_list_prepend (db
->priv
->added_entries_to_emit
, entry
);
966 process_deleted_entries_cb (RhythmDBEntry
*entry
,
970 if (thread
!= g_thread_self ())
973 rhythmdb_entry_ref (entry
);
974 db
->priv
->deleted_entries_to_emit
= g_list_prepend (db
->priv
->deleted_entries_to_emit
, entry
);
980 rhythmdb_commit_internal (RhythmDB
*db
,
981 gboolean sync_changes
,
984 g_mutex_lock (db
->priv
->change_mutex
);
986 g_hash_table_foreach (db
->priv
->changed_entries
, (GHFunc
) emit_entry_changed
, db
);
988 g_hash_table_foreach (db
->priv
->changed_entries
, (GHFunc
) sync_entry_changed
, db
);
989 g_hash_table_foreach_remove (db
->priv
->changed_entries
, (GHRFunc
) free_entry_changes
, db
);
991 /* update the lists of entry added/deleted signals to emit */
992 g_hash_table_foreach_remove (db
->priv
->added_entries
, (GHRFunc
) process_added_entries_cb
, db
);
993 g_hash_table_foreach_remove (db
->priv
->deleted_entries
, (GHRFunc
) process_deleted_entries_cb
, db
);
995 /* if there are some signals to emit, add a new idle callback if required */
996 if (db
->priv
->added_entries_to_emit
|| db
->priv
->deleted_entries_to_emit
) {
997 if (db
->priv
->emit_entry_signals_id
== 0)
998 db
->priv
->emit_entry_signals_id
= g_idle_add ((GSourceFunc
) rhythmdb_emit_entry_signals_idle
, db
);
1001 g_mutex_unlock (db
->priv
->change_mutex
);
1008 } RhythmDBTimeoutCommitData
;
1011 timeout_rhythmdb_commit (RhythmDBTimeoutCommitData
*data
)
1013 rhythmdb_commit_internal (data
->db
, data
->sync
, data
->thread
);
1014 g_object_unref (data
->db
);
1020 rhythmdb_add_timeout_commit (RhythmDB
*db
,
1023 RhythmDBTimeoutCommitData
*data
;
1025 g_assert (rb_is_main_thread ());
1027 data
= g_new0 (RhythmDBTimeoutCommitData
, 1);
1028 data
->db
= g_object_ref (db
);
1030 data
->thread
= g_thread_self ();
1031 g_timeout_add (100, (GSourceFunc
)timeout_rhythmdb_commit
, data
);
1038 * Apply all database changes, and send notification of changes and new entries.
1039 * This needs to be called after any changes have been made, such as a group of
1040 * rhythmdb_entry_set() calls, or a new entry has been added.
1043 rhythmdb_commit (RhythmDB
*db
)
1045 rhythmdb_commit_internal (db
, TRUE
, g_thread_self ());
1049 rhythmdb_error_quark (void)
1051 static GQuark quark
;
1053 quark
= g_quark_from_static_string ("rhythmdb_error");
1058 /* structure alignment magic, stolen from glib */
1059 #define STRUCT_ALIGNMENT (2 * sizeof (gsize))
1060 #define ALIGN_STRUCT(offset) \
1061 ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
1064 * rhythmdb_entry_allocate:
1066 * @type: type of entry to allocate
1068 * Allocate and initialise memory for a new #RhythmDBEntry of the type @type.
1069 * The entry's initial properties needs to be set with rhythmdb_entry_set (),
1070 * the entry added to the database with rhythmdb_entry_insert(), and committed with
1071 * rhythmdb_commit().
1073 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
1075 * Returns: the newly allocated #RhythmDBEntry
1078 rhythmdb_entry_allocate (RhythmDB
*db
,
1079 RhythmDBEntryType type
)
1082 gsize size
= sizeof (RhythmDBEntry
);
1084 if (type
->entry_type_data_size
) {
1085 size
= ALIGN_STRUCT (sizeof (RhythmDBEntry
)) + type
->entry_type_data_size
;
1087 ret
= g_malloc0 (size
);
1088 ret
->id
= (guint
) g_atomic_int_exchange_and_add (&db
->priv
->next_entry_id
, 1);
1091 ret
->title
= rb_refstring_ref (db
->priv
->empty_string
);
1092 ret
->genre
= rb_refstring_ref (db
->priv
->empty_string
);
1093 ret
->artist
= rb_refstring_ref (db
->priv
->empty_string
);
1094 ret
->album
= rb_refstring_ref (db
->priv
->empty_string
);
1095 ret
->musicbrainz_trackid
= rb_refstring_ref (db
->priv
->empty_string
);
1096 ret
->mimetype
= rb_refstring_ref (db
->priv
->octet_stream_str
);
1098 ret
->flags
|= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
|
1099 RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
|
1100 RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
1102 /* The refcount is initially 0, we want to set it to 1 */
1105 if (type
->post_entry_create
)
1106 (type
->post_entry_create
)(ret
, type
->post_entry_create_data
);
1112 * rhythmdb_entry_get_type_data:
1113 * @entry: a #RhythmDBEntry
1114 * @expected_size: expected size of the type-specific data.
1116 * Returns a pointer to the entry's type-specific data, checking that
1117 * the size of the data structure matches what is expected.
1118 * Callers should use the RHYTHMDB_ENTRY_GET_TYPE_DATA macro for
1119 * a slightly more friendly interface to this functionality.
1122 rhythmdb_entry_get_type_data (RhythmDBEntry
*entry
,
1123 guint expected_size
)
1125 g_return_val_if_fail (entry
!= NULL
, NULL
);
1127 g_assert (expected_size
== entry
->type
->entry_type_data_size
);
1128 gsize offset
= ALIGN_STRUCT (sizeof (RhythmDBEntry
));
1130 return (gpointer
) (((guint8
*)entry
) + offset
);
1134 * rhythmdb_entry_insert:
1136 * @entry: the entry to insert.
1138 * Inserts a newly-created entry into the database.
1140 * Note that you must call rhythmdb_commit() at some point after invoking
1144 rhythmdb_entry_insert (RhythmDB
*db
,
1145 RhythmDBEntry
*entry
)
1147 g_return_if_fail (RHYTHMDB_IS (db
));
1148 g_return_if_fail (entry
!= NULL
);
1150 g_assert ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) == 0);
1151 g_return_if_fail (entry
->location
!= NULL
);
1153 /* ref the entry before adding to hash, it is unreffed when removed */
1154 rhythmdb_entry_ref (entry
);
1155 g_mutex_lock (db
->priv
->change_mutex
);
1156 g_hash_table_insert (db
->priv
->added_entries
, entry
, g_thread_self ());
1157 g_mutex_unlock (db
->priv
->change_mutex
);
1161 * rhythmdb_entry_new:
1163 * @type: type of entry to create
1164 * @uri: the location of the entry, this be unique amongst all entries.
1166 * Creates a new entry of type @type and location @uri, and inserts
1167 * it into the database. You must call rhythmdb_commit() at some point
1168 * after invoking this function.
1170 * This may return NULL if entry creation fails. This can occur if there is
1171 * already an entry with the given uri.
1173 * Returns: the newly created #RhythmDBEntry
1176 rhythmdb_entry_new (RhythmDB
*db
,
1177 RhythmDBEntryType type
,
1181 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
1183 ret
= rhythmdb_entry_lookup_by_location (db
, uri
);
1185 g_warning ("attempting to create entry that already exists: %s", uri
);
1189 ret
= rhythmdb_entry_allocate (db
, type
);
1190 ret
->location
= rb_refstring_new (uri
);
1191 klass
->impl_entry_new (db
, ret
);
1192 rb_debug ("emitting entry added");
1193 rhythmdb_entry_insert (db
, ret
);
1199 * rhythmdb_entry_example_new:
1201 * @type: type of entry to create
1202 * @uri: the location of the entry, this be unique amongst all entries.
1204 * Creates a new sample entry of type @type and location @uri, it does not insert
1205 * it into the database. This is indended for use as a example entry.
1207 * This may return NULL if entry creation fails.
1209 * Returns: the newly created #RhythmDBEntry
1212 rhythmdb_entry_example_new (RhythmDB
*db
,
1213 RhythmDBEntryType type
,
1218 ret
= rhythmdb_entry_allocate (db
, type
);
1220 ret
->location
= rb_refstring_new (uri
);
1222 if (type
== RHYTHMDB_ENTRY_TYPE_SONG
) {
1223 rb_refstring_unref (ret
->artist
);
1224 ret
->artist
= rb_refstring_new ("The Beatles");
1225 rb_refstring_unref (ret
->album
);
1226 ret
->album
= rb_refstring_new ("Help!");
1227 rb_refstring_unref (ret
->title
);
1228 ret
->title
= rb_refstring_new ("Ticket To Ride");
1237 * rhythmdb_entry_ref:
1239 * @entry: a #RhythmDBEntry.
1241 * Increase the reference count of the entry.
1244 rhythmdb_entry_ref (RhythmDBEntry
*entry
)
1246 g_return_val_if_fail (entry
!= NULL
, NULL
);
1247 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
1249 g_atomic_int_inc (&entry
->refcount
);
1255 rhythmdb_entry_finalize (RhythmDBEntry
*entry
)
1257 RhythmDBEntryType type
;
1259 type
= rhythmdb_entry_get_entry_type (entry
);
1261 if (type
->pre_entry_destroy
)
1262 (type
->pre_entry_destroy
)(entry
, type
->pre_entry_destroy_data
);
1264 rb_refstring_unref (entry
->location
);
1265 rb_refstring_unref (entry
->playback_error
);
1266 rb_refstring_unref (entry
->title
);
1267 rb_refstring_unref (entry
->genre
);
1268 rb_refstring_unref (entry
->artist
);
1269 rb_refstring_unref (entry
->album
);
1270 rb_refstring_unref (entry
->musicbrainz_trackid
);
1271 rb_refstring_unref (entry
->mimetype
);
1277 * rhythmdb_entry_unref:
1279 * @entry: a #RhythmDBEntry.
1281 * Decrease the reference count of the entry, and destroy it if there are
1282 * no references left.
1285 rhythmdb_entry_unref (RhythmDBEntry
*entry
)
1289 g_return_if_fail (entry
!= NULL
);
1290 g_return_if_fail (entry
->refcount
> 0);
1292 is_zero
= g_atomic_int_dec_and_test (&entry
->refcount
);
1293 if (G_UNLIKELY (is_zero
)) {
1294 rhythmdb_entry_finalize (entry
);
1299 * rhythmdb_entry_is_editable:
1301 * @entry: a #RhythmDBEntry.
1303 * This determines whether any changes to the entries metadata can be saved.
1304 * Usually this is only true for entries backed by files, where tag-writing is
1305 * enabled, and the appropriate tag-writing facilities are available.
1307 * Returns: whether the entries metadata can be changed.
1311 rhythmdb_entry_is_editable (RhythmDB
*db
,
1312 RhythmDBEntry
*entry
)
1314 RhythmDBEntryType entry_type
;
1316 g_return_val_if_fail (RHYTHMDB_IS (db
), FALSE
);
1317 g_return_val_if_fail (entry
!= NULL
, FALSE
);
1319 entry_type
= rhythmdb_entry_get_entry_type (entry
);
1320 return entry_type
->can_sync_metadata (db
, entry
, entry_type
->can_sync_metadata_data
);
1324 set_metadata_string_default_unknown (RhythmDB
*db
,
1325 RBMetaData
*metadata
,
1326 RhythmDBEntry
*entry
,
1327 RBMetaDataField field
,
1328 RhythmDBPropType prop
)
1330 const char *unknown
= _("Unknown");
1333 if (!(rb_metadata_get (metadata
,
1336 g_value_init (&val
, G_TYPE_STRING
);
1337 g_value_set_static_string (&val
, unknown
);
1339 const gchar
*str
= g_value_get_string (&val
);
1340 if (str
== NULL
|| str
[0] == '\0')
1341 g_value_set_static_string (&val
, unknown
);
1343 rhythmdb_entry_set_internal (db
, entry
, TRUE
, prop
, &val
);
1344 g_value_unset (&val
);
1348 set_props_from_metadata (RhythmDB
*db
,
1349 RhythmDBEntry
*entry
,
1350 GnomeVFSFileInfo
*vfsinfo
,
1351 RBMetaData
*metadata
)
1356 g_value_init (&val
, G_TYPE_STRING
);
1357 mime
= rb_metadata_get_mime (metadata
);
1359 g_value_set_string (&val
, mime
);
1360 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1361 RHYTHMDB_PROP_MIMETYPE
, &val
);
1363 g_value_unset (&val
);
1366 if (!rb_metadata_get (metadata
,
1367 RB_METADATA_FIELD_TRACK_NUMBER
,
1369 g_value_init (&val
, G_TYPE_ULONG
);
1370 g_value_set_ulong (&val
, 0);
1372 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1373 RHYTHMDB_PROP_TRACK_NUMBER
, &val
);
1374 g_value_unset (&val
);
1377 if (!rb_metadata_get (metadata
,
1378 RB_METADATA_FIELD_DISC_NUMBER
,
1380 g_value_init (&val
, G_TYPE_ULONG
);
1381 g_value_set_ulong (&val
, 0);
1383 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1384 RHYTHMDB_PROP_DISC_NUMBER
, &val
);
1385 g_value_unset (&val
);
1388 if (rb_metadata_get (metadata
,
1389 RB_METADATA_FIELD_DURATION
,
1391 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1392 RHYTHMDB_PROP_DURATION
, &val
);
1393 g_value_unset (&val
);
1397 if (rb_metadata_get (metadata
,
1398 RB_METADATA_FIELD_BITRATE
,
1400 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1401 RHYTHMDB_PROP_BITRATE
, &val
);
1402 g_value_unset (&val
);
1406 if (rb_metadata_get (metadata
,
1407 RB_METADATA_FIELD_DATE
,
1409 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1410 RHYTHMDB_PROP_DATE
, &val
);
1411 g_value_unset (&val
);
1414 /* musicbrainz trackid */
1415 if (rb_metadata_get (metadata
,
1416 RB_METADATA_FIELD_MUSICBRAINZ_TRACKID
,
1418 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1419 RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
, &val
);
1420 g_value_unset (&val
);
1424 g_value_init (&val
, G_TYPE_UINT64
);
1425 g_value_set_uint64 (&val
, vfsinfo
->size
);
1426 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_FILE_SIZE
, &val
);
1427 g_value_unset (&val
);
1430 if (!rb_metadata_get (metadata
,
1431 RB_METADATA_FIELD_TITLE
,
1432 &val
) || g_value_get_string (&val
)[0] == '\0') {
1434 utf8name
= g_filename_to_utf8 (vfsinfo
->name
, -1, NULL
, NULL
, NULL
);
1436 utf8name
= g_strdup (_("<invalid filename>"));
1438 if (G_VALUE_HOLDS_STRING (&val
))
1439 g_value_reset (&val
);
1441 g_value_init (&val
, G_TYPE_STRING
);
1442 g_value_set_string (&val
, utf8name
);
1445 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_TITLE
, &val
);
1446 g_value_unset (&val
);
1449 set_metadata_string_default_unknown (db
, metadata
, entry
,
1450 RB_METADATA_FIELD_GENRE
,
1451 RHYTHMDB_PROP_GENRE
);
1454 set_metadata_string_default_unknown (db
, metadata
, entry
,
1455 RB_METADATA_FIELD_ARTIST
,
1456 RHYTHMDB_PROP_ARTIST
);
1458 set_metadata_string_default_unknown (db
, metadata
, entry
,
1459 RB_METADATA_FIELD_ALBUM
,
1460 RHYTHMDB_PROP_ALBUM
);
1462 /* replaygain track gain */
1463 if (rb_metadata_get (metadata
,
1464 RB_METADATA_FIELD_TRACK_GAIN
,
1466 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1467 RHYTHMDB_PROP_TRACK_GAIN
, &val
);
1468 g_value_unset (&val
);
1471 /* replaygain track peak */
1472 if (rb_metadata_get (metadata
,
1473 RB_METADATA_FIELD_TRACK_PEAK
,
1475 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1476 RHYTHMDB_PROP_TRACK_PEAK
, &val
);
1477 g_value_unset (&val
);
1480 /* replaygain album gain */
1481 if (rb_metadata_get (metadata
,
1482 RB_METADATA_FIELD_ALBUM_GAIN
,
1484 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1485 RHYTHMDB_PROP_ALBUM_GAIN
, &val
);
1486 g_value_unset (&val
);
1489 /* replaygain album peak */
1490 if (rb_metadata_get (metadata
,
1491 RB_METADATA_FIELD_ALBUM_PEAK
,
1493 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1494 RHYTHMDB_PROP_ALBUM_PEAK
, &val
);
1495 g_value_unset (&val
);
1500 is_ghost_entry (RhythmDBEntry
*entry
)
1504 gulong grace_period
;
1506 GConfClient
*client
;
1508 client
= gconf_client_get_default ();
1509 if (client
== NULL
) {
1513 grace_period
= gconf_client_get_int (client
, CONF_GRACE_PERIOD
,
1515 g_object_unref (G_OBJECT (client
));
1516 if (error
!= NULL
) {
1517 g_error_free (error
);
1521 /* This is a bit silly, but I prefer to make sure we won't
1522 * overflow in the following calculations
1524 if ((grace_period
<= 0) || (grace_period
> 20000)) {
1528 /* Convert from days to seconds */
1529 grace_period
= grace_period
* 60 * 60 * 24;
1530 g_get_current_time (&time
);
1531 last_seen
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_LAST_SEEN
);
1533 return (last_seen
+ grace_period
< time
.tv_sec
);
1537 rhythmdb_process_stat_event (RhythmDB
*db
,
1538 RhythmDBEvent
*event
)
1540 RhythmDBEntry
*entry
;
1541 RhythmDBAction
*action
;
1543 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1545 time_t mtime
= (time_t) entry
->mtime
;
1547 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) && (entry
->type
!= event
->entry_type
))
1548 g_warning ("attempt to use same location in multiple entry types");
1550 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_IGNORE
)
1551 rb_debug ("ignoring %p", entry
);
1554 if (!is_ghost_entry (entry
)) {
1555 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
1557 rb_debug ("error accessing %s: %s", rb_refstring_get (event
->real_uri
),
1558 event
->error
->message
);
1559 rhythmdb_entry_delete (db
, entry
);
1564 const char *mount_point
;
1566 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1568 /* Update mount point if necessary (main reason is
1569 * that we want to set the mount point in legacy
1570 * rhythmdb that doesn't have it already
1572 mount_point
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_MOUNTPOINT
);
1573 if (mount_point
== NULL
) {
1574 rhythmdb_entry_set_mount_point (db
, entry
,
1575 rb_refstring_get (event
->real_uri
));
1578 /* Update last seen time. It will also be updated
1579 * upon saving and when a volume is unmounted
1581 g_get_current_time (&time
);
1582 g_value_init (&val
, G_TYPE_ULONG
);
1583 g_value_set_ulong (&val
, time
.tv_sec
);
1584 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1585 RHYTHMDB_PROP_LAST_SEEN
,
1587 /* Old rhythmdb.xml files won't have a value for
1588 * FIRST_SEEN, so set it here
1590 if (rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_FIRST_SEEN
) == 0) {
1591 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1592 RHYTHMDB_PROP_FIRST_SEEN
,
1595 g_value_unset (&val
);
1597 if (mtime
== event
->vfsinfo
->mtime
) {
1598 rb_debug ("not modified: %s", rb_refstring_get (event
->real_uri
));
1600 RhythmDBEvent
*new_event
;
1602 rb_debug ("changed: %s", rb_refstring_get (event
->real_uri
));
1603 new_event
= g_new0 (RhythmDBEvent
, 1);
1605 new_event
->uri
= rb_refstring_ref (event
->real_uri
);
1606 new_event
->type
= RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED
;
1607 g_async_queue_push (db
->priv
->event_queue
,
1612 rhythmdb_commit (db
);
1614 action
= g_new0 (RhythmDBAction
, 1);
1615 action
->type
= RHYTHMDB_ACTION_LOAD
;
1616 action
->uri
= rb_refstring_ref (event
->real_uri
);
1617 action
->entry_type
= event
->entry_type
;
1618 rb_debug ("queuing a RHYTHMDB_ACTION_LOAD: %s", rb_refstring_get (action
->uri
));
1619 g_async_queue_push (db
->priv
->action_queue
, action
);
1628 } RhythmDBLoadErrorData
;
1631 rhythmdb_add_import_error_entry (RhythmDB
*db
,
1632 RhythmDBEvent
*event
)
1634 RhythmDBEntry
*entry
;
1635 GValue value
= {0,};
1636 RhythmDBEntryType error_entry_type
= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
;
1638 if (g_error_matches (event
->error
, RB_METADATA_ERROR
, RB_METADATA_ERROR_NOT_AUDIO_IGNORE
)) {
1639 /* only add an ignore entry for the main library */
1640 if (event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_SONG
&&
1641 event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
)
1644 error_entry_type
= RHYTHMDB_ENTRY_TYPE_IGNORE
;
1647 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1649 RhythmDBEntryType entry_type
= rhythmdb_entry_get_entry_type (entry
);
1650 if (entry_type
!= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
&&
1651 entry_type
!= RHYTHMDB_ENTRY_TYPE_IGNORE
) {
1652 /* FIXME we've successfully read this file before.. so what should we do? */
1653 rb_debug ("%s already exists in the library.. ignoring import error?", rb_refstring_get (event
->real_uri
));
1657 if (entry_type
!= error_entry_type
) {
1658 /* delete the existing entry, then create a new one below */
1659 rhythmdb_entry_delete (db
, entry
);
1661 } else if (error_entry_type
== RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
) {
1662 /* we've already got an error for this file, so just update it */
1663 g_value_init (&value
, G_TYPE_STRING
);
1664 g_value_set_string (&value
, event
->error
->message
);
1665 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
1666 g_value_unset (&value
);
1668 /* no need to update the ignored file entry */
1671 if (entry
&& event
->vfsinfo
) {
1673 g_value_init (&value
, G_TYPE_ULONG
);
1674 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1675 rhythmdb_entry_set(db
, entry
, RHYTHMDB_PROP_MTIME
, &value
);
1676 g_value_unset (&value
);
1679 rhythmdb_add_timeout_commit (db
, FALSE
);
1682 if (entry
== NULL
) {
1683 /* create a new import error or ignore entry */
1684 entry
= rhythmdb_entry_new (db
, error_entry_type
, rb_refstring_get (event
->real_uri
));
1688 if (error_entry_type
== RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
&& event
->error
->message
) {
1689 g_value_init (&value
, G_TYPE_STRING
);
1690 if (g_utf8_validate (event
->error
->message
, -1, NULL
))
1691 g_value_set_string (&value
, event
->error
->message
);
1693 g_value_set_static_string (&value
, _("invalid unicode in error message"));
1694 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
1695 g_value_unset (&value
);
1699 if (event
->vfsinfo
) {
1700 g_value_init (&value
, G_TYPE_ULONG
);
1701 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1702 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_MTIME
, &value
);
1703 g_value_unset (&value
);
1706 /* record the mount point so we can delete entries for unmounted volumes */
1707 rhythmdb_entry_set_mount_point (db
, entry
, rb_refstring_get (event
->real_uri
));
1709 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1711 rhythmdb_add_timeout_commit (db
, FALSE
);
1716 rhythmdb_process_metadata_load (RhythmDB
*db
,
1717 RhythmDBEvent
*event
)
1719 RhythmDBEntry
*entry
;
1720 GValue value
= {0,};
1724 if (rhythmdb_get_readonly (db
)) {
1725 rb_debug ("database is read-only right now, re-queuing event");
1726 g_async_queue_push (db
->priv
->event_queue
, event
);
1731 rhythmdb_add_import_error_entry (db
, event
);
1735 /* do we really need to do this? */
1736 mime
= rb_metadata_get_mime (event
->metadata
);
1738 rb_debug ("unsupported file");
1742 g_get_current_time (&time
);
1744 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1746 if (entry
!= NULL
) {
1747 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) &&
1748 (rhythmdb_entry_get_entry_type (entry
) != event
->entry_type
)) {
1749 /* switching from IGNORE to SONG or vice versa, recreate the entry */
1750 rhythmdb_entry_delete (db
, entry
);
1751 rhythmdb_add_timeout_commit (db
, FALSE
);
1756 if (entry
== NULL
) {
1757 if (event
->entry_type
== RHYTHMDB_ENTRY_TYPE_INVALID
)
1758 event
->entry_type
= RHYTHMDB_ENTRY_TYPE_SONG
;
1760 entry
= rhythmdb_entry_new (db
, event
->entry_type
, rb_refstring_get (event
->real_uri
));
1761 if (entry
== NULL
) {
1762 rb_debug ("entry already exists");
1766 /* initialize the last played date to 0=never */
1767 g_value_init (&value
, G_TYPE_ULONG
);
1768 g_value_set_ulong (&value
, 0);
1769 rhythmdb_entry_set (db
, entry
,
1770 RHYTHMDB_PROP_LAST_PLAYED
, &value
);
1771 g_value_unset (&value
);
1773 /* initialize the rating */
1774 g_value_init (&value
, G_TYPE_DOUBLE
);
1775 g_value_set_double (&value
, 0);
1776 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_RATING
, &value
);
1777 g_value_unset (&value
);
1780 g_value_init (&value
, G_TYPE_ULONG
);
1781 g_value_set_ulong (&value
, time
.tv_sec
);
1782 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_FIRST_SEEN
, &value
);
1783 g_value_unset (&value
);
1786 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) && (entry
->type
!= event
->entry_type
))
1787 g_warning ("attempt to use same location in multiple entry types");
1790 if (event
->vfsinfo
) {
1791 g_value_init (&value
, G_TYPE_ULONG
);
1792 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1793 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_MTIME
, &value
);
1794 g_value_unset (&value
);
1797 if (event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_IGNORE
&&
1798 event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
) {
1799 set_props_from_metadata (db
, entry
, event
->vfsinfo
, event
->metadata
);
1802 /* we've seen this entry */
1803 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1805 g_value_init (&value
, G_TYPE_ULONG
);
1806 g_value_set_ulong (&value
, time
.tv_sec
);
1807 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_LAST_SEEN
, &value
);
1808 g_value_unset (&value
);
1810 /* Remember the mount point of the volume the song is on */
1811 rhythmdb_entry_set_mount_point (db
, entry
, rb_refstring_get (event
->real_uri
));
1813 /* monitor the file for changes */
1814 /* FIXME: watch for errors */
1815 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY
) && event
->entry_type
== RHYTHMDB_ENTRY_TYPE_SONG
)
1816 rhythmdb_monitor_uri_path (db
, rb_refstring_get (entry
->location
), NULL
);
1818 rhythmdb_add_timeout_commit (db
, FALSE
);
1824 rhythmdb_process_queued_entry_set_event (RhythmDB
*db
,
1825 RhythmDBEvent
*event
)
1827 rhythmdb_entry_set_internal (db
, event
->entry
,
1828 event
->signal_change
,
1830 &event
->change
.new);
1831 /* Don't run rhythmdb_commit right now in case there
1832 * we can run a single commit for several queued
1835 rhythmdb_add_timeout_commit (db
, TRUE
);
1839 rhythmdb_process_file_created_or_modified (RhythmDB
*db
,
1840 RhythmDBEvent
*event
)
1842 RhythmDBAction
*action
;
1844 action
= g_new0 (RhythmDBAction
, 1);
1845 action
->type
= RHYTHMDB_ACTION_LOAD
;
1846 action
->uri
= rb_refstring_ref (event
->uri
);
1847 action
->entry_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
1848 g_async_queue_push (db
->priv
->action_queue
, action
);
1852 rhythmdb_process_file_deleted (RhythmDB
*db
,
1853 RhythmDBEvent
*event
)
1855 RhythmDBEntry
*entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->uri
);
1857 g_hash_table_remove (db
->priv
->changed_files
, event
->uri
);
1860 rb_debug ("deleting entry for %s", rb_refstring_get (event
->uri
));
1861 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
1862 rhythmdb_commit (db
);
1867 rhythmdb_process_events (RhythmDB
*db
,
1870 RhythmDBEvent
*event
;
1873 while ((event
= g_async_queue_try_pop (db
->priv
->event_queue
)) != NULL
) {
1874 gboolean free
= TRUE
;
1876 /* if the database is read-only, we can't process those events
1877 * since they call rhythmdb_entry_set. Doing it this way
1878 * is safe if we assume all calls to read_enter/read_leave
1879 * are done from the main thread (the thread this function
1882 if (rhythmdb_get_readonly (db
) &&
1883 ((event
->type
== RHYTHMDB_EVENT_STAT
)
1884 || (event
->type
== RHYTHMDB_EVENT_METADATA_LOAD
)
1885 || (event
->type
== RHYTHMDB_EVENT_ENTRY_SET
))) {
1886 if (count
>= g_async_queue_length (db
->priv
->event_queue
)) {
1887 rb_debug ("Database is read-only, and we can't process any more events");
1888 /* give the running query some time to complete */
1891 rb_debug ("Database is read-only, delaying event processing\n");
1892 g_async_queue_push (db
->priv
->event_queue
, event
);
1896 switch (event
->type
) {
1897 case RHYTHMDB_EVENT_STAT
:
1898 rb_debug ("processing RHYTHMDB_EVENT_STAT");
1899 rhythmdb_process_stat_event (db
, event
);
1901 case RHYTHMDB_EVENT_METADATA_LOAD
:
1902 rb_debug ("processing RHYTHMDB_EVENT_METADATA_LOAD");
1903 free
= rhythmdb_process_metadata_load (db
, event
);
1905 case RHYTHMDB_EVENT_ENTRY_SET
:
1906 rb_debug ("processing RHYTHMDB_EVENT_ENTRY_SET");
1907 rhythmdb_process_queued_entry_set_event (db
, event
);
1909 case RHYTHMDB_EVENT_DB_LOAD
:
1910 rb_debug ("processing RHYTHMDB_EVENT_DB_LOAD");
1911 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[LOAD_COMPLETE
], 0);
1913 /* save the db every five minutes */
1914 if (db
->priv
->save_timeout_id
> 0) {
1915 g_source_remove (db
->priv
->save_timeout_id
);
1917 db
->priv
->save_timeout_id
= g_timeout_add_full (G_PRIORITY_LOW
,
1919 (GSourceFunc
) rhythmdb_idle_save
,
1923 case RHYTHMDB_EVENT_THREAD_EXITED
:
1924 rb_debug ("processing RHYTHMDB_EVENT_THREAD_EXITED");
1926 case RHYTHMDB_EVENT_DB_SAVED
:
1927 rb_debug ("processing RHYTHMDB_EVENT_DB_SAVED");
1928 rhythmdb_read_leave (db
);
1930 case RHYTHMDB_EVENT_QUERY_COMPLETE
:
1931 rb_debug ("processing RHYTHMDB_EVENT_QUERY_COMPLETE");
1932 rhythmdb_read_leave (db
);
1934 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED
:
1935 rb_debug ("processing RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED");
1936 rhythmdb_process_file_created_or_modified (db
, event
);
1938 case RHYTHMDB_EVENT_FILE_DELETED
:
1939 rb_debug ("processing RHYTHMDB_EVENT_FILE_DELETED");
1940 rhythmdb_process_file_deleted (db
, event
);
1944 rhythmdb_event_free (db
, event
);
1948 if (timeout
&& (count
% 8 == 0)) {
1950 g_get_current_time (&now
);
1951 if (rb_compare_gtimeval (timeout
,&now
) < 0) {
1952 /* probably more work to do, so try to come back as soon as possible */
1958 /* queue is empty, so we can wait a while before checking it again */
1963 rhythmdb_idle_poll_events (RhythmDB
*db
)
1968 g_get_current_time (&timeout
);
1969 g_time_val_add (&timeout
, G_USEC_PER_SEC
*0.75);
1971 GDK_THREADS_ENTER ();
1973 poll_soon
= rhythmdb_process_events (db
, &timeout
);
1976 db
->priv
->event_poll_id
=
1977 g_idle_add_full (G_PRIORITY_LOW
, (GSourceFunc
) rhythmdb_idle_poll_events
,
1980 db
->priv
->event_poll_id
=
1981 g_timeout_add (1000, (GSourceFunc
) rhythmdb_idle_poll_events
, db
);
1983 GDK_THREADS_LEAVE ();
1988 #define READ_QUEUE_TIMEOUT G_USEC_PER_SEC / 10
1991 read_queue (GAsyncQueue
*queue
, gboolean
*cancel
)
1996 g_get_current_time (&timeout
);
1997 g_time_val_add (&timeout
, READ_QUEUE_TIMEOUT
);
1999 if (G_UNLIKELY (*cancel
))
2001 while ((ret
= g_async_queue_timed_pop (queue
, &timeout
)) == NULL
) {
2002 if (G_UNLIKELY (*cancel
))
2004 g_get_current_time (&timeout
);
2005 g_time_val_add (&timeout
, G_USEC_PER_SEC
);
2012 rhythmdb_execute_stat_info_cb (GnomeVFSAsyncHandle
*handle
,
2014 /* GnomeVFSGetFileInfoResult* items */
2015 RhythmDBEvent
*event
)
2017 /* this is in the main thread, so we can't do any long operation here */
2018 GnomeVFSGetFileInfoResult
*info_result
= results
->data
;
2020 g_mutex_lock (event
->db
->priv
->stat_mutex
);
2021 event
->db
->priv
->outstanding_stats
= g_list_remove (event
->db
->priv
->outstanding_stats
, event
);
2022 event
->handle
= NULL
;
2023 g_mutex_unlock (event
->db
->priv
->stat_mutex
);
2025 if (info_result
->result
== GNOME_VFS_OK
) {
2026 event
->vfsinfo
= gnome_vfs_file_info_dup (info_result
->file_info
);
2028 event
->error
= make_access_failed_error (rb_refstring_get (event
->real_uri
),
2029 info_result
->result
);
2030 event
->vfsinfo
= NULL
;
2032 g_async_queue_push (event
->db
->priv
->event_queue
, event
);
2036 rhythmdb_execute_stat (RhythmDB
*db
,
2038 RhythmDBEvent
*event
)
2040 GnomeVFSURI
*vfs_uri
= gnome_vfs_uri_new (uri
);
2042 GList
*uri_list
= g_list_append (NULL
, vfs_uri
);
2043 event
->real_uri
= rb_refstring_new (uri
);
2045 g_mutex_lock (db
->priv
->stat_mutex
);
2046 db
->priv
->outstanding_stats
= g_list_prepend (db
->priv
->outstanding_stats
, event
);
2047 g_mutex_unlock (db
->priv
->stat_mutex
);
2049 gnome_vfs_async_get_file_info (&event
->handle
, uri_list
,
2050 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
,
2051 GNOME_VFS_PRIORITY_MIN
,
2052 (GnomeVFSAsyncGetFileInfoCallback
) rhythmdb_execute_stat_info_cb
,
2054 gnome_vfs_uri_unref (vfs_uri
);
2055 g_list_free (uri_list
);
2059 queue_stat_uri (const char *uri
,
2061 RhythmDBEntryType type
)
2063 RhythmDBEvent
*result
;
2065 rb_debug ("queueing stat for \"%s\"", uri
);
2066 g_assert (uri
&& *uri
);
2068 result
= g_new0 (RhythmDBEvent
, 1);
2070 result
->type
= RHYTHMDB_EVENT_STAT
;
2071 result
->entry_type
= type
;
2074 * before the action thread is started, we queue up stat events,
2075 * as we're still creating and running queries, as well as loading
2076 * the database. when we start the action thread, we'll kick off
2077 * a gnome-vfs job to run all the stat events too.
2079 * when the action thread is already running, we can start the
2080 * async_get_file_info job directly.
2082 g_mutex_lock (db
->priv
->stat_mutex
);
2083 if (db
->priv
->action_thread_running
) {
2084 g_mutex_unlock (db
->priv
->stat_mutex
);
2085 rhythmdb_execute_stat (db
, uri
, result
);
2087 GnomeVFSURI
*vfs_uri
;
2089 vfs_uri
= gnome_vfs_uri_new (uri
);
2091 /* construct a list of URIs and a hash table containing
2092 * stat events to fill in and post on the event queue.
2094 if (g_hash_table_lookup (db
->priv
->stat_events
, vfs_uri
)) {
2096 gnome_vfs_uri_unref (vfs_uri
);
2098 result
->real_uri
= rb_refstring_new (uri
);
2099 g_hash_table_insert (db
->priv
->stat_events
, vfs_uri
, result
);
2100 db
->priv
->stat_list
= g_list_prepend (db
->priv
->stat_list
, vfs_uri
);
2103 g_mutex_unlock (db
->priv
->stat_mutex
);
2108 queue_stat_uri_tad (const char *uri
,
2110 RhythmDBAddThreadData
*data
)
2113 queue_stat_uri (uri
, data
->db
, data
->type
);
2117 add_thread_main (RhythmDBAddThreadData
*data
)
2119 RhythmDBEvent
*result
;
2121 rb_uri_handle_recursively (data
->uri
, (RBUriRecurseFunc
) queue_stat_uri_tad
,
2122 &data
->db
->priv
->exiting
, data
);
2124 rb_debug ("exiting");
2125 result
= g_new0 (RhythmDBEvent
, 1);
2126 result
->db
= data
->db
;
2127 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2128 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
2135 rhythmdb_execute_load (RhythmDB
*db
,
2137 RhythmDBEvent
*event
)
2139 GnomeVFSURI
*vfs_uri
= gnome_vfs_uri_new (uri
);
2140 GnomeVFSResult vfsresult
;
2143 resolved
= rb_uri_resolve_symlink (uri
);
2144 if (resolved
!= NULL
) {
2145 event
->real_uri
= rb_refstring_new (resolved
);
2146 event
->vfsinfo
= gnome_vfs_file_info_new ();
2148 vfsresult
= gnome_vfs_get_file_info (uri
,
2150 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
);
2153 event
->real_uri
= rb_refstring_new (uri
);
2154 vfsresult
= GNOME_VFS_ERROR_LOOP
;
2157 if (vfsresult
!= GNOME_VFS_OK
) {
2158 event
->error
= make_access_failed_error (uri
, vfsresult
);
2160 gnome_vfs_file_info_unref (event
->vfsinfo
);
2161 event
->vfsinfo
= NULL
;
2163 if (event
->type
== RHYTHMDB_EVENT_METADATA_LOAD
) {
2164 event
->metadata
= rb_metadata_new ();
2165 rb_metadata_load (event
->metadata
, rb_refstring_get (event
->real_uri
),
2170 gnome_vfs_uri_unref (vfs_uri
);
2171 g_async_queue_push (db
->priv
->event_queue
, event
);
2175 * rhythmdb_entry_get:
2176 * @entry: a #RhythmDBEntry.
2177 * @propid: the id of the property to get.
2178 * @val: return location for the property value.
2180 * Gets a property of an entry, storing it in the given #GValue.
2183 rhythmdb_entry_get (RhythmDB
*db
,
2184 RhythmDBEntry
*entry
,
2185 RhythmDBPropType propid
,
2188 g_return_if_fail (RHYTHMDB_IS (db
));
2189 g_return_if_fail (entry
!= NULL
);
2190 g_return_if_fail (entry
->refcount
> 0);
2192 rhythmdb_entry_sync_mirrored (entry
, propid
);
2194 g_assert (G_VALUE_TYPE (val
) == rhythmdb_get_property_type (db
, propid
));
2195 switch (rhythmdb_property_type_map
[propid
]) {
2197 g_value_set_string (val
, rhythmdb_entry_get_string (entry
, propid
));
2199 case G_TYPE_BOOLEAN
:
2200 g_value_set_boolean (val
, rhythmdb_entry_get_boolean (entry
, propid
));
2203 g_value_set_ulong (val
, rhythmdb_entry_get_ulong (entry
, propid
));
2206 g_value_set_uint64 (val
, rhythmdb_entry_get_uint64 (entry
, propid
));
2209 g_value_set_double (val
, rhythmdb_entry_get_double (entry
, propid
));
2211 case G_TYPE_POINTER
:
2212 g_value_set_pointer (val
, rhythmdb_entry_get_pointer (entry
, propid
));
2215 g_assert_not_reached ();
2221 entry_to_rb_metadata (RhythmDB
*db
,
2222 RhythmDBEntry
*entry
,
2223 RBMetaData
*metadata
)
2228 for (i
= RHYTHMDB_PROP_TYPE
; i
!= RHYTHMDB_NUM_PROPERTIES
; i
++) {
2229 RBMetaDataField field
;
2231 if (metadata_field_from_prop (i
, &field
) == FALSE
) {
2235 g_value_init (&val
, rhythmdb_property_type_map
[i
]);
2236 rhythmdb_entry_get (db
, entry
, i
, &val
);
2237 rb_metadata_set (metadata
,
2240 g_value_unset (&val
);
2249 } RhythmDBSaveErrorData
;
2252 emit_save_error_idle (RhythmDBSaveErrorData
*data
)
2254 g_signal_emit (G_OBJECT (data
->db
), rhythmdb_signals
[SAVE_ERROR
], 0, data
->uri
, data
->error
);
2255 g_object_unref (G_OBJECT (data
->db
));
2257 g_error_free (data
->error
);
2263 action_thread_main (RhythmDB
*db
)
2265 RhythmDBEvent
*result
;
2268 RhythmDBAction
*action
;
2270 action
= read_queue (db
->priv
->action_queue
, &db
->priv
->exiting
);
2275 switch (action
->type
) {
2276 case RHYTHMDB_ACTION_STAT
:
2278 result
= g_new0 (RhythmDBEvent
, 1);
2280 result
->type
= RHYTHMDB_EVENT_STAT
;
2281 result
->entry_type
= action
->entry_type
;
2283 rb_debug ("executing RHYTHMDB_ACTION_STAT for \"%s\"", rb_refstring_get (action
->uri
));
2285 rhythmdb_execute_stat (db
, rb_refstring_get (action
->uri
), result
);
2288 case RHYTHMDB_ACTION_LOAD
:
2290 result
= g_new0 (RhythmDBEvent
, 1);
2292 result
->type
= RHYTHMDB_EVENT_METADATA_LOAD
;
2293 result
->entry_type
= action
->entry_type
;
2295 rb_debug ("executing RHYTHMDB_ACTION_LOAD for \"%s\"", rb_refstring_get (action
->uri
));
2297 rhythmdb_execute_load (db
, rb_refstring_get (action
->uri
), result
);
2300 case RHYTHMDB_ACTION_SYNC
:
2302 GError
*error
= NULL
;
2303 RhythmDBEntry
*entry
;
2304 RhythmDBEntryType entry_type
;
2306 if (db
->priv
->dry_run
) {
2307 rb_debug ("dry run is enabled, not syncing metadata");
2311 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, action
->uri
);
2315 entry_type
= rhythmdb_entry_get_entry_type (entry
);
2316 entry_type
->sync_metadata (db
, entry
, &error
, entry_type
->sync_metadata_data
);
2318 if (error
!= NULL
) {
2319 RhythmDBSaveErrorData
*data
;
2321 data
= g_new0 (RhythmDBSaveErrorData
, 1);
2324 data
->uri
= g_strdup (rb_refstring_get (action
->uri
));
2325 data
->error
= error
;
2326 g_idle_add ((GSourceFunc
)emit_save_error_idle
, data
);
2333 g_assert_not_reached ();
2336 rhythmdb_action_free (db
, action
);
2340 rb_debug ("exiting main thread");
2341 result
= g_new0 (RhythmDBEvent
, 1);
2343 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2344 g_async_queue_push (db
->priv
->event_queue
, result
);
2352 * @uri: the URI to add an entry/entries for
2354 * Adds the file(s) pointed to by @uri to the database, as entries of type
2355 * RHYTHMDB_ENTRY_TYPE_SONG. If the URI is that of a file, they will be added.
2356 * If the URI is that of a directory, everything under it will be added recursively.
2359 rhythmdb_add_uri (RhythmDB
*db
,
2362 rhythmdb_add_uri_with_type (db
, uri
, RHYTHMDB_ENTRY_TYPE_INVALID
);
2366 rhythmdb_add_uri_with_type (RhythmDB
*db
,
2368 RhythmDBEntryType type
)
2373 canon_uri
= rb_canonicalise_uri (uri
);
2374 realuri
= rb_uri_resolve_symlink (canon_uri
);
2376 if (realuri
== NULL
) {
2377 /* create import error entry */
2378 RhythmDBEvent
*event
= g_new0 (RhythmDBEvent
, 1);
2381 event
->real_uri
= rb_refstring_new (canon_uri
);
2382 event
->error
= make_access_failed_error (canon_uri
, GNOME_VFS_ERROR_LOOP
);
2383 rhythmdb_add_import_error_entry (db
, event
);
2386 } else if (rb_uri_is_directory (realuri
)) {
2387 RhythmDBAddThreadData
*data
= g_new0 (RhythmDBAddThreadData
, 1);
2389 data
->uri
= g_strdup (realuri
);
2392 rhythmdb_thread_create (db
, db
->priv
->add_thread_pool
, NULL
, data
);
2394 queue_stat_uri (realuri
, db
, type
);
2402 rhythmdb_sync_library_idle (RhythmDB
*db
)
2404 rhythmdb_sync_library_location (db
);
2405 g_object_unref (db
);
2410 rhythmdb_load_error_cb (GError
*error
)
2412 GDK_THREADS_ENTER ();
2413 rb_error_dialog (NULL
,
2414 _("Could not load the music database"),
2416 g_error_free (error
);
2418 GDK_THREADS_LEAVE ();
2423 rhythmdb_load_thread_main (RhythmDB
*db
)
2425 RhythmDBEvent
*result
;
2426 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2427 GError
*error
= NULL
;
2429 rb_profile_start ("loading db");
2430 g_mutex_lock (db
->priv
->saving_mutex
);
2431 if (klass
->impl_load (db
, &db
->priv
->exiting
, &error
) == FALSE
) {
2432 rb_debug ("db load failed: disabling saving");
2433 db
->priv
->can_save
= FALSE
;
2436 g_idle_add ((GSourceFunc
) rhythmdb_load_error_cb
, error
);
2439 g_mutex_unlock (db
->priv
->saving_mutex
);
2442 g_timeout_add (10000, (GSourceFunc
) rhythmdb_sync_library_idle
, db
);
2444 rb_debug ("queuing db load complete signal");
2445 result
= g_new0 (RhythmDBEvent
, 1);
2446 result
->type
= RHYTHMDB_EVENT_DB_LOAD
;
2447 g_async_queue_push (db
->priv
->event_queue
, result
);
2449 rb_debug ("exiting");
2450 result
= g_new0 (RhythmDBEvent
, 1);
2451 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2452 g_async_queue_push (db
->priv
->event_queue
, result
);
2454 rb_profile_end ("loading db");
2462 * Load the database from disk.
2465 rhythmdb_load (RhythmDB
*db
)
2467 rhythmdb_thread_create (db
, NULL
, (GThreadFunc
) rhythmdb_load_thread_main
, db
);
2471 rhythmdb_save_thread_main (RhythmDB
*db
)
2473 RhythmDBClass
*klass
;
2474 RhythmDBEvent
*result
;
2476 rb_debug ("entering save thread");
2478 g_mutex_lock (db
->priv
->saving_mutex
);
2480 if (!db
->priv
->dirty
&& !db
->priv
->can_save
) {
2481 rb_debug ("no save needed, ignoring");
2482 g_mutex_unlock (db
->priv
->saving_mutex
);
2486 while (db
->priv
->saving
)
2487 g_cond_wait (db
->priv
->saving_condition
, db
->priv
->saving_mutex
);
2489 db
->priv
->saving
= TRUE
;
2491 rb_debug ("saving rhythmdb");
2493 klass
= RHYTHMDB_GET_CLASS (db
);
2494 klass
->impl_save (db
);
2496 db
->priv
->saving
= FALSE
;
2497 db
->priv
->dirty
= FALSE
;
2499 g_mutex_unlock (db
->priv
->saving_mutex
);
2501 g_cond_broadcast (db
->priv
->saving_condition
);
2504 result
= g_new0 (RhythmDBEvent
, 1);
2506 result
->type
= RHYTHMDB_EVENT_DB_SAVED
;
2507 g_async_queue_push (db
->priv
->event_queue
, result
);
2509 result
= g_new0 (RhythmDBEvent
, 1);
2511 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2512 g_async_queue_push (db
->priv
->event_queue
, result
);
2517 * rhythmdb_save_async:
2520 * Save the database to disk, asynchronously.
2523 rhythmdb_save_async (RhythmDB
*db
)
2525 rb_debug ("saving the rhythmdb in the background");
2527 rhythmdb_read_enter (db
);
2529 rhythmdb_thread_create (db
, NULL
, (GThreadFunc
) rhythmdb_save_thread_main
, db
);
2536 * Save the database to disk, not returning until it has been saved.
2539 rhythmdb_save (RhythmDB
*db
)
2541 rb_debug("saving the rhythmdb and blocking");
2543 rhythmdb_save_async (db
);
2545 g_mutex_lock (db
->priv
->saving_mutex
);
2547 while (db
->priv
->saving
)
2548 g_cond_wait (db
->priv
->saving_condition
, db
->priv
->saving_mutex
);
2550 g_mutex_unlock (db
->priv
->saving_mutex
);
2554 * rhythmdb_entry_set:
2556 * @entry: a #RhythmDBEntry.
2557 * @propid: the id of the property to set.
2558 * @value: the property value.
2560 * This function can be called by any code which wishes to change a
2561 * song property and send a notification. It may be called when the
2562 * database is read-only; in this case the change will be queued for
2563 * an unspecified time in the future. The implication of this is that
2564 * rhythmdb_entry_get() may not reflect the changes immediately. However,
2565 * if this property is exposed in the user interface, you should still
2566 * make the change in the widget. Then when the database returns to a
2567 * writable state, your change will take effect in the database too,
2568 * and a notification will be sent at that point.
2570 * Note that you must call rhythmdb_commit() at some point after invoking
2571 * this function, and that even after the commit, your change may not
2572 * have taken effect.
2575 rhythmdb_entry_set (RhythmDB
*db
,
2576 RhythmDBEntry
*entry
,
2578 const GValue
*value
)
2580 g_return_if_fail (RHYTHMDB_IS (db
));
2581 g_return_if_fail (entry
!= NULL
);
2583 if ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) != 0) {
2584 if (!rhythmdb_get_readonly (db
) && rb_is_main_thread ()) {
2585 rhythmdb_entry_set_internal (db
, entry
, TRUE
, propid
, value
);
2587 RhythmDBEvent
*result
;
2589 result
= g_new0 (RhythmDBEvent
, 1);
2591 result
->type
= RHYTHMDB_EVENT_ENTRY_SET
;
2593 rb_debug ("queuing RHYTHMDB_ACTION_ENTRY_SET");
2595 result
->entry
= rhythmdb_entry_ref (entry
);
2596 result
->change
.prop
= propid
;
2597 result
->signal_change
= TRUE
;
2598 g_value_init (&result
->change
.new, G_VALUE_TYPE (value
));
2599 g_value_copy (value
, &result
->change
.new);
2600 g_async_queue_push (db
->priv
->event_queue
, result
);
2603 rhythmdb_entry_set_internal (db
, entry
, FALSE
, propid
, value
);
2608 record_entry_change (RhythmDB
*db
,
2609 RhythmDBEntry
*entry
,
2611 const GValue
*value
)
2613 RhythmDBEntryChange
*changedata
;
2616 changedata
= g_new0 (RhythmDBEntryChange
, 1);
2617 changedata
->prop
= propid
;
2619 /* Copy a temporary gvalue, since _entry_get uses
2620 * _set_static_string to avoid memory allocations. */
2623 g_value_init (&tem
, G_VALUE_TYPE (value
));
2624 rhythmdb_entry_get (db
, entry
, propid
, &tem
);
2625 g_value_init (&changedata
->old
, G_VALUE_TYPE (value
));
2626 g_value_copy (&tem
, &changedata
->old
);
2627 g_value_unset (&tem
);
2629 g_value_init (&changedata
->new, G_VALUE_TYPE (value
));
2630 g_value_copy (value
, &changedata
->new);
2632 g_mutex_lock (db
->priv
->change_mutex
);
2633 /* ref the entry before adding to hash, it is unreffed when removed */
2634 rhythmdb_entry_ref (entry
);
2635 changelist
= g_hash_table_lookup (db
->priv
->changed_entries
, entry
);
2636 changelist
= g_slist_append (changelist
, changedata
);
2637 g_hash_table_insert (db
->priv
->changed_entries
, entry
, changelist
);
2638 g_mutex_unlock (db
->priv
->change_mutex
);
2642 rhythmdb_entry_set_internal (RhythmDB
*db
,
2643 RhythmDBEntry
*entry
,
2644 gboolean notify_if_inserted
,
2646 const GValue
*value
)
2648 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2650 RhythmDBPodcastFields
*podcast
= NULL
;
2652 #ifndef G_DISABLE_ASSERT
2653 switch (G_VALUE_TYPE (value
)) {
2655 /* the playback error is allowed to be NULL */
2656 if (propid
!= RHYTHMDB_PROP_PLAYBACK_ERROR
|| g_value_get_string (value
))
2657 g_assert (g_utf8_validate (g_value_get_string (value
), -1, NULL
));
2659 case G_TYPE_BOOLEAN
:
2665 g_assert_not_reached ();
2670 if ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) && notify_if_inserted
) {
2671 record_entry_change (db
, entry
, propid
, value
);
2674 handled
= klass
->impl_entry_set (db
, entry
, propid
, value
);
2677 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
2678 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
2679 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
2682 case RHYTHMDB_PROP_TYPE
:
2683 case RHYTHMDB_PROP_ENTRY_ID
:
2684 g_assert_not_reached ();
2686 case RHYTHMDB_PROP_TITLE
:
2687 if (entry
->title
!= NULL
) {
2688 rb_refstring_unref (entry
->title
);
2690 entry
->title
= rb_refstring_new (g_value_get_string (value
));
2692 case RHYTHMDB_PROP_ALBUM
:
2693 if (entry
->album
!= NULL
) {
2694 rb_refstring_unref (entry
->album
);
2696 entry
->album
= rb_refstring_new (g_value_get_string (value
));
2698 case RHYTHMDB_PROP_ARTIST
:
2699 if (entry
->artist
!= NULL
) {
2700 rb_refstring_unref (entry
->artist
);
2702 entry
->artist
= rb_refstring_new (g_value_get_string (value
));
2704 case RHYTHMDB_PROP_GENRE
:
2705 if (entry
->genre
!= NULL
) {
2706 rb_refstring_unref (entry
->genre
);
2708 entry
->genre
= rb_refstring_new (g_value_get_string (value
));
2710 case RHYTHMDB_PROP_TRACK_NUMBER
:
2711 entry
->tracknum
= g_value_get_ulong (value
);
2713 case RHYTHMDB_PROP_DISC_NUMBER
:
2714 entry
->discnum
= g_value_get_ulong (value
);
2716 case RHYTHMDB_PROP_DURATION
:
2717 entry
->duration
= g_value_get_ulong (value
);
2719 case RHYTHMDB_PROP_BITRATE
:
2720 entry
->bitrate
= g_value_get_ulong (value
);
2722 case RHYTHMDB_PROP_DATE
:
2725 julian
= g_value_get_ulong (value
);
2727 g_date_set_julian (&entry
->date
, julian
);
2729 g_date_clear (&entry
->date
, 1);
2732 case RHYTHMDB_PROP_TRACK_GAIN
:
2733 entry
->track_gain
= g_value_get_double (value
);
2735 case RHYTHMDB_PROP_TRACK_PEAK
:
2736 entry
->track_peak
= g_value_get_double (value
);
2738 case RHYTHMDB_PROP_ALBUM_GAIN
:
2739 entry
->album_gain
= g_value_get_double (value
);
2741 case RHYTHMDB_PROP_ALBUM_PEAK
:
2742 entry
->album_peak
= g_value_get_double (value
);
2744 case RHYTHMDB_PROP_LOCATION
:
2745 rb_refstring_unref (entry
->location
);
2746 entry
->location
= rb_refstring_new (g_value_get_string (value
));
2748 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
2749 rb_refstring_unref (entry
->playback_error
);
2750 if (g_value_get_string (value
))
2751 entry
->playback_error
= rb_refstring_new (g_value_get_string (value
));
2753 entry
->playback_error
= NULL
;
2755 case RHYTHMDB_PROP_MOUNTPOINT
:
2756 if (entry
->mountpoint
!= NULL
) {
2757 rb_refstring_unref (entry
->mountpoint
);
2759 entry
->mountpoint
= rb_refstring_new (g_value_get_string (value
));
2761 case RHYTHMDB_PROP_FILE_SIZE
:
2762 entry
->file_size
= g_value_get_uint64 (value
);
2764 case RHYTHMDB_PROP_MIMETYPE
:
2765 if (entry
->mimetype
!= NULL
) {
2766 rb_refstring_unref (entry
->mimetype
);
2768 entry
->mimetype
= rb_refstring_new (g_value_get_string (value
));
2770 case RHYTHMDB_PROP_MTIME
:
2771 entry
->mtime
= g_value_get_ulong (value
);
2773 case RHYTHMDB_PROP_FIRST_SEEN
:
2774 entry
->first_seen
= g_value_get_ulong (value
);
2775 entry
->flags
|= RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
;
2777 case RHYTHMDB_PROP_LAST_SEEN
:
2778 entry
->last_seen
= g_value_get_ulong (value
);
2779 entry
->flags
|= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
2781 case RHYTHMDB_PROP_RATING
:
2782 entry
->rating
= g_value_get_double (value
);
2784 case RHYTHMDB_PROP_PLAY_COUNT
:
2785 entry
->play_count
= g_value_get_ulong (value
);
2787 case RHYTHMDB_PROP_LAST_PLAYED
:
2788 entry
->last_played
= g_value_get_ulong (value
);
2789 entry
->flags
|= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
;
2791 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
2792 rb_refstring_unref (entry
->musicbrainz_trackid
);
2793 entry
->musicbrainz_trackid
= rb_refstring_new (g_value_get_string (value
));
2795 case RHYTHMDB_PROP_HIDDEN
:
2796 if (g_value_get_boolean (value
)) {
2797 entry
->flags
|= RHYTHMDB_ENTRY_HIDDEN
;
2799 entry
->flags
&= ~RHYTHMDB_ENTRY_HIDDEN
;
2801 entry
->flags
|= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
2803 case RHYTHMDB_PROP_STATUS
:
2805 podcast
->status
= g_value_get_ulong (value
);
2807 case RHYTHMDB_PROP_DESCRIPTION
:
2809 rb_refstring_unref (podcast
->description
);
2810 podcast
->description
= rb_refstring_new (g_value_get_string (value
));
2812 case RHYTHMDB_PROP_SUBTITLE
:
2814 rb_refstring_unref (podcast
->subtitle
);
2815 podcast
->subtitle
= rb_refstring_new (g_value_get_string (value
));
2817 case RHYTHMDB_PROP_SUMMARY
:
2819 rb_refstring_unref (podcast
->summary
);
2820 podcast
->summary
= rb_refstring_new (g_value_get_string (value
));
2822 case RHYTHMDB_PROP_LANG
:
2824 if (podcast
->lang
!= NULL
) {
2825 rb_refstring_unref (podcast
->lang
);
2827 podcast
->lang
= rb_refstring_new (g_value_get_string (value
));
2829 case RHYTHMDB_PROP_COPYRIGHT
:
2831 if (podcast
->copyright
!= NULL
) {
2832 rb_refstring_unref (podcast
->copyright
);
2834 podcast
->copyright
= rb_refstring_new (g_value_get_string (value
));
2836 case RHYTHMDB_PROP_IMAGE
:
2838 if (podcast
->image
!= NULL
) {
2839 rb_refstring_unref (podcast
->image
);
2841 podcast
->image
= rb_refstring_new (g_value_get_string (value
));
2843 case RHYTHMDB_PROP_POST_TIME
:
2845 podcast
->post_time
= g_value_get_ulong (value
);
2847 case RHYTHMDB_NUM_PROPERTIES
:
2848 g_assert_not_reached ();
2853 /* set the dirty state */
2854 db
->priv
->dirty
= TRUE
;
2858 * rhythmdb_entry_sync_mirrored:
2860 * @type: a #RhythmDBEntry.
2861 * @propid: the property to sync the mirrored version of.
2863 * Synchronise "mirrored" properties, such as the string version of the last-played
2864 * time. This should be called when a property is directly modified, passing the
2865 * original property.
2867 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
2871 rhythmdb_entry_sync_mirrored (RhythmDBEntry
*entry
,
2874 static const char *format
;
2875 static const char *never
;
2878 /* PERFORMANCE: doing these lookups every call creates a noticable slowdown */
2880 format
= _("%Y-%m-%d %H:%M");
2885 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
2887 RBRefString
*old
, *new;
2889 if (!(entry
->flags
& RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
))
2892 old
= g_atomic_pointer_get (&entry
->last_played_str
);
2893 if (entry
->last_played
== 0) {
2894 new = rb_refstring_new (never
);
2896 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->last_played
));
2897 new = rb_refstring_new (val
);
2901 if (g_atomic_pointer_compare_and_exchange (&entry
->last_played_str
, old
, new)) {
2903 rb_refstring_unref (old
);
2906 rb_refstring_unref (new);
2911 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
2913 RBRefString
*old
, *new;
2915 if (!(entry
->flags
& RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
))
2918 old
= g_atomic_pointer_get (&entry
->first_seen_str
);
2919 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->first_seen
));
2920 new = rb_refstring_new (val
);
2923 if (g_atomic_pointer_compare_and_exchange (&entry
->first_seen_str
, old
, new)) {
2925 rb_refstring_unref (old
);
2928 rb_refstring_unref (new);
2933 case RHYTHMDB_PROP_LAST_SEEN_STR
:
2935 RBRefString
*old
, *new;
2937 if (!(entry
->flags
& RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
))
2940 old
= g_atomic_pointer_get (&entry
->last_seen_str
);
2941 /* only store last seen time as a string for hidden entries */
2942 if (entry
->flags
& RHYTHMDB_ENTRY_HIDDEN
) {
2943 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->last_seen
));
2944 new = rb_refstring_new (val
);
2950 if (g_atomic_pointer_compare_and_exchange (&entry
->first_seen_str
, old
, new)) {
2952 rb_refstring_unref (old
);
2955 rb_refstring_unref (new);
2966 * rhythmdb_entry_delete:
2968 * @entry: a #RhythmDBEntry.
2970 * Delete entry @entry from the database, sending notification of it's deletion.
2971 * This is usually used by sources where entries can disappear randomly, such
2972 * as a network source.
2975 rhythmdb_entry_delete (RhythmDB
*db
,
2976 RhythmDBEntry
*entry
)
2978 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2980 g_return_if_fail (RHYTHMDB_IS (db
));
2981 g_return_if_fail (entry
!= NULL
);
2983 /* ref the entry before adding to hash, it is unreffed when removed */
2984 rhythmdb_entry_ref (entry
);
2986 klass
->impl_entry_delete (db
, entry
);
2988 g_mutex_lock (db
->priv
->change_mutex
);
2989 g_hash_table_insert (db
->priv
->deleted_entries
, entry
, g_thread_self ());
2990 g_mutex_unlock (db
->priv
->change_mutex
);
2992 /* deleting an entry makes the db dirty */
2993 db
->priv
->dirty
= TRUE
;
2997 rhythmdb_entry_move_to_trash_cb (GnomeVFSXferProgressInfo
*info
,
3000 /* Abort immediately if anything happens */
3001 if (info
->status
== GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR
)
3002 return GNOME_VFS_XFER_ERROR_ACTION_ABORT
;
3003 /* Don't overwrite files */
3004 if (info
->status
== GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE
)
3010 rhythmdb_entry_move_to_trash_set_error (RhythmDB
*db
,
3011 RhythmDBEntry
*entry
,
3014 GValue value
= { 0, };
3017 res
= GNOME_VFS_ERROR_INTERNAL
;
3019 g_value_init (&value
, G_TYPE_STRING
);
3020 g_value_set_string (&value
, gnome_vfs_result_to_string (res
));
3021 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
3022 g_value_unset (&value
);
3024 rb_debug ("Deleting %s failed: %s", rb_refstring_get (entry
->location
),
3025 gnome_vfs_result_to_string (res
));
3029 rhythmdb_entry_move_to_trash (RhythmDB
*db
,
3030 RhythmDBEntry
*entry
)
3033 GnomeVFSURI
*uri
, *trash
, *dest
;
3036 uri
= gnome_vfs_uri_new (rb_refstring_get (entry
->location
));
3038 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3042 res
= gnome_vfs_find_directory (uri
,
3043 GNOME_VFS_DIRECTORY_KIND_TRASH
,
3047 if (res
!= GNOME_VFS_OK
|| trash
== NULL
) {
3048 /* If the file doesn't exist, or trash isn't support,
3049 * remove it from the db */
3050 if (res
== GNOME_VFS_ERROR_NOT_FOUND
||
3051 res
== GNOME_VFS_ERROR_NOT_SUPPORTED
) {
3052 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
3054 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3057 gnome_vfs_uri_unref (uri
);
3061 /* Is the file already in the Trash? If so it should be hidden */
3062 if (gnome_vfs_uri_is_parent (trash
, uri
, TRUE
)) {
3063 GValue value
= { 0, };
3064 g_value_init (&value
, G_TYPE_BOOLEAN
);
3065 g_value_set_boolean (&value
, TRUE
);
3066 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_HIDDEN
, &value
);
3067 rhythmdb_commit (db
);
3069 gnome_vfs_uri_unref (trash
);
3070 gnome_vfs_uri_unref (uri
);
3074 shortname
= gnome_vfs_uri_extract_short_name (uri
);
3075 if (shortname
== NULL
) {
3076 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3077 rhythmdb_commit (db
);
3078 gnome_vfs_uri_unref (uri
);
3079 gnome_vfs_uri_unref (trash
);
3083 /* Compute the destination URI */
3084 dest
= gnome_vfs_uri_append_path (trash
, shortname
);
3085 gnome_vfs_uri_unref (trash
);
3088 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3089 rhythmdb_commit (db
);
3090 gnome_vfs_uri_unref (uri
);
3094 /* RB can't tell that a file's moved, so no unique names */
3095 res
= gnome_vfs_xfer_uri (uri
, dest
,
3096 GNOME_VFS_XFER_REMOVESOURCE
,
3097 GNOME_VFS_XFER_ERROR_MODE_ABORT
,
3098 GNOME_VFS_XFER_OVERWRITE_MODE_SKIP
,
3099 rhythmdb_entry_move_to_trash_cb
,
3102 if (res
== GNOME_VFS_OK
) {
3103 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
3105 rhythmdb_entry_move_to_trash_set_error (db
, entry
, res
);
3107 rhythmdb_commit (db
);
3109 gnome_vfs_uri_unref (dest
);
3110 gnome_vfs_uri_unref (uri
);
3114 * rhythmdb_entry_delete_by_type:
3116 * @type: type of entried to delete.
3118 * Delete all entries from the database of the given type.
3119 * This is usually used by non-permanent sources when they disappear, such as
3120 * removable media being removed, or a network share becoming unavailable.
3123 rhythmdb_entry_delete_by_type (RhythmDB
*db
,
3124 RhythmDBEntryType type
)
3126 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3128 if (klass
->impl_entry_delete_by_type
) {
3129 klass
->impl_entry_delete_by_type (db
, type
);
3131 g_warning ("delete_by_type not implemented");
3136 rhythmdb_nice_elt_name_from_propid (RhythmDB
*db
,
3137 RhythmDBPropType propid
)
3139 return db
->priv
->column_xml_names
[propid
];
3143 rhythmdb_propid_from_nice_elt_name (RhythmDB
*db
,
3144 const xmlChar
*name
)
3147 if (g_hash_table_lookup_extended (db
->priv
->propname_map
, name
,
3149 return GPOINTER_TO_INT (ret
);
3155 * rhythmdb_entry_lookup_by_location:
3157 * @uri: the URI of the entry to lookup.
3159 * Looks up the entry with location @uri.
3161 * Returns: the entry with location @uri, or NULL if no such entry exists.
3164 rhythmdb_entry_lookup_by_location (RhythmDB
*db
,
3169 rs
= rb_refstring_find (uri
);
3171 return rhythmdb_entry_lookup_by_location_refstring (db
, rs
);
3178 rhythmdb_entry_lookup_by_location_refstring (RhythmDB
*db
,
3181 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3183 return klass
->impl_lookup_by_location (db
, uri
);
3187 *rhythmdb_entry_lookup_by_id:
3191 * Looks up the entry with id @id.
3193 * Returns: the entry with id @id, or NULL if no such entry exists.
3196 rhythmdb_entry_lookup_by_id (RhythmDB
*db
,
3199 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3201 return klass
->impl_lookup_by_id (db
, id
);
3205 *rhythmdb_entry_lookup_from_string:
3208 * @is_id: whether the string is an entry ID or a location.
3210 * Locates an entry using a string containing either an entry ID
3213 * Returns: the entry matching the string, or NULL if no such entry exists.
3216 rhythmdb_entry_lookup_from_string (RhythmDB
*db
,
3223 id
= strtoul (str
, NULL
, 10);
3227 return rhythmdb_entry_lookup_by_id (db
, id
);
3229 return rhythmdb_entry_lookup_by_location (db
, str
);
3234 *rhythmdb_entry_foreach:
3236 * @func: the function to call with each entry.
3237 * @data: user data to pass to the function.
3239 * Calls the given function for each of the entries in the database.
3242 rhythmdb_entry_foreach (RhythmDB
*db
,
3246 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3248 klass
->impl_entry_foreach (db
, func
, data
);
3252 *rhythmdb_entry_count:
3255 * Returns: the number of entries in the database.
3258 rhythmdb_entry_count (RhythmDB
*db
)
3260 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3262 return klass
->impl_entry_count (db
);
3266 *rhythmdb_entry_foreach_by_type:
3268 * @entry_type: the type of entry to retrieve
3269 * @func: the function to call with each entry
3270 * @data: user data to pass to the function.
3272 * Calls the given function for each of the entries in the database
3276 rhythmdb_entry_foreach_by_type (RhythmDB
*db
,
3277 RhythmDBEntryType entry_type
,
3281 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3283 klass
->impl_entry_foreach_by_type (db
, entry_type
, func
, data
);
3287 *rhythmdb_entry_count_by_type:
3289 * @entry_type: a #RhythmDBEntryType.
3291 * Returns: the number of entries in the database of a particular type.
3294 rhythmdb_entry_count_by_type (RhythmDB
*db
,
3295 RhythmDBEntryType entry_type
)
3297 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3299 return klass
->impl_entry_count_by_type (db
, entry_type
);
3304 * rhythmdb_evaluate_query:
3307 * @entry a @RhythmDBEntry.
3309 * Evaluates the given entry against the given query.
3311 * Returns: whether the given entry matches the criteria of the given query.
3314 rhythmdb_evaluate_query (RhythmDB
*db
,
3316 RhythmDBEntry
*entry
)
3318 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3320 return klass
->impl_evaluate_query (db
, query
, entry
);
3324 rhythmdb_query_internal (RhythmDBQueryThreadData
*data
)
3326 RhythmDBEvent
*result
;
3327 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (data
->db
);
3329 rhythmdb_query_preprocess (data
->db
, data
->query
);
3331 rb_debug ("doing query");
3333 klass
->impl_do_full_query (data
->db
, data
->query
,
3337 rb_debug ("completed");
3338 rhythmdb_query_results_query_complete (data
->results
);
3340 result
= g_new0 (RhythmDBEvent
, 1);
3341 result
->db
= data
->db
;
3342 result
->type
= RHYTHMDB_EVENT_QUERY_COMPLETE
;
3343 result
->results
= data
->results
;
3344 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
3346 rhythmdb_query_free (data
->query
);
3350 query_thread_main (RhythmDBQueryThreadData
*data
)
3352 RhythmDBEvent
*result
;
3354 rb_debug ("entering query thread");
3356 rhythmdb_query_internal (data
);
3358 result
= g_new0 (RhythmDBEvent
, 1);
3359 result
->db
= data
->db
;
3360 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
3361 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
3367 rhythmdb_do_full_query_async_parsed (RhythmDB
*db
,
3368 RhythmDBQueryResults
*results
,
3371 RhythmDBQueryThreadData
*data
;
3373 data
= g_new0 (RhythmDBQueryThreadData
, 1);
3375 data
->query
= rhythmdb_query_copy (query
);
3376 data
->results
= results
;
3377 data
->cancel
= FALSE
;
3379 rhythmdb_read_enter (db
);
3381 rhythmdb_query_results_set_query (results
, query
);
3383 g_object_ref (results
);
3385 g_atomic_int_inc (&db
->priv
->outstanding_threads
);
3386 g_async_queue_ref (db
->priv
->action_queue
);
3387 g_async_queue_ref (db
->priv
->event_queue
);
3388 g_thread_pool_push (db
->priv
->query_thread_pool
, data
, NULL
);
3392 rhythmdb_do_full_query_async (RhythmDB
*db
,
3393 RhythmDBQueryResults
*results
,
3399 va_start (args
, results
);
3401 query
= rhythmdb_query_parse_valist (db
, args
);
3403 rhythmdb_do_full_query_async_parsed (db
, results
, query
);
3405 rhythmdb_query_free (query
);
3411 rhythmdb_do_full_query_internal (RhythmDB
*db
,
3412 RhythmDBQueryResults
*results
,
3415 RhythmDBQueryThreadData
*data
;
3417 data
= g_new0 (RhythmDBQueryThreadData
, 1);
3419 data
->query
= rhythmdb_query_copy (query
);
3420 data
->results
= results
;
3421 data
->cancel
= FALSE
;
3423 rhythmdb_read_enter (db
);
3425 rhythmdb_query_results_set_query (results
, query
);
3426 g_object_ref (results
);
3428 rhythmdb_query_internal (data
);
3433 rhythmdb_do_full_query_parsed (RhythmDB
*db
,
3434 RhythmDBQueryResults
*results
,
3437 rhythmdb_do_full_query_internal (db
, results
, query
);
3441 rhythmdb_do_full_query (RhythmDB
*db
,
3442 RhythmDBQueryResults
*results
,
3448 va_start (args
, results
);
3450 query
= rhythmdb_query_parse_valist (db
, args
);
3452 rhythmdb_do_full_query_internal (db
, results
, query
);
3454 rhythmdb_query_free (query
);
3459 /* This should really be standard. */
3460 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3463 rhythmdb_query_type_get_type (void)
3465 static GType etype
= 0;
3469 static const GEnumValue values
[] =
3472 ENUM_ENTRY (RHYTHMDB_QUERY_END
, "Query end marker"),
3473 ENUM_ENTRY (RHYTHMDB_QUERY_DISJUNCTION
, "Disjunctive marker"),
3474 ENUM_ENTRY (RHYTHMDB_QUERY_SUBQUERY
, "Subquery"),
3475 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_EQUALS
, "Property equivalence"),
3476 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LIKE
, "Fuzzy property matching"),
3477 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_LIKE
, "Inverted fuzzy property matching"),
3478 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_PREFIX
, "Starts with"),
3479 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_SUFFIX
, "Ends with"),
3480 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_GREATER
, "True if property1 >= property2"),
3481 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LESS
, "True if property1 <= property2"),
3482 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
, "True if property1 is within property2 of the current time"),
3483 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN
, "True if property1 is not within property2 of the current time"),
3484 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_EQUALS
, "Year equivalence: true if date within year"),
3485 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_GREATER
, "True if date greater than year"),
3486 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_LESS
, "True if date less than year"),
3490 etype
= g_enum_register_static ("RhythmDBQueryType", values
);
3497 rhythmdb_prop_type_get_type (void)
3499 static GType etype
= 0;
3503 static const GEnumValue values
[] =
3505 /* We reuse the description to store extra data about
3506 * a property. The first part is just a generic
3507 * human-readable description. Next, there is
3508 * a string describing the GType of the property, in
3510 * Finally, there is the XML element name in brackets.
3512 ENUM_ENTRY (RHYTHMDB_PROP_TYPE
, "Type of entry (gpointer) [type]"),
3513 ENUM_ENTRY (RHYTHMDB_PROP_ENTRY_ID
, "Numeric ID (guint) [entry-id]"),
3514 ENUM_ENTRY (RHYTHMDB_PROP_TITLE
, "Title (gchararray) [title]"),
3515 ENUM_ENTRY (RHYTHMDB_PROP_GENRE
, "Genre (gchararray) [genre]"),
3516 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST
, "Artist (gchararray) [artist]"),
3517 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM
, "Album (gchararray) [album]"),
3518 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_NUMBER
, "Track Number (gulong) [track-number]"),
3519 ENUM_ENTRY (RHYTHMDB_PROP_DISC_NUMBER
, "Disc Number (gulong) [disc-number]"),
3520 ENUM_ENTRY (RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
, "Musicbrainz Track ID (gchararray) [mb-trackid]"),
3522 ENUM_ENTRY (RHYTHMDB_PROP_DURATION
, "Duration (gulong) [duration]"),
3523 ENUM_ENTRY (RHYTHMDB_PROP_FILE_SIZE
, "File Size (guint64) [file-size]"),
3524 ENUM_ENTRY (RHYTHMDB_PROP_LOCATION
, "Location (gchararray) [location]"),
3525 ENUM_ENTRY (RHYTHMDB_PROP_MOUNTPOINT
, "Mount point it's located in (gchararray) [mountpoint]"),
3526 ENUM_ENTRY (RHYTHMDB_PROP_MTIME
, "Modification time (gulong) [mtime]"),
3527 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN
, "Time the song was added to the library (gulong) [first-seen]"),
3528 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN
, "Last time the song was available (gulong) [last-seen]"),
3529 ENUM_ENTRY (RHYTHMDB_PROP_RATING
, "Rating (gdouble) [rating]"),
3530 ENUM_ENTRY (RHYTHMDB_PROP_PLAY_COUNT
, "Play Count (gulong) [play-count]"),
3531 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED
, "Last Played (gulong) [last-played]"),
3532 ENUM_ENTRY (RHYTHMDB_PROP_BITRATE
, "Bitrate (gulong) [bitrate]"),
3533 ENUM_ENTRY (RHYTHMDB_PROP_DATE
, "Date of release (gulong) [date]"),
3534 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_GAIN
, "Replaygain track gain (gdouble) [replaygain-track-gain]"),
3535 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_PEAK
, "Replaygain track peak (gdouble) [replaygain-track-peak]"),
3536 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_GAIN
, "Replaygain album pain (gdouble) [replaygain-album-gain]"),
3537 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_PEAK
, "Replaygain album peak (gdouble) [replaygain-album-peak]"),
3538 ENUM_ENTRY (RHYTHMDB_PROP_MIMETYPE
, "Mime Type (gchararray) [mimetype]"),
3539 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_SORT_KEY
, "Title sort key (gchararray) [title-sort-key]"),
3540 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_SORT_KEY
, "Genre sort key (gchararray) [genre-sort-key]"),
3541 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_SORT_KEY
, "Artist sort key (gchararray) [artist-sort-key]"),
3542 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_SORT_KEY
, "Album sort key (gchararray) [album-sort-key]"),
3544 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_FOLDED
, "Title folded (gchararray) [title-folded]"),
3545 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_FOLDED
, "Genre folded (gchararray) [genre-folded]"),
3546 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_FOLDED
, "Artist folded (gchararray) [artist-folded]"),
3547 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_FOLDED
, "Album folded (gchararray) [album-folded]"),
3548 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED_STR
, "Last Played (gchararray) [last-played-str]"),
3549 ENUM_ENTRY (RHYTHMDB_PROP_PLAYBACK_ERROR
, "Playback error string (gchararray) [playback-error]"),
3550 ENUM_ENTRY (RHYTHMDB_PROP_HIDDEN
, "Hidden (gboolean) [hidden]"),
3551 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN_STR
, "Time Added to Library (gchararray) [first-seen-str]"),
3552 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN_STR
, "Last time the song was available (gchararray) [last-seen-str]"),
3553 ENUM_ENTRY (RHYTHMDB_PROP_SEARCH_MATCH
, "Search matching key (gchararray) [search-match]"),
3554 ENUM_ENTRY (RHYTHMDB_PROP_YEAR
, "Year of date (gulong) [year]"),
3556 ENUM_ENTRY (RHYTHMDB_PROP_STATUS
, "Status of file (gulong) [status]"),
3557 ENUM_ENTRY (RHYTHMDB_PROP_DESCRIPTION
, "Podcast description(gchararray) [description]"),
3558 ENUM_ENTRY (RHYTHMDB_PROP_SUBTITLE
, "Podcast subtitle (gchararray) [subtitle]"),
3559 ENUM_ENTRY (RHYTHMDB_PROP_SUMMARY
, "Podcast summary (gchararray) [summary]"),
3560 ENUM_ENTRY (RHYTHMDB_PROP_LANG
, "Podcast language (gchararray) [lang]"),
3561 ENUM_ENTRY (RHYTHMDB_PROP_COPYRIGHT
, "Podcast copyright (gchararray) [copyright]"),
3562 ENUM_ENTRY (RHYTHMDB_PROP_IMAGE
, "Podcast image(gchararray) [image]"),
3563 ENUM_ENTRY (RHYTHMDB_PROP_POST_TIME
, "Podcast time of post (gulong) [post-time]"),
3566 g_assert ((sizeof (values
) / sizeof (values
[0]) - 1) == RHYTHMDB_NUM_PROPERTIES
);
3567 etype
= g_enum_register_static ("RhythmDBPropType", values
);
3574 rhythmdb_emit_entry_deleted (RhythmDB
*db
,
3575 RhythmDBEntry
*entry
)
3577 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_DELETED
], 0, entry
);
3581 rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint
*ihint
,
3582 GValue
*return_accu
,
3583 const GValue
*handler_return
,
3586 if (handler_return
== NULL
)
3589 g_value_copy (handler_return
, return_accu
);
3590 return (g_value_get_boxed (return_accu
) == NULL
);
3594 * rhythmdb_entry_request_extra_metadata:
3596 * @entry: a #RhythmDBEntry
3597 * @property_name: the metadata predicate
3599 * Emits a request for extra metadata for the @entry.
3600 * The @property_name argument is emitted as the ::detail part of the
3601 * "entry_extra_metadata_request" signal. It should be a namespaced RDF
3602 * predicate e.g. from Dublin Core, MusicBrainz, or internal to Rhythmbox
3603 * (namespace "rb:"). Suitable predicates would be those that are expensive to
3604 * acquire or only apply to a limited range of entries.
3605 * Handlers capable of providing a particular predicate may ensure they only
3606 * see appropriate requests by supplying an appropriate ::detail part when
3607 * connecting to the signal. Upon a handler returning a non-%NULL value,
3608 * emission will be stopped and the value returned to the caller; if no
3609 * handlers return a non-%NULL value, the caller will receive %NULL. Priority
3610 * is determined by signal connection order, with %G_CONNECT_AFTER providing a
3611 * second, lower rank of priority.
3612 * A handler returning a value should do so in a #GValue allocated on the heap;
3613 * the accumulator will take ownership. The caller should unset and free the
3614 * #GValue if non-%NULL when finished with it.
3616 * Returns: an allocated, initialised, set #GValue, or NULL
3619 rhythmdb_entry_request_extra_metadata (RhythmDB
*db
,
3620 RhythmDBEntry
*entry
,
3621 const gchar
*property_name
)
3623 GValue
*value
= NULL
;
3625 g_signal_emit (G_OBJECT (db
),
3626 rhythmdb_signals
[ENTRY_EXTRA_METADATA_REQUEST
],
3627 g_quark_from_string (property_name
),
3635 * rhythmdb_emit_entry_extra_metadata_notify:
3637 * @entry: a #RhythmDBEntry
3638 * @property_name: the metadata predicate
3639 * @metadata: a #GValue
3641 * Emits a signal describing extra metadata for the @entry. The @property_name
3642 * argument is emitted as the ::detail part of the
3643 * "entry_extra_metadata_notify" signal and as the 'field' parameter. Handlers
3644 * can ensure they only get metadata they are interested in by supplying an
3645 * appropriate ::detail part when connecting to the signal. If handlers are
3646 * interested in the metadata they should ref or copy the contents of @metadata
3647 * and unref or free it when they are finished with it.
3650 rhythmdb_emit_entry_extra_metadata_notify (RhythmDB
*db
,
3651 RhythmDBEntry
*entry
,
3652 const gchar
*property_name
,
3653 const GValue
*metadata
)
3655 g_signal_emit (G_OBJECT (db
),
3656 rhythmdb_signals
[ENTRY_EXTRA_METADATA_NOTIFY
],
3657 g_quark_from_string (property_name
),
3664 unset_and_free_g_value (gpointer valpointer
)
3666 GValue
*value
= valpointer
;
3667 g_value_unset (value
);
3672 * rhythmdb_entry_extra_gather:
3674 * @entry: a #RhythmDBEntry
3676 * Gathers all metadata for the @entry. The returned GHashTable maps property
3677 * names and extra metadata names (described under
3678 * @rhythmdb_entry_request_extra_metadata) to GValues. Anything wanting to
3679 * provide extra metadata should connect to the "entry_extra_metadata_gather"
3682 * Returns: a GHashTable containing metadata for the entry. This must be freed
3683 * using g_hash_table_destroy.
3686 rhythmdb_entry_gather_metadata (RhythmDB
*db
,
3687 RhythmDBEntry
*entry
)
3689 GHashTable
*metadata
;
3693 metadata
= g_hash_table_new_full (g_str_hash
,
3696 unset_and_free_g_value
);
3698 /* add core properties */
3699 klass
= g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE
);
3700 for (i
= 0; i
< klass
->n_values
; i
++) {
3706 prop
= klass
->values
[i
].value
;
3708 /* only include easily marshallable types in the hash table */
3709 value_type
= rhythmdb_get_property_type (db
, prop
);
3710 switch (value_type
) {
3712 case G_TYPE_BOOLEAN
:
3721 value
= g_new0 (GValue
, 1);
3722 g_value_init (value
, value_type
);
3723 rhythmdb_entry_get (db
, entry
, prop
, value
);
3724 name
= (char *)rhythmdb_nice_elt_name_from_propid (db
, prop
);
3725 g_hash_table_insert (metadata
,
3726 (gpointer
) g_strdup (name
),
3729 g_type_class_unref (klass
);
3731 /* gather extra metadata */
3732 g_signal_emit (G_OBJECT (db
),
3733 rhythmdb_signals
[ENTRY_EXTRA_METADATA_GATHER
], 0,
3741 queue_is_empty (GAsyncQueue
*queue
)
3743 return g_async_queue_length (queue
) <= 0;
3750 * Returns: whether the #RhythmDB has events to process.
3753 rhythmdb_is_busy (RhythmDB
*db
)
3755 return (!db
->priv
->action_thread_running
||
3756 !queue_is_empty (db
->priv
->event_queue
) ||
3757 !queue_is_empty (db
->priv
->action_queue
) ||
3758 (db
->priv
->stat_handle
!= NULL
) ||
3759 (db
->priv
->outstanding_stats
!= NULL
));
3763 * rhythmdb_compute_status_normal:
3764 * @n_songs: the number of tracks.
3765 * @duration: the total duration of the tracks.
3766 * @size: the total size of the tracks.
3767 * @singular: singular form of the format string to use for entries (eg "%d song")
3768 * @plural: plural form of the format string to use for entries (eg "%d songs")
3770 * Creates a string containing the "status" information about a list of tracks.
3771 * The singular and plural strings must be used in a direct ngettext call
3772 * elsewhere in order for them to be marked for translation correctly.
3774 * Returns: the string, which should be freed with g_free.
3777 rhythmdb_compute_status_normal (gint n_songs
,
3780 const char *singular
,
3783 long days
, hours
, minutes
, seconds
;
3784 char *songcount
= NULL
;
3786 char *size_str
= NULL
;
3788 const char *minutefmt
;
3789 const char *hourfmt
;
3792 songcount
= g_strdup_printf (ngettext (singular
, plural
, n_songs
), n_songs
);
3794 days
= duration
/ (60 * 60 * 24);
3795 hours
= (duration
/ (60 * 60)) - (days
* 24);
3796 minutes
= (duration
/ 60) - ((days
* 24 * 60) + (hours
* 60));
3797 seconds
= duration
% 60;
3799 minutefmt
= ngettext ("%ld minute", "%ld minutes", minutes
);
3800 hourfmt
= ngettext ("%ld hour", "%ld hours", hours
);
3801 dayfmt
= ngettext ("%ld day", "%ld days", days
);
3806 /* Translators: the format is "X days, X hours and X minutes" */
3807 fmt
= g_strdup_printf (_("%s, %s and %s"), dayfmt
, hourfmt
, minutefmt
);
3808 time
= g_strdup_printf (fmt
, days
, hours
, minutes
);
3812 /* Translators: the format is "X days and X hours" */
3813 fmt
= g_strdup_printf (_("%s and %s"), dayfmt
, hourfmt
);
3814 time
= g_strdup_printf (fmt
, days
, hours
);
3820 /* Translators: the format is "X days and X minutes" */
3821 fmt
= g_strdup_printf (_("%s and %s"), dayfmt
, minutefmt
);
3822 time
= g_strdup_printf (fmt
, days
, minutes
);
3825 time
= g_strdup_printf (dayfmt
, days
);
3831 /* Translators: the format is "X hours and X minutes" */
3832 fmt
= g_strdup_printf (_("%s and %s"), hourfmt
, minutefmt
);
3833 time
= g_strdup_printf (fmt
, hours
, minutes
);
3836 time
= g_strdup_printf (hourfmt
, hours
);
3840 time
= g_strdup_printf (minutefmt
, minutes
);
3844 size_str
= gnome_vfs_format_file_size_for_display (size
);
3846 if (size
> 0 && duration
> 0) {
3847 ret
= g_strdup_printf ("%s, %s, %s", songcount
, time
, size_str
);
3848 } else if (duration
> 0) {
3849 ret
= g_strdup_printf ("%s, %s", songcount
, time
);
3850 } else if (size
> 0) {
3851 ret
= g_strdup_printf ("%s, %s", songcount
, size_str
);
3853 ret
= g_strdup (songcount
);
3864 default_sync_metadata (RhythmDB
*db
,
3865 RhythmDBEntry
*entry
,
3870 GError
*local_error
= NULL
;
3872 uri
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
3873 rb_metadata_load (db
->priv
->metadata
,
3875 if (local_error
!= NULL
) {
3876 g_propagate_error (error
, local_error
);
3880 entry_to_rb_metadata (db
, entry
, db
->priv
->metadata
);
3882 rb_metadata_save (db
->priv
->metadata
, &local_error
);
3883 if (local_error
!= NULL
) {
3884 RhythmDBAction
*load_action
;
3886 /* reload the metadata, to revert the db changes */
3887 load_action
= g_new0 (RhythmDBAction
, 1);
3888 load_action
->type
= RHYTHMDB_ACTION_LOAD
;
3889 load_action
->uri
= rb_refstring_ref (entry
->location
);
3890 load_action
->entry_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3891 g_async_queue_push (db
->priv
->action_queue
, load_action
);
3893 g_propagate_error (error
, local_error
);
3898 * rhythmdb_entry_register_type:
3900 * @name: optional name for the entry type
3902 * Registers a new #RhythmDBEntryType. This should be called to create a new
3903 * entry type for non-permanent sources.
3905 * Returns: the new #RhythmDBEntryType.
3908 rhythmdb_entry_register_type (RhythmDB
*db
,
3911 RhythmDBEntryType type
;
3912 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3914 type
= g_new0 (RhythmDBEntryType_
, 1);
3915 type
->can_sync_metadata
= (RhythmDBEntryCanSyncFunc
)rb_false_function
;
3916 type
->sync_metadata
= default_sync_metadata
;
3918 type
->name
= g_strdup (name
);
3919 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
3920 g_hash_table_insert (db
->priv
->entry_type_map
, g_strdup (type
->name
), type
);
3921 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
3924 if (klass
->impl_entry_type_registered
)
3925 klass
->impl_entry_type_registered (db
, name
, type
);
3931 rhythmdb_entry_register_type_alias (RhythmDB
*db
,
3932 RhythmDBEntryType type
,
3935 char *dn
= g_strdup (name
);
3937 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
3938 g_hash_table_insert (db
->priv
->entry_type_map
, dn
, type
);
3939 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
3945 } RhythmDBEntryTypeForeachData
;
3948 rhythmdb_entry_type_foreach_cb (const char *name
,
3949 RhythmDBEntryType entry_type
,
3950 RhythmDBEntryTypeForeachData
*data
)
3953 if (strcmp (entry_type
->name
, name
))
3956 data
->func ((gpointer
) name
, entry_type
, data
->data
);
3960 * rhythmdb_entry_type_foreach:
3962 * @func: callback function to call for each registered entry type
3963 * @data: data to pass to the callback
3965 * Calls a function for each registered entry type.
3968 rhythmdb_entry_type_foreach (RhythmDB
*db
,
3972 RhythmDBEntryTypeForeachData d
;
3977 g_mutex_lock (db
->priv
->entry_type_mutex
);
3978 g_hash_table_foreach (db
->priv
->entry_type_map
,
3979 (GHFunc
) rhythmdb_entry_type_foreach_cb
,
3981 g_mutex_unlock (db
->priv
->entry_type_mutex
);
3985 * rhythmdb_entry_type_get_by_name:
3987 * @name: name of the type to look for
3989 * Locates a #RhythmDBEntryType by name. Returns
3990 * RHYTHMDB_ENTRY_TYPE_INVALID if no entry type
3991 * is registered with the specified name.
3993 * Returns: the #RhythmDBEntryType
3996 rhythmdb_entry_type_get_by_name (RhythmDB
*db
,
4001 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
4002 if (db
->priv
->entry_type_map
) {
4003 t
= g_hash_table_lookup (db
->priv
->entry_type_map
, name
);
4005 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
4008 return (RhythmDBEntryType
) t
;
4010 return RHYTHMDB_ENTRY_TYPE_INVALID
;
4014 song_can_sync_metadata (RhythmDB
*db
,
4015 RhythmDBEntry
*entry
,
4018 const char *mimetype
;
4020 mimetype
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_MIMETYPE
);
4021 return rb_metadata_can_save (db
->priv
->metadata
, mimetype
);
4025 podcast_get_playback_uri (RhythmDBEntry
*entry
,
4028 return rhythmdb_entry_dup_string (entry
, RHYTHMDB_PROP_MOUNTPOINT
);
4032 podcast_data_destroy (RhythmDBEntry
*entry
,
4035 RhythmDBPodcastFields
*podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
4036 rb_refstring_unref (podcast
->description
);
4037 rb_refstring_unref (podcast
->subtitle
);
4038 rb_refstring_unref (podcast
->summary
);
4039 rb_refstring_unref (podcast
->lang
);
4040 rb_refstring_unref (podcast
->copyright
);
4041 rb_refstring_unref (podcast
->image
);
4044 static RhythmDBEntryType song_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
4045 static RhythmDBEntryType ignore_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
4046 static RhythmDBEntryType import_error_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
4049 static RhythmDBEntryType podcast_post_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
4050 static RhythmDBEntryType podcast_feed_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
4053 rhythmdb_register_core_entry_types (RhythmDB
*db
)
4056 song_type
= rhythmdb_entry_register_type (db
, "song");
4057 rhythmdb_entry_register_type_alias (db
, song_type
, "0");
4058 song_type
->save_to_disk
= TRUE
;
4059 song_type
->category
= RHYTHMDB_ENTRY_NORMAL
;
4060 song_type
->can_sync_metadata
= song_can_sync_metadata
;
4063 import_error_type
= rhythmdb_entry_register_type (db
, "import-error");
4064 import_error_type
->get_playback_uri
= (RhythmDBEntryStringFunc
)rb_null_function
;
4065 import_error_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
4068 ignore_type
= rhythmdb_entry_register_type (db
, "ignore");
4069 ignore_type
->save_to_disk
= TRUE
;
4070 ignore_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
4073 podcast_post_type
= rhythmdb_entry_register_type (db
, "podcast-post");
4074 podcast_post_type
->entry_type_data_size
= sizeof (RhythmDBPodcastFields
);
4075 podcast_post_type
->save_to_disk
= TRUE
;
4076 podcast_post_type
->category
= RHYTHMDB_ENTRY_NORMAL
;
4077 podcast_post_type
->pre_entry_destroy
= (RhythmDBEntryActionFunc
) podcast_data_destroy
;
4078 podcast_post_type
->get_playback_uri
= podcast_get_playback_uri
;
4081 podcast_feed_type
= rhythmdb_entry_register_type (db
, "podcast-feed");
4082 podcast_feed_type
->entry_type_data_size
= sizeof (RhythmDBPodcastFields
);
4083 podcast_feed_type
->save_to_disk
= TRUE
;
4084 podcast_feed_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
4085 podcast_feed_type
->pre_entry_destroy
= (RhythmDBEntryActionFunc
) podcast_data_destroy
;
4089 rhythmdb_entry_song_get_type (void)
4095 rhythmdb_entry_ignore_get_type (void)
4101 rhythmdb_entry_import_error_get_type (void)
4103 return import_error_type
;
4107 rhythmdb_entry_podcast_post_get_type (void)
4109 return podcast_post_type
;
4113 rhythmdb_entry_podcast_feed_get_type (void)
4115 return podcast_feed_type
;
4119 rhythmdb_entry_set_mount_point (RhythmDB
*db
,
4120 RhythmDBEntry
*entry
,
4121 const gchar
*realuri
)
4124 GValue value
= {0, };
4126 mount_point
= rb_uri_get_mount_point (realuri
);
4127 if (mount_point
!= NULL
) {
4128 g_value_init (&value
, G_TYPE_STRING
);
4129 g_value_set_string_take_ownership (&value
, mount_point
);
4130 rhythmdb_entry_set_internal (db
, entry
, FALSE
,
4131 RHYTHMDB_PROP_MOUNTPOINT
,
4133 g_value_unset (&value
);
4138 rhythmdb_entry_set_visibility (RhythmDB
*db
,
4139 RhythmDBEntry
*entry
,
4142 GValue old_val
= {0, };
4143 gboolean old_visible
;
4145 g_return_if_fail (RHYTHMDB_IS (db
));
4146 g_return_if_fail (entry
!= NULL
);
4148 g_value_init (&old_val
, G_TYPE_BOOLEAN
);
4150 rhythmdb_entry_get (db
, entry
, RHYTHMDB_PROP_HIDDEN
, &old_val
);
4151 old_visible
= !g_value_get_boolean (&old_val
);
4153 if ((old_visible
&& !visible
) || (!old_visible
&& visible
)) {
4154 GValue new_val
= {0, };
4156 g_value_init (&new_val
, G_TYPE_BOOLEAN
);
4157 g_value_set_boolean (&new_val
, !visible
);
4158 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
4159 RHYTHMDB_PROP_HIDDEN
, &new_val
);
4160 g_value_unset (&new_val
);
4162 g_value_unset (&old_val
);
4166 rhythmdb_idle_save (RhythmDB
*db
)
4168 if (db
->priv
->dirty
) {
4169 rb_debug ("database is dirty, doing regular save");
4170 rhythmdb_save_async (db
);
4177 rhythmdb_sync_library_location (RhythmDB
*db
)
4179 gboolean reload
= (db
->priv
->library_locations
!= NULL
);
4181 if (db
->priv
->library_location_notify_id
== 0) {
4182 db
->priv
->library_location_notify_id
=
4183 eel_gconf_notification_add (CONF_LIBRARY_LOCATION
,
4184 (GConfClientNotifyFunc
) library_location_changed_cb
,
4189 rb_debug ("ending monitor of old library directories");
4191 rhythmdb_stop_monitoring (db
);
4193 g_slist_foreach (db
->priv
->library_locations
, (GFunc
) g_free
, NULL
);
4194 g_slist_free (db
->priv
->library_locations
);
4195 db
->priv
->library_locations
= NULL
;
4198 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY
)) {
4199 db
->priv
->library_locations
= eel_gconf_get_string_list (CONF_LIBRARY_LOCATION
);
4201 rhythmdb_start_monitoring (db
);
4206 library_location_changed_cb (GConfClient
*client
,
4211 rhythmdb_sync_library_location (db
);
4215 rhythmdb_entry_dup_string (RhythmDBEntry
*entry
,
4216 RhythmDBPropType propid
)
4220 g_return_val_if_fail (entry
!= NULL
, NULL
);
4222 s
= rhythmdb_entry_get_string (entry
, propid
);
4224 return g_strdup (s
);
4231 rhythmdb_entry_get_string (RhythmDBEntry
*entry
,
4232 RhythmDBPropType propid
)
4234 RhythmDBPodcastFields
*podcast
= NULL
;
4236 g_return_val_if_fail (entry
!= NULL
, NULL
);
4237 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
4239 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
4240 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
4241 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
4243 rhythmdb_entry_sync_mirrored (entry
, propid
);
4246 case RHYTHMDB_PROP_TITLE
:
4247 return rb_refstring_get (entry
->title
);
4248 case RHYTHMDB_PROP_ALBUM
:
4249 return rb_refstring_get (entry
->album
);
4250 case RHYTHMDB_PROP_ARTIST
:
4251 return rb_refstring_get (entry
->artist
);
4252 case RHYTHMDB_PROP_GENRE
:
4253 return rb_refstring_get (entry
->genre
);
4254 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
4255 return rb_refstring_get (entry
->musicbrainz_trackid
);
4256 case RHYTHMDB_PROP_MIMETYPE
:
4257 return rb_refstring_get (entry
->mimetype
);
4258 case RHYTHMDB_PROP_TITLE_SORT_KEY
:
4259 return rb_refstring_get_sort_key (entry
->title
);
4260 case RHYTHMDB_PROP_ALBUM_SORT_KEY
:
4261 return rb_refstring_get_sort_key (entry
->album
);
4262 case RHYTHMDB_PROP_ARTIST_SORT_KEY
:
4263 return rb_refstring_get_sort_key (entry
->artist
);
4264 case RHYTHMDB_PROP_GENRE_SORT_KEY
:
4265 return rb_refstring_get_sort_key (entry
->genre
);
4266 case RHYTHMDB_PROP_TITLE_FOLDED
:
4267 return rb_refstring_get_folded (entry
->title
);
4268 case RHYTHMDB_PROP_ALBUM_FOLDED
:
4269 return rb_refstring_get_folded (entry
->album
);
4270 case RHYTHMDB_PROP_ARTIST_FOLDED
:
4271 return rb_refstring_get_folded (entry
->artist
);
4272 case RHYTHMDB_PROP_GENRE_FOLDED
:
4273 return rb_refstring_get_folded (entry
->genre
);
4274 case RHYTHMDB_PROP_LOCATION
:
4275 return rb_refstring_get (entry
->location
);
4276 case RHYTHMDB_PROP_MOUNTPOINT
:
4277 return rb_refstring_get (entry
->mountpoint
);
4278 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
4279 return rb_refstring_get (entry
->last_played_str
);
4280 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
4281 return rb_refstring_get (entry
->playback_error
);
4282 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
4283 return rb_refstring_get (entry
->first_seen_str
);
4284 case RHYTHMDB_PROP_LAST_SEEN_STR
:
4285 return rb_refstring_get (entry
->last_seen_str
);
4286 case RHYTHMDB_PROP_SEARCH_MATCH
:
4287 return NULL
; /* synthetic property */
4288 /* Podcast properties */
4289 case RHYTHMDB_PROP_DESCRIPTION
:
4291 return rb_refstring_get (podcast
->description
);
4294 case RHYTHMDB_PROP_SUBTITLE
:
4296 return rb_refstring_get (podcast
->subtitle
);
4299 case RHYTHMDB_PROP_SUMMARY
:
4301 return rb_refstring_get (podcast
->summary
);
4304 case RHYTHMDB_PROP_LANG
:
4306 return rb_refstring_get (podcast
->lang
);
4309 case RHYTHMDB_PROP_COPYRIGHT
:
4311 return rb_refstring_get (podcast
->copyright
);
4314 case RHYTHMDB_PROP_IMAGE
:
4316 return rb_refstring_get (podcast
->image
);
4321 g_assert_not_reached ();
4327 rhythmdb_entry_get_refstring (RhythmDBEntry
*entry
,
4328 RhythmDBPropType propid
)
4330 g_return_val_if_fail (entry
!= NULL
, NULL
);
4331 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
4333 rhythmdb_entry_sync_mirrored (entry
, propid
);
4336 case RHYTHMDB_PROP_TITLE
:
4337 return rb_refstring_ref (entry
->title
);
4338 case RHYTHMDB_PROP_ALBUM
:
4339 return rb_refstring_ref (entry
->album
);
4340 case RHYTHMDB_PROP_ARTIST
:
4341 return rb_refstring_ref (entry
->artist
);
4342 case RHYTHMDB_PROP_GENRE
:
4343 return rb_refstring_ref (entry
->genre
);
4344 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
4345 return rb_refstring_ref (entry
->musicbrainz_trackid
);
4346 case RHYTHMDB_PROP_MIMETYPE
:
4347 return rb_refstring_ref (entry
->mimetype
);
4348 case RHYTHMDB_PROP_MOUNTPOINT
:
4349 return rb_refstring_ref (entry
->mountpoint
);
4350 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
4351 return rb_refstring_ref (entry
->last_played_str
);
4352 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
4353 return rb_refstring_ref (entry
->first_seen_str
);
4354 case RHYTHMDB_PROP_LAST_SEEN_STR
:
4355 return rb_refstring_ref (entry
->last_seen_str
);
4356 case RHYTHMDB_PROP_LOCATION
:
4357 return rb_refstring_ref (entry
->location
);
4358 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
4359 return rb_refstring_ref (entry
->playback_error
);
4361 g_assert_not_reached ();
4367 rhythmdb_entry_get_boolean (RhythmDBEntry
*entry
,
4368 RhythmDBPropType propid
)
4370 g_return_val_if_fail (entry
!= NULL
, FALSE
);
4373 case RHYTHMDB_PROP_HIDDEN
:
4374 return ((entry
->flags
& RHYTHMDB_ENTRY_HIDDEN
) != 0);
4376 g_assert_not_reached ();
4382 rhythmdb_entry_get_uint64 (RhythmDBEntry
*entry
,
4383 RhythmDBPropType propid
)
4385 g_return_val_if_fail (entry
!= NULL
, 0);
4388 case RHYTHMDB_PROP_FILE_SIZE
:
4389 return entry
->file_size
;
4391 g_assert_not_reached ();
4397 rhythmdb_entry_get_entry_type (RhythmDBEntry
*entry
)
4399 g_return_val_if_fail (entry
!= NULL
, RHYTHMDB_ENTRY_TYPE_INVALID
);
4405 rhythmdb_entry_get_pointer (RhythmDBEntry
*entry
,
4406 RhythmDBPropType propid
)
4408 g_return_val_if_fail (entry
!= NULL
, NULL
);
4411 case RHYTHMDB_PROP_TYPE
:
4414 g_assert_not_reached ();
4420 rhythmdb_entry_get_ulong (RhythmDBEntry
*entry
,
4421 RhythmDBPropType propid
)
4423 RhythmDBPodcastFields
*podcast
= NULL
;
4425 g_return_val_if_fail (entry
!= NULL
, 0);
4427 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
4428 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
4429 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
4432 case RHYTHMDB_PROP_ENTRY_ID
:
4434 case RHYTHMDB_PROP_TRACK_NUMBER
:
4435 return entry
->tracknum
;
4436 case RHYTHMDB_PROP_DISC_NUMBER
:
4437 return entry
->discnum
;
4438 case RHYTHMDB_PROP_DURATION
:
4439 return entry
->duration
;
4440 case RHYTHMDB_PROP_MTIME
:
4441 return entry
->mtime
;
4442 case RHYTHMDB_PROP_FIRST_SEEN
:
4443 return entry
->first_seen
;
4444 case RHYTHMDB_PROP_LAST_SEEN
:
4445 return entry
->last_seen
;
4446 case RHYTHMDB_PROP_LAST_PLAYED
:
4447 return entry
->last_played
;
4448 case RHYTHMDB_PROP_PLAY_COUNT
:
4449 return entry
->play_count
;
4450 case RHYTHMDB_PROP_BITRATE
:
4451 return entry
->bitrate
;
4452 case RHYTHMDB_PROP_DATE
:
4453 if (g_date_valid (&entry
->date
))
4454 return g_date_get_julian (&entry
->date
);
4457 case RHYTHMDB_PROP_YEAR
:
4458 if (g_date_valid (&entry
->date
))
4459 return g_date_get_year (&entry
->date
);
4462 case RHYTHMDB_PROP_POST_TIME
:
4464 return podcast
->post_time
;
4467 case RHYTHMDB_PROP_STATUS
:
4469 return podcast
->status
;
4473 g_assert_not_reached ();
4479 rhythmdb_entry_get_double (RhythmDBEntry
*entry
,
4480 RhythmDBPropType propid
)
4482 g_return_val_if_fail (entry
!= NULL
, 0);
4485 case RHYTHMDB_PROP_TRACK_GAIN
:
4486 return entry
->track_gain
;
4487 case RHYTHMDB_PROP_TRACK_PEAK
:
4488 return entry
->track_peak
;
4489 case RHYTHMDB_PROP_ALBUM_GAIN
:
4490 return entry
->album_gain
;
4491 case RHYTHMDB_PROP_ALBUM_PEAK
:
4492 return entry
->album_peak
;
4493 case RHYTHMDB_PROP_RATING
:
4494 return entry
->rating
;
4496 g_assert_not_reached ();
4502 rhythmdb_entry_get_playback_uri (RhythmDBEntry
*entry
)
4504 RhythmDBEntryType type
;
4506 g_return_val_if_fail (entry
!= NULL
, NULL
);
4508 type
= rhythmdb_entry_get_entry_type (entry
);
4509 if (type
->get_playback_uri
)
4510 return (type
->get_playback_uri
) (entry
, type
->get_playback_uri_data
);
4512 return rhythmdb_entry_dup_string (entry
, RHYTHMDB_PROP_LOCATION
);
4516 rhythmdb_get_property_type (RhythmDB
*db
,
4519 g_assert (property_id
>= 0 && property_id
< RHYTHMDB_NUM_PROPERTIES
);
4520 return rhythmdb_property_type_map
[property_id
];
4524 rhythmdb_entry_get_type (void)
4526 static GType type
= 0;
4528 if (G_UNLIKELY (type
== 0)) {
4529 type
= g_boxed_type_register_static ("RhythmDBEntry",
4530 (GBoxedCopyFunc
)rhythmdb_entry_ref
,
4531 (GBoxedFreeFunc
)rhythmdb_entry_unref
);
4538 rhythmdb_entry_type_get_type (void)
4540 static GType type
= 0;
4542 if (G_UNLIKELY (type
== 0)) {
4543 type
= g_boxed_type_register_static ("RhythmDBEntryType",
4544 (GBoxedCopyFunc
)rb_copy_function
,
4545 (GBoxedFreeFunc
)rb_null_function
);