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