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 rhythmdb_finalize (GObject
*object
)
674 g_return_if_fail (object
!= NULL
);
675 g_return_if_fail (RHYTHMDB_IS (object
));
677 db
= RHYTHMDB (object
);
679 g_return_if_fail (db
->priv
!= NULL
);
681 rhythmdb_finalize_monitoring (db
);
683 g_source_remove (db
->priv
->event_poll_id
);
684 if (db
->priv
->save_timeout_id
> 0)
685 g_source_remove (db
->priv
->save_timeout_id
);
686 if (db
->priv
->emit_entry_signals_id
> 0) {
687 g_source_remove (db
->priv
->emit_entry_signals_id
);
688 g_list_foreach (db
->priv
->added_entries_to_emit
, (GFunc
)rhythmdb_entry_unref
, NULL
);
689 g_list_foreach (db
->priv
->deleted_entries_to_emit
, (GFunc
)rhythmdb_entry_unref
, NULL
);
692 g_thread_pool_free (db
->priv
->query_thread_pool
, FALSE
, TRUE
);
693 g_thread_pool_free (db
->priv
->add_thread_pool
, FALSE
, TRUE
);
694 g_async_queue_unref (db
->priv
->action_queue
);
695 g_async_queue_unref (db
->priv
->event_queue
);
696 g_async_queue_unref (db
->priv
->restored_queue
);
698 g_mutex_free (db
->priv
->saving_mutex
);
699 g_cond_free (db
->priv
->saving_condition
);
701 g_hash_table_destroy (db
->priv
->stat_events
);
702 g_mutex_free (db
->priv
->stat_mutex
);
704 g_mutex_free (db
->priv
->change_mutex
);
706 g_hash_table_destroy (db
->priv
->propname_map
);
708 g_hash_table_destroy (db
->priv
->added_entries
);
709 g_hash_table_destroy (db
->priv
->deleted_entries
);
710 g_hash_table_destroy (db
->priv
->changed_entries
);
712 rb_refstring_unref (db
->priv
->empty_string
);
713 rb_refstring_unref (db
->priv
->octet_stream_str
);
715 g_hash_table_destroy (db
->priv
->entry_type_map
);
716 g_mutex_free (db
->priv
->entry_type_map_mutex
);
717 g_mutex_free (db
->priv
->entry_type_mutex
);
719 g_object_unref (db
->priv
->metadata
);
721 for (i
= 0; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
722 xmlFree (db
->priv
->column_xml_names
[i
]);
724 g_free (db
->priv
->column_xml_names
);
726 g_free (db
->priv
->name
);
728 G_OBJECT_CLASS (rhythmdb_parent_class
)->finalize (object
);
732 rhythmdb_set_property (GObject
*object
,
737 RhythmDB
*source
= RHYTHMDB (object
);
741 source
->priv
->name
= g_strdup (g_value_get_string (value
));
744 source
->priv
->dry_run
= g_value_get_boolean (value
);
747 source
->priv
->no_update
= g_value_get_boolean (value
);
750 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
756 rhythmdb_get_property (GObject
*object
,
761 RhythmDB
*source
= RHYTHMDB (object
);
765 g_value_set_string (value
, source
->priv
->name
);
768 g_value_set_boolean (value
, source
->priv
->dry_run
);
771 g_value_set_boolean (value
, source
->priv
->no_update
);
774 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
780 rhythmdb_thread_create (RhythmDB
*db
,
786 g_atomic_int_inc (&db
->priv
->outstanding_threads
);
787 g_async_queue_ref (db
->priv
->action_queue
);
788 g_async_queue_ref (db
->priv
->event_queue
);
791 g_thread_pool_push (pool
, data
, NULL
);
793 g_thread_create ((GThreadFunc
) func
, data
, FALSE
, NULL
);
797 rhythmdb_get_readonly (RhythmDB
*db
)
799 return (g_atomic_int_get (&db
->priv
->read_counter
) > 0);
803 rhythmdb_read_enter (RhythmDB
*db
)
806 g_return_if_fail (g_atomic_int_get (&db
->priv
->read_counter
) >= 0);
807 g_assert (rb_is_main_thread ());
809 count
= g_atomic_int_exchange_and_add (&db
->priv
->read_counter
, 1);
810 rb_debug ("counter: %d", count
+1);
812 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[READ_ONLY
],
817 rhythmdb_read_leave (RhythmDB
*db
)
820 g_return_if_fail (rhythmdb_get_readonly (db
));
821 g_assert (rb_is_main_thread ());
823 count
= g_atomic_int_exchange_and_add (&db
->priv
->read_counter
, -1);
824 rb_debug ("counter: %d", count
-1);
826 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[READ_ONLY
],
831 free_entry_changes (RhythmDBEntry
*entry
,
836 for (t
= changes
; t
; t
= t
->next
) {
837 RhythmDBEntryChange
*change
= t
->data
;
838 g_value_unset (&change
->old
);
839 g_value_unset (&change
->new);
842 g_slist_free (changes
);
848 emit_entry_changed (RhythmDBEntry
*entry
,
852 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_CHANGED
], 0, entry
, changes
);
856 sync_entry_changed (RhythmDBEntry
*entry
,
862 for (t
= changes
; t
; t
= t
->next
) {
863 RBMetaDataField field
;
864 RhythmDBEntryChange
*change
= t
->data
;
866 if (metadata_field_from_prop (change
->prop
, &field
)) {
867 RhythmDBAction
*action
;
869 if (!rhythmdb_entry_is_editable (db
, entry
)) {
870 g_warning ("trying to sync properties of non-editable file");
874 action
= g_new0 (RhythmDBAction
, 1);
875 action
->type
= RHYTHMDB_ACTION_SYNC
;
876 action
->uri
= rb_refstring_ref (entry
->location
);
877 g_async_queue_push (db
->priv
->action_queue
, action
);
884 rhythmdb_emit_entry_signals_idle (RhythmDB
*db
)
886 GList
*added_entries
;
887 GList
*deleted_entries
;
890 /* get lists of entries to emit, reset source id value */
891 g_mutex_lock (db
->priv
->change_mutex
);
893 added_entries
= db
->priv
->added_entries_to_emit
;
894 db
->priv
->added_entries_to_emit
= NULL
;
896 deleted_entries
= db
->priv
->deleted_entries_to_emit
;
897 db
->priv
->deleted_entries_to_emit
= NULL
;
899 db
->priv
->emit_entry_signals_id
= 0;
901 g_mutex_unlock (db
->priv
->change_mutex
);
903 /* emit added entries */
904 for (l
= added_entries
; l
; l
= g_list_next (l
)) {
905 RhythmDBEntry
*entry
= (RhythmDBEntry
*)l
->data
;
906 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_ADDED
], 0, entry
);
907 rhythmdb_entry_unref (entry
);
910 /* emit deleted entries */
911 for (l
= deleted_entries
; l
; l
= g_list_next (l
)) {
912 RhythmDBEntry
*entry
= (RhythmDBEntry
*)l
->data
;
913 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_DELETED
], 0, entry
);
914 rhythmdb_entry_unref (entry
);
917 g_list_free (added_entries
);
918 g_list_free (deleted_entries
);
923 process_added_entries_cb (RhythmDBEntry
*entry
,
927 if (thread
!= g_thread_self ())
930 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_SONG
) {
933 uri
= rhythmdb_entry_get_string (entry
,
934 RHYTHMDB_PROP_LOCATION
);
936 #ifdef HAVE_GSTREAMER_0_8
937 /* always start remote files hidden*/
938 if (!g_str_has_prefix (uri
, "file://")) {
939 entry
->flags
|= RHYTHMDB_ENTRY_HIDDEN
;
943 queue_stat_uri (uri
, db
, RHYTHMDB_ENTRY_TYPE_INVALID
);
946 g_assert ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) == 0);
947 entry
->flags
|= RHYTHMDB_ENTRY_INSERTED
;
949 rhythmdb_entry_ref (entry
);
950 db
->priv
->added_entries_to_emit
= g_list_prepend (db
->priv
->added_entries_to_emit
, entry
);
956 process_deleted_entries_cb (RhythmDBEntry
*entry
,
960 if (thread
!= g_thread_self ())
963 rhythmdb_entry_ref (entry
);
964 db
->priv
->deleted_entries_to_emit
= g_list_prepend (db
->priv
->deleted_entries_to_emit
, entry
);
970 rhythmdb_commit_internal (RhythmDB
*db
,
971 gboolean sync_changes
,
974 g_mutex_lock (db
->priv
->change_mutex
);
976 g_hash_table_foreach (db
->priv
->changed_entries
, (GHFunc
) emit_entry_changed
, db
);
978 g_hash_table_foreach (db
->priv
->changed_entries
, (GHFunc
) sync_entry_changed
, db
);
979 g_hash_table_foreach_remove (db
->priv
->changed_entries
, (GHRFunc
) free_entry_changes
, db
);
981 /* update the lists of entry added/deleted signals to emit */
982 g_hash_table_foreach_remove (db
->priv
->added_entries
, (GHRFunc
) process_added_entries_cb
, db
);
983 g_hash_table_foreach_remove (db
->priv
->deleted_entries
, (GHRFunc
) process_deleted_entries_cb
, db
);
985 /* if there are some signals to emit, add a new idle callback if required */
986 if (db
->priv
->added_entries_to_emit
|| db
->priv
->deleted_entries_to_emit
) {
987 if (db
->priv
->emit_entry_signals_id
== 0)
988 db
->priv
->emit_entry_signals_id
= g_idle_add ((GSourceFunc
) rhythmdb_emit_entry_signals_idle
, db
);
991 g_mutex_unlock (db
->priv
->change_mutex
);
998 } RhythmDBTimeoutCommitData
;
1001 timeout_rhythmdb_commit (RhythmDBTimeoutCommitData
*data
)
1003 rhythmdb_commit_internal (data
->db
, data
->sync
, data
->thread
);
1004 g_object_unref (data
->db
);
1010 rhythmdb_add_timeout_commit (RhythmDB
*db
,
1013 RhythmDBTimeoutCommitData
*data
;
1015 g_assert (rb_is_main_thread ());
1017 data
= g_new0 (RhythmDBTimeoutCommitData
, 1);
1018 data
->db
= g_object_ref (db
);
1020 data
->thread
= g_thread_self ();
1021 g_timeout_add (100, (GSourceFunc
)timeout_rhythmdb_commit
, data
);
1028 * Apply all database changes, and send notification of changes and new entries.
1029 * This needs to be called after any changes have been made, such as a group of
1030 * rhythmdb_entry_set() calls, or a new entry has been added.
1033 rhythmdb_commit (RhythmDB
*db
)
1035 rhythmdb_commit_internal (db
, TRUE
, g_thread_self ());
1039 rhythmdb_error_quark (void)
1041 static GQuark quark
;
1043 quark
= g_quark_from_static_string ("rhythmdb_error");
1048 /* structure alignment magic, stolen from glib */
1049 #define STRUCT_ALIGNMENT (2 * sizeof (gsize))
1050 #define ALIGN_STRUCT(offset) \
1051 ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
1054 * rhythmdb_entry_allocate:
1056 * @type: type of entry to allocate
1058 * Allocate and initialise memory for a new #RhythmDBEntry of the type @type.
1059 * The entry's initial properties needs to be set with rhythmdb_entry_set (),
1060 * the entry added to the database with rhythmdb_entry_insert(), and committed with
1061 * rhythmdb_commit().
1063 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
1065 * Returns: the newly allocated #RhythmDBEntry
1068 rhythmdb_entry_allocate (RhythmDB
*db
,
1069 RhythmDBEntryType type
)
1072 gsize size
= sizeof (RhythmDBEntry
);
1074 if (type
->entry_type_data_size
) {
1075 size
= ALIGN_STRUCT (sizeof (RhythmDBEntry
)) + type
->entry_type_data_size
;
1077 ret
= g_malloc0 (size
);
1078 ret
->id
= (guint
) g_atomic_int_exchange_and_add (&db
->priv
->next_entry_id
, 1);
1081 ret
->title
= rb_refstring_ref (db
->priv
->empty_string
);
1082 ret
->genre
= rb_refstring_ref (db
->priv
->empty_string
);
1083 ret
->artist
= rb_refstring_ref (db
->priv
->empty_string
);
1084 ret
->album
= rb_refstring_ref (db
->priv
->empty_string
);
1085 ret
->musicbrainz_trackid
= rb_refstring_ref (db
->priv
->empty_string
);
1086 ret
->mimetype
= rb_refstring_ref (db
->priv
->octet_stream_str
);
1088 ret
->flags
|= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
|
1089 RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
|
1090 RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
1092 /* The refcount is initially 0, we want to set it to 1 */
1095 if (type
->post_entry_create
)
1096 (type
->post_entry_create
)(ret
, type
->post_entry_create_data
);
1102 * rhythmdb_entry_get_type_data:
1103 * @entry: a #RhythmDBEntry
1104 * @expected_size: expected size of the type-specific data.
1106 * Returns a pointer to the entry's type-specific data, checking that
1107 * the size of the data structure matches what is expected.
1108 * Callers should use the RHYTHMDB_ENTRY_GET_TYPE_DATA macro for
1109 * a slightly more friendly interface to this functionality.
1112 rhythmdb_entry_get_type_data (RhythmDBEntry
*entry
,
1113 guint expected_size
)
1115 g_return_val_if_fail (entry
!= NULL
, NULL
);
1117 g_assert (expected_size
== entry
->type
->entry_type_data_size
);
1118 gsize offset
= ALIGN_STRUCT (sizeof (RhythmDBEntry
));
1120 return (gpointer
) (((guint8
*)entry
) + offset
);
1124 * rhythmdb_entry_insert:
1126 * @entry: the entry to insert.
1128 * Inserts a newly-created entry into the database.
1130 * Note that you must call rhythmdb_commit() at some point after invoking
1134 rhythmdb_entry_insert (RhythmDB
*db
,
1135 RhythmDBEntry
*entry
)
1137 g_return_if_fail (RHYTHMDB_IS (db
));
1138 g_return_if_fail (entry
!= NULL
);
1140 g_assert ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) == 0);
1141 g_return_if_fail (entry
->location
!= NULL
);
1143 /* ref the entry before adding to hash, it is unreffed when removed */
1144 rhythmdb_entry_ref (entry
);
1145 g_mutex_lock (db
->priv
->change_mutex
);
1146 g_hash_table_insert (db
->priv
->added_entries
, entry
, g_thread_self ());
1147 g_mutex_unlock (db
->priv
->change_mutex
);
1151 * rhythmdb_entry_new:
1153 * @type: type of entry to create
1154 * @uri: the location of the entry, this be unique amongst all entries.
1156 * Creates a new entry of type @type and location @uri, and inserts
1157 * it into the database. You must call rhythmdb_commit() at some point
1158 * after invoking this function.
1160 * This may return NULL if entry creation fails. This can occur if there is
1161 * already an entry with the given uri.
1163 * Returns: the newly created #RhythmDBEntry
1166 rhythmdb_entry_new (RhythmDB
*db
,
1167 RhythmDBEntryType type
,
1171 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
1173 ret
= rhythmdb_entry_lookup_by_location (db
, uri
);
1175 g_warning ("attempting to create entry that already exists: %s", uri
);
1179 ret
= rhythmdb_entry_allocate (db
, type
);
1180 ret
->location
= rb_refstring_new (uri
);
1181 klass
->impl_entry_new (db
, ret
);
1182 rb_debug ("emitting entry added");
1183 rhythmdb_entry_insert (db
, ret
);
1189 * rhythmdb_entry_example_new:
1191 * @type: type of entry to create
1192 * @uri: the location of the entry, this be unique amongst all entries.
1194 * Creates a new sample entry of type @type and location @uri, it does not insert
1195 * it into the database. This is indended for use as a example entry.
1197 * This may return NULL if entry creation fails.
1199 * Returns: the newly created #RhythmDBEntry
1202 rhythmdb_entry_example_new (RhythmDB
*db
,
1203 RhythmDBEntryType type
,
1208 ret
= rhythmdb_entry_allocate (db
, type
);
1210 ret
->location
= rb_refstring_new (uri
);
1212 if (type
== RHYTHMDB_ENTRY_TYPE_SONG
) {
1213 rb_refstring_unref (ret
->artist
);
1214 ret
->artist
= rb_refstring_new ("The Beatles");
1215 rb_refstring_unref (ret
->album
);
1216 ret
->album
= rb_refstring_new ("Help!");
1217 rb_refstring_unref (ret
->title
);
1218 ret
->title
= rb_refstring_new ("Ticket To Ride");
1227 * rhythmdb_entry_ref:
1229 * @entry: a #RhythmDBEntry.
1231 * Increase the reference count of the entry.
1234 rhythmdb_entry_ref (RhythmDBEntry
*entry
)
1236 g_return_val_if_fail (entry
!= NULL
, NULL
);
1237 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
1239 g_atomic_int_inc (&entry
->refcount
);
1245 rhythmdb_entry_finalize (RhythmDBEntry
*entry
)
1247 RhythmDBEntryType type
;
1249 type
= rhythmdb_entry_get_entry_type (entry
);
1251 if (type
->pre_entry_destroy
)
1252 (type
->pre_entry_destroy
)(entry
, type
->pre_entry_destroy_data
);
1254 rb_refstring_unref (entry
->location
);
1255 rb_refstring_unref (entry
->playback_error
);
1256 rb_refstring_unref (entry
->title
);
1257 rb_refstring_unref (entry
->genre
);
1258 rb_refstring_unref (entry
->artist
);
1259 rb_refstring_unref (entry
->album
);
1260 rb_refstring_unref (entry
->musicbrainz_trackid
);
1261 rb_refstring_unref (entry
->mimetype
);
1267 * rhythmdb_entry_unref:
1269 * @entry: a #RhythmDBEntry.
1271 * Decrease the reference count of the entry, and destroy it if there are
1272 * no references left.
1275 rhythmdb_entry_unref (RhythmDBEntry
*entry
)
1279 g_return_if_fail (entry
!= NULL
);
1280 g_return_if_fail (entry
->refcount
> 0);
1282 is_zero
= g_atomic_int_dec_and_test (&entry
->refcount
);
1283 if (G_UNLIKELY (is_zero
)) {
1284 rhythmdb_entry_finalize (entry
);
1289 * rhythmdb_entry_is_editable:
1291 * @entry: a #RhythmDBEntry.
1293 * This determines whether any changes to the entries metadata can be saved.
1294 * Usually this is only true for entries backed by files, where tag-writing is
1295 * enabled, and the appropriate tag-writing facilities are available.
1297 * Returns: whether the entries metadata can be changed.
1301 rhythmdb_entry_is_editable (RhythmDB
*db
,
1302 RhythmDBEntry
*entry
)
1304 RhythmDBEntryType entry_type
;
1306 g_return_val_if_fail (RHYTHMDB_IS (db
), FALSE
);
1307 g_return_val_if_fail (entry
!= NULL
, FALSE
);
1309 entry_type
= rhythmdb_entry_get_entry_type (entry
);
1310 return entry_type
->can_sync_metadata (db
, entry
, entry_type
->can_sync_metadata_data
);
1314 set_metadata_string_default_unknown (RhythmDB
*db
,
1315 RBMetaData
*metadata
,
1316 RhythmDBEntry
*entry
,
1317 RBMetaDataField field
,
1318 RhythmDBPropType prop
)
1320 const char *unknown
= _("Unknown");
1323 if (!(rb_metadata_get (metadata
,
1326 g_value_init (&val
, G_TYPE_STRING
);
1327 g_value_set_static_string (&val
, unknown
);
1329 const gchar
*str
= g_value_get_string (&val
);
1330 if (str
== NULL
|| str
[0] == '\0')
1331 g_value_set_static_string (&val
, unknown
);
1333 rhythmdb_entry_set_internal (db
, entry
, TRUE
, prop
, &val
);
1334 g_value_unset (&val
);
1338 set_props_from_metadata (RhythmDB
*db
,
1339 RhythmDBEntry
*entry
,
1340 GnomeVFSFileInfo
*vfsinfo
,
1341 RBMetaData
*metadata
)
1346 g_value_init (&val
, G_TYPE_STRING
);
1347 mime
= rb_metadata_get_mime (metadata
);
1349 g_value_set_string (&val
, mime
);
1350 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1351 RHYTHMDB_PROP_MIMETYPE
, &val
);
1353 g_value_unset (&val
);
1356 if (!rb_metadata_get (metadata
,
1357 RB_METADATA_FIELD_TRACK_NUMBER
,
1359 g_value_init (&val
, G_TYPE_ULONG
);
1360 g_value_set_ulong (&val
, 0);
1362 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1363 RHYTHMDB_PROP_TRACK_NUMBER
, &val
);
1364 g_value_unset (&val
);
1367 if (!rb_metadata_get (metadata
,
1368 RB_METADATA_FIELD_DISC_NUMBER
,
1370 g_value_init (&val
, G_TYPE_ULONG
);
1371 g_value_set_ulong (&val
, 0);
1373 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1374 RHYTHMDB_PROP_DISC_NUMBER
, &val
);
1375 g_value_unset (&val
);
1378 if (rb_metadata_get (metadata
,
1379 RB_METADATA_FIELD_DURATION
,
1381 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1382 RHYTHMDB_PROP_DURATION
, &val
);
1383 g_value_unset (&val
);
1387 if (rb_metadata_get (metadata
,
1388 RB_METADATA_FIELD_BITRATE
,
1390 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1391 RHYTHMDB_PROP_BITRATE
, &val
);
1392 g_value_unset (&val
);
1396 if (rb_metadata_get (metadata
,
1397 RB_METADATA_FIELD_DATE
,
1399 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1400 RHYTHMDB_PROP_DATE
, &val
);
1401 g_value_unset (&val
);
1404 /* musicbrainz trackid */
1405 if (rb_metadata_get (metadata
,
1406 RB_METADATA_FIELD_MUSICBRAINZ_TRACKID
,
1408 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1409 RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
, &val
);
1410 g_value_unset (&val
);
1414 g_value_init (&val
, G_TYPE_UINT64
);
1415 g_value_set_uint64 (&val
, vfsinfo
->size
);
1416 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_FILE_SIZE
, &val
);
1417 g_value_unset (&val
);
1420 if (!rb_metadata_get (metadata
,
1421 RB_METADATA_FIELD_TITLE
,
1422 &val
) || g_value_get_string (&val
)[0] == '\0') {
1424 utf8name
= g_filename_to_utf8 (vfsinfo
->name
, -1, NULL
, NULL
, NULL
);
1426 utf8name
= g_strdup (_("<invalid filename>"));
1428 if (G_VALUE_HOLDS_STRING (&val
))
1429 g_value_reset (&val
);
1431 g_value_init (&val
, G_TYPE_STRING
);
1432 g_value_set_string (&val
, utf8name
);
1435 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_TITLE
, &val
);
1436 g_value_unset (&val
);
1439 set_metadata_string_default_unknown (db
, metadata
, entry
,
1440 RB_METADATA_FIELD_GENRE
,
1441 RHYTHMDB_PROP_GENRE
);
1444 set_metadata_string_default_unknown (db
, metadata
, entry
,
1445 RB_METADATA_FIELD_ARTIST
,
1446 RHYTHMDB_PROP_ARTIST
);
1448 set_metadata_string_default_unknown (db
, metadata
, entry
,
1449 RB_METADATA_FIELD_ALBUM
,
1450 RHYTHMDB_PROP_ALBUM
);
1452 /* replaygain track gain */
1453 if (rb_metadata_get (metadata
,
1454 RB_METADATA_FIELD_TRACK_GAIN
,
1456 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1457 RHYTHMDB_PROP_TRACK_GAIN
, &val
);
1458 g_value_unset (&val
);
1461 /* replaygain track peak */
1462 if (rb_metadata_get (metadata
,
1463 RB_METADATA_FIELD_TRACK_PEAK
,
1465 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1466 RHYTHMDB_PROP_TRACK_PEAK
, &val
);
1467 g_value_unset (&val
);
1470 /* replaygain album gain */
1471 if (rb_metadata_get (metadata
,
1472 RB_METADATA_FIELD_ALBUM_GAIN
,
1474 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1475 RHYTHMDB_PROP_ALBUM_GAIN
, &val
);
1476 g_value_unset (&val
);
1479 /* replaygain album peak */
1480 if (rb_metadata_get (metadata
,
1481 RB_METADATA_FIELD_ALBUM_PEAK
,
1483 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1484 RHYTHMDB_PROP_ALBUM_PEAK
, &val
);
1485 g_value_unset (&val
);
1490 is_ghost_entry (RhythmDBEntry
*entry
)
1494 gulong grace_period
;
1496 GConfClient
*client
;
1498 client
= gconf_client_get_default ();
1499 if (client
== NULL
) {
1503 grace_period
= gconf_client_get_int (client
, CONF_GRACE_PERIOD
,
1505 g_object_unref (G_OBJECT (client
));
1506 if (error
!= NULL
) {
1507 g_error_free (error
);
1511 /* This is a bit silly, but I prefer to make sure we won't
1512 * overflow in the following calculations
1514 if ((grace_period
< 0) || (grace_period
> 20000)) {
1518 /* Convert from days to seconds */
1519 grace_period
= grace_period
* 60 * 60 * 24;
1520 g_get_current_time (&time
);
1521 last_seen
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_LAST_SEEN
);
1523 return (last_seen
+ grace_period
< time
.tv_sec
);
1527 rhythmdb_process_stat_event (RhythmDB
*db
,
1528 RhythmDBEvent
*event
)
1530 RhythmDBEntry
*entry
;
1531 RhythmDBAction
*action
;
1533 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1535 time_t mtime
= (time_t) entry
->mtime
;
1537 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) && (entry
->type
!= event
->entry_type
))
1538 g_warning ("attempt to use same location in multiple entry types");
1540 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_IGNORE
)
1541 rb_debug ("ignoring %p", entry
);
1544 if (!is_ghost_entry (entry
)) {
1545 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
1547 rb_debug ("error accessing %s: %s", rb_refstring_get (event
->real_uri
),
1548 event
->error
->message
);
1549 rhythmdb_entry_delete (db
, entry
);
1554 const char *mount_point
;
1556 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1558 /* Update mount point if necessary (main reason is
1559 * that we want to set the mount point in legacy
1560 * rhythmdb that doesn't have it already
1562 mount_point
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_MOUNTPOINT
);
1563 if (mount_point
== NULL
) {
1564 rhythmdb_entry_set_mount_point (db
, entry
,
1565 rb_refstring_get (event
->real_uri
));
1568 /* Update last seen time. It will also be updated
1569 * upon saving and when a volume is unmounted
1571 g_get_current_time (&time
);
1572 g_value_init (&val
, G_TYPE_ULONG
);
1573 g_value_set_ulong (&val
, time
.tv_sec
);
1574 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1575 RHYTHMDB_PROP_LAST_SEEN
,
1577 /* Old rhythmdb.xml files won't have a value for
1578 * FIRST_SEEN, so set it here
1580 if (rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_FIRST_SEEN
) == 0) {
1581 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1582 RHYTHMDB_PROP_FIRST_SEEN
,
1585 g_value_unset (&val
);
1587 if (mtime
== event
->vfsinfo
->mtime
) {
1588 rb_debug ("not modified: %s", rb_refstring_get (event
->real_uri
));
1589 /* monitor the file for changes */
1590 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY
))
1591 rhythmdb_monitor_uri_path (db
, rb_refstring_get (entry
->location
), NULL
/* FIXME */);
1593 RhythmDBEvent
*new_event
;
1595 rb_debug ("changed: %s", rb_refstring_get (event
->real_uri
));
1596 new_event
= g_new0 (RhythmDBEvent
, 1);
1598 new_event
->uri
= rb_refstring_ref (event
->real_uri
);
1599 new_event
->type
= RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED
;
1600 g_async_queue_push (db
->priv
->event_queue
,
1605 rhythmdb_commit (db
);
1607 action
= g_new0 (RhythmDBAction
, 1);
1608 action
->type
= RHYTHMDB_ACTION_LOAD
;
1609 action
->uri
= rb_refstring_ref (event
->real_uri
);
1610 action
->entry_type
= event
->entry_type
;
1611 rb_debug ("queuing a RHYTHMDB_ACTION_LOAD: %s", rb_refstring_get (action
->uri
));
1612 g_async_queue_push (db
->priv
->action_queue
, action
);
1621 } RhythmDBLoadErrorData
;
1624 rhythmdb_add_import_error_entry (RhythmDB
*db
,
1625 RhythmDBEvent
*event
)
1627 RhythmDBEntry
*entry
;
1628 GValue value
= {0,};
1629 RhythmDBEntryType error_entry_type
= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
;
1631 if (g_error_matches (event
->error
, RB_METADATA_ERROR
, RB_METADATA_ERROR_NOT_AUDIO_IGNORE
)) {
1632 /* only add an ignore entry for the main library */
1633 if (event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_SONG
&&
1634 event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
)
1637 error_entry_type
= RHYTHMDB_ENTRY_TYPE_IGNORE
;
1640 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1642 RhythmDBEntryType entry_type
= rhythmdb_entry_get_entry_type (entry
);
1643 if (entry_type
!= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
&&
1644 entry_type
!= RHYTHMDB_ENTRY_TYPE_IGNORE
) {
1645 /* FIXME we've successfully read this file before.. so what should we do? */
1646 rb_debug ("%s already exists in the library.. ignoring import error?", rb_refstring_get (event
->real_uri
));
1650 if (entry_type
!= error_entry_type
) {
1651 /* delete the existing entry, then create a new one below */
1652 rhythmdb_entry_delete (db
, entry
);
1654 } else if (error_entry_type
== RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
) {
1655 /* we've already got an error for this file, so just update it */
1656 g_value_init (&value
, G_TYPE_STRING
);
1657 g_value_set_string (&value
, event
->error
->message
);
1658 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
1659 g_value_unset (&value
);
1661 /* no need to update the ignored file entry */
1664 if (entry
&& event
->vfsinfo
) {
1666 g_value_init (&value
, G_TYPE_ULONG
);
1667 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1668 rhythmdb_entry_set(db
, entry
, RHYTHMDB_PROP_MTIME
, &value
);
1669 g_value_unset (&value
);
1672 rhythmdb_add_timeout_commit (db
, FALSE
);
1675 if (entry
== NULL
) {
1676 /* create a new import error or ignore entry */
1677 entry
= rhythmdb_entry_new (db
, error_entry_type
, rb_refstring_get (event
->real_uri
));
1681 if (error_entry_type
== RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
&& event
->error
->message
) {
1682 g_value_init (&value
, G_TYPE_STRING
);
1683 if (g_utf8_validate (event
->error
->message
, -1, NULL
))
1684 g_value_set_string (&value
, event
->error
->message
);
1686 g_value_set_static_string (&value
, _("invalid unicode in error message"));
1687 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
1688 g_value_unset (&value
);
1692 if (event
->vfsinfo
) {
1693 g_value_init (&value
, G_TYPE_ULONG
);
1694 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1695 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_MTIME
, &value
);
1696 g_value_unset (&value
);
1699 /* record the mount point so we can delete entries for unmounted volumes */
1700 rhythmdb_entry_set_mount_point (db
, entry
, rb_refstring_get (event
->real_uri
));
1702 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1704 rhythmdb_add_timeout_commit (db
, FALSE
);
1709 rhythmdb_process_metadata_load (RhythmDB
*db
,
1710 RhythmDBEvent
*event
)
1712 RhythmDBEntry
*entry
;
1713 GValue value
= {0,};
1717 if (rhythmdb_get_readonly (db
)) {
1718 rb_debug ("database is read-only right now, re-queuing event");
1719 g_async_queue_push (db
->priv
->event_queue
, event
);
1724 rhythmdb_add_import_error_entry (db
, event
);
1728 /* do we really need to do this? */
1729 mime
= rb_metadata_get_mime (event
->metadata
);
1731 rb_debug ("unsupported file");
1735 g_get_current_time (&time
);
1737 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1739 if (entry
!= NULL
) {
1740 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) &&
1741 (rhythmdb_entry_get_entry_type (entry
) != event
->entry_type
)) {
1742 /* switching from IGNORE to SONG or vice versa, recreate the entry */
1743 rhythmdb_entry_delete (db
, entry
);
1744 rhythmdb_add_timeout_commit (db
, FALSE
);
1749 if (entry
== NULL
) {
1750 if (event
->entry_type
== RHYTHMDB_ENTRY_TYPE_INVALID
)
1751 event
->entry_type
= RHYTHMDB_ENTRY_TYPE_SONG
;
1753 entry
= rhythmdb_entry_new (db
, event
->entry_type
, rb_refstring_get (event
->real_uri
));
1754 if (entry
== NULL
) {
1755 rb_debug ("entry already exists");
1759 /* initialize the last played date to 0=never */
1760 g_value_init (&value
, G_TYPE_ULONG
);
1761 g_value_set_ulong (&value
, 0);
1762 rhythmdb_entry_set (db
, entry
,
1763 RHYTHMDB_PROP_LAST_PLAYED
, &value
);
1764 g_value_unset (&value
);
1766 /* initialize the rating */
1767 g_value_init (&value
, G_TYPE_DOUBLE
);
1768 g_value_set_double (&value
, 0);
1769 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_RATING
, &value
);
1770 g_value_unset (&value
);
1773 g_value_init (&value
, G_TYPE_ULONG
);
1774 g_value_set_ulong (&value
, time
.tv_sec
);
1775 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_FIRST_SEEN
, &value
);
1776 g_value_unset (&value
);
1779 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) && (entry
->type
!= event
->entry_type
))
1780 g_warning ("attempt to use same location in multiple entry types");
1783 if (event
->vfsinfo
) {
1784 g_value_init (&value
, G_TYPE_ULONG
);
1785 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1786 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_MTIME
, &value
);
1787 g_value_unset (&value
);
1790 if (event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_IGNORE
&&
1791 event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
) {
1792 set_props_from_metadata (db
, entry
, event
->vfsinfo
, event
->metadata
);
1795 /* we've seen this entry */
1796 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1798 g_value_init (&value
, G_TYPE_ULONG
);
1799 g_value_set_ulong (&value
, time
.tv_sec
);
1800 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_LAST_SEEN
, &value
);
1801 g_value_unset (&value
);
1803 /* Remember the mount point of the volume the song is on */
1804 rhythmdb_entry_set_mount_point (db
, entry
, rb_refstring_get (event
->real_uri
));
1806 /* monitor the file for changes */
1807 /* FIXME: watch for errors */
1808 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY
) && event
->entry_type
== RHYTHMDB_ENTRY_TYPE_SONG
)
1809 rhythmdb_monitor_uri_path (db
, rb_refstring_get (entry
->location
), NULL
);
1811 rhythmdb_add_timeout_commit (db
, FALSE
);
1817 rhythmdb_process_queued_entry_set_event (RhythmDB
*db
,
1818 RhythmDBEvent
*event
)
1820 rhythmdb_entry_set_internal (db
, event
->entry
,
1821 event
->signal_change
,
1823 &event
->change
.new);
1824 /* Don't run rhythmdb_commit right now in case there
1825 * we can run a single commit for several queued
1828 rhythmdb_add_timeout_commit (db
, TRUE
);
1832 rhythmdb_process_file_created_or_modified (RhythmDB
*db
,
1833 RhythmDBEvent
*event
)
1835 RhythmDBAction
*action
;
1837 action
= g_new0 (RhythmDBAction
, 1);
1838 action
->type
= RHYTHMDB_ACTION_LOAD
;
1839 action
->uri
= rb_refstring_ref (event
->uri
);
1840 action
->entry_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
1841 g_async_queue_push (db
->priv
->action_queue
, action
);
1845 rhythmdb_process_file_deleted (RhythmDB
*db
,
1846 RhythmDBEvent
*event
)
1848 RhythmDBEntry
*entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->uri
);
1850 g_hash_table_remove (db
->priv
->changed_files
, event
->uri
);
1853 rb_debug ("deleting entry for %s", rb_refstring_get (event
->uri
));
1854 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
1855 rhythmdb_commit (db
);
1860 rhythmdb_process_events (RhythmDB
*db
,
1863 RhythmDBEvent
*event
;
1866 while ((event
= g_async_queue_try_pop (db
->priv
->event_queue
)) != NULL
) {
1867 gboolean free
= TRUE
;
1869 /* if the database is read-only, we can't process those events
1870 * since they call rhythmdb_entry_set. Doing it this way
1871 * is safe if we assume all calls to read_enter/read_leave
1872 * are done from the main thread (the thread this function
1875 if (rhythmdb_get_readonly (db
) &&
1876 ((event
->type
== RHYTHMDB_EVENT_STAT
)
1877 || (event
->type
== RHYTHMDB_EVENT_METADATA_LOAD
)
1878 || (event
->type
== RHYTHMDB_EVENT_ENTRY_SET
))) {
1879 if (count
>= g_async_queue_length (db
->priv
->event_queue
)) {
1880 rb_debug ("Database is read-only, and we can't process any more events");
1881 /* give the running query some time to complete */
1884 rb_debug ("Database is read-only, delaying event processing\n");
1885 g_async_queue_push (db
->priv
->event_queue
, event
);
1889 switch (event
->type
) {
1890 case RHYTHMDB_EVENT_STAT
:
1891 rb_debug ("processing RHYTHMDB_EVENT_STAT");
1892 rhythmdb_process_stat_event (db
, event
);
1894 case RHYTHMDB_EVENT_METADATA_LOAD
:
1895 rb_debug ("processing RHYTHMDB_EVENT_METADATA_LOAD");
1896 free
= rhythmdb_process_metadata_load (db
, event
);
1898 case RHYTHMDB_EVENT_ENTRY_SET
:
1899 rb_debug ("processing RHYTHMDB_EVENT_ENTRY_SET");
1900 rhythmdb_process_queued_entry_set_event (db
, event
);
1902 case RHYTHMDB_EVENT_DB_LOAD
:
1903 rb_debug ("processing RHYTHMDB_EVENT_DB_LOAD");
1904 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[LOAD_COMPLETE
], 0);
1906 /* save the db every five minutes */
1907 if (db
->priv
->save_timeout_id
> 0) {
1908 g_source_remove (db
->priv
->save_timeout_id
);
1910 db
->priv
->save_timeout_id
= g_timeout_add_full (G_PRIORITY_LOW
,
1912 (GSourceFunc
) rhythmdb_idle_save
,
1916 case RHYTHMDB_EVENT_THREAD_EXITED
:
1917 rb_debug ("processing RHYTHMDB_EVENT_THREAD_EXITED");
1919 case RHYTHMDB_EVENT_DB_SAVED
:
1920 rb_debug ("processing RHYTHMDB_EVENT_DB_SAVED");
1921 rhythmdb_read_leave (db
);
1923 case RHYTHMDB_EVENT_QUERY_COMPLETE
:
1924 rb_debug ("processing RHYTHMDB_EVENT_QUERY_COMPLETE");
1925 rhythmdb_read_leave (db
);
1927 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED
:
1928 rb_debug ("processing RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED");
1929 rhythmdb_process_file_created_or_modified (db
, event
);
1931 case RHYTHMDB_EVENT_FILE_DELETED
:
1932 rb_debug ("processing RHYTHMDB_EVENT_FILE_DELETED");
1933 rhythmdb_process_file_deleted (db
, event
);
1937 rhythmdb_event_free (db
, event
);
1941 if (timeout
&& (count
% 8 == 0)) {
1943 g_get_current_time (&now
);
1944 if (rb_compare_gtimeval (timeout
,&now
) < 0) {
1945 /* probably more work to do, so try to come back as soon as possible */
1951 /* queue is empty, so we can wait a while before checking it again */
1956 rhythmdb_idle_poll_events (RhythmDB
*db
)
1961 g_get_current_time (&timeout
);
1962 g_time_val_add (&timeout
, G_USEC_PER_SEC
*0.75);
1964 GDK_THREADS_ENTER ();
1966 poll_soon
= rhythmdb_process_events (db
, &timeout
);
1969 db
->priv
->event_poll_id
=
1970 g_idle_add_full (G_PRIORITY_LOW
, (GSourceFunc
) rhythmdb_idle_poll_events
,
1973 db
->priv
->event_poll_id
=
1974 g_timeout_add (1000, (GSourceFunc
) rhythmdb_idle_poll_events
, db
);
1976 GDK_THREADS_LEAVE ();
1981 #define READ_QUEUE_TIMEOUT G_USEC_PER_SEC / 10
1984 read_queue (GAsyncQueue
*queue
, gboolean
*cancel
)
1989 g_get_current_time (&timeout
);
1990 g_time_val_add (&timeout
, READ_QUEUE_TIMEOUT
);
1992 if (G_UNLIKELY (*cancel
))
1994 while ((ret
= g_async_queue_timed_pop (queue
, &timeout
)) == NULL
) {
1995 if (G_UNLIKELY (*cancel
))
1997 g_get_current_time (&timeout
);
1998 g_time_val_add (&timeout
, G_USEC_PER_SEC
);
2005 rhythmdb_execute_stat_info_cb (GnomeVFSAsyncHandle
*handle
,
2007 /* GnomeVFSGetFileInfoResult* items */
2008 RhythmDBEvent
*event
)
2010 /* this is in the main thread, so we can't do any long operation here */
2011 GnomeVFSGetFileInfoResult
*info_result
= results
->data
;
2013 g_mutex_lock (event
->db
->priv
->stat_mutex
);
2014 event
->db
->priv
->outstanding_stats
= g_list_remove (event
->db
->priv
->outstanding_stats
, event
);
2015 event
->handle
= NULL
;
2016 g_mutex_unlock (event
->db
->priv
->stat_mutex
);
2018 if (info_result
->result
== GNOME_VFS_OK
) {
2019 event
->vfsinfo
= gnome_vfs_file_info_dup (info_result
->file_info
);
2021 event
->error
= make_access_failed_error (rb_refstring_get (event
->real_uri
),
2022 info_result
->result
);
2023 event
->vfsinfo
= NULL
;
2025 g_async_queue_push (event
->db
->priv
->event_queue
, event
);
2029 rhythmdb_execute_stat (RhythmDB
*db
,
2031 RhythmDBEvent
*event
)
2033 GnomeVFSURI
*vfs_uri
= gnome_vfs_uri_new (uri
);
2035 GList
*uri_list
= g_list_append (NULL
, vfs_uri
);
2036 event
->real_uri
= rb_refstring_new (uri
);
2038 g_mutex_lock (db
->priv
->stat_mutex
);
2039 db
->priv
->outstanding_stats
= g_list_prepend (db
->priv
->outstanding_stats
, event
);
2040 g_mutex_unlock (db
->priv
->stat_mutex
);
2042 gnome_vfs_async_get_file_info (&event
->handle
, uri_list
,
2043 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
,
2044 GNOME_VFS_PRIORITY_MIN
,
2045 (GnomeVFSAsyncGetFileInfoCallback
) rhythmdb_execute_stat_info_cb
,
2047 gnome_vfs_uri_unref (vfs_uri
);
2048 g_list_free (uri_list
);
2052 queue_stat_uri (const char *uri
,
2054 RhythmDBEntryType type
)
2056 RhythmDBEvent
*result
;
2058 rb_debug ("queueing stat for \"%s\"", uri
);
2059 g_assert (uri
&& *uri
);
2061 result
= g_new0 (RhythmDBEvent
, 1);
2063 result
->type
= RHYTHMDB_EVENT_STAT
;
2064 result
->entry_type
= type
;
2067 * before the action thread is started, we queue up stat events,
2068 * as we're still creating and running queries, as well as loading
2069 * the database. when we start the action thread, we'll kick off
2070 * a gnome-vfs job to run all the stat events too.
2072 * when the action thread is already running, we can start the
2073 * async_get_file_info job directly.
2075 g_mutex_lock (db
->priv
->stat_mutex
);
2076 if (db
->priv
->action_thread_running
) {
2077 g_mutex_unlock (db
->priv
->stat_mutex
);
2078 rhythmdb_execute_stat (db
, uri
, result
);
2080 GnomeVFSURI
*vfs_uri
;
2082 vfs_uri
= gnome_vfs_uri_new (uri
);
2084 /* construct a list of URIs and a hash table containing
2085 * stat events to fill in and post on the event queue.
2087 if (g_hash_table_lookup (db
->priv
->stat_events
, vfs_uri
)) {
2089 gnome_vfs_uri_unref (vfs_uri
);
2091 result
->real_uri
= rb_refstring_new (uri
);
2092 g_hash_table_insert (db
->priv
->stat_events
, vfs_uri
, result
);
2093 db
->priv
->stat_list
= g_list_prepend (db
->priv
->stat_list
, vfs_uri
);
2096 g_mutex_unlock (db
->priv
->stat_mutex
);
2101 queue_stat_uri_tad (const char *uri
,
2102 RhythmDBAddThreadData
*data
)
2104 queue_stat_uri (uri
, data
->db
, data
->type
);
2108 add_thread_main (RhythmDBAddThreadData
*data
)
2110 RhythmDBEvent
*result
;
2112 rb_uri_handle_recursively (data
->uri
, (GFunc
) queue_stat_uri_tad
,
2113 &data
->db
->priv
->exiting
, data
);
2115 rb_debug ("exiting");
2116 result
= g_new0 (RhythmDBEvent
, 1);
2117 result
->db
= data
->db
;
2118 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2119 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
2126 rhythmdb_execute_load (RhythmDB
*db
,
2128 RhythmDBEvent
*event
)
2130 GnomeVFSURI
*vfs_uri
= gnome_vfs_uri_new (uri
);
2131 GnomeVFSResult vfsresult
;
2133 event
->real_uri
= rb_refstring_new (rb_uri_resolve_symlink (uri
));
2134 event
->vfsinfo
= gnome_vfs_file_info_new ();
2136 vfsresult
= gnome_vfs_get_file_info (uri
,
2138 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
);
2139 if (vfsresult
!= GNOME_VFS_OK
) {
2140 event
->error
= make_access_failed_error (uri
, vfsresult
);
2142 gnome_vfs_file_info_unref (event
->vfsinfo
);
2143 event
->vfsinfo
= NULL
;
2145 if (event
->type
== RHYTHMDB_EVENT_METADATA_LOAD
) {
2146 event
->metadata
= rb_metadata_new ();
2147 rb_metadata_load (event
->metadata
, rb_refstring_get (event
->real_uri
),
2152 gnome_vfs_uri_unref (vfs_uri
);
2153 g_async_queue_push (db
->priv
->event_queue
, event
);
2157 * rhythmdb_entry_get:
2158 * @entry: a #RhythmDBEntry.
2159 * @propid: the id of the property to get.
2160 * @val: return location for the property value.
2162 * Gets a property of an entry, storing it in the given #GValue.
2165 rhythmdb_entry_get (RhythmDB
*db
,
2166 RhythmDBEntry
*entry
,
2167 RhythmDBPropType propid
,
2170 g_return_if_fail (RHYTHMDB_IS (db
));
2171 g_return_if_fail (entry
!= NULL
);
2172 g_return_if_fail (entry
->refcount
> 0);
2174 rhythmdb_entry_sync_mirrored (entry
, propid
);
2176 g_assert (G_VALUE_TYPE (val
) == rhythmdb_get_property_type (db
, propid
));
2177 switch (rhythmdb_property_type_map
[propid
]) {
2179 g_value_set_string (val
, rhythmdb_entry_get_string (entry
, propid
));
2181 case G_TYPE_BOOLEAN
:
2182 g_value_set_boolean (val
, rhythmdb_entry_get_boolean (entry
, propid
));
2185 g_value_set_ulong (val
, rhythmdb_entry_get_ulong (entry
, propid
));
2188 g_value_set_uint64 (val
, rhythmdb_entry_get_uint64 (entry
, propid
));
2191 g_value_set_double (val
, rhythmdb_entry_get_double (entry
, propid
));
2193 case G_TYPE_POINTER
:
2194 g_value_set_pointer (val
, rhythmdb_entry_get_pointer (entry
, propid
));
2197 g_assert_not_reached ();
2203 entry_to_rb_metadata (RhythmDB
*db
,
2204 RhythmDBEntry
*entry
,
2205 RBMetaData
*metadata
)
2210 for (i
= RHYTHMDB_PROP_TYPE
; i
!= RHYTHMDB_NUM_PROPERTIES
; i
++) {
2211 RBMetaDataField field
;
2213 if (metadata_field_from_prop (i
, &field
) == FALSE
) {
2217 g_value_init (&val
, rhythmdb_property_type_map
[i
]);
2218 rhythmdb_entry_get (db
, entry
, i
, &val
);
2219 rb_metadata_set (metadata
,
2222 g_value_unset (&val
);
2231 } RhythmDBSaveErrorData
;
2234 emit_save_error_idle (RhythmDBSaveErrorData
*data
)
2236 g_signal_emit (G_OBJECT (data
->db
), rhythmdb_signals
[SAVE_ERROR
], 0, data
->uri
, data
->error
);
2237 g_object_unref (G_OBJECT (data
->db
));
2239 g_error_free (data
->error
);
2245 action_thread_main (RhythmDB
*db
)
2247 RhythmDBEvent
*result
;
2250 RhythmDBAction
*action
;
2252 action
= read_queue (db
->priv
->action_queue
, &db
->priv
->exiting
);
2257 switch (action
->type
) {
2258 case RHYTHMDB_ACTION_STAT
:
2260 result
= g_new0 (RhythmDBEvent
, 1);
2262 result
->type
= RHYTHMDB_EVENT_STAT
;
2263 result
->entry_type
= action
->entry_type
;
2265 rb_debug ("executing RHYTHMDB_ACTION_STAT for \"%s\"", rb_refstring_get (action
->uri
));
2267 rhythmdb_execute_stat (db
, rb_refstring_get (action
->uri
), result
);
2270 case RHYTHMDB_ACTION_LOAD
:
2272 result
= g_new0 (RhythmDBEvent
, 1);
2274 result
->type
= RHYTHMDB_EVENT_METADATA_LOAD
;
2275 result
->entry_type
= action
->entry_type
;
2277 rb_debug ("executing RHYTHMDB_ACTION_LOAD for \"%s\"", rb_refstring_get (action
->uri
));
2279 rhythmdb_execute_load (db
, rb_refstring_get (action
->uri
), result
);
2282 case RHYTHMDB_ACTION_SYNC
:
2284 GError
*error
= NULL
;
2285 RhythmDBEntry
*entry
;
2286 RhythmDBEntryType entry_type
;
2288 if (db
->priv
->dry_run
) {
2289 rb_debug ("dry run is enabled, not syncing metadata");
2293 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, action
->uri
);
2297 entry_type
= rhythmdb_entry_get_entry_type (entry
);
2298 entry_type
->sync_metadata (db
, entry
, &error
, entry_type
->sync_metadata_data
);
2300 if (error
!= NULL
) {
2301 RhythmDBSaveErrorData
*data
;
2303 data
= g_new0 (RhythmDBSaveErrorData
, 1);
2306 data
->uri
= g_strdup (rb_refstring_get (action
->uri
));
2307 data
->error
= error
;
2308 g_idle_add ((GSourceFunc
)emit_save_error_idle
, data
);
2315 g_assert_not_reached ();
2318 rhythmdb_action_free (db
, action
);
2322 rb_debug ("exiting main thread");
2323 result
= g_new0 (RhythmDBEvent
, 1);
2325 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2326 g_async_queue_push (db
->priv
->event_queue
, result
);
2334 * @uri: the URI to add an entry/entries for
2336 * Adds the file(s) pointed to by @uri to the database, as entries of type
2337 * RHYTHMDB_ENTRY_TYPE_SONG. If the URI is that of a file, they will be added.
2338 * If the URI is that of a directory, everything under it will be added recursively.
2341 rhythmdb_add_uri (RhythmDB
*db
,
2344 rhythmdb_add_uri_with_type (db
, uri
, RHYTHMDB_ENTRY_TYPE_INVALID
);
2348 rhythmdb_add_uri_with_type (RhythmDB
*db
,
2350 RhythmDBEntryType type
)
2355 canon_uri
= rb_canonicalise_uri (uri
);
2356 realuri
= rb_uri_resolve_symlink (canon_uri
);
2359 if (rb_uri_is_directory (realuri
)) {
2360 RhythmDBAddThreadData
*data
= g_new0 (RhythmDBAddThreadData
, 1);
2362 data
->uri
= g_strdup (realuri
);
2365 rhythmdb_thread_create (db
, db
->priv
->add_thread_pool
, NULL
, data
);
2367 queue_stat_uri (realuri
, db
, type
);
2374 rhythmdb_sync_library_idle (RhythmDB
*db
)
2376 rhythmdb_sync_library_location (db
);
2377 g_object_unref (db
);
2382 rhythmdb_load_error_cb (GError
*error
)
2384 GDK_THREADS_ENTER ();
2385 rb_error_dialog (NULL
,
2386 _("Could not load the music database"),
2388 g_error_free (error
);
2390 GDK_THREADS_LEAVE ();
2395 rhythmdb_load_thread_main (RhythmDB
*db
)
2397 RhythmDBEvent
*result
;
2398 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2399 GError
*error
= NULL
;
2401 rb_profile_start ("loading db");
2402 g_mutex_lock (db
->priv
->saving_mutex
);
2403 if (klass
->impl_load (db
, &db
->priv
->exiting
, &error
) == FALSE
) {
2404 rb_debug ("db load failed: disabling saving");
2405 db
->priv
->can_save
= FALSE
;
2408 g_idle_add ((GSourceFunc
) rhythmdb_load_error_cb
, error
);
2411 g_mutex_unlock (db
->priv
->saving_mutex
);
2414 g_timeout_add (10000, (GSourceFunc
) rhythmdb_sync_library_idle
, db
);
2416 rb_debug ("queuing db load complete signal");
2417 result
= g_new0 (RhythmDBEvent
, 1);
2418 result
->type
= RHYTHMDB_EVENT_DB_LOAD
;
2419 g_async_queue_push (db
->priv
->event_queue
, result
);
2421 rb_debug ("exiting");
2422 result
= g_new0 (RhythmDBEvent
, 1);
2423 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2424 g_async_queue_push (db
->priv
->event_queue
, result
);
2426 rb_profile_end ("loading db");
2434 * Load the database from disk.
2437 rhythmdb_load (RhythmDB
*db
)
2439 rhythmdb_thread_create (db
, NULL
, (GThreadFunc
) rhythmdb_load_thread_main
, db
);
2443 rhythmdb_save_thread_main (RhythmDB
*db
)
2445 RhythmDBClass
*klass
;
2446 RhythmDBEvent
*result
;
2448 rb_debug ("entering save thread");
2450 g_mutex_lock (db
->priv
->saving_mutex
);
2452 if (!db
->priv
->dirty
&& !db
->priv
->can_save
) {
2453 rb_debug ("no save needed, ignoring");
2454 g_mutex_unlock (db
->priv
->saving_mutex
);
2458 while (db
->priv
->saving
)
2459 g_cond_wait (db
->priv
->saving_condition
, db
->priv
->saving_mutex
);
2461 db
->priv
->saving
= TRUE
;
2463 rb_debug ("saving rhythmdb");
2465 klass
= RHYTHMDB_GET_CLASS (db
);
2466 klass
->impl_save (db
);
2468 db
->priv
->saving
= FALSE
;
2469 db
->priv
->dirty
= FALSE
;
2471 g_mutex_unlock (db
->priv
->saving_mutex
);
2473 g_cond_broadcast (db
->priv
->saving_condition
);
2476 result
= g_new0 (RhythmDBEvent
, 1);
2478 result
->type
= RHYTHMDB_EVENT_DB_SAVED
;
2479 g_async_queue_push (db
->priv
->event_queue
, result
);
2481 result
= g_new0 (RhythmDBEvent
, 1);
2483 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2484 g_async_queue_push (db
->priv
->event_queue
, result
);
2489 * rhythmdb_save_async:
2492 * Save the database to disk, asynchronously.
2495 rhythmdb_save_async (RhythmDB
*db
)
2497 rb_debug ("saving the rhythmdb in the background");
2499 rhythmdb_read_enter (db
);
2501 rhythmdb_thread_create (db
, NULL
, (GThreadFunc
) rhythmdb_save_thread_main
, db
);
2508 * Save the database to disk, not returning until it has been saved.
2511 rhythmdb_save (RhythmDB
*db
)
2513 rb_debug("saving the rhythmdb and blocking");
2515 rhythmdb_save_async (db
);
2517 g_mutex_lock (db
->priv
->saving_mutex
);
2519 while (db
->priv
->saving
)
2520 g_cond_wait (db
->priv
->saving_condition
, db
->priv
->saving_mutex
);
2522 g_mutex_unlock (db
->priv
->saving_mutex
);
2526 * rhythmdb_entry_set:
2528 * @entry: a #RhythmDBEntry.
2529 * @propid: the id of the property to set.
2530 * @value: the property value.
2532 * This function can be called by any code which wishes to change a
2533 * song property and send a notification. It may be called when the
2534 * database is read-only; in this case the change will be queued for
2535 * an unspecified time in the future. The implication of this is that
2536 * rhythmdb_entry_get() may not reflect the changes immediately. However,
2537 * if this property is exposed in the user interface, you should still
2538 * make the change in the widget. Then when the database returns to a
2539 * writable state, your change will take effect in the database too,
2540 * and a notification will be sent at that point.
2542 * Note that you must call rhythmdb_commit() at some point after invoking
2543 * this function, and that even after the commit, your change may not
2544 * have taken effect.
2547 rhythmdb_entry_set (RhythmDB
*db
,
2548 RhythmDBEntry
*entry
,
2550 const GValue
*value
)
2552 g_return_if_fail (RHYTHMDB_IS (db
));
2553 g_return_if_fail (entry
!= NULL
);
2555 if ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) != 0) {
2556 if (!rhythmdb_get_readonly (db
) && rb_is_main_thread ()) {
2557 rhythmdb_entry_set_internal (db
, entry
, TRUE
, propid
, value
);
2559 RhythmDBEvent
*result
;
2561 result
= g_new0 (RhythmDBEvent
, 1);
2563 result
->type
= RHYTHMDB_EVENT_ENTRY_SET
;
2565 rb_debug ("queuing RHYTHMDB_ACTION_ENTRY_SET");
2567 result
->entry
= rhythmdb_entry_ref (entry
);
2568 result
->change
.prop
= propid
;
2569 result
->signal_change
= TRUE
;
2570 g_value_init (&result
->change
.new, G_VALUE_TYPE (value
));
2571 g_value_copy (value
, &result
->change
.new);
2572 g_async_queue_push (db
->priv
->event_queue
, result
);
2575 rhythmdb_entry_set_internal (db
, entry
, FALSE
, propid
, value
);
2580 record_entry_change (RhythmDB
*db
,
2581 RhythmDBEntry
*entry
,
2583 const GValue
*value
)
2585 RhythmDBEntryChange
*changedata
;
2588 changedata
= g_new0 (RhythmDBEntryChange
, 1);
2589 changedata
->prop
= propid
;
2591 /* Copy a temporary gvalue, since _entry_get uses
2592 * _set_static_string to avoid memory allocations. */
2595 g_value_init (&tem
, G_VALUE_TYPE (value
));
2596 rhythmdb_entry_get (db
, entry
, propid
, &tem
);
2597 g_value_init (&changedata
->old
, G_VALUE_TYPE (value
));
2598 g_value_copy (&tem
, &changedata
->old
);
2599 g_value_unset (&tem
);
2601 g_value_init (&changedata
->new, G_VALUE_TYPE (value
));
2602 g_value_copy (value
, &changedata
->new);
2604 g_mutex_lock (db
->priv
->change_mutex
);
2605 /* ref the entry before adding to hash, it is unreffed when removed */
2606 rhythmdb_entry_ref (entry
);
2607 changelist
= g_hash_table_lookup (db
->priv
->changed_entries
, entry
);
2608 changelist
= g_slist_append (changelist
, changedata
);
2609 g_hash_table_insert (db
->priv
->changed_entries
, entry
, changelist
);
2610 g_mutex_unlock (db
->priv
->change_mutex
);
2614 rhythmdb_entry_set_internal (RhythmDB
*db
,
2615 RhythmDBEntry
*entry
,
2616 gboolean notify_if_inserted
,
2618 const GValue
*value
)
2620 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2622 RhythmDBPodcastFields
*podcast
= NULL
;
2624 #ifndef G_DISABLE_ASSERT
2625 switch (G_VALUE_TYPE (value
)) {
2627 /* the playback error is allowed to be NULL */
2628 if (propid
!= RHYTHMDB_PROP_PLAYBACK_ERROR
|| g_value_get_string (value
))
2629 g_assert (g_utf8_validate (g_value_get_string (value
), -1, NULL
));
2631 case G_TYPE_BOOLEAN
:
2637 g_assert_not_reached ();
2642 if ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) && notify_if_inserted
) {
2643 record_entry_change (db
, entry
, propid
, value
);
2646 handled
= klass
->impl_entry_set (db
, entry
, propid
, value
);
2649 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
2650 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
2651 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
2654 case RHYTHMDB_PROP_TYPE
:
2655 case RHYTHMDB_PROP_ENTRY_ID
:
2656 g_assert_not_reached ();
2658 case RHYTHMDB_PROP_TITLE
:
2659 if (entry
->title
!= NULL
) {
2660 rb_refstring_unref (entry
->title
);
2662 entry
->title
= rb_refstring_new (g_value_get_string (value
));
2664 case RHYTHMDB_PROP_ALBUM
:
2665 if (entry
->album
!= NULL
) {
2666 rb_refstring_unref (entry
->album
);
2668 entry
->album
= rb_refstring_new (g_value_get_string (value
));
2670 case RHYTHMDB_PROP_ARTIST
:
2671 if (entry
->artist
!= NULL
) {
2672 rb_refstring_unref (entry
->artist
);
2674 entry
->artist
= rb_refstring_new (g_value_get_string (value
));
2676 case RHYTHMDB_PROP_GENRE
:
2677 if (entry
->genre
!= NULL
) {
2678 rb_refstring_unref (entry
->genre
);
2680 entry
->genre
= rb_refstring_new (g_value_get_string (value
));
2682 case RHYTHMDB_PROP_TRACK_NUMBER
:
2683 entry
->tracknum
= g_value_get_ulong (value
);
2685 case RHYTHMDB_PROP_DISC_NUMBER
:
2686 entry
->discnum
= g_value_get_ulong (value
);
2688 case RHYTHMDB_PROP_DURATION
:
2689 entry
->duration
= g_value_get_ulong (value
);
2691 case RHYTHMDB_PROP_BITRATE
:
2692 entry
->bitrate
= g_value_get_ulong (value
);
2694 case RHYTHMDB_PROP_DATE
:
2697 julian
= g_value_get_ulong (value
);
2699 g_date_set_julian (&entry
->date
, julian
);
2701 g_date_clear (&entry
->date
, 1);
2704 case RHYTHMDB_PROP_TRACK_GAIN
:
2705 entry
->track_gain
= g_value_get_double (value
);
2707 case RHYTHMDB_PROP_TRACK_PEAK
:
2708 entry
->track_peak
= g_value_get_double (value
);
2710 case RHYTHMDB_PROP_ALBUM_GAIN
:
2711 entry
->album_gain
= g_value_get_double (value
);
2713 case RHYTHMDB_PROP_ALBUM_PEAK
:
2714 entry
->album_peak
= g_value_get_double (value
);
2716 case RHYTHMDB_PROP_LOCATION
:
2717 rb_refstring_unref (entry
->location
);
2718 entry
->location
= rb_refstring_new (g_value_get_string (value
));
2720 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
2721 rb_refstring_unref (entry
->playback_error
);
2722 if (g_value_get_string (value
))
2723 entry
->playback_error
= rb_refstring_new (g_value_get_string (value
));
2725 entry
->playback_error
= NULL
;
2727 case RHYTHMDB_PROP_MOUNTPOINT
:
2728 if (entry
->mountpoint
!= NULL
) {
2729 rb_refstring_unref (entry
->mountpoint
);
2731 entry
->mountpoint
= rb_refstring_new (g_value_get_string (value
));
2733 case RHYTHMDB_PROP_FILE_SIZE
:
2734 entry
->file_size
= g_value_get_uint64 (value
);
2736 case RHYTHMDB_PROP_MIMETYPE
:
2737 if (entry
->mimetype
!= NULL
) {
2738 rb_refstring_unref (entry
->mimetype
);
2740 entry
->mimetype
= rb_refstring_new (g_value_get_string (value
));
2742 case RHYTHMDB_PROP_MTIME
:
2743 entry
->mtime
= g_value_get_ulong (value
);
2745 case RHYTHMDB_PROP_FIRST_SEEN
:
2746 entry
->first_seen
= g_value_get_ulong (value
);
2747 entry
->flags
|= RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
;
2749 case RHYTHMDB_PROP_LAST_SEEN
:
2750 entry
->last_seen
= g_value_get_ulong (value
);
2751 entry
->flags
|= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
2753 case RHYTHMDB_PROP_RATING
:
2754 entry
->rating
= g_value_get_double (value
);
2756 case RHYTHMDB_PROP_PLAY_COUNT
:
2757 entry
->play_count
= g_value_get_ulong (value
);
2759 case RHYTHMDB_PROP_LAST_PLAYED
:
2760 entry
->last_played
= g_value_get_ulong (value
);
2761 entry
->flags
|= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
;
2763 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
2764 rb_refstring_unref (entry
->musicbrainz_trackid
);
2765 entry
->musicbrainz_trackid
= rb_refstring_new (g_value_get_string (value
));
2767 case RHYTHMDB_PROP_HIDDEN
:
2768 if (g_value_get_boolean (value
)) {
2769 entry
->flags
|= RHYTHMDB_ENTRY_HIDDEN
;
2771 entry
->flags
&= ~RHYTHMDB_ENTRY_HIDDEN
;
2773 entry
->flags
|= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
2775 case RHYTHMDB_PROP_STATUS
:
2777 podcast
->status
= g_value_get_ulong (value
);
2779 case RHYTHMDB_PROP_DESCRIPTION
:
2781 rb_refstring_unref (podcast
->description
);
2782 podcast
->description
= rb_refstring_new (g_value_get_string (value
));
2784 case RHYTHMDB_PROP_SUBTITLE
:
2786 rb_refstring_unref (podcast
->subtitle
);
2787 podcast
->subtitle
= rb_refstring_new (g_value_get_string (value
));
2789 case RHYTHMDB_PROP_SUMMARY
:
2791 rb_refstring_unref (podcast
->summary
);
2792 podcast
->summary
= rb_refstring_new (g_value_get_string (value
));
2794 case RHYTHMDB_PROP_LANG
:
2796 if (podcast
->lang
!= NULL
) {
2797 rb_refstring_unref (podcast
->lang
);
2799 podcast
->lang
= rb_refstring_new (g_value_get_string (value
));
2801 case RHYTHMDB_PROP_COPYRIGHT
:
2803 if (podcast
->copyright
!= NULL
) {
2804 rb_refstring_unref (podcast
->copyright
);
2806 podcast
->copyright
= rb_refstring_new (g_value_get_string (value
));
2808 case RHYTHMDB_PROP_IMAGE
:
2810 if (podcast
->image
!= NULL
) {
2811 rb_refstring_unref (podcast
->image
);
2813 podcast
->image
= rb_refstring_new (g_value_get_string (value
));
2815 case RHYTHMDB_PROP_POST_TIME
:
2817 podcast
->post_time
= g_value_get_ulong (value
);
2819 case RHYTHMDB_NUM_PROPERTIES
:
2820 g_assert_not_reached ();
2825 /* set the dirty state */
2826 db
->priv
->dirty
= TRUE
;
2830 * rhythmdb_entry_sync_mirrored:
2832 * @type: a #RhythmDBEntry.
2833 * @propid: the property to sync the mirrored version of.
2835 * Synchronise "mirrored" properties, such as the string version of the last-played
2836 * time. This should be called when a property is directly modified, passing the
2837 * original property.
2839 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
2843 rhythmdb_entry_sync_mirrored (RhythmDBEntry
*entry
,
2846 static const char *format
;
2847 static const char *never
;
2850 /* PERFORMANCE: doing these lookups every call creates a noticable slowdown */
2852 format
= _("%Y-%m-%d %H:%M");
2857 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
2859 RBRefString
*old
, *new;
2861 if (!(entry
->flags
& RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
))
2864 old
= g_atomic_pointer_get (&entry
->last_played_str
);
2865 if (entry
->last_played
== 0) {
2866 new = rb_refstring_new (never
);
2868 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->last_played
));
2869 new = rb_refstring_new (val
);
2873 if (g_atomic_pointer_compare_and_exchange (&entry
->last_played_str
, old
, new)) {
2875 rb_refstring_unref (old
);
2878 rb_refstring_unref (new);
2883 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
2885 RBRefString
*old
, *new;
2887 if (!(entry
->flags
& RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
))
2890 old
= g_atomic_pointer_get (&entry
->first_seen_str
);
2891 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->first_seen
));
2892 new = rb_refstring_new (val
);
2895 if (g_atomic_pointer_compare_and_exchange (&entry
->first_seen_str
, old
, new)) {
2897 rb_refstring_unref (old
);
2900 rb_refstring_unref (new);
2905 case RHYTHMDB_PROP_LAST_SEEN_STR
:
2907 RBRefString
*old
, *new;
2909 if (!(entry
->flags
& RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
))
2912 old
= g_atomic_pointer_get (&entry
->last_seen_str
);
2913 /* only store last seen time as a string for hidden entries */
2914 if (entry
->flags
& RHYTHMDB_ENTRY_HIDDEN
) {
2915 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->last_seen
));
2916 new = rb_refstring_new (val
);
2922 if (g_atomic_pointer_compare_and_exchange (&entry
->first_seen_str
, old
, new)) {
2924 rb_refstring_unref (old
);
2927 rb_refstring_unref (new);
2938 * rhythmdb_entry_delete:
2940 * @entry: a #RhythmDBEntry.
2942 * Delete entry @entry from the database, sending notification of it's deletion.
2943 * This is usually used by sources where entries can disappear randomly, such
2944 * as a network source.
2947 rhythmdb_entry_delete (RhythmDB
*db
,
2948 RhythmDBEntry
*entry
)
2950 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2952 g_return_if_fail (RHYTHMDB_IS (db
));
2953 g_return_if_fail (entry
!= NULL
);
2955 klass
->impl_entry_delete (db
, entry
);
2957 /* ref the entry before adding to hash, it is unreffed when removed */
2958 rhythmdb_entry_ref (entry
);
2959 g_mutex_lock (db
->priv
->change_mutex
);
2960 g_hash_table_insert (db
->priv
->deleted_entries
, entry
, g_thread_self ());
2961 g_mutex_unlock (db
->priv
->change_mutex
);
2963 /* deleting an entry makes the db dirty */
2964 db
->priv
->dirty
= TRUE
;
2968 rhythmdb_entry_move_to_trash_cb (GnomeVFSXferProgressInfo
*info
,
2971 /* Abort immediately if anything happens */
2972 if (info
->status
== GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR
)
2973 return GNOME_VFS_XFER_ERROR_ACTION_ABORT
;
2974 /* Don't overwrite files */
2975 if (info
->status
== GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE
)
2981 rhythmdb_entry_move_to_trash_set_error (RhythmDB
*db
,
2982 RhythmDBEntry
*entry
,
2985 GValue value
= { 0, };
2988 res
= GNOME_VFS_ERROR_INTERNAL
;
2990 g_value_init (&value
, G_TYPE_STRING
);
2991 g_value_set_string (&value
, gnome_vfs_result_to_string (res
));
2992 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
2993 g_value_unset (&value
);
2995 rb_debug ("Deleting %s failed: %s", rb_refstring_get (entry
->location
),
2996 gnome_vfs_result_to_string (res
));
3000 rhythmdb_entry_move_to_trash (RhythmDB
*db
,
3001 RhythmDBEntry
*entry
)
3004 GnomeVFSURI
*uri
, *trash
, *dest
;
3007 uri
= gnome_vfs_uri_new (rb_refstring_get (entry
->location
));
3009 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3013 res
= gnome_vfs_find_directory (uri
,
3014 GNOME_VFS_DIRECTORY_KIND_TRASH
,
3018 if (res
!= GNOME_VFS_OK
|| trash
== NULL
) {
3019 /* If the file doesn't exist, or trash isn't support,
3020 * remove it from the db */
3021 if (res
== GNOME_VFS_ERROR_NOT_FOUND
||
3022 res
== GNOME_VFS_ERROR_NOT_SUPPORTED
) {
3023 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
3025 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3028 gnome_vfs_uri_unref (uri
);
3032 /* Is the file already in the Trash? If so it should be hidden */
3033 if (gnome_vfs_uri_is_parent (trash
, uri
, TRUE
)) {
3034 GValue value
= { 0, };
3035 g_value_init (&value
, G_TYPE_BOOLEAN
);
3036 g_value_set_boolean (&value
, TRUE
);
3037 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_HIDDEN
, &value
);
3038 rhythmdb_commit (db
);
3040 gnome_vfs_uri_unref (trash
);
3041 gnome_vfs_uri_unref (uri
);
3045 shortname
= gnome_vfs_uri_extract_short_name (uri
);
3046 if (shortname
== NULL
) {
3047 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3048 rhythmdb_commit (db
);
3049 gnome_vfs_uri_unref (uri
);
3050 gnome_vfs_uri_unref (trash
);
3054 /* Compute the destination URI */
3055 dest
= gnome_vfs_uri_append_path (trash
, shortname
);
3056 gnome_vfs_uri_unref (trash
);
3059 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3060 rhythmdb_commit (db
);
3061 gnome_vfs_uri_unref (uri
);
3065 /* RB can't tell that a file's moved, so no unique names */
3066 res
= gnome_vfs_xfer_uri (uri
, dest
,
3067 GNOME_VFS_XFER_REMOVESOURCE
,
3068 GNOME_VFS_XFER_ERROR_MODE_ABORT
,
3069 GNOME_VFS_XFER_OVERWRITE_MODE_SKIP
,
3070 rhythmdb_entry_move_to_trash_cb
,
3073 if (res
== GNOME_VFS_OK
) {
3074 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
3076 rhythmdb_entry_move_to_trash_set_error (db
, entry
, res
);
3078 rhythmdb_commit (db
);
3080 gnome_vfs_uri_unref (dest
);
3081 gnome_vfs_uri_unref (uri
);
3085 * rhythmdb_entry_delete_by_type:
3087 * @type: type of entried to delete.
3089 * Delete all entries from the database of the given type.
3090 * This is usually used by non-permanent sources when they disappear, such as
3091 * removable media being removed, or a network share becoming unavailable.
3094 rhythmdb_entry_delete_by_type (RhythmDB
*db
,
3095 RhythmDBEntryType type
)
3097 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3099 if (klass
->impl_entry_delete_by_type
) {
3100 klass
->impl_entry_delete_by_type (db
, type
);
3102 g_warning ("delete_by_type not implemented");
3107 rhythmdb_nice_elt_name_from_propid (RhythmDB
*db
,
3108 RhythmDBPropType propid
)
3110 return db
->priv
->column_xml_names
[propid
];
3114 rhythmdb_propid_from_nice_elt_name (RhythmDB
*db
,
3115 const xmlChar
*name
)
3118 if (g_hash_table_lookup_extended (db
->priv
->propname_map
, name
,
3120 return GPOINTER_TO_INT (ret
);
3126 * rhythmdb_entry_lookup_by_location:
3128 * @uri: the URI of the entry to lookup.
3130 * Looks up the entry with location @uri.
3132 * Returns: the entry with location @uri, or NULL if no such entry exists.
3135 rhythmdb_entry_lookup_by_location (RhythmDB
*db
,
3140 rs
= rb_refstring_find (uri
);
3142 return rhythmdb_entry_lookup_by_location_refstring (db
, rs
);
3149 rhythmdb_entry_lookup_by_location_refstring (RhythmDB
*db
,
3152 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3154 return klass
->impl_lookup_by_location (db
, uri
);
3158 *rhythmdb_entry_foreach:
3160 * @func: the function to call with each entry.
3161 * @data: user data to pass to the function.
3163 * Calls the given function for each of the entries in the database.
3166 rhythmdb_entry_foreach (RhythmDB
*db
,
3170 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3172 klass
->impl_entry_foreach (db
, func
, data
);
3176 * rhythmdb_evaluate_query:
3179 * @entry a @RhythmDBEntry.
3181 * Evaluates the given entry against the given query.
3183 * Returns: whether the given entry matches the criteria of the given query.
3186 rhythmdb_evaluate_query (RhythmDB
*db
,
3188 RhythmDBEntry
*entry
)
3190 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3192 return klass
->impl_evaluate_query (db
, query
, entry
);
3196 rhythmdb_query_internal (RhythmDBQueryThreadData
*data
)
3198 RhythmDBEvent
*result
;
3199 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (data
->db
);
3201 rhythmdb_query_preprocess (data
->db
, data
->query
);
3203 rb_debug ("doing query");
3205 klass
->impl_do_full_query (data
->db
, data
->query
,
3209 rb_debug ("completed");
3210 rhythmdb_query_results_query_complete (data
->results
);
3212 result
= g_new0 (RhythmDBEvent
, 1);
3213 result
->db
= data
->db
;
3214 result
->type
= RHYTHMDB_EVENT_QUERY_COMPLETE
;
3215 result
->results
= data
->results
;
3216 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
3218 rhythmdb_query_free (data
->query
);
3222 query_thread_main (RhythmDBQueryThreadData
*data
)
3224 RhythmDBEvent
*result
;
3226 rb_debug ("entering query thread");
3228 rhythmdb_query_internal (data
);
3230 result
= g_new0 (RhythmDBEvent
, 1);
3231 result
->db
= data
->db
;
3232 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
3233 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
3239 rhythmdb_do_full_query_async_parsed (RhythmDB
*db
,
3240 RhythmDBQueryResults
*results
,
3243 RhythmDBQueryThreadData
*data
;
3245 data
= g_new0 (RhythmDBQueryThreadData
, 1);
3247 data
->query
= rhythmdb_query_copy (query
);
3248 data
->results
= results
;
3249 data
->cancel
= FALSE
;
3251 rhythmdb_read_enter (db
);
3253 rhythmdb_query_results_set_query (results
, query
);
3255 g_object_ref (results
);
3257 g_atomic_int_inc (&db
->priv
->outstanding_threads
);
3258 g_async_queue_ref (db
->priv
->action_queue
);
3259 g_async_queue_ref (db
->priv
->event_queue
);
3260 g_thread_pool_push (db
->priv
->query_thread_pool
, data
, NULL
);
3264 rhythmdb_do_full_query_async (RhythmDB
*db
,
3265 RhythmDBQueryResults
*results
,
3271 va_start (args
, results
);
3273 query
= rhythmdb_query_parse_valist (db
, args
);
3275 rhythmdb_do_full_query_async_parsed (db
, results
, query
);
3277 rhythmdb_query_free (query
);
3283 rhythmdb_do_full_query_internal (RhythmDB
*db
,
3284 RhythmDBQueryResults
*results
,
3287 RhythmDBQueryThreadData
*data
;
3289 data
= g_new0 (RhythmDBQueryThreadData
, 1);
3291 data
->query
= rhythmdb_query_copy (query
);
3292 data
->results
= results
;
3293 data
->cancel
= FALSE
;
3295 rhythmdb_read_enter (db
);
3297 rhythmdb_query_results_set_query (results
, query
);
3298 g_object_ref (results
);
3300 rhythmdb_query_internal (data
);
3305 rhythmdb_do_full_query_parsed (RhythmDB
*db
,
3306 RhythmDBQueryResults
*results
,
3309 rhythmdb_do_full_query_internal (db
, results
, query
);
3313 rhythmdb_do_full_query (RhythmDB
*db
,
3314 RhythmDBQueryResults
*results
,
3320 va_start (args
, results
);
3322 query
= rhythmdb_query_parse_valist (db
, args
);
3324 rhythmdb_do_full_query_internal (db
, results
, query
);
3326 rhythmdb_query_free (query
);
3331 /* This should really be standard. */
3332 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3335 rhythmdb_query_type_get_type (void)
3337 static GType etype
= 0;
3341 static const GEnumValue values
[] =
3344 ENUM_ENTRY (RHYTHMDB_QUERY_END
, "Query end marker"),
3345 ENUM_ENTRY (RHYTHMDB_QUERY_DISJUNCTION
, "Disjunctive marker"),
3346 ENUM_ENTRY (RHYTHMDB_QUERY_SUBQUERY
, "Subquery"),
3347 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_EQUALS
, "Property equivalence"),
3348 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LIKE
, "Fuzzy property matching"),
3349 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_LIKE
, "Inverted fuzzy property matching"),
3350 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_PREFIX
, "Starts with"),
3351 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_SUFFIX
, "Ends with"),
3352 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_GREATER
, "True if property1 >= property2"),
3353 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LESS
, "True if property1 <= property2"),
3354 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
, "True if property1 is within property2 of the current time"),
3355 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN
, "True if property1 is not within property2 of the current time"),
3356 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_EQUALS
, "Year equivalence: true if date within year"),
3357 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_GREATER
, "True if date greater than year"),
3358 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_LESS
, "True if date less than year"),
3362 etype
= g_enum_register_static ("RhythmDBQueryType", values
);
3369 rhythmdb_prop_type_get_type (void)
3371 static GType etype
= 0;
3375 static const GEnumValue values
[] =
3377 /* We reuse the description to store extra data about
3378 * a property. The first part is just a generic
3379 * human-readable description. Next, there is
3380 * a string describing the GType of the property, in
3382 * Finally, there is the XML element name in brackets.
3384 ENUM_ENTRY (RHYTHMDB_PROP_TYPE
, "Type of entry (gpointer) [type]"),
3385 ENUM_ENTRY (RHYTHMDB_PROP_ENTRY_ID
, "Numeric ID (guint) [entry-id]"),
3386 ENUM_ENTRY (RHYTHMDB_PROP_TITLE
, "Title (gchararray) [title]"),
3387 ENUM_ENTRY (RHYTHMDB_PROP_GENRE
, "Genre (gchararray) [genre]"),
3388 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST
, "Artist (gchararray) [artist]"),
3389 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM
, "Album (gchararray) [album]"),
3390 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_NUMBER
, "Track Number (gulong) [track-number]"),
3391 ENUM_ENTRY (RHYTHMDB_PROP_DISC_NUMBER
, "Disc Number (gulong) [disc-number]"),
3392 ENUM_ENTRY (RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
, "Musicbrainz Track ID (gchararray) [mb-trackid]"),
3394 ENUM_ENTRY (RHYTHMDB_PROP_DURATION
, "Duration (gulong) [duration]"),
3395 ENUM_ENTRY (RHYTHMDB_PROP_FILE_SIZE
, "File Size (guint64) [file-size]"),
3396 ENUM_ENTRY (RHYTHMDB_PROP_LOCATION
, "Location (gchararray) [location]"),
3397 ENUM_ENTRY (RHYTHMDB_PROP_MOUNTPOINT
, "Mount point it's located in (gchararray) [mountpoint]"),
3398 ENUM_ENTRY (RHYTHMDB_PROP_MTIME
, "Modification time (gulong) [mtime]"),
3399 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN
, "Time the song was added to the library (gulong) [first-seen]"),
3400 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN
, "Last time the song was available (gulong) [last-seen]"),
3401 ENUM_ENTRY (RHYTHMDB_PROP_RATING
, "Rating (gdouble) [rating]"),
3402 ENUM_ENTRY (RHYTHMDB_PROP_PLAY_COUNT
, "Play Count (gulong) [play-count]"),
3403 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED
, "Last Played (gulong) [last-played]"),
3404 ENUM_ENTRY (RHYTHMDB_PROP_BITRATE
, "Bitrate (gulong) [bitrate]"),
3405 ENUM_ENTRY (RHYTHMDB_PROP_DATE
, "Date of release (gulong) [date]"),
3406 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_GAIN
, "Replaygain track gain (gdouble) [replaygain-track-gain]"),
3407 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_PEAK
, "Replaygain track peak (gdouble) [replaygain-track-peak]"),
3408 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_GAIN
, "Replaygain album pain (gdouble) [replaygain-album-gain]"),
3409 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_PEAK
, "Replaygain album peak (gdouble) [replaygain-album-peak]"),
3410 ENUM_ENTRY (RHYTHMDB_PROP_MIMETYPE
, "Mime Type (gchararray) [mimetype]"),
3411 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_SORT_KEY
, "Title sort key (gchararray) [title-sort-key]"),
3412 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_SORT_KEY
, "Genre sort key (gchararray) [genre-sort-key]"),
3413 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_SORT_KEY
, "Artist sort key (gchararray) [artist-sort-key]"),
3414 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_SORT_KEY
, "Album sort key (gchararray) [album-sort-key]"),
3416 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_FOLDED
, "Title folded (gchararray) [title-folded]"),
3417 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_FOLDED
, "Genre folded (gchararray) [genre-folded]"),
3418 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_FOLDED
, "Artist folded (gchararray) [artist-folded]"),
3419 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_FOLDED
, "Album folded (gchararray) [album-folded]"),
3420 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED_STR
, "Last Played (gchararray) [last-played-str]"),
3421 ENUM_ENTRY (RHYTHMDB_PROP_PLAYBACK_ERROR
, "Playback error string (gchararray) [playback-error]"),
3422 ENUM_ENTRY (RHYTHMDB_PROP_HIDDEN
, "Hidden (gboolean) [hidden]"),
3423 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN_STR
, "Time Added to Library (gchararray) [first-seen-str]"),
3424 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN_STR
, "Last time the song was available (gchararray) [last-seen-str]"),
3425 ENUM_ENTRY (RHYTHMDB_PROP_SEARCH_MATCH
, "Search matching key (gchararray) [search-match]"),
3426 ENUM_ENTRY (RHYTHMDB_PROP_YEAR
, "Year of date (gulong) [year]"),
3428 ENUM_ENTRY (RHYTHMDB_PROP_STATUS
, "Status of file (gulong) [status]"),
3429 ENUM_ENTRY (RHYTHMDB_PROP_DESCRIPTION
, "Podcast description(gchararray) [description]"),
3430 ENUM_ENTRY (RHYTHMDB_PROP_SUBTITLE
, "Podcast subtitle (gchararray) [subtitle]"),
3431 ENUM_ENTRY (RHYTHMDB_PROP_SUMMARY
, "Podcast summary (gchararray) [summary]"),
3432 ENUM_ENTRY (RHYTHMDB_PROP_LANG
, "Podcast language (gchararray) [lang]"),
3433 ENUM_ENTRY (RHYTHMDB_PROP_COPYRIGHT
, "Podcast copyright (gchararray) [copyright]"),
3434 ENUM_ENTRY (RHYTHMDB_PROP_IMAGE
, "Podcast image(gchararray) [image]"),
3435 ENUM_ENTRY (RHYTHMDB_PROP_POST_TIME
, "Podcast time of post (gulong) [post-time]"),
3438 g_assert ((sizeof (values
) / sizeof (values
[0]) - 1) == RHYTHMDB_NUM_PROPERTIES
);
3439 etype
= g_enum_register_static ("RhythmDBPropType", values
);
3446 rhythmdb_emit_entry_deleted (RhythmDB
*db
,
3447 RhythmDBEntry
*entry
)
3449 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_DELETED
], 0, entry
);
3453 rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint
*ihint
,
3454 GValue
*return_accu
,
3455 const GValue
*handler_return
,
3458 if (handler_return
== NULL
)
3461 g_value_copy (handler_return
, return_accu
);
3462 return (g_value_get_boxed (return_accu
) == NULL
);
3466 * rhythmdb_entry_request_extra_metadata:
3468 * @entry: a #RhythmDBEntry
3469 * @property_name: the metadata predicate
3471 * Emits a request for extra metadata for the @entry.
3472 * The @property_name argument is emitted as the ::detail part of the
3473 * "entry_extra_metadata_request" signal. It should be a namespaced RDF
3474 * predicate e.g. from Dublin Core, MusicBrainz, or internal to Rhythmbox
3475 * (namespace "rb:"). Suitable predicates would be those that are expensive to
3476 * acquire or only apply to a limited range of entries.
3477 * Handlers capable of providing a particular predicate may ensure they only
3478 * see appropriate requests by supplying an appropriate ::detail part when
3479 * connecting to the signal. Upon a handler returning a non-%NULL value,
3480 * emission will be stopped and the value returned to the caller; if no
3481 * handlers return a non-%NULL value, the caller will receive %NULL. Priority
3482 * is determined by signal connection order, with %G_CONNECT_AFTER providing a
3483 * second, lower rank of priority.
3484 * A handler returning a value should do so in a #GValue allocated on the heap;
3485 * the accumulator will take ownership. The caller should unset and free the
3486 * #GValue if non-%NULL when finished with it.
3488 * Returns: an allocated, initialised, set #GValue, or NULL
3491 rhythmdb_entry_request_extra_metadata (RhythmDB
*db
,
3492 RhythmDBEntry
*entry
,
3493 const gchar
*property_name
)
3495 GValue
*value
= NULL
;
3497 g_signal_emit (G_OBJECT (db
),
3498 rhythmdb_signals
[ENTRY_EXTRA_METADATA_REQUEST
],
3499 g_quark_from_string (property_name
),
3507 * rhythmdb_emit_entry_extra_metadata_notify:
3509 * @entry: a #RhythmDBEntry
3510 * @property_name: the metadata predicate
3511 * @metadata: a #GValue
3513 * Emits a signal describing extra metadata for the @entry. The @property_name
3514 * argument is emitted as the ::detail part of the
3515 * "entry_extra_metadata_notify" signal and as the 'field' parameter. Handlers
3516 * can ensure they only get metadata they are interested in by supplying an
3517 * appropriate ::detail part when connecting to the signal. If handlers are
3518 * interested in the metadata they should ref or copy the contents of @metadata
3519 * and unref or free it when they are finished with it.
3522 rhythmdb_emit_entry_extra_metadata_notify (RhythmDB
*db
,
3523 RhythmDBEntry
*entry
,
3524 const gchar
*property_name
,
3525 const GValue
*metadata
)
3527 g_signal_emit (G_OBJECT (db
),
3528 rhythmdb_signals
[ENTRY_EXTRA_METADATA_NOTIFY
],
3529 g_quark_from_string (property_name
),
3536 unset_and_free_g_value (gpointer valpointer
)
3538 GValue
*value
= valpointer
;
3539 g_value_unset (value
);
3544 * rhythmdb_entry_extra_gather:
3546 * @entry: a #RhythmDBEntry
3548 * Gathers all metadata for the @entry. The returned GHashTable maps property
3549 * names and extra metadata names (described under
3550 * @rhythmdb_entry_request_extra_metadata) to GValues. Anything wanting to
3551 * provide extra metadata should connect to the "entry_extra_metadata_gather"
3554 * Returns: a GHashTable containing metadata for the entry. This must be freed
3555 * using g_hash_table_destroy.
3558 rhythmdb_entry_gather_metadata (RhythmDB
*db
,
3559 RhythmDBEntry
*entry
)
3561 GHashTable
*metadata
;
3565 metadata
= g_hash_table_new_full (g_str_hash
,
3568 unset_and_free_g_value
);
3570 /* add core properties */
3571 klass
= g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE
);
3572 for (i
= 0; i
< klass
->n_values
; i
++) {
3578 prop
= klass
->values
[i
].value
;
3580 /* only include easily marshallable types in the hash table */
3581 value_type
= rhythmdb_get_property_type (db
, prop
);
3582 switch (value_type
) {
3584 case G_TYPE_BOOLEAN
:
3593 value
= g_new0 (GValue
, 1);
3594 g_value_init (value
, value_type
);
3595 rhythmdb_entry_get (db
, entry
, prop
, value
);
3596 name
= (char *)rhythmdb_nice_elt_name_from_propid (db
, prop
);
3597 g_hash_table_insert (metadata
,
3598 (gpointer
) g_strdup (name
),
3601 g_type_class_unref (klass
);
3603 /* gather extra metadata */
3604 g_signal_emit (G_OBJECT (db
),
3605 rhythmdb_signals
[ENTRY_EXTRA_METADATA_GATHER
], 0,
3613 queue_is_empty (GAsyncQueue
*queue
)
3615 return g_async_queue_length (queue
) <= 0;
3622 * Returns: whether the #RhythmDB has events to process.
3625 rhythmdb_is_busy (RhythmDB
*db
)
3627 return (!db
->priv
->action_thread_running
||
3628 !queue_is_empty (db
->priv
->event_queue
) ||
3629 !queue_is_empty (db
->priv
->action_queue
) ||
3630 (db
->priv
->stat_handle
!= NULL
) ||
3631 (db
->priv
->outstanding_stats
!= NULL
));
3635 * rhythmdb_compute_status_normal:
3636 * @n_songs: the number of tracks.
3637 * @duration: the total duration of the tracks.
3638 * @size: the total size of the tracks.
3639 * @singular: singular form of the format string to use for entries (eg "%d song")
3640 * @plural: plural form of the format string to use for entries (eg "%d songs")
3642 * Creates a string containing the "status" information about a list of tracks.
3643 * The singular and plural strings must be used in a direct ngettext call
3644 * elsewhere in order for them to be marked for translation correctly.
3646 * Returns: the string, which should be freed with g_free.
3649 rhythmdb_compute_status_normal (gint n_songs
,
3652 const char *singular
,
3655 long days
, hours
, minutes
, seconds
;
3656 char *songcount
= NULL
;
3658 char *size_str
= NULL
;
3660 const char *minutefmt
;
3661 const char *hourfmt
;
3664 songcount
= g_strdup_printf (ngettext (singular
, plural
, n_songs
), n_songs
);
3666 days
= duration
/ (60 * 60 * 24);
3667 hours
= (duration
/ (60 * 60)) - (days
* 24);
3668 minutes
= (duration
/ 60) - ((days
* 24 * 60) + (hours
* 60));
3669 seconds
= duration
% 60;
3671 minutefmt
= ngettext ("%ld minute", "%ld minutes", minutes
);
3672 hourfmt
= ngettext ("%ld hour", "%ld hours", hours
);
3673 dayfmt
= ngettext ("%ld day", "%ld days", days
);
3678 /* Translators: the format is "X days, X hours and X minutes" */
3679 fmt
= g_strdup_printf (_("%s, %s and %s"), dayfmt
, hourfmt
, minutefmt
);
3680 time
= g_strdup_printf (fmt
, days
, hours
, minutes
);
3684 /* Translators: the format is "X days and X hours" */
3685 fmt
= g_strdup_printf (_("%s and %s"), dayfmt
, hourfmt
);
3686 time
= g_strdup_printf (fmt
, days
, hours
);
3692 /* Translators: the format is "X days and X minutes" */
3693 fmt
= g_strdup_printf (_("%s and %s"), dayfmt
, minutefmt
);
3694 time
= g_strdup_printf (fmt
, days
, minutes
);
3697 time
= g_strdup_printf (dayfmt
, days
);
3703 /* Translators: the format is "X hours and X minutes" */
3704 fmt
= g_strdup_printf (_("%s and %s"), hourfmt
, minutefmt
);
3705 time
= g_strdup_printf (fmt
, hours
, minutes
);
3708 time
= g_strdup_printf (hourfmt
, hours
);
3712 time
= g_strdup_printf (minutefmt
, minutes
);
3716 size_str
= gnome_vfs_format_file_size_for_display (size
);
3718 if (size
> 0 && duration
> 0) {
3719 ret
= g_strdup_printf ("%s, %s, %s", songcount
, time
, size_str
);
3720 } else if (duration
> 0) {
3721 ret
= g_strdup_printf ("%s, %s", songcount
, time
);
3722 } else if (size
> 0) {
3723 ret
= g_strdup_printf ("%s, %s", songcount
, size_str
);
3725 ret
= g_strdup (songcount
);
3736 default_sync_metadata (RhythmDB
*db
,
3737 RhythmDBEntry
*entry
,
3742 GError
*local_error
= NULL
;
3744 uri
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
3745 rb_metadata_load (db
->priv
->metadata
,
3747 if (local_error
!= NULL
) {
3748 g_propagate_error (error
, local_error
);
3752 entry_to_rb_metadata (db
, entry
, db
->priv
->metadata
);
3754 rb_metadata_save (db
->priv
->metadata
, &local_error
);
3755 if (local_error
!= NULL
) {
3756 RhythmDBAction
*load_action
;
3758 /* reload the metadata, to revert the db changes */
3759 load_action
= g_new0 (RhythmDBAction
, 1);
3760 load_action
->type
= RHYTHMDB_ACTION_LOAD
;
3761 load_action
->uri
= rb_refstring_ref (entry
->location
);
3762 load_action
->entry_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3763 g_async_queue_push (db
->priv
->action_queue
, load_action
);
3765 g_propagate_error (error
, local_error
);
3770 * rhythmdb_entry_register_type:
3772 * @name: optional name for the entry type
3774 * Registers a new #RhythmDBEntryType. This should be called to create a new
3775 * entry type for non-permanent sources.
3777 * Returns: the new #RhythmDBEntryType.
3780 rhythmdb_entry_register_type (RhythmDB
*db
,
3783 RhythmDBEntryType type
;
3784 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3786 type
= g_new0 (RhythmDBEntryType_
, 1);
3787 type
->can_sync_metadata
= (RhythmDBEntryCanSyncFunc
)rb_false_function
;
3788 type
->sync_metadata
= default_sync_metadata
;
3790 type
->name
= g_strdup (name
);
3791 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
3792 g_hash_table_insert (db
->priv
->entry_type_map
, g_strdup (type
->name
), type
);
3793 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
3796 if (klass
->impl_entry_type_registered
)
3797 klass
->impl_entry_type_registered (db
, name
, type
);
3803 rhythmdb_entry_register_type_alias (RhythmDB
*db
,
3804 RhythmDBEntryType type
,
3807 char *dn
= g_strdup (name
);
3809 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
3810 g_hash_table_insert (db
->priv
->entry_type_map
, dn
, type
);
3811 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
3817 } RhythmDBEntryTypeForeachData
;
3820 rhythmdb_entry_type_foreach_cb (const char *name
,
3821 RhythmDBEntryType entry_type
,
3822 RhythmDBEntryTypeForeachData
*data
)
3825 if (strcmp (entry_type
->name
, name
))
3828 data
->func ((gpointer
) name
, entry_type
, data
->data
);
3832 * rhythmdb_entry_type_foreach:
3834 * @func: callback function to call for each registered entry type
3835 * @data: data to pass to the callback
3837 * Calls a function for each registered entry type.
3840 rhythmdb_entry_type_foreach (RhythmDB
*db
,
3844 RhythmDBEntryTypeForeachData d
;
3849 g_mutex_lock (db
->priv
->entry_type_mutex
);
3850 g_hash_table_foreach (db
->priv
->entry_type_map
,
3851 (GHFunc
) rhythmdb_entry_type_foreach_cb
,
3853 g_mutex_unlock (db
->priv
->entry_type_mutex
);
3857 * rhythmdb_entry_type_get_by_name:
3859 * @name: name of the type to look for
3861 * Locates a #RhythmDBEntryType by name. Returns
3862 * RHYTHMDB_ENTRY_TYPE_INVALID if no entry type
3863 * is registered with the specified name.
3865 * Returns: the #RhythmDBEntryType
3868 rhythmdb_entry_type_get_by_name (RhythmDB
*db
,
3873 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
3874 if (db
->priv
->entry_type_map
) {
3875 t
= g_hash_table_lookup (db
->priv
->entry_type_map
, name
);
3877 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
3880 return (RhythmDBEntryType
) t
;
3882 return RHYTHMDB_ENTRY_TYPE_INVALID
;
3886 song_can_sync_metadata (RhythmDB
*db
,
3887 RhythmDBEntry
*entry
,
3890 const char *mimetype
;
3892 mimetype
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_MIMETYPE
);
3893 return rb_metadata_can_save (db
->priv
->metadata
, mimetype
);
3897 podcast_get_playback_uri (RhythmDBEntry
*entry
,
3900 return rhythmdb_entry_dup_string (entry
, RHYTHMDB_PROP_MOUNTPOINT
);
3904 podcast_data_destroy (RhythmDBEntry
*entry
,
3907 RhythmDBPodcastFields
*podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
3908 rb_refstring_unref (podcast
->description
);
3909 rb_refstring_unref (podcast
->subtitle
);
3910 rb_refstring_unref (podcast
->summary
);
3911 rb_refstring_unref (podcast
->lang
);
3912 rb_refstring_unref (podcast
->copyright
);
3913 rb_refstring_unref (podcast
->image
);
3916 static RhythmDBEntryType song_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3917 static RhythmDBEntryType ignore_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3918 static RhythmDBEntryType import_error_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3921 static RhythmDBEntryType podcast_post_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3922 static RhythmDBEntryType podcast_feed_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3925 rhythmdb_register_core_entry_types (RhythmDB
*db
)
3928 song_type
= rhythmdb_entry_register_type (db
, "song");
3929 rhythmdb_entry_register_type_alias (db
, song_type
, "0");
3930 song_type
->save_to_disk
= TRUE
;
3931 song_type
->category
= RHYTHMDB_ENTRY_NORMAL
;
3932 song_type
->can_sync_metadata
= song_can_sync_metadata
;
3935 import_error_type
= rhythmdb_entry_register_type (db
, "import-error");
3936 import_error_type
->get_playback_uri
= (RhythmDBEntryStringFunc
)rb_null_function
;
3937 import_error_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
3940 ignore_type
= rhythmdb_entry_register_type (db
, "ignore");
3941 ignore_type
->save_to_disk
= TRUE
;
3942 ignore_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
3945 podcast_post_type
= rhythmdb_entry_register_type (db
, "podcast-post");
3946 podcast_post_type
->entry_type_data_size
= sizeof (RhythmDBPodcastFields
);
3947 podcast_post_type
->save_to_disk
= TRUE
;
3948 podcast_post_type
->category
= RHYTHMDB_ENTRY_NORMAL
;
3949 podcast_post_type
->pre_entry_destroy
= (RhythmDBEntryActionFunc
) podcast_data_destroy
;
3950 podcast_post_type
->get_playback_uri
= podcast_get_playback_uri
;
3953 podcast_feed_type
= rhythmdb_entry_register_type (db
, "podcast-feed");
3954 podcast_feed_type
->entry_type_data_size
= sizeof (RhythmDBPodcastFields
);
3955 podcast_feed_type
->save_to_disk
= TRUE
;
3956 podcast_feed_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
3957 podcast_feed_type
->pre_entry_destroy
= (RhythmDBEntryActionFunc
) podcast_data_destroy
;
3961 rhythmdb_entry_song_get_type (void)
3967 rhythmdb_entry_ignore_get_type (void)
3973 rhythmdb_entry_import_error_get_type (void)
3975 return import_error_type
;
3979 rhythmdb_entry_podcast_post_get_type (void)
3981 return podcast_post_type
;
3985 rhythmdb_entry_podcast_feed_get_type (void)
3987 return podcast_feed_type
;
3991 rhythmdb_entry_set_mount_point (RhythmDB
*db
,
3992 RhythmDBEntry
*entry
,
3993 const gchar
*realuri
)
3996 GValue value
= {0, };
3998 mount_point
= rb_uri_get_mount_point (realuri
);
3999 if (mount_point
!= NULL
) {
4000 g_value_init (&value
, G_TYPE_STRING
);
4001 g_value_set_string_take_ownership (&value
, mount_point
);
4002 rhythmdb_entry_set_internal (db
, entry
, FALSE
,
4003 RHYTHMDB_PROP_MOUNTPOINT
,
4005 g_value_unset (&value
);
4010 rhythmdb_entry_set_visibility (RhythmDB
*db
,
4011 RhythmDBEntry
*entry
,
4014 GValue old_val
= {0, };
4015 gboolean old_visible
;
4017 g_return_if_fail (RHYTHMDB_IS (db
));
4018 g_return_if_fail (entry
!= NULL
);
4020 g_value_init (&old_val
, G_TYPE_BOOLEAN
);
4022 rhythmdb_entry_get (db
, entry
, RHYTHMDB_PROP_HIDDEN
, &old_val
);
4023 old_visible
= !g_value_get_boolean (&old_val
);
4025 if ((old_visible
&& !visible
) || (!old_visible
&& visible
)) {
4026 GValue new_val
= {0, };
4028 g_value_init (&new_val
, G_TYPE_BOOLEAN
);
4029 g_value_set_boolean (&new_val
, !visible
);
4030 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
4031 RHYTHMDB_PROP_HIDDEN
, &new_val
);
4032 g_value_unset (&new_val
);
4034 g_value_unset (&old_val
);
4038 rhythmdb_idle_save (RhythmDB
*db
)
4040 if (db
->priv
->dirty
) {
4041 rb_debug ("database is dirty, doing regular save");
4042 rhythmdb_save_async (db
);
4049 rhythmdb_sync_library_location (RhythmDB
*db
)
4051 gboolean reload
= (db
->priv
->library_locations
!= NULL
);
4053 if (db
->priv
->library_location_notify_id
== 0) {
4054 db
->priv
->library_location_notify_id
=
4055 eel_gconf_notification_add (CONF_LIBRARY_LOCATION
,
4056 (GConfClientNotifyFunc
) library_location_changed_cb
,
4061 rb_debug ("ending monitor of old library directories");
4063 rhythmdb_stop_monitoring (db
);
4065 g_slist_foreach (db
->priv
->library_locations
, (GFunc
) g_free
, NULL
);
4066 g_slist_free (db
->priv
->library_locations
);
4067 db
->priv
->library_locations
= NULL
;
4070 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY
)) {
4071 db
->priv
->library_locations
= eel_gconf_get_string_list (CONF_LIBRARY_LOCATION
);
4073 rhythmdb_start_monitoring (db
);
4078 library_location_changed_cb (GConfClient
*client
,
4083 rhythmdb_sync_library_location (db
);
4087 rhythmdb_entry_dup_string (RhythmDBEntry
*entry
,
4088 RhythmDBPropType propid
)
4092 g_return_val_if_fail (entry
!= NULL
, NULL
);
4094 s
= rhythmdb_entry_get_string (entry
, propid
);
4096 return g_strdup (s
);
4103 rhythmdb_entry_get_string (RhythmDBEntry
*entry
,
4104 RhythmDBPropType propid
)
4106 RhythmDBPodcastFields
*podcast
= NULL
;
4108 g_return_val_if_fail (entry
!= NULL
, NULL
);
4109 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
4111 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
4112 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
4113 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
4115 rhythmdb_entry_sync_mirrored (entry
, propid
);
4118 case RHYTHMDB_PROP_TITLE
:
4119 return rb_refstring_get (entry
->title
);
4120 case RHYTHMDB_PROP_ALBUM
:
4121 return rb_refstring_get (entry
->album
);
4122 case RHYTHMDB_PROP_ARTIST
:
4123 return rb_refstring_get (entry
->artist
);
4124 case RHYTHMDB_PROP_GENRE
:
4125 return rb_refstring_get (entry
->genre
);
4126 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
4127 return rb_refstring_get (entry
->musicbrainz_trackid
);
4128 case RHYTHMDB_PROP_MIMETYPE
:
4129 return rb_refstring_get (entry
->mimetype
);
4130 case RHYTHMDB_PROP_TITLE_SORT_KEY
:
4131 return rb_refstring_get_sort_key (entry
->title
);
4132 case RHYTHMDB_PROP_ALBUM_SORT_KEY
:
4133 return rb_refstring_get_sort_key (entry
->album
);
4134 case RHYTHMDB_PROP_ARTIST_SORT_KEY
:
4135 return rb_refstring_get_sort_key (entry
->artist
);
4136 case RHYTHMDB_PROP_GENRE_SORT_KEY
:
4137 return rb_refstring_get_sort_key (entry
->genre
);
4138 case RHYTHMDB_PROP_TITLE_FOLDED
:
4139 return rb_refstring_get_folded (entry
->title
);
4140 case RHYTHMDB_PROP_ALBUM_FOLDED
:
4141 return rb_refstring_get_folded (entry
->album
);
4142 case RHYTHMDB_PROP_ARTIST_FOLDED
:
4143 return rb_refstring_get_folded (entry
->artist
);
4144 case RHYTHMDB_PROP_GENRE_FOLDED
:
4145 return rb_refstring_get_folded (entry
->genre
);
4146 case RHYTHMDB_PROP_LOCATION
:
4147 return rb_refstring_get (entry
->location
);
4148 case RHYTHMDB_PROP_MOUNTPOINT
:
4149 return rb_refstring_get (entry
->mountpoint
);
4150 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
4151 return rb_refstring_get (entry
->last_played_str
);
4152 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
4153 return rb_refstring_get (entry
->playback_error
);
4154 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
4155 return rb_refstring_get (entry
->first_seen_str
);
4156 case RHYTHMDB_PROP_LAST_SEEN_STR
:
4157 return rb_refstring_get (entry
->last_seen_str
);
4158 case RHYTHMDB_PROP_SEARCH_MATCH
:
4159 return NULL
; /* synthetic property */
4160 /* Podcast properties */
4161 case RHYTHMDB_PROP_DESCRIPTION
:
4163 return rb_refstring_get (podcast
->description
);
4166 case RHYTHMDB_PROP_SUBTITLE
:
4168 return rb_refstring_get (podcast
->subtitle
);
4171 case RHYTHMDB_PROP_SUMMARY
:
4173 return rb_refstring_get (podcast
->summary
);
4176 case RHYTHMDB_PROP_LANG
:
4178 return rb_refstring_get (podcast
->lang
);
4181 case RHYTHMDB_PROP_COPYRIGHT
:
4183 return rb_refstring_get (podcast
->copyright
);
4186 case RHYTHMDB_PROP_IMAGE
:
4188 return rb_refstring_get (podcast
->image
);
4193 g_assert_not_reached ();
4199 rhythmdb_entry_get_refstring (RhythmDBEntry
*entry
,
4200 RhythmDBPropType propid
)
4202 g_return_val_if_fail (entry
!= NULL
, NULL
);
4203 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
4205 rhythmdb_entry_sync_mirrored (entry
, propid
);
4208 case RHYTHMDB_PROP_TITLE
:
4209 return rb_refstring_ref (entry
->title
);
4210 case RHYTHMDB_PROP_ALBUM
:
4211 return rb_refstring_ref (entry
->album
);
4212 case RHYTHMDB_PROP_ARTIST
:
4213 return rb_refstring_ref (entry
->artist
);
4214 case RHYTHMDB_PROP_GENRE
:
4215 return rb_refstring_ref (entry
->genre
);
4216 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
4217 return rb_refstring_ref (entry
->musicbrainz_trackid
);
4218 case RHYTHMDB_PROP_MIMETYPE
:
4219 return rb_refstring_ref (entry
->mimetype
);
4220 case RHYTHMDB_PROP_MOUNTPOINT
:
4221 return rb_refstring_ref (entry
->mountpoint
);
4222 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
4223 return rb_refstring_ref (entry
->last_played_str
);
4224 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
4225 return rb_refstring_ref (entry
->first_seen_str
);
4226 case RHYTHMDB_PROP_LAST_SEEN_STR
:
4227 return rb_refstring_ref (entry
->last_seen_str
);
4228 case RHYTHMDB_PROP_LOCATION
:
4229 return rb_refstring_ref (entry
->location
);
4230 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
4231 return rb_refstring_ref (entry
->playback_error
);
4233 g_assert_not_reached ();
4239 rhythmdb_entry_get_boolean (RhythmDBEntry
*entry
,
4240 RhythmDBPropType propid
)
4242 g_return_val_if_fail (entry
!= NULL
, FALSE
);
4245 case RHYTHMDB_PROP_HIDDEN
:
4246 return ((entry
->flags
& RHYTHMDB_ENTRY_HIDDEN
) != 0);
4248 g_assert_not_reached ();
4254 rhythmdb_entry_get_uint64 (RhythmDBEntry
*entry
,
4255 RhythmDBPropType propid
)
4257 g_return_val_if_fail (entry
!= NULL
, 0);
4260 case RHYTHMDB_PROP_FILE_SIZE
:
4261 return entry
->file_size
;
4263 g_assert_not_reached ();
4269 rhythmdb_entry_get_entry_type (RhythmDBEntry
*entry
)
4271 g_return_val_if_fail (entry
!= NULL
, RHYTHMDB_ENTRY_TYPE_INVALID
);
4277 rhythmdb_entry_get_pointer (RhythmDBEntry
*entry
,
4278 RhythmDBPropType propid
)
4280 g_return_val_if_fail (entry
!= NULL
, NULL
);
4283 case RHYTHMDB_PROP_TYPE
:
4286 g_assert_not_reached ();
4292 rhythmdb_entry_get_ulong (RhythmDBEntry
*entry
,
4293 RhythmDBPropType propid
)
4295 RhythmDBPodcastFields
*podcast
= NULL
;
4297 g_return_val_if_fail (entry
!= NULL
, 0);
4299 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
4300 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
4301 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
4304 case RHYTHMDB_PROP_ENTRY_ID
:
4306 case RHYTHMDB_PROP_TRACK_NUMBER
:
4307 return entry
->tracknum
;
4308 case RHYTHMDB_PROP_DISC_NUMBER
:
4309 return entry
->discnum
;
4310 case RHYTHMDB_PROP_DURATION
:
4311 return entry
->duration
;
4312 case RHYTHMDB_PROP_MTIME
:
4313 return entry
->mtime
;
4314 case RHYTHMDB_PROP_FIRST_SEEN
:
4315 return entry
->first_seen
;
4316 case RHYTHMDB_PROP_LAST_SEEN
:
4317 return entry
->last_seen
;
4318 case RHYTHMDB_PROP_LAST_PLAYED
:
4319 return entry
->last_played
;
4320 case RHYTHMDB_PROP_PLAY_COUNT
:
4321 return entry
->play_count
;
4322 case RHYTHMDB_PROP_BITRATE
:
4323 return entry
->bitrate
;
4324 case RHYTHMDB_PROP_DATE
:
4325 if (g_date_valid (&entry
->date
))
4326 return g_date_get_julian (&entry
->date
);
4329 case RHYTHMDB_PROP_YEAR
:
4330 if (g_date_valid (&entry
->date
))
4331 return g_date_get_year (&entry
->date
);
4334 case RHYTHMDB_PROP_POST_TIME
:
4336 return podcast
->post_time
;
4339 case RHYTHMDB_PROP_STATUS
:
4341 return podcast
->status
;
4345 g_assert_not_reached ();
4351 rhythmdb_entry_get_double (RhythmDBEntry
*entry
,
4352 RhythmDBPropType propid
)
4354 g_return_val_if_fail (entry
!= NULL
, 0);
4357 case RHYTHMDB_PROP_TRACK_GAIN
:
4358 return entry
->track_gain
;
4359 case RHYTHMDB_PROP_TRACK_PEAK
:
4360 return entry
->track_peak
;
4361 case RHYTHMDB_PROP_ALBUM_GAIN
:
4362 return entry
->album_gain
;
4363 case RHYTHMDB_PROP_ALBUM_PEAK
:
4364 return entry
->album_peak
;
4365 case RHYTHMDB_PROP_RATING
:
4366 return entry
->rating
;
4368 g_assert_not_reached ();
4374 rhythmdb_entry_get_playback_uri (RhythmDBEntry
*entry
)
4376 RhythmDBEntryType type
;
4378 g_return_val_if_fail (entry
!= NULL
, NULL
);
4380 type
= rhythmdb_entry_get_entry_type (entry
);
4381 if (type
->get_playback_uri
)
4382 return (type
->get_playback_uri
) (entry
, type
->get_playback_uri_data
);
4384 return rhythmdb_entry_dup_string (entry
, RHYTHMDB_PROP_LOCATION
);
4388 rhythmdb_get_property_type (RhythmDB
*db
,
4391 g_assert (property_id
>= 0 && property_id
< RHYTHMDB_NUM_PROPERTIES
);
4392 return rhythmdb_property_type_map
[property_id
];
4396 rhythmdb_entry_get_type (void)
4398 static GType type
= 0;
4400 if (G_UNLIKELY (type
== 0)) {
4401 type
= g_boxed_type_register_static ("RhythmDBEntry",
4402 (GBoxedCopyFunc
)rhythmdb_entry_ref
,
4403 (GBoxedFreeFunc
)rhythmdb_entry_unref
);
4410 rhythmdb_entry_type_get_type (void)
4412 static GType type
= 0;
4414 if (G_UNLIKELY (type
== 0)) {
4415 type
= g_boxed_type_register_static ("RhythmDBEntryType",
4416 (GBoxedCopyFunc
)rb_copy_function
,
4417 (GBoxedFreeFunc
)rb_null_function
);