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"
50 #define RB_PARSE_NICK_START (xmlChar *) "["
51 #define RB_PARSE_NICK_END (xmlChar *) "]"
53 GType rhythmdb_property_type_map
[RHYTHMDB_NUM_PROPERTIES
];
55 #define RHYTHMDB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE, RhythmDBPrivate))
56 G_DEFINE_ABSTRACT_TYPE(RhythmDB
, rhythmdb
, G_TYPE_OBJECT
)
63 RhythmDBQueryResults
*results
;
65 } RhythmDBQueryThreadData
;
71 RhythmDBEntryType type
;
72 } RhythmDBAddThreadData
;
82 RhythmDBEntryType entry_type
;
85 static void rhythmdb_finalize (GObject
*object
);
86 static void rhythmdb_set_property (GObject
*object
,
90 static void rhythmdb_get_property (GObject
*object
,
94 static void rhythmdb_thread_create (RhythmDB
*db
,
98 static void rhythmdb_read_enter (RhythmDB
*db
);
99 static void rhythmdb_read_leave (RhythmDB
*db
);
100 static gboolean
rhythmdb_idle_poll_events (RhythmDB
*db
);
101 static gpointer
add_thread_main (RhythmDBAddThreadData
*data
);
102 static gpointer
action_thread_main (RhythmDB
*db
);
103 static gpointer
query_thread_main (RhythmDBQueryThreadData
*data
);
104 static void rhythmdb_entry_set_mount_point (RhythmDB
*db
,
105 RhythmDBEntry
*entry
,
106 const gchar
*realuri
);
108 static gboolean
free_entry_changes (RhythmDBEntry
*entry
,
111 static gboolean
rhythmdb_idle_save (RhythmDB
*db
);
112 static void library_location_changed_cb (GConfClient
*client
,
116 static void rhythmdb_sync_library_location (RhythmDB
*db
);
117 static void rhythmdb_entry_sync_mirrored (RhythmDBEntry
*entry
,
119 static void rhythmdb_register_core_entry_types (RhythmDB
*db
);
120 static gboolean
rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint
*ihint
,
122 const GValue
*handler_return
,
138 ENTRY_EXTRA_METADATA_REQUEST
,
139 ENTRY_EXTRA_METADATA_NOTIFY
,
140 ENTRY_EXTRA_METADATA_GATHER
,
148 static guint rhythmdb_signals
[LAST_SIGNAL
] = { 0 };
151 rhythmdb_class_init (RhythmDBClass
*klass
)
153 GObjectClass
*object_class
= G_OBJECT_CLASS (klass
);
155 object_class
->finalize
= rhythmdb_finalize
;
157 object_class
->set_property
= rhythmdb_set_property
;
158 object_class
->get_property
= rhythmdb_get_property
;
160 g_object_class_install_property (object_class
,
162 g_param_spec_string ("name",
168 g_object_class_install_property (object_class
,
170 g_param_spec_boolean ("dry-run",
172 "Whether or not changes should be saved",
175 g_object_class_install_property (object_class
,
177 g_param_spec_boolean ("no-update",
179 "Whether or not to update the database",
182 rhythmdb_signals
[ENTRY_ADDED
] =
183 g_signal_new ("entry_added",
186 G_STRUCT_OFFSET (RhythmDBClass
, entry_added
),
188 g_cclosure_marshal_VOID__BOXED
,
190 1, RHYTHMDB_TYPE_ENTRY
);
192 rhythmdb_signals
[ENTRY_DELETED
] =
193 g_signal_new ("entry_deleted",
196 G_STRUCT_OFFSET (RhythmDBClass
, entry_deleted
),
198 g_cclosure_marshal_VOID__BOXED
,
200 1, RHYTHMDB_TYPE_ENTRY
);
202 rhythmdb_signals
[ENTRY_CHANGED
] =
203 g_signal_new ("entry_changed",
206 G_STRUCT_OFFSET (RhythmDBClass
, entry_changed
),
208 rb_marshal_VOID__BOXED_POINTER
,
210 RHYTHMDB_TYPE_ENTRY
, G_TYPE_POINTER
);
212 rhythmdb_signals
[ENTRY_EXTRA_METADATA_REQUEST
] =
213 g_signal_new ("entry_extra_metadata_request",
214 G_OBJECT_CLASS_TYPE (object_class
),
215 G_SIGNAL_RUN_LAST
| G_SIGNAL_DETAILED
,
216 G_STRUCT_OFFSET (RhythmDBClass
, entry_extra_metadata_request
),
217 rhythmdb_entry_extra_metadata_accumulator
, NULL
,
218 rb_marshal_BOXED__BOXED
,
220 RHYTHMDB_TYPE_ENTRY
);
222 rhythmdb_signals
[ENTRY_EXTRA_METADATA_NOTIFY
] =
223 g_signal_new ("entry_extra_metadata_notify",
224 G_OBJECT_CLASS_TYPE (object_class
),
225 G_SIGNAL_RUN_LAST
| G_SIGNAL_DETAILED
,
226 G_STRUCT_OFFSET (RhythmDBClass
, entry_extra_metadata_notify
),
228 rb_marshal_VOID__BOXED_STRING_BOXED
,
230 RHYTHMDB_TYPE_ENTRY
, G_TYPE_STRING
, G_TYPE_VALUE
);
232 rhythmdb_signals
[ENTRY_EXTRA_METADATA_GATHER
] =
233 g_signal_new ("entry_extra_metadata_gather",
234 G_OBJECT_CLASS_TYPE (object_class
),
236 G_STRUCT_OFFSET (RhythmDBClass
, entry_extra_metadata_gather
),
238 rb_marshal_VOID__BOXED_BOXED
,
240 RHYTHMDB_TYPE_ENTRY
, G_TYPE_HASH_TABLE
);
242 rhythmdb_signals
[LOAD_COMPLETE
] =
243 g_signal_new ("load_complete",
246 G_STRUCT_OFFSET (RhythmDBClass
, load_complete
),
248 g_cclosure_marshal_VOID__VOID
,
252 rhythmdb_signals
[SAVE_COMPLETE
] =
253 g_signal_new ("save_complete",
256 G_STRUCT_OFFSET (RhythmDBClass
, save_complete
),
258 g_cclosure_marshal_VOID__VOID
,
262 rhythmdb_signals
[SAVE_ERROR
] =
263 g_signal_new ("save-error",
264 G_OBJECT_CLASS_TYPE (object_class
),
266 G_STRUCT_OFFSET (RhythmDBClass
, save_error
),
268 rb_marshal_VOID__STRING_POINTER
,
274 rhythmdb_signals
[READ_ONLY
] =
275 g_signal_new ("read-only",
276 G_OBJECT_CLASS_TYPE (object_class
),
278 G_STRUCT_OFFSET (RhythmDBClass
, read_only
),
280 g_cclosure_marshal_VOID__BOOLEAN
,
285 g_type_class_add_private (klass
, sizeof (RhythmDBPrivate
));
289 metadata_field_from_prop (RhythmDBPropType prop
,
290 RBMetaDataField
*field
)
293 case RHYTHMDB_PROP_TITLE
:
294 *field
= RB_METADATA_FIELD_TITLE
;
296 case RHYTHMDB_PROP_ARTIST
:
297 *field
= RB_METADATA_FIELD_ARTIST
;
299 case RHYTHMDB_PROP_ALBUM
:
300 *field
= RB_METADATA_FIELD_ALBUM
;
302 case RHYTHMDB_PROP_GENRE
:
303 *field
= RB_METADATA_FIELD_GENRE
;
305 case RHYTHMDB_PROP_TRACK_NUMBER
:
306 *field
= RB_METADATA_FIELD_TRACK_NUMBER
;
308 case RHYTHMDB_PROP_DISC_NUMBER
:
309 *field
= RB_METADATA_FIELD_DISC_NUMBER
;
311 case RHYTHMDB_PROP_DATE
:
312 *field
= RB_METADATA_FIELD_DATE
;
314 case RHYTHMDB_PROP_TRACK_GAIN
:
315 *field
= RB_METADATA_FIELD_TRACK_GAIN
;
317 case RHYTHMDB_PROP_TRACK_PEAK
:
318 *field
= RB_METADATA_FIELD_TRACK_PEAK
;
320 case RHYTHMDB_PROP_ALBUM_GAIN
:
321 *field
= RB_METADATA_FIELD_ALBUM_GAIN
;
323 case RHYTHMDB_PROP_ALBUM_PEAK
:
324 *field
= RB_METADATA_FIELD_ALBUM_PEAK
;
326 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
327 *field
= RB_METADATA_FIELD_MUSICBRAINZ_TRACKID
;
335 extract_gtype_from_enum_entry (RhythmDB
*db
,
341 RBMetaDataField field
;
345 value
= g_enum_get_value (klass
, i
);
347 typename
= strstr (value
->value_nick
, "(");
348 g_assert (typename
!= NULL
);
350 typename_end
= strstr (typename
, ")");
351 g_assert (typename_end
);
354 typename
= g_strndup (typename
, typename_end
-typename
);
355 ret
= g_type_from_name (typename
);
358 /* Check to see whether this is a property that maps to
359 a RBMetaData property. */
360 if (metadata_field_from_prop (value
->value
, &field
))
361 g_assert (ret
== rb_metadata_get_field_type (field
));
366 extract_nice_name_from_enum_entry (RhythmDB
*db
,
373 const xmlChar
*name_end
;
375 value
= g_enum_get_value (klass
, i
);
376 nick
= BAD_CAST value
->value_nick
;
378 name
= xmlStrstr (nick
, RB_PARSE_NICK_START
);
379 g_return_val_if_fail (name
!= NULL
, NULL
);
380 name_end
= xmlStrstr (name
, RB_PARSE_NICK_END
);
383 return xmlStrndup (name
, name_end
- name
);
387 rhythmdb_init (RhythmDB
*db
)
390 GEnumClass
*prop_class
;
392 db
->priv
= RHYTHMDB_GET_PRIVATE (db
);
394 db
->priv
->action_queue
= g_async_queue_new ();
395 db
->priv
->event_queue
= g_async_queue_new ();
396 db
->priv
->restored_queue
= g_async_queue_new ();
398 db
->priv
->query_thread_pool
= g_thread_pool_new ((GFunc
)query_thread_main
,
401 /* Limit this pool to 3 threads. They'll each be thrashing the disk,
402 * so parallelism is limited.
404 db
->priv
->add_thread_pool
= g_thread_pool_new ((GFunc
)add_thread_main
,
408 db
->priv
->metadata
= rb_metadata_new ();
410 prop_class
= g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE
);
412 g_assert (prop_class
->n_values
== RHYTHMDB_NUM_PROPERTIES
);
413 db
->priv
->column_xml_names
= g_new0 (xmlChar
*, RHYTHMDB_NUM_PROPERTIES
);
415 /* Now, extract the GType and XML tag of each column from the
416 * enum descriptions, and cache that for later use. */
417 for (i
= 0; i
< prop_class
->n_values
; i
++) {
418 rhythmdb_property_type_map
[i
] = extract_gtype_from_enum_entry (db
, prop_class
, i
);
419 g_assert (rhythmdb_property_type_map
[i
] != G_TYPE_INVALID
);
421 db
->priv
->column_xml_names
[i
] = extract_nice_name_from_enum_entry (db
, prop_class
, i
);
422 g_assert (db
->priv
->column_xml_names
[i
]);
425 g_type_class_unref (prop_class
);
427 db
->priv
->propname_map
= g_hash_table_new (g_str_hash
, g_str_equal
);
429 for (i
= 0; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
430 const xmlChar
*name
= rhythmdb_nice_elt_name_from_propid (db
, i
);
431 g_hash_table_insert (db
->priv
->propname_map
, (gpointer
) name
, GINT_TO_POINTER (i
));
434 db
->priv
->entry_type_map
= g_hash_table_new_full (g_str_hash
, g_str_equal
, g_free
, NULL
);
435 db
->priv
->entry_type_map_mutex
= g_mutex_new ();
436 db
->priv
->entry_type_mutex
= g_mutex_new ();
437 rhythmdb_register_core_entry_types (db
);
439 db
->priv
->stat_events
= g_hash_table_new_full (gnome_vfs_uri_hash
,
440 (GEqualFunc
) gnome_vfs_uri_equal
,
441 (GDestroyNotify
) gnome_vfs_uri_unref
,
443 db
->priv
->stat_mutex
= g_mutex_new ();
445 db
->priv
->change_mutex
= g_mutex_new ();
447 db
->priv
->changed_entries
= g_hash_table_new_full (NULL
,
449 (GDestroyNotify
) rhythmdb_entry_unref
,
451 db
->priv
->added_entries
= g_hash_table_new_full (NULL
,
453 (GDestroyNotify
) rhythmdb_entry_unref
,
455 db
->priv
->deleted_entries
= g_hash_table_new_full (NULL
,
457 (GDestroyNotify
) rhythmdb_entry_unref
,
460 db
->priv
->event_poll_id
= g_idle_add ((GSourceFunc
) rhythmdb_idle_poll_events
, db
);
462 db
->priv
->saving_condition
= g_cond_new ();
463 db
->priv
->saving_mutex
= g_mutex_new ();
465 db
->priv
->can_save
= TRUE
;
466 db
->priv
->exiting
= FALSE
;
467 db
->priv
->saving
= FALSE
;
468 db
->priv
->dirty
= FALSE
;
470 db
->priv
->empty_string
= rb_refstring_new ("");
471 db
->priv
->octet_stream_str
= rb_refstring_new ("application/octet-stream");
473 db
->priv
->next_entry_id
= 1;
475 rhythmdb_init_monitoring (db
);
479 make_access_failed_error (const char *uri
, GnomeVFSResult result
)
485 /* make sure the URI we put in the error message is valid utf8 */
486 unescaped
= gnome_vfs_unescape_string_for_display (uri
);
487 utf8ised
= rb_make_valid_utf8 (unescaped
, '?');
489 error
= g_error_new (RHYTHMDB_ERROR
,
490 RHYTHMDB_ERROR_ACCESS_FAILED
,
491 _("Couldn't access %s: %s"),
493 gnome_vfs_result_to_string (result
));
494 rb_debug ("got error on %s: %s", utf8ised
, error
->message
);
501 rhythmdb_execute_multi_stat_info_cb (GnomeVFSAsyncHandle
*handle
,
503 /* GnomeVFSGetFileInfoResult* items */
506 g_mutex_lock (db
->priv
->stat_mutex
);
507 while (results
!= NULL
) {
508 GnomeVFSGetFileInfoResult
*info_result
= results
->data
;
509 RhythmDBEvent
*event
;
511 event
= g_hash_table_lookup (db
->priv
->stat_events
, info_result
->uri
);
514 uri_string
= gnome_vfs_uri_to_string (info_result
->uri
, GNOME_VFS_URI_HIDE_NONE
);
515 rb_debug ("ignoring unexpected uri in gnome_vfs_async_get_file_info response: %s",
518 results
= results
->next
;
521 g_hash_table_remove (db
->priv
->stat_events
, info_result
->uri
);
523 if (info_result
->result
== GNOME_VFS_OK
) {
524 event
->vfsinfo
= gnome_vfs_file_info_dup (info_result
->file_info
);
526 event
->error
= make_access_failed_error (rb_refstring_get (event
->real_uri
),
527 info_result
->result
);
528 event
->vfsinfo
= NULL
;
530 g_async_queue_push (db
->priv
->event_queue
, event
);
532 results
= results
->next
;
534 db
->priv
->stat_handle
= NULL
;
535 g_mutex_unlock (db
->priv
->stat_mutex
);
539 rhythmdb_start_action_thread (RhythmDB
*db
)
541 g_mutex_lock (db
->priv
->stat_mutex
);
542 db
->priv
->action_thread_running
= TRUE
;
543 rhythmdb_thread_create (db
, NULL
, (GThreadFunc
) action_thread_main
, db
);
545 if (db
->priv
->stat_list
!= NULL
) {
546 gnome_vfs_async_get_file_info (&db
->priv
->stat_handle
, db
->priv
->stat_list
,
547 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
,
548 GNOME_VFS_PRIORITY_MIN
,
549 (GnomeVFSAsyncGetFileInfoCallback
) rhythmdb_execute_multi_stat_info_cb
,
551 g_list_free (db
->priv
->stat_list
);
552 db
->priv
->stat_list
= NULL
;
555 g_mutex_unlock (db
->priv
->stat_mutex
);
559 rhythmdb_action_free (RhythmDB
*db
,
560 RhythmDBAction
*action
)
562 rb_refstring_unref (action
->uri
);
567 rhythmdb_event_free (RhythmDB
*db
,
568 RhythmDBEvent
*result
)
570 switch (result
->type
) {
571 case RHYTHMDB_EVENT_THREAD_EXITED
:
573 g_assert (g_atomic_int_dec_and_test (&db
->priv
->outstanding_threads
) >= 0);
574 g_async_queue_unref (db
->priv
->action_queue
);
575 g_async_queue_unref (db
->priv
->event_queue
);
577 case RHYTHMDB_EVENT_STAT
:
578 case RHYTHMDB_EVENT_METADATA_LOAD
:
579 case RHYTHMDB_EVENT_DB_LOAD
:
580 case RHYTHMDB_EVENT_DB_SAVED
:
581 case RHYTHMDB_EVENT_QUERY_COMPLETE
:
582 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED
:
583 case RHYTHMDB_EVENT_FILE_DELETED
:
585 case RHYTHMDB_EVENT_ENTRY_SET
:
586 g_value_unset (&result
->change
.new);
590 g_error_free (result
->error
);
591 rb_refstring_unref (result
->uri
);
592 rb_refstring_unref (result
->real_uri
);
594 gnome_vfs_file_info_unref (result
->vfsinfo
);
595 if (result
->metadata
)
596 g_object_unref (result
->metadata
);
598 g_object_unref (result
->results
);
600 gnome_vfs_async_cancel (result
->handle
);
601 if (result
->entry
!= NULL
) {
602 rhythmdb_entry_unref (result
->entry
);
610 * Ceases all #RhythmDB operations, including stopping all directory monitoring, and
611 * removing all actions and events currently queued.
614 _shutdown_foreach_swapped (RhythmDBEvent
*event
, RhythmDB
*db
)
616 rhythmdb_event_free (db
, event
);
620 rhythmdb_shutdown (RhythmDB
*db
)
622 RhythmDBEvent
*result
;
623 RhythmDBAction
*action
;
625 g_return_if_fail (RHYTHMDB_IS (db
));
627 db
->priv
->exiting
= TRUE
;
629 eel_gconf_notification_remove (db
->priv
->library_location_notify_id
);
630 g_slist_foreach (db
->priv
->library_locations
, (GFunc
) g_free
, NULL
);
631 g_slist_free (db
->priv
->library_locations
);
632 db
->priv
->library_locations
= NULL
;
634 /* abort all async vfs operations */
635 g_mutex_lock (db
->priv
->stat_mutex
);
636 if (db
->priv
->stat_handle
) {
637 gnome_vfs_async_cancel (db
->priv
->stat_handle
);
638 db
->priv
->stat_handle
= NULL
;
640 g_list_foreach (db
->priv
->outstanding_stats
, (GFunc
)_shutdown_foreach_swapped
, db
);
641 g_list_free (db
->priv
->outstanding_stats
);
642 db
->priv
->outstanding_stats
= NULL
;
643 g_mutex_unlock (db
->priv
->stat_mutex
);
645 while ((action
= g_async_queue_try_pop (db
->priv
->action_queue
)) != NULL
) {
646 rhythmdb_action_free (db
, action
);
649 rb_debug ("%d outstanding threads", g_atomic_int_get (&db
->priv
->outstanding_threads
));
650 while (g_atomic_int_get (&db
->priv
->outstanding_threads
) > 0) {
651 result
= g_async_queue_pop (db
->priv
->event_queue
);
652 rhythmdb_event_free (db
, result
);
656 while ((result
= g_async_queue_try_pop (db
->priv
->event_queue
)) != NULL
)
657 rhythmdb_event_free (db
, result
);
661 rhythmdb_finalize (GObject
*object
)
666 g_return_if_fail (object
!= NULL
);
667 g_return_if_fail (RHYTHMDB_IS (object
));
669 db
= RHYTHMDB (object
);
671 g_return_if_fail (db
->priv
!= NULL
);
673 rhythmdb_finalize_monitoring (db
);
675 g_source_remove (db
->priv
->event_poll_id
);
676 if (db
->priv
->save_timeout_id
> 0)
677 g_source_remove (db
->priv
->save_timeout_id
);
678 if (db
->priv
->emit_entry_signals_id
> 0) {
679 g_source_remove (db
->priv
->emit_entry_signals_id
);
680 g_list_foreach (db
->priv
->added_entries_to_emit
, (GFunc
)rhythmdb_entry_unref
, NULL
);
681 g_list_foreach (db
->priv
->deleted_entries_to_emit
, (GFunc
)rhythmdb_entry_unref
, NULL
);
684 g_thread_pool_free (db
->priv
->query_thread_pool
, FALSE
, TRUE
);
685 g_thread_pool_free (db
->priv
->add_thread_pool
, FALSE
, TRUE
);
686 g_async_queue_unref (db
->priv
->action_queue
);
687 g_async_queue_unref (db
->priv
->event_queue
);
688 g_async_queue_unref (db
->priv
->restored_queue
);
690 g_mutex_free (db
->priv
->saving_mutex
);
691 g_cond_free (db
->priv
->saving_condition
);
693 g_hash_table_destroy (db
->priv
->stat_events
);
694 g_mutex_free (db
->priv
->stat_mutex
);
696 g_mutex_free (db
->priv
->change_mutex
);
698 g_hash_table_destroy (db
->priv
->propname_map
);
700 g_hash_table_destroy (db
->priv
->added_entries
);
701 g_hash_table_destroy (db
->priv
->deleted_entries
);
702 g_hash_table_destroy (db
->priv
->changed_entries
);
704 rb_refstring_unref (db
->priv
->empty_string
);
705 rb_refstring_unref (db
->priv
->octet_stream_str
);
707 g_hash_table_destroy (db
->priv
->entry_type_map
);
708 g_mutex_free (db
->priv
->entry_type_map_mutex
);
709 g_mutex_free (db
->priv
->entry_type_mutex
);
711 g_object_unref (db
->priv
->metadata
);
713 for (i
= 0; i
< RHYTHMDB_NUM_PROPERTIES
; i
++) {
714 xmlFree (db
->priv
->column_xml_names
[i
]);
716 g_free (db
->priv
->column_xml_names
);
718 g_free (db
->priv
->name
);
720 G_OBJECT_CLASS (rhythmdb_parent_class
)->finalize (object
);
724 rhythmdb_set_property (GObject
*object
,
729 RhythmDB
*source
= RHYTHMDB (object
);
733 source
->priv
->name
= g_strdup (g_value_get_string (value
));
736 source
->priv
->dry_run
= g_value_get_boolean (value
);
739 source
->priv
->no_update
= g_value_get_boolean (value
);
742 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
748 rhythmdb_get_property (GObject
*object
,
753 RhythmDB
*source
= RHYTHMDB (object
);
757 g_value_set_string (value
, source
->priv
->name
);
760 g_value_set_boolean (value
, source
->priv
->dry_run
);
763 g_value_set_boolean (value
, source
->priv
->no_update
);
766 G_OBJECT_WARN_INVALID_PROPERTY_ID (object
, prop_id
, pspec
);
772 rhythmdb_thread_create (RhythmDB
*db
,
778 g_atomic_int_inc (&db
->priv
->outstanding_threads
);
779 g_async_queue_ref (db
->priv
->action_queue
);
780 g_async_queue_ref (db
->priv
->event_queue
);
783 g_thread_pool_push (pool
, data
, NULL
);
785 g_thread_create ((GThreadFunc
) func
, data
, FALSE
, NULL
);
789 rhythmdb_get_readonly (RhythmDB
*db
)
791 return (g_atomic_int_get (&db
->priv
->read_counter
) > 0);
795 rhythmdb_read_enter (RhythmDB
*db
)
798 g_return_if_fail (g_atomic_int_get (&db
->priv
->read_counter
) >= 0);
799 g_assert (rb_is_main_thread ());
801 count
= g_atomic_int_exchange_and_add (&db
->priv
->read_counter
, 1);
802 rb_debug ("counter: %d", count
+1);
804 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[READ_ONLY
],
809 rhythmdb_read_leave (RhythmDB
*db
)
812 g_return_if_fail (rhythmdb_get_readonly (db
));
813 g_assert (rb_is_main_thread ());
815 count
= g_atomic_int_exchange_and_add (&db
->priv
->read_counter
, -1);
816 rb_debug ("counter: %d", count
-1);
818 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[READ_ONLY
],
823 free_entry_changes (RhythmDBEntry
*entry
,
828 for (t
= changes
; t
; t
= t
->next
) {
829 RhythmDBEntryChange
*change
= t
->data
;
830 g_value_unset (&change
->old
);
831 g_value_unset (&change
->new);
834 g_slist_free (changes
);
840 emit_entry_changed (RhythmDBEntry
*entry
,
844 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_CHANGED
], 0, entry
, changes
);
848 sync_entry_changed (RhythmDBEntry
*entry
,
854 for (t
= changes
; t
; t
= t
->next
) {
855 RBMetaDataField field
;
856 RhythmDBEntryChange
*change
= t
->data
;
858 if (metadata_field_from_prop (change
->prop
, &field
)) {
859 RhythmDBAction
*action
;
861 if (!rhythmdb_entry_is_editable (db
, entry
)) {
862 g_warning ("trying to sync properties of non-editable file");
866 action
= g_new0 (RhythmDBAction
, 1);
867 action
->type
= RHYTHMDB_ACTION_SYNC
;
868 action
->uri
= rb_refstring_ref (entry
->location
);
869 g_async_queue_push (db
->priv
->action_queue
, action
);
876 rhythmdb_emit_entry_signals_idle (RhythmDB
*db
)
878 GList
*added_entries
;
879 GList
*deleted_entries
;
882 /* get lists of entries to emit, reset source id value */
883 g_mutex_lock (db
->priv
->change_mutex
);
885 added_entries
= db
->priv
->added_entries_to_emit
;
886 db
->priv
->added_entries_to_emit
= NULL
;
888 deleted_entries
= db
->priv
->deleted_entries_to_emit
;
889 db
->priv
->deleted_entries_to_emit
= NULL
;
891 db
->priv
->emit_entry_signals_id
= 0;
893 g_mutex_unlock (db
->priv
->change_mutex
);
895 /* emit added entries */
896 for (l
= added_entries
; l
; l
= g_list_next (l
)) {
897 RhythmDBEntry
*entry
= (RhythmDBEntry
*)l
->data
;
898 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_ADDED
], 0, entry
);
899 rhythmdb_entry_unref (entry
);
902 /* emit deleted entries */
903 for (l
= deleted_entries
; l
; l
= g_list_next (l
)) {
904 RhythmDBEntry
*entry
= (RhythmDBEntry
*)l
->data
;
905 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_DELETED
], 0, entry
);
906 rhythmdb_entry_unref (entry
);
909 g_list_free (added_entries
);
910 g_list_free (deleted_entries
);
915 process_added_entries_cb (RhythmDBEntry
*entry
,
919 if (thread
!= g_thread_self ())
922 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_SONG
) {
925 uri
= rhythmdb_entry_get_string (entry
,
926 RHYTHMDB_PROP_LOCATION
);
928 #ifdef HAVE_GSTREAMER_0_8
929 /* always start remote files hidden*/
930 if (!g_str_has_prefix (uri
, "file://")) {
931 entry
->flags
|= RHYTHMDB_ENTRY_HIDDEN
;
935 queue_stat_uri (uri
, db
, RHYTHMDB_ENTRY_TYPE_INVALID
);
938 g_assert ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) == 0);
939 entry
->flags
|= RHYTHMDB_ENTRY_INSERTED
;
941 rhythmdb_entry_ref (entry
);
942 db
->priv
->added_entries_to_emit
= g_list_prepend (db
->priv
->added_entries_to_emit
, entry
);
948 process_deleted_entries_cb (RhythmDBEntry
*entry
,
952 if (thread
!= g_thread_self ())
955 rhythmdb_entry_ref (entry
);
956 db
->priv
->deleted_entries_to_emit
= g_list_prepend (db
->priv
->deleted_entries_to_emit
, entry
);
962 rhythmdb_commit_internal (RhythmDB
*db
,
963 gboolean sync_changes
,
966 g_mutex_lock (db
->priv
->change_mutex
);
968 g_hash_table_foreach (db
->priv
->changed_entries
, (GHFunc
) emit_entry_changed
, db
);
970 g_hash_table_foreach (db
->priv
->changed_entries
, (GHFunc
) sync_entry_changed
, db
);
971 g_hash_table_foreach_remove (db
->priv
->changed_entries
, (GHRFunc
) free_entry_changes
, db
);
973 /* update the lists of entry added/deleted signals to emit */
974 g_hash_table_foreach_remove (db
->priv
->added_entries
, (GHRFunc
) process_added_entries_cb
, db
);
975 g_hash_table_foreach_remove (db
->priv
->deleted_entries
, (GHRFunc
) process_deleted_entries_cb
, db
);
977 /* if there are some signals to emit, add a new idle callback if required */
978 if (db
->priv
->added_entries_to_emit
|| db
->priv
->deleted_entries_to_emit
) {
979 if (db
->priv
->emit_entry_signals_id
== 0)
980 db
->priv
->emit_entry_signals_id
= g_idle_add ((GSourceFunc
) rhythmdb_emit_entry_signals_idle
, db
);
983 g_mutex_unlock (db
->priv
->change_mutex
);
990 } RhythmDBTimeoutCommitData
;
993 timeout_rhythmdb_commit (RhythmDBTimeoutCommitData
*data
)
995 rhythmdb_commit_internal (data
->db
, data
->sync
, data
->thread
);
996 g_object_unref (data
->db
);
1002 rhythmdb_add_timeout_commit (RhythmDB
*db
,
1005 RhythmDBTimeoutCommitData
*data
;
1007 g_assert (rb_is_main_thread ());
1009 data
= g_new0 (RhythmDBTimeoutCommitData
, 1);
1010 data
->db
= g_object_ref (db
);
1012 data
->thread
= g_thread_self ();
1013 g_timeout_add (100, (GSourceFunc
)timeout_rhythmdb_commit
, data
);
1020 * Apply all database changes, and send notification of changes and new entries.
1021 * This needs to be called after any changes have been made, such as a group of
1022 * rhythmdb_entry_set() calls, or a new entry has been added.
1025 rhythmdb_commit (RhythmDB
*db
)
1027 rhythmdb_commit_internal (db
, TRUE
, g_thread_self ());
1031 rhythmdb_error_quark (void)
1033 static GQuark quark
;
1035 quark
= g_quark_from_static_string ("rhythmdb_error");
1040 /* structure alignment magic, stolen from glib */
1041 #define STRUCT_ALIGNMENT (2 * sizeof (gsize))
1042 #define ALIGN_STRUCT(offset) \
1043 ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
1046 * rhythmdb_entry_allocate:
1048 * @type: type of entry to allocate
1050 * Allocate and initialise memory for a new #RhythmDBEntry of the type @type.
1051 * The entry's initial properties needs to be set with rhythmdb_entry_set (),
1052 * the entry added to the database with rhythmdb_entry_insert(), and committed with
1053 * rhythmdb_commit().
1055 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
1057 * Returns: the newly allocated #RhythmDBEntry
1060 rhythmdb_entry_allocate (RhythmDB
*db
,
1061 RhythmDBEntryType type
)
1064 gsize size
= sizeof (RhythmDBEntry
);
1066 if (type
->entry_type_data_size
) {
1067 size
= ALIGN_STRUCT (sizeof (RhythmDBEntry
)) + type
->entry_type_data_size
;
1069 ret
= g_malloc0 (size
);
1070 ret
->id
= (guint
) g_atomic_int_exchange_and_add (&db
->priv
->next_entry_id
, 1);
1073 ret
->title
= rb_refstring_ref (db
->priv
->empty_string
);
1074 ret
->genre
= rb_refstring_ref (db
->priv
->empty_string
);
1075 ret
->artist
= rb_refstring_ref (db
->priv
->empty_string
);
1076 ret
->album
= rb_refstring_ref (db
->priv
->empty_string
);
1077 ret
->musicbrainz_trackid
= rb_refstring_ref (db
->priv
->empty_string
);
1078 ret
->mimetype
= rb_refstring_ref (db
->priv
->octet_stream_str
);
1080 ret
->flags
|= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
|
1081 RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
|
1082 RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
1084 /* The refcount is initially 0, we want to set it to 1 */
1087 if (type
->post_entry_create
)
1088 (type
->post_entry_create
)(ret
, type
->post_entry_create_data
);
1094 * rhythmdb_entry_get_type_data:
1095 * @entry: a #RhythmDBEntry
1096 * @expected_size: expected size of the type-specific data.
1098 * Returns a pointer to the entry's type-specific data, checking that
1099 * the size of the data structure matches what is expected.
1100 * Callers should use the RHYTHMDB_ENTRY_GET_TYPE_DATA macro for
1101 * a slightly more friendly interface to this functionality.
1104 rhythmdb_entry_get_type_data (RhythmDBEntry
*entry
,
1105 guint expected_size
)
1107 g_return_val_if_fail (entry
!= NULL
, NULL
);
1109 g_assert (expected_size
== entry
->type
->entry_type_data_size
);
1110 gsize offset
= ALIGN_STRUCT (sizeof (RhythmDBEntry
));
1112 return (gpointer
) (((guint8
*)entry
) + offset
);
1116 * rhythmdb_entry_insert:
1118 * @entry: the entry to insert.
1120 * Inserts a newly-created entry into the database.
1122 * Note that you must call rhythmdb_commit() at some point after invoking
1126 rhythmdb_entry_insert (RhythmDB
*db
,
1127 RhythmDBEntry
*entry
)
1129 g_return_if_fail (RHYTHMDB_IS (db
));
1130 g_return_if_fail (entry
!= NULL
);
1132 g_assert ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) == 0);
1133 g_return_if_fail (entry
->location
!= NULL
);
1135 /* ref the entry before adding to hash, it is unreffed when removed */
1136 rhythmdb_entry_ref (entry
);
1137 g_mutex_lock (db
->priv
->change_mutex
);
1138 g_hash_table_insert (db
->priv
->added_entries
, entry
, g_thread_self ());
1139 g_mutex_unlock (db
->priv
->change_mutex
);
1143 * rhythmdb_entry_new:
1145 * @type: type of entry to create
1146 * @uri: the location of the entry, this be unique amongst all entries.
1148 * Creates a new entry of type @type and location @uri, and inserts
1149 * it into the database. You must call rhythmdb_commit() at some point
1150 * after invoking this function.
1152 * This may return NULL if entry creation fails. This can occur if there is
1153 * already an entry with the given uri.
1155 * Returns: the newly created #RhythmDBEntry
1158 rhythmdb_entry_new (RhythmDB
*db
,
1159 RhythmDBEntryType type
,
1163 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
1165 ret
= rhythmdb_entry_lookup_by_location (db
, uri
);
1167 g_warning ("attempting to create entry that already exists: %s", uri
);
1171 ret
= rhythmdb_entry_allocate (db
, type
);
1172 ret
->location
= rb_refstring_new (uri
);
1173 klass
->impl_entry_new (db
, ret
);
1174 rb_debug ("emitting entry added");
1175 rhythmdb_entry_insert (db
, ret
);
1181 * rhythmdb_entry_example_new:
1183 * @type: type of entry to create
1184 * @uri: the location of the entry, this be unique amongst all entries.
1186 * Creates a new sample entry of type @type and location @uri, it does not insert
1187 * it into the database. This is indended for use as a example entry.
1189 * This may return NULL if entry creation fails.
1191 * Returns: the newly created #RhythmDBEntry
1194 rhythmdb_entry_example_new (RhythmDB
*db
,
1195 RhythmDBEntryType type
,
1200 ret
= rhythmdb_entry_allocate (db
, type
);
1202 ret
->location
= rb_refstring_new (uri
);
1204 if (type
== RHYTHMDB_ENTRY_TYPE_SONG
) {
1205 rb_refstring_unref (ret
->artist
);
1206 ret
->artist
= rb_refstring_new ("The Beatles");
1207 rb_refstring_unref (ret
->album
);
1208 ret
->album
= rb_refstring_new ("Help!");
1209 rb_refstring_unref (ret
->title
);
1210 ret
->title
= rb_refstring_new ("Ticket To Ride");
1219 * rhythmdb_entry_ref:
1221 * @entry: a #RhythmDBEntry.
1223 * Increase the reference count of the entry.
1226 rhythmdb_entry_ref (RhythmDBEntry
*entry
)
1228 g_return_val_if_fail (entry
!= NULL
, NULL
);
1229 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
1231 g_atomic_int_inc (&entry
->refcount
);
1237 rhythmdb_entry_finalize (RhythmDBEntry
*entry
)
1239 RhythmDBEntryType type
;
1241 type
= rhythmdb_entry_get_entry_type (entry
);
1243 if (type
->pre_entry_destroy
)
1244 (type
->pre_entry_destroy
)(entry
, type
->pre_entry_destroy_data
);
1246 rb_refstring_unref (entry
->location
);
1247 rb_refstring_unref (entry
->playback_error
);
1248 rb_refstring_unref (entry
->title
);
1249 rb_refstring_unref (entry
->genre
);
1250 rb_refstring_unref (entry
->artist
);
1251 rb_refstring_unref (entry
->album
);
1252 rb_refstring_unref (entry
->musicbrainz_trackid
);
1253 rb_refstring_unref (entry
->mimetype
);
1259 * rhythmdb_entry_unref:
1261 * @entry: a #RhythmDBEntry.
1263 * Decrease the reference count of the entry, and destroy it if there are
1264 * no references left.
1267 rhythmdb_entry_unref (RhythmDBEntry
*entry
)
1271 g_return_if_fail (entry
!= NULL
);
1272 g_return_if_fail (entry
->refcount
> 0);
1274 is_zero
= g_atomic_int_dec_and_test (&entry
->refcount
);
1275 if (G_UNLIKELY (is_zero
)) {
1276 rhythmdb_entry_finalize (entry
);
1281 * rhythmdb_entry_is_editable:
1283 * @entry: a #RhythmDBEntry.
1285 * This determines whether any changes to the entries metadata can be saved.
1286 * Usually this is only true for entries backed by files, where tag-writing is
1287 * enabled, and the appropriate tag-writing facilities are available.
1289 * Returns: whether the entries metadata can be changed.
1293 rhythmdb_entry_is_editable (RhythmDB
*db
,
1294 RhythmDBEntry
*entry
)
1296 RhythmDBEntryType entry_type
;
1298 g_return_val_if_fail (RHYTHMDB_IS (db
), FALSE
);
1299 g_return_val_if_fail (entry
!= NULL
, FALSE
);
1301 entry_type
= rhythmdb_entry_get_entry_type (entry
);
1302 return entry_type
->can_sync_metadata (db
, entry
, entry_type
->can_sync_metadata_data
);
1306 set_metadata_string_default_unknown (RhythmDB
*db
,
1307 RBMetaData
*metadata
,
1308 RhythmDBEntry
*entry
,
1309 RBMetaDataField field
,
1310 RhythmDBPropType prop
)
1312 const char *unknown
= _("Unknown");
1315 if (!(rb_metadata_get (metadata
,
1318 g_value_init (&val
, G_TYPE_STRING
);
1319 g_value_set_static_string (&val
, unknown
);
1321 const gchar
*str
= g_value_get_string (&val
);
1322 if (str
== NULL
|| str
[0] == '\0')
1323 g_value_set_static_string (&val
, unknown
);
1325 rhythmdb_entry_set_internal (db
, entry
, TRUE
, prop
, &val
);
1326 g_value_unset (&val
);
1330 set_props_from_metadata (RhythmDB
*db
,
1331 RhythmDBEntry
*entry
,
1332 GnomeVFSFileInfo
*vfsinfo
,
1333 RBMetaData
*metadata
)
1338 g_value_init (&val
, G_TYPE_STRING
);
1339 mime
= rb_metadata_get_mime (metadata
);
1341 g_value_set_string (&val
, mime
);
1342 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1343 RHYTHMDB_PROP_MIMETYPE
, &val
);
1345 g_value_unset (&val
);
1348 if (!rb_metadata_get (metadata
,
1349 RB_METADATA_FIELD_TRACK_NUMBER
,
1351 g_value_init (&val
, G_TYPE_ULONG
);
1352 g_value_set_ulong (&val
, 0);
1354 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1355 RHYTHMDB_PROP_TRACK_NUMBER
, &val
);
1356 g_value_unset (&val
);
1359 if (!rb_metadata_get (metadata
,
1360 RB_METADATA_FIELD_DISC_NUMBER
,
1362 g_value_init (&val
, G_TYPE_ULONG
);
1363 g_value_set_ulong (&val
, 0);
1365 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1366 RHYTHMDB_PROP_DISC_NUMBER
, &val
);
1367 g_value_unset (&val
);
1370 if (rb_metadata_get (metadata
,
1371 RB_METADATA_FIELD_DURATION
,
1373 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1374 RHYTHMDB_PROP_DURATION
, &val
);
1375 g_value_unset (&val
);
1379 if (rb_metadata_get (metadata
,
1380 RB_METADATA_FIELD_BITRATE
,
1382 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1383 RHYTHMDB_PROP_BITRATE
, &val
);
1384 g_value_unset (&val
);
1388 if (rb_metadata_get (metadata
,
1389 RB_METADATA_FIELD_DATE
,
1391 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1392 RHYTHMDB_PROP_DATE
, &val
);
1393 g_value_unset (&val
);
1396 /* musicbrainz trackid */
1397 if (rb_metadata_get (metadata
,
1398 RB_METADATA_FIELD_MUSICBRAINZ_TRACKID
,
1400 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1401 RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
, &val
);
1402 g_value_unset (&val
);
1406 g_value_init (&val
, G_TYPE_UINT64
);
1407 g_value_set_uint64 (&val
, vfsinfo
->size
);
1408 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_FILE_SIZE
, &val
);
1409 g_value_unset (&val
);
1412 if (!rb_metadata_get (metadata
,
1413 RB_METADATA_FIELD_TITLE
,
1414 &val
) || g_value_get_string (&val
)[0] == '\0') {
1416 utf8name
= g_filename_to_utf8 (vfsinfo
->name
, -1, NULL
, NULL
, NULL
);
1418 utf8name
= g_strdup (_("<invalid filename>"));
1420 if (G_VALUE_HOLDS_STRING (&val
))
1421 g_value_reset (&val
);
1423 g_value_init (&val
, G_TYPE_STRING
);
1424 g_value_set_string (&val
, utf8name
);
1427 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_TITLE
, &val
);
1428 g_value_unset (&val
);
1431 set_metadata_string_default_unknown (db
, metadata
, entry
,
1432 RB_METADATA_FIELD_GENRE
,
1433 RHYTHMDB_PROP_GENRE
);
1436 set_metadata_string_default_unknown (db
, metadata
, entry
,
1437 RB_METADATA_FIELD_ARTIST
,
1438 RHYTHMDB_PROP_ARTIST
);
1440 set_metadata_string_default_unknown (db
, metadata
, entry
,
1441 RB_METADATA_FIELD_ALBUM
,
1442 RHYTHMDB_PROP_ALBUM
);
1444 /* replaygain track gain */
1445 if (rb_metadata_get (metadata
,
1446 RB_METADATA_FIELD_TRACK_GAIN
,
1448 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1449 RHYTHMDB_PROP_TRACK_GAIN
, &val
);
1450 g_value_unset (&val
);
1453 /* replaygain track peak */
1454 if (rb_metadata_get (metadata
,
1455 RB_METADATA_FIELD_TRACK_PEAK
,
1457 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1458 RHYTHMDB_PROP_TRACK_PEAK
, &val
);
1459 g_value_unset (&val
);
1462 /* replaygain album gain */
1463 if (rb_metadata_get (metadata
,
1464 RB_METADATA_FIELD_ALBUM_GAIN
,
1466 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1467 RHYTHMDB_PROP_ALBUM_GAIN
, &val
);
1468 g_value_unset (&val
);
1471 /* replaygain album peak */
1472 if (rb_metadata_get (metadata
,
1473 RB_METADATA_FIELD_ALBUM_PEAK
,
1475 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1476 RHYTHMDB_PROP_ALBUM_PEAK
, &val
);
1477 g_value_unset (&val
);
1482 is_ghost_entry (RhythmDBEntry
*entry
)
1486 gulong grace_period
;
1488 GConfClient
*client
;
1490 client
= gconf_client_get_default ();
1491 if (client
== NULL
) {
1495 grace_period
= gconf_client_get_int (client
, CONF_GRACE_PERIOD
,
1497 g_object_unref (G_OBJECT (client
));
1498 if (error
!= NULL
) {
1499 g_error_free (error
);
1503 /* This is a bit silly, but I prefer to make sure we won't
1504 * overflow in the following calculations
1506 if ((grace_period
< 0) || (grace_period
> 20000)) {
1510 /* Convert from days to seconds */
1511 grace_period
= grace_period
* 60 * 60 * 24;
1512 g_get_current_time (&time
);
1513 last_seen
= rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_LAST_SEEN
);
1515 return (last_seen
+ grace_period
< time
.tv_sec
);
1519 rhythmdb_process_stat_event (RhythmDB
*db
,
1520 RhythmDBEvent
*event
)
1522 RhythmDBEntry
*entry
;
1523 RhythmDBAction
*action
;
1525 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1527 time_t mtime
= (time_t) entry
->mtime
;
1529 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) && (entry
->type
!= event
->entry_type
))
1530 g_warning ("attempt to use same location in multiple entry types");
1532 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_IGNORE
)
1533 rb_debug ("ignoring %p", entry
);
1536 if (!is_ghost_entry (entry
)) {
1537 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
1539 rb_debug ("error accessing %s: %s", rb_refstring_get (event
->real_uri
),
1540 event
->error
->message
);
1541 rhythmdb_entry_delete (db
, entry
);
1546 const char *mount_point
;
1548 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1550 /* Update mount point if necessary (main reason is
1551 * that we want to set the mount point in legacy
1552 * rhythmdb that doesn't have it already
1554 mount_point
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_MOUNTPOINT
);
1555 if (mount_point
== NULL
) {
1556 rhythmdb_entry_set_mount_point (db
, entry
,
1557 rb_refstring_get (event
->real_uri
));
1560 /* Update last seen time. It will also be updated
1561 * upon saving and when a volume is unmounted
1563 g_get_current_time (&time
);
1564 g_value_init (&val
, G_TYPE_ULONG
);
1565 g_value_set_ulong (&val
, time
.tv_sec
);
1566 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1567 RHYTHMDB_PROP_LAST_SEEN
,
1569 /* Old rhythmdb.xml files won't have a value for
1570 * FIRST_SEEN, so set it here
1572 if (rhythmdb_entry_get_ulong (entry
, RHYTHMDB_PROP_FIRST_SEEN
) == 0) {
1573 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
1574 RHYTHMDB_PROP_FIRST_SEEN
,
1577 g_value_unset (&val
);
1579 if (mtime
== event
->vfsinfo
->mtime
) {
1580 rb_debug ("not modified: %s", rb_refstring_get (event
->real_uri
));
1581 /* monitor the file for changes */
1582 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY
))
1583 rhythmdb_monitor_uri_path (db
, rb_refstring_get (entry
->location
), NULL
/* FIXME */);
1585 RhythmDBEvent
*new_event
;
1587 rb_debug ("changed: %s", rb_refstring_get (event
->real_uri
));
1588 new_event
= g_new0 (RhythmDBEvent
, 1);
1590 new_event
->uri
= rb_refstring_ref (event
->real_uri
);
1591 new_event
->type
= RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED
;
1592 g_async_queue_push (db
->priv
->event_queue
,
1597 rhythmdb_commit (db
);
1599 action
= g_new0 (RhythmDBAction
, 1);
1600 action
->type
= RHYTHMDB_ACTION_LOAD
;
1601 action
->uri
= rb_refstring_ref (event
->real_uri
);
1602 action
->entry_type
= event
->entry_type
;
1603 rb_debug ("queuing a RHYTHMDB_ACTION_LOAD: %s", rb_refstring_get (action
->uri
));
1604 g_async_queue_push (db
->priv
->action_queue
, action
);
1613 } RhythmDBLoadErrorData
;
1616 rhythmdb_add_import_error_entry (RhythmDB
*db
,
1617 RhythmDBEvent
*event
)
1619 RhythmDBEntry
*entry
;
1620 GValue value
= {0,};
1621 RhythmDBEntryType error_entry_type
= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
;
1623 if (g_error_matches (event
->error
, RB_METADATA_ERROR
, RB_METADATA_ERROR_NOT_AUDIO_IGNORE
)) {
1624 /* only add an ignore entry for the main library */
1625 if (event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_SONG
&&
1626 event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
)
1629 error_entry_type
= RHYTHMDB_ENTRY_TYPE_IGNORE
;
1632 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1634 RhythmDBEntryType entry_type
= rhythmdb_entry_get_entry_type (entry
);
1635 if (entry_type
!= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
&&
1636 entry_type
!= RHYTHMDB_ENTRY_TYPE_IGNORE
) {
1637 /* FIXME we've successfully read this file before.. so what should we do? */
1638 rb_debug ("%s already exists in the library.. ignoring import error?", rb_refstring_get (event
->real_uri
));
1642 if (entry_type
!= error_entry_type
) {
1643 /* delete the existing entry, then create a new one below */
1644 rhythmdb_entry_delete (db
, entry
);
1646 } else if (error_entry_type
== RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
) {
1647 /* we've already got an error for this file, so just update it */
1648 g_value_init (&value
, G_TYPE_STRING
);
1649 g_value_set_string (&value
, event
->error
->message
);
1650 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
1651 g_value_unset (&value
);
1653 /* no need to update the ignored file entry */
1656 if (entry
&& event
->vfsinfo
) {
1658 g_value_init (&value
, G_TYPE_ULONG
);
1659 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1660 rhythmdb_entry_set(db
, entry
, RHYTHMDB_PROP_MTIME
, &value
);
1661 g_value_unset (&value
);
1664 rhythmdb_add_timeout_commit (db
, FALSE
);
1667 if (entry
== NULL
) {
1668 /* create a new import error or ignore entry */
1669 entry
= rhythmdb_entry_new (db
, error_entry_type
, rb_refstring_get (event
->real_uri
));
1673 if (error_entry_type
== RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
&& event
->error
->message
) {
1674 g_value_init (&value
, G_TYPE_STRING
);
1675 if (g_utf8_validate (event
->error
->message
, -1, NULL
))
1676 g_value_set_string (&value
, event
->error
->message
);
1678 g_value_set_static_string (&value
, _("invalid unicode in error message"));
1679 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
1680 g_value_unset (&value
);
1684 if (event
->vfsinfo
) {
1685 g_value_init (&value
, G_TYPE_ULONG
);
1686 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1687 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_MTIME
, &value
);
1688 g_value_unset (&value
);
1691 /* record the mount point so we can delete entries for unmounted volumes */
1692 rhythmdb_entry_set_mount_point (db
, entry
, rb_refstring_get (event
->real_uri
));
1694 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1696 rhythmdb_add_timeout_commit (db
, FALSE
);
1701 rhythmdb_process_metadata_load (RhythmDB
*db
,
1702 RhythmDBEvent
*event
)
1704 RhythmDBEntry
*entry
;
1705 GValue value
= {0,};
1709 if (rhythmdb_get_readonly (db
)) {
1710 rb_debug ("database is read-only right now, re-queuing event");
1711 g_async_queue_push (db
->priv
->event_queue
, event
);
1716 rhythmdb_add_import_error_entry (db
, event
);
1720 /* do we really need to do this? */
1721 mime
= rb_metadata_get_mime (event
->metadata
);
1723 rb_debug ("unsupported file");
1727 g_get_current_time (&time
);
1729 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->real_uri
);
1731 if (entry
!= NULL
) {
1732 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) &&
1733 (rhythmdb_entry_get_entry_type (entry
) != event
->entry_type
)) {
1734 /* switching from IGNORE to SONG or vice versa, recreate the entry */
1735 rhythmdb_entry_delete (db
, entry
);
1736 rhythmdb_add_timeout_commit (db
, FALSE
);
1741 if (entry
== NULL
) {
1742 if (event
->entry_type
== RHYTHMDB_ENTRY_TYPE_INVALID
)
1743 event
->entry_type
= RHYTHMDB_ENTRY_TYPE_SONG
;
1745 entry
= rhythmdb_entry_new (db
, event
->entry_type
, rb_refstring_get (event
->real_uri
));
1746 if (entry
== NULL
) {
1747 rb_debug ("entry already exists");
1751 /* initialize the last played date to 0=never */
1752 g_value_init (&value
, G_TYPE_ULONG
);
1753 g_value_set_ulong (&value
, 0);
1754 rhythmdb_entry_set (db
, entry
,
1755 RHYTHMDB_PROP_LAST_PLAYED
, &value
);
1756 g_value_unset (&value
);
1758 /* initialize the rating */
1759 g_value_init (&value
, G_TYPE_DOUBLE
);
1760 g_value_set_double (&value
, 0);
1761 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_RATING
, &value
);
1762 g_value_unset (&value
);
1765 g_value_init (&value
, G_TYPE_ULONG
);
1766 g_value_set_ulong (&value
, time
.tv_sec
);
1767 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_FIRST_SEEN
, &value
);
1768 g_value_unset (&value
);
1771 if ((event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_INVALID
) && (entry
->type
!= event
->entry_type
))
1772 g_warning ("attempt to use same location in multiple entry types");
1775 if (event
->vfsinfo
) {
1776 g_value_init (&value
, G_TYPE_ULONG
);
1777 g_value_set_ulong (&value
, event
->vfsinfo
->mtime
);
1778 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_MTIME
, &value
);
1779 g_value_unset (&value
);
1782 if (event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_IGNORE
&&
1783 event
->entry_type
!= RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR
) {
1784 set_props_from_metadata (db
, entry
, event
->vfsinfo
, event
->metadata
);
1787 /* we've seen this entry */
1788 rhythmdb_entry_set_visibility (db
, entry
, TRUE
);
1790 g_value_init (&value
, G_TYPE_ULONG
);
1791 g_value_set_ulong (&value
, time
.tv_sec
);
1792 rhythmdb_entry_set_internal (db
, entry
, TRUE
, RHYTHMDB_PROP_LAST_SEEN
, &value
);
1793 g_value_unset (&value
);
1795 /* Remember the mount point of the volume the song is on */
1796 rhythmdb_entry_set_mount_point (db
, entry
, rb_refstring_get (event
->real_uri
));
1798 /* monitor the file for changes */
1799 /* FIXME: watch for errors */
1800 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY
) && event
->entry_type
== RHYTHMDB_ENTRY_TYPE_SONG
)
1801 rhythmdb_monitor_uri_path (db
, rb_refstring_get (entry
->location
), NULL
);
1803 rhythmdb_add_timeout_commit (db
, FALSE
);
1809 rhythmdb_process_queued_entry_set_event (RhythmDB
*db
,
1810 RhythmDBEvent
*event
)
1812 rhythmdb_entry_set_internal (db
, event
->entry
,
1813 event
->signal_change
,
1815 &event
->change
.new);
1816 /* Don't run rhythmdb_commit right now in case there
1817 * we can run a single commit for several queued
1820 rhythmdb_add_timeout_commit (db
, TRUE
);
1824 rhythmdb_process_file_created_or_modified (RhythmDB
*db
,
1825 RhythmDBEvent
*event
)
1827 RhythmDBAction
*action
;
1829 action
= g_new0 (RhythmDBAction
, 1);
1830 action
->type
= RHYTHMDB_ACTION_LOAD
;
1831 action
->uri
= rb_refstring_ref (event
->uri
);
1832 action
->entry_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
1833 g_async_queue_push (db
->priv
->action_queue
, action
);
1837 rhythmdb_process_file_deleted (RhythmDB
*db
,
1838 RhythmDBEvent
*event
)
1840 RhythmDBEntry
*entry
= rhythmdb_entry_lookup_by_location_refstring (db
, event
->uri
);
1842 g_hash_table_remove (db
->priv
->changed_files
, event
->uri
);
1845 rb_debug ("deleting entry for %s", rb_refstring_get (event
->uri
));
1846 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
1847 rhythmdb_commit (db
);
1852 rhythmdb_process_events (RhythmDB
*db
,
1855 RhythmDBEvent
*event
;
1858 while ((event
= g_async_queue_try_pop (db
->priv
->event_queue
)) != NULL
) {
1859 gboolean free
= TRUE
;
1861 /* if the database is read-only, we can't process those events
1862 * since they call rhythmdb_entry_set. Doing it this way
1863 * is safe if we assume all calls to read_enter/read_leave
1864 * are done from the main thread (the thread this function
1867 if (rhythmdb_get_readonly (db
) &&
1868 ((event
->type
== RHYTHMDB_EVENT_STAT
)
1869 || (event
->type
== RHYTHMDB_EVENT_METADATA_LOAD
)
1870 || (event
->type
== RHYTHMDB_EVENT_ENTRY_SET
))) {
1871 if (count
>= g_async_queue_length (db
->priv
->event_queue
)) {
1872 rb_debug ("Database is read-only, and we can't process any more events");
1873 /* give the running query some time to complete */
1876 rb_debug ("Database is read-only, delaying event processing\n");
1877 g_async_queue_push (db
->priv
->event_queue
, event
);
1881 switch (event
->type
) {
1882 case RHYTHMDB_EVENT_STAT
:
1883 rb_debug ("processing RHYTHMDB_EVENT_STAT");
1884 rhythmdb_process_stat_event (db
, event
);
1886 case RHYTHMDB_EVENT_METADATA_LOAD
:
1887 rb_debug ("processing RHYTHMDB_EVENT_METADATA_LOAD");
1888 free
= rhythmdb_process_metadata_load (db
, event
);
1890 case RHYTHMDB_EVENT_ENTRY_SET
:
1891 rb_debug ("processing RHYTHMDB_EVENT_ENTRY_SET");
1892 rhythmdb_process_queued_entry_set_event (db
, event
);
1894 case RHYTHMDB_EVENT_DB_LOAD
:
1895 rb_debug ("processing RHYTHMDB_EVENT_DB_LOAD");
1896 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[LOAD_COMPLETE
], 0);
1898 /* save the db every five minutes */
1899 if (db
->priv
->save_timeout_id
> 0) {
1900 g_source_remove (db
->priv
->save_timeout_id
);
1902 db
->priv
->save_timeout_id
= g_timeout_add_full (G_PRIORITY_LOW
,
1904 (GSourceFunc
) rhythmdb_idle_save
,
1908 case RHYTHMDB_EVENT_THREAD_EXITED
:
1909 rb_debug ("processing RHYTHMDB_EVENT_THREAD_EXITED");
1911 case RHYTHMDB_EVENT_DB_SAVED
:
1912 rb_debug ("processing RHYTHMDB_EVENT_DB_SAVED");
1913 rhythmdb_read_leave (db
);
1915 case RHYTHMDB_EVENT_QUERY_COMPLETE
:
1916 rb_debug ("processing RHYTHMDB_EVENT_QUERY_COMPLETE");
1917 rhythmdb_read_leave (db
);
1919 case RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED
:
1920 rb_debug ("processing RHYTHMDB_EVENT_FILE_CREATED_OR_MODIFIED");
1921 rhythmdb_process_file_created_or_modified (db
, event
);
1923 case RHYTHMDB_EVENT_FILE_DELETED
:
1924 rb_debug ("processing RHYTHMDB_EVENT_FILE_DELETED");
1925 rhythmdb_process_file_deleted (db
, event
);
1929 rhythmdb_event_free (db
, event
);
1933 if (timeout
&& (count
% 8 == 0)) {
1935 g_get_current_time (&now
);
1936 if (rb_compare_gtimeval (timeout
,&now
) < 0) {
1937 /* probably more work to do, so try to come back as soon as possible */
1943 /* queue is empty, so we can wait a while before checking it again */
1948 rhythmdb_idle_poll_events (RhythmDB
*db
)
1953 g_get_current_time (&timeout
);
1954 g_time_val_add (&timeout
, G_USEC_PER_SEC
*0.75);
1956 GDK_THREADS_ENTER ();
1958 poll_soon
= rhythmdb_process_events (db
, &timeout
);
1961 db
->priv
->event_poll_id
=
1962 g_idle_add_full (G_PRIORITY_LOW
, (GSourceFunc
) rhythmdb_idle_poll_events
,
1965 db
->priv
->event_poll_id
=
1966 g_timeout_add (1000, (GSourceFunc
) rhythmdb_idle_poll_events
, db
);
1968 GDK_THREADS_LEAVE ();
1973 #define READ_QUEUE_TIMEOUT G_USEC_PER_SEC / 10
1976 read_queue (GAsyncQueue
*queue
, gboolean
*cancel
)
1981 g_get_current_time (&timeout
);
1982 g_time_val_add (&timeout
, READ_QUEUE_TIMEOUT
);
1984 if (G_UNLIKELY (*cancel
))
1986 while ((ret
= g_async_queue_timed_pop (queue
, &timeout
)) == NULL
) {
1987 if (G_UNLIKELY (*cancel
))
1989 g_get_current_time (&timeout
);
1990 g_time_val_add (&timeout
, G_USEC_PER_SEC
);
1997 rhythmdb_execute_stat_info_cb (GnomeVFSAsyncHandle
*handle
,
1999 /* GnomeVFSGetFileInfoResult* items */
2000 RhythmDBEvent
*event
)
2002 /* this is in the main thread, so we can't do any long operation here */
2003 GnomeVFSGetFileInfoResult
*info_result
= results
->data
;
2005 g_mutex_lock (event
->db
->priv
->stat_mutex
);
2006 event
->db
->priv
->outstanding_stats
= g_list_remove (event
->db
->priv
->outstanding_stats
, event
);
2007 event
->handle
= NULL
;
2008 g_mutex_unlock (event
->db
->priv
->stat_mutex
);
2010 if (info_result
->result
== GNOME_VFS_OK
) {
2011 event
->vfsinfo
= gnome_vfs_file_info_dup (info_result
->file_info
);
2013 event
->error
= make_access_failed_error (rb_refstring_get (event
->real_uri
),
2014 info_result
->result
);
2015 event
->vfsinfo
= NULL
;
2017 g_async_queue_push (event
->db
->priv
->event_queue
, event
);
2021 rhythmdb_execute_stat (RhythmDB
*db
,
2023 RhythmDBEvent
*event
)
2025 GnomeVFSURI
*vfs_uri
= gnome_vfs_uri_new (uri
);
2027 GList
*uri_list
= g_list_append (NULL
, vfs_uri
);
2028 event
->real_uri
= rb_refstring_new (uri
);
2030 g_mutex_lock (db
->priv
->stat_mutex
);
2031 db
->priv
->outstanding_stats
= g_list_prepend (db
->priv
->outstanding_stats
, event
);
2032 g_mutex_unlock (db
->priv
->stat_mutex
);
2034 gnome_vfs_async_get_file_info (&event
->handle
, uri_list
,
2035 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
,
2036 GNOME_VFS_PRIORITY_MIN
,
2037 (GnomeVFSAsyncGetFileInfoCallback
) rhythmdb_execute_stat_info_cb
,
2039 gnome_vfs_uri_unref (vfs_uri
);
2040 g_list_free (uri_list
);
2044 queue_stat_uri (const char *uri
,
2046 RhythmDBEntryType type
)
2048 RhythmDBEvent
*result
;
2050 rb_debug ("queueing stat for \"%s\"", uri
);
2051 g_assert (uri
&& *uri
);
2053 result
= g_new0 (RhythmDBEvent
, 1);
2055 result
->type
= RHYTHMDB_EVENT_STAT
;
2056 result
->entry_type
= type
;
2059 * before the action thread is started, we queue up stat events,
2060 * as we're still creating and running queries, as well as loading
2061 * the database. when we start the action thread, we'll kick off
2062 * a gnome-vfs job to run all the stat events too.
2064 * when the action thread is already running, we can start the
2065 * async_get_file_info job directly.
2067 g_mutex_lock (db
->priv
->stat_mutex
);
2068 if (db
->priv
->action_thread_running
) {
2069 g_mutex_unlock (db
->priv
->stat_mutex
);
2070 rhythmdb_execute_stat (db
, uri
, result
);
2072 GnomeVFSURI
*vfs_uri
;
2074 vfs_uri
= gnome_vfs_uri_new (uri
);
2076 /* construct a list of URIs and a hash table containing
2077 * stat events to fill in and post on the event queue.
2079 if (g_hash_table_lookup (db
->priv
->stat_events
, vfs_uri
)) {
2081 gnome_vfs_uri_unref (vfs_uri
);
2083 result
->real_uri
= rb_refstring_new (uri
);
2084 g_hash_table_insert (db
->priv
->stat_events
, vfs_uri
, result
);
2085 db
->priv
->stat_list
= g_list_prepend (db
->priv
->stat_list
, vfs_uri
);
2088 g_mutex_unlock (db
->priv
->stat_mutex
);
2093 queue_stat_uri_tad (const char *uri
,
2094 RhythmDBAddThreadData
*data
)
2096 queue_stat_uri (uri
, data
->db
, data
->type
);
2100 add_thread_main (RhythmDBAddThreadData
*data
)
2102 RhythmDBEvent
*result
;
2104 rb_uri_handle_recursively (data
->uri
, (GFunc
) queue_stat_uri_tad
,
2105 &data
->db
->priv
->exiting
, data
);
2107 rb_debug ("exiting");
2108 result
= g_new0 (RhythmDBEvent
, 1);
2109 result
->db
= data
->db
;
2110 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2111 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
2118 rhythmdb_execute_load (RhythmDB
*db
,
2120 RhythmDBEvent
*event
)
2122 GnomeVFSURI
*vfs_uri
= gnome_vfs_uri_new (uri
);
2123 GnomeVFSResult vfsresult
;
2125 event
->real_uri
= rb_refstring_new (rb_uri_resolve_symlink (uri
));
2126 event
->vfsinfo
= gnome_vfs_file_info_new ();
2128 vfsresult
= gnome_vfs_get_file_info (uri
,
2130 GNOME_VFS_FILE_INFO_FOLLOW_LINKS
);
2131 if (vfsresult
!= GNOME_VFS_OK
) {
2132 event
->error
= make_access_failed_error (uri
, vfsresult
);
2134 gnome_vfs_file_info_unref (event
->vfsinfo
);
2135 event
->vfsinfo
= NULL
;
2137 if (event
->type
== RHYTHMDB_EVENT_METADATA_LOAD
) {
2138 event
->metadata
= rb_metadata_new ();
2139 rb_metadata_load (event
->metadata
, rb_refstring_get (event
->real_uri
),
2144 gnome_vfs_uri_unref (vfs_uri
);
2145 g_async_queue_push (db
->priv
->event_queue
, event
);
2149 * rhythmdb_entry_get:
2150 * @entry: a #RhythmDBEntry.
2151 * @propid: the id of the property to get.
2152 * @val: return location for the property value.
2154 * Gets a property of an entry, storing it in the given #GValue.
2157 rhythmdb_entry_get (RhythmDB
*db
,
2158 RhythmDBEntry
*entry
,
2159 RhythmDBPropType propid
,
2162 g_return_if_fail (RHYTHMDB_IS (db
));
2163 g_return_if_fail (entry
!= NULL
);
2164 g_return_if_fail (entry
->refcount
> 0);
2166 rhythmdb_entry_sync_mirrored (entry
, propid
);
2168 g_assert (G_VALUE_TYPE (val
) == rhythmdb_get_property_type (db
, propid
));
2169 switch (rhythmdb_property_type_map
[propid
]) {
2171 g_value_set_string (val
, rhythmdb_entry_get_string (entry
, propid
));
2173 case G_TYPE_BOOLEAN
:
2174 g_value_set_boolean (val
, rhythmdb_entry_get_boolean (entry
, propid
));
2177 g_value_set_ulong (val
, rhythmdb_entry_get_ulong (entry
, propid
));
2180 g_value_set_uint64 (val
, rhythmdb_entry_get_uint64 (entry
, propid
));
2183 g_value_set_double (val
, rhythmdb_entry_get_double (entry
, propid
));
2185 case G_TYPE_POINTER
:
2186 g_value_set_pointer (val
, rhythmdb_entry_get_pointer (entry
, propid
));
2189 g_assert_not_reached ();
2195 entry_to_rb_metadata (RhythmDB
*db
,
2196 RhythmDBEntry
*entry
,
2197 RBMetaData
*metadata
)
2202 for (i
= RHYTHMDB_PROP_TYPE
; i
!= RHYTHMDB_NUM_PROPERTIES
; i
++) {
2203 RBMetaDataField field
;
2205 if (metadata_field_from_prop (i
, &field
) == FALSE
) {
2209 g_value_init (&val
, rhythmdb_property_type_map
[i
]);
2210 rhythmdb_entry_get (db
, entry
, i
, &val
);
2211 rb_metadata_set (metadata
,
2214 g_value_unset (&val
);
2223 } RhythmDBSaveErrorData
;
2226 emit_save_error_idle (RhythmDBSaveErrorData
*data
)
2228 g_signal_emit (G_OBJECT (data
->db
), rhythmdb_signals
[SAVE_ERROR
], 0, data
->uri
, data
->error
);
2229 g_object_unref (G_OBJECT (data
->db
));
2231 g_error_free (data
->error
);
2237 action_thread_main (RhythmDB
*db
)
2239 RhythmDBEvent
*result
;
2242 RhythmDBAction
*action
;
2244 action
= read_queue (db
->priv
->action_queue
, &db
->priv
->exiting
);
2249 switch (action
->type
) {
2250 case RHYTHMDB_ACTION_STAT
:
2252 result
= g_new0 (RhythmDBEvent
, 1);
2254 result
->type
= RHYTHMDB_EVENT_STAT
;
2255 result
->entry_type
= action
->entry_type
;
2257 rb_debug ("executing RHYTHMDB_ACTION_STAT for \"%s\"", rb_refstring_get (action
->uri
));
2259 rhythmdb_execute_stat (db
, rb_refstring_get (action
->uri
), result
);
2262 case RHYTHMDB_ACTION_LOAD
:
2264 result
= g_new0 (RhythmDBEvent
, 1);
2266 result
->type
= RHYTHMDB_EVENT_METADATA_LOAD
;
2267 result
->entry_type
= action
->entry_type
;
2269 rb_debug ("executing RHYTHMDB_ACTION_LOAD for \"%s\"", rb_refstring_get (action
->uri
));
2271 rhythmdb_execute_load (db
, rb_refstring_get (action
->uri
), result
);
2274 case RHYTHMDB_ACTION_SYNC
:
2276 GError
*error
= NULL
;
2277 RhythmDBEntry
*entry
;
2278 RhythmDBEntryType entry_type
;
2280 if (db
->priv
->dry_run
) {
2281 rb_debug ("dry run is enabled, not syncing metadata");
2285 entry
= rhythmdb_entry_lookup_by_location_refstring (db
, action
->uri
);
2289 entry_type
= rhythmdb_entry_get_entry_type (entry
);
2290 entry_type
->sync_metadata (db
, entry
, &error
, entry_type
->sync_metadata_data
);
2292 if (error
!= NULL
) {
2293 RhythmDBSaveErrorData
*data
;
2295 data
= g_new0 (RhythmDBSaveErrorData
, 1);
2298 data
->uri
= g_strdup (rb_refstring_get (action
->uri
));
2299 data
->error
= error
;
2300 g_idle_add ((GSourceFunc
)emit_save_error_idle
, data
);
2307 g_assert_not_reached ();
2310 rhythmdb_action_free (db
, action
);
2314 rb_debug ("exiting main thread");
2315 result
= g_new0 (RhythmDBEvent
, 1);
2317 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2318 g_async_queue_push (db
->priv
->event_queue
, result
);
2326 * @uri: the URI to add an entry/entries for
2328 * Adds the file(s) pointed to by @uri to the database, as entries of type
2329 * RHYTHMDB_ENTRY_TYPE_SONG. If the URI is that of a file, they will be added.
2330 * If the URI is that of a directory, everything under it will be added recursively.
2333 rhythmdb_add_uri (RhythmDB
*db
,
2336 rhythmdb_add_uri_with_type (db
, uri
, RHYTHMDB_ENTRY_TYPE_INVALID
);
2340 rhythmdb_add_uri_with_type (RhythmDB
*db
,
2342 RhythmDBEntryType type
)
2347 canon_uri
= rb_canonicalise_uri (uri
);
2348 realuri
= rb_uri_resolve_symlink (canon_uri
);
2351 if (rb_uri_is_directory (realuri
)) {
2352 RhythmDBAddThreadData
*data
= g_new0 (RhythmDBAddThreadData
, 1);
2354 data
->uri
= g_strdup (realuri
);
2357 rhythmdb_thread_create (db
, db
->priv
->add_thread_pool
, NULL
, data
);
2359 queue_stat_uri (realuri
, db
, type
);
2366 rhythmdb_sync_library_idle (RhythmDB
*db
)
2368 rhythmdb_sync_library_location (db
);
2369 g_object_unref (db
);
2374 rhythmdb_load_error_cb (GError
*error
)
2376 GDK_THREADS_ENTER ();
2377 rb_error_dialog (NULL
,
2378 _("Could not load the music database"),
2380 g_error_free (error
);
2382 GDK_THREADS_LEAVE ();
2387 rhythmdb_load_thread_main (RhythmDB
*db
)
2389 RhythmDBEvent
*result
;
2390 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2391 GError
*error
= NULL
;
2393 rb_profile_start ("loading db");
2394 g_mutex_lock (db
->priv
->saving_mutex
);
2395 if (klass
->impl_load (db
, &db
->priv
->exiting
, &error
) == FALSE
) {
2396 rb_debug ("db load failed: disabling saving");
2397 db
->priv
->can_save
= FALSE
;
2400 g_idle_add ((GSourceFunc
) rhythmdb_load_error_cb
, error
);
2403 g_mutex_unlock (db
->priv
->saving_mutex
);
2406 g_timeout_add (10000, (GSourceFunc
) rhythmdb_sync_library_idle
, db
);
2408 rb_debug ("queuing db load complete signal");
2409 result
= g_new0 (RhythmDBEvent
, 1);
2410 result
->type
= RHYTHMDB_EVENT_DB_LOAD
;
2411 g_async_queue_push (db
->priv
->event_queue
, result
);
2413 rb_debug ("exiting");
2414 result
= g_new0 (RhythmDBEvent
, 1);
2415 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2416 g_async_queue_push (db
->priv
->event_queue
, result
);
2418 rb_profile_end ("loading db");
2426 * Load the database from disk.
2429 rhythmdb_load (RhythmDB
*db
)
2431 rhythmdb_thread_create (db
, NULL
, (GThreadFunc
) rhythmdb_load_thread_main
, db
);
2435 rhythmdb_save_thread_main (RhythmDB
*db
)
2437 RhythmDBClass
*klass
;
2438 RhythmDBEvent
*result
;
2440 rb_debug ("entering save thread");
2442 g_mutex_lock (db
->priv
->saving_mutex
);
2444 if (!db
->priv
->dirty
&& !db
->priv
->can_save
) {
2445 rb_debug ("no save needed, ignoring");
2446 g_mutex_unlock (db
->priv
->saving_mutex
);
2450 while (db
->priv
->saving
)
2451 g_cond_wait (db
->priv
->saving_condition
, db
->priv
->saving_mutex
);
2453 db
->priv
->saving
= TRUE
;
2455 rb_debug ("saving rhythmdb");
2457 klass
= RHYTHMDB_GET_CLASS (db
);
2458 klass
->impl_save (db
);
2460 db
->priv
->saving
= FALSE
;
2461 db
->priv
->dirty
= FALSE
;
2463 g_mutex_unlock (db
->priv
->saving_mutex
);
2465 g_cond_broadcast (db
->priv
->saving_condition
);
2468 result
= g_new0 (RhythmDBEvent
, 1);
2470 result
->type
= RHYTHMDB_EVENT_DB_SAVED
;
2471 g_async_queue_push (db
->priv
->event_queue
, result
);
2473 result
= g_new0 (RhythmDBEvent
, 1);
2475 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
2476 g_async_queue_push (db
->priv
->event_queue
, result
);
2481 * rhythmdb_save_async:
2484 * Save the database to disk, asynchronously.
2487 rhythmdb_save_async (RhythmDB
*db
)
2489 rb_debug ("saving the rhythmdb in the background");
2491 rhythmdb_read_enter (db
);
2493 rhythmdb_thread_create (db
, NULL
, (GThreadFunc
) rhythmdb_save_thread_main
, db
);
2500 * Save the database to disk, not returning until it has been saved.
2503 rhythmdb_save (RhythmDB
*db
)
2505 rb_debug("saving the rhythmdb and blocking");
2507 rhythmdb_save_async (db
);
2509 g_mutex_lock (db
->priv
->saving_mutex
);
2511 while (db
->priv
->saving
)
2512 g_cond_wait (db
->priv
->saving_condition
, db
->priv
->saving_mutex
);
2514 g_mutex_unlock (db
->priv
->saving_mutex
);
2518 * rhythmdb_entry_set:
2520 * @entry: a #RhythmDBEntry.
2521 * @propid: the id of the property to set.
2522 * @value: the property value.
2524 * This function can be called by any code which wishes to change a
2525 * song property and send a notification. It may be called when the
2526 * database is read-only; in this case the change will be queued for
2527 * an unspecified time in the future. The implication of this is that
2528 * rhythmdb_entry_get() may not reflect the changes immediately. However,
2529 * if this property is exposed in the user interface, you should still
2530 * make the change in the widget. Then when the database returns to a
2531 * writable state, your change will take effect in the database too,
2532 * and a notification will be sent at that point.
2534 * Note that you must call rhythmdb_commit() at some point after invoking
2535 * this function, and that even after the commit, your change may not
2536 * have taken effect.
2539 rhythmdb_entry_set (RhythmDB
*db
,
2540 RhythmDBEntry
*entry
,
2542 const GValue
*value
)
2544 g_return_if_fail (RHYTHMDB_IS (db
));
2545 g_return_if_fail (entry
!= NULL
);
2547 if ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) != 0) {
2548 if (!rhythmdb_get_readonly (db
) && rb_is_main_thread ()) {
2549 rhythmdb_entry_set_internal (db
, entry
, TRUE
, propid
, value
);
2551 RhythmDBEvent
*result
;
2553 result
= g_new0 (RhythmDBEvent
, 1);
2555 result
->type
= RHYTHMDB_EVENT_ENTRY_SET
;
2557 rb_debug ("queuing RHYTHMDB_ACTION_ENTRY_SET");
2559 result
->entry
= rhythmdb_entry_ref (entry
);
2560 result
->change
.prop
= propid
;
2561 result
->signal_change
= TRUE
;
2562 g_value_init (&result
->change
.new, G_VALUE_TYPE (value
));
2563 g_value_copy (value
, &result
->change
.new);
2564 g_async_queue_push (db
->priv
->event_queue
, result
);
2567 rhythmdb_entry_set_internal (db
, entry
, FALSE
, propid
, value
);
2572 record_entry_change (RhythmDB
*db
,
2573 RhythmDBEntry
*entry
,
2575 const GValue
*value
)
2577 RhythmDBEntryChange
*changedata
;
2580 changedata
= g_new0 (RhythmDBEntryChange
, 1);
2581 changedata
->prop
= propid
;
2583 /* Copy a temporary gvalue, since _entry_get uses
2584 * _set_static_string to avoid memory allocations. */
2587 g_value_init (&tem
, G_VALUE_TYPE (value
));
2588 rhythmdb_entry_get (db
, entry
, propid
, &tem
);
2589 g_value_init (&changedata
->old
, G_VALUE_TYPE (value
));
2590 g_value_copy (&tem
, &changedata
->old
);
2591 g_value_unset (&tem
);
2593 g_value_init (&changedata
->new, G_VALUE_TYPE (value
));
2594 g_value_copy (value
, &changedata
->new);
2596 g_mutex_lock (db
->priv
->change_mutex
);
2597 /* ref the entry before adding to hash, it is unreffed when removed */
2598 rhythmdb_entry_ref (entry
);
2599 changelist
= g_hash_table_lookup (db
->priv
->changed_entries
, entry
);
2600 changelist
= g_slist_append (changelist
, changedata
);
2601 g_hash_table_insert (db
->priv
->changed_entries
, entry
, changelist
);
2602 g_mutex_unlock (db
->priv
->change_mutex
);
2606 rhythmdb_entry_set_internal (RhythmDB
*db
,
2607 RhythmDBEntry
*entry
,
2608 gboolean notify_if_inserted
,
2610 const GValue
*value
)
2612 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2614 RhythmDBPodcastFields
*podcast
= NULL
;
2616 #ifndef G_DISABLE_ASSERT
2617 switch (G_VALUE_TYPE (value
)) {
2619 /* the playback error is allowed to be NULL */
2620 if (propid
!= RHYTHMDB_PROP_PLAYBACK_ERROR
|| g_value_get_string (value
))
2621 g_assert (g_utf8_validate (g_value_get_string (value
), -1, NULL
));
2623 case G_TYPE_BOOLEAN
:
2629 g_assert_not_reached ();
2634 if ((entry
->flags
& RHYTHMDB_ENTRY_INSERTED
) && notify_if_inserted
) {
2635 record_entry_change (db
, entry
, propid
, value
);
2638 handled
= klass
->impl_entry_set (db
, entry
, propid
, value
);
2641 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
2642 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
2643 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
2646 case RHYTHMDB_PROP_TYPE
:
2647 case RHYTHMDB_PROP_ENTRY_ID
:
2648 g_assert_not_reached ();
2650 case RHYTHMDB_PROP_TITLE
:
2651 if (entry
->title
!= NULL
) {
2652 rb_refstring_unref (entry
->title
);
2654 entry
->title
= rb_refstring_new (g_value_get_string (value
));
2656 case RHYTHMDB_PROP_ALBUM
:
2657 if (entry
->album
!= NULL
) {
2658 rb_refstring_unref (entry
->album
);
2660 entry
->album
= rb_refstring_new (g_value_get_string (value
));
2662 case RHYTHMDB_PROP_ARTIST
:
2663 if (entry
->artist
!= NULL
) {
2664 rb_refstring_unref (entry
->artist
);
2666 entry
->artist
= rb_refstring_new (g_value_get_string (value
));
2668 case RHYTHMDB_PROP_GENRE
:
2669 if (entry
->genre
!= NULL
) {
2670 rb_refstring_unref (entry
->genre
);
2672 entry
->genre
= rb_refstring_new (g_value_get_string (value
));
2674 case RHYTHMDB_PROP_TRACK_NUMBER
:
2675 entry
->tracknum
= g_value_get_ulong (value
);
2677 case RHYTHMDB_PROP_DISC_NUMBER
:
2678 entry
->discnum
= g_value_get_ulong (value
);
2680 case RHYTHMDB_PROP_DURATION
:
2681 entry
->duration
= g_value_get_ulong (value
);
2683 case RHYTHMDB_PROP_BITRATE
:
2684 entry
->bitrate
= g_value_get_ulong (value
);
2686 case RHYTHMDB_PROP_DATE
:
2689 julian
= g_value_get_ulong (value
);
2691 g_date_set_julian (&entry
->date
, julian
);
2693 g_date_clear (&entry
->date
, 1);
2696 case RHYTHMDB_PROP_TRACK_GAIN
:
2697 entry
->track_gain
= g_value_get_double (value
);
2699 case RHYTHMDB_PROP_TRACK_PEAK
:
2700 entry
->track_peak
= g_value_get_double (value
);
2702 case RHYTHMDB_PROP_ALBUM_GAIN
:
2703 entry
->album_gain
= g_value_get_double (value
);
2705 case RHYTHMDB_PROP_ALBUM_PEAK
:
2706 entry
->album_peak
= g_value_get_double (value
);
2708 case RHYTHMDB_PROP_LOCATION
:
2709 rb_refstring_unref (entry
->location
);
2710 entry
->location
= rb_refstring_new (g_value_get_string (value
));
2712 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
2713 rb_refstring_unref (entry
->playback_error
);
2714 if (g_value_get_string (value
))
2715 entry
->playback_error
= rb_refstring_new (g_value_get_string (value
));
2717 entry
->playback_error
= NULL
;
2719 case RHYTHMDB_PROP_MOUNTPOINT
:
2720 if (entry
->mountpoint
!= NULL
) {
2721 rb_refstring_unref (entry
->mountpoint
);
2723 entry
->mountpoint
= rb_refstring_new (g_value_get_string (value
));
2725 case RHYTHMDB_PROP_FILE_SIZE
:
2726 entry
->file_size
= g_value_get_uint64 (value
);
2728 case RHYTHMDB_PROP_MIMETYPE
:
2729 if (entry
->mimetype
!= NULL
) {
2730 rb_refstring_unref (entry
->mimetype
);
2732 entry
->mimetype
= rb_refstring_new (g_value_get_string (value
));
2734 case RHYTHMDB_PROP_MTIME
:
2735 entry
->mtime
= g_value_get_ulong (value
);
2737 case RHYTHMDB_PROP_FIRST_SEEN
:
2738 entry
->first_seen
= g_value_get_ulong (value
);
2739 entry
->flags
|= RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
;
2741 case RHYTHMDB_PROP_LAST_SEEN
:
2742 entry
->last_seen
= g_value_get_ulong (value
);
2743 entry
->flags
|= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
2745 case RHYTHMDB_PROP_RATING
:
2746 entry
->rating
= g_value_get_double (value
);
2748 case RHYTHMDB_PROP_PLAY_COUNT
:
2749 entry
->play_count
= g_value_get_ulong (value
);
2751 case RHYTHMDB_PROP_LAST_PLAYED
:
2752 entry
->last_played
= g_value_get_ulong (value
);
2753 entry
->flags
|= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
;
2755 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
2756 rb_refstring_unref (entry
->musicbrainz_trackid
);
2757 entry
->musicbrainz_trackid
= rb_refstring_new (g_value_get_string (value
));
2759 case RHYTHMDB_PROP_HIDDEN
:
2760 if (g_value_get_boolean (value
)) {
2761 entry
->flags
|= RHYTHMDB_ENTRY_HIDDEN
;
2763 entry
->flags
&= ~RHYTHMDB_ENTRY_HIDDEN
;
2765 entry
->flags
|= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
;
2767 case RHYTHMDB_PROP_STATUS
:
2769 podcast
->status
= g_value_get_ulong (value
);
2771 case RHYTHMDB_PROP_DESCRIPTION
:
2773 rb_refstring_unref (podcast
->description
);
2774 podcast
->description
= rb_refstring_new (g_value_get_string (value
));
2776 case RHYTHMDB_PROP_SUBTITLE
:
2778 rb_refstring_unref (podcast
->subtitle
);
2779 podcast
->subtitle
= rb_refstring_new (g_value_get_string (value
));
2781 case RHYTHMDB_PROP_SUMMARY
:
2783 rb_refstring_unref (podcast
->summary
);
2784 podcast
->summary
= rb_refstring_new (g_value_get_string (value
));
2786 case RHYTHMDB_PROP_LANG
:
2788 if (podcast
->lang
!= NULL
) {
2789 rb_refstring_unref (podcast
->lang
);
2791 podcast
->lang
= rb_refstring_new (g_value_get_string (value
));
2793 case RHYTHMDB_PROP_COPYRIGHT
:
2795 if (podcast
->copyright
!= NULL
) {
2796 rb_refstring_unref (podcast
->copyright
);
2798 podcast
->copyright
= rb_refstring_new (g_value_get_string (value
));
2800 case RHYTHMDB_PROP_IMAGE
:
2802 if (podcast
->image
!= NULL
) {
2803 rb_refstring_unref (podcast
->image
);
2805 podcast
->image
= rb_refstring_new (g_value_get_string (value
));
2807 case RHYTHMDB_PROP_POST_TIME
:
2809 podcast
->post_time
= g_value_get_ulong (value
);
2811 case RHYTHMDB_NUM_PROPERTIES
:
2812 g_assert_not_reached ();
2817 /* set the dirty state */
2818 db
->priv
->dirty
= TRUE
;
2822 * rhythmdb_entry_sync_mirrored:
2824 * @type: a #RhythmDBEntry.
2825 * @propid: the property to sync the mirrored version of.
2827 * Synchronise "mirrored" properties, such as the string version of the last-played
2828 * time. This should be called when a property is directly modified, passing the
2829 * original property.
2831 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
2835 rhythmdb_entry_sync_mirrored (RhythmDBEntry
*entry
,
2838 static const char *format
;
2839 static const char *never
;
2842 /* PERFORMANCE: doing these lookups every call creates a noticable slowdown */
2844 format
= _("%Y-%m-%d %H:%M");
2849 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
2851 RBRefString
*old
, *new;
2853 if (!(entry
->flags
& RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY
))
2856 old
= g_atomic_pointer_get (&entry
->last_played_str
);
2857 if (entry
->last_played
== 0) {
2858 new = rb_refstring_new (never
);
2860 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->last_played
));
2861 new = rb_refstring_new (val
);
2865 if (g_atomic_pointer_compare_and_exchange (&entry
->last_played_str
, old
, new)) {
2867 rb_refstring_unref (old
);
2870 rb_refstring_unref (new);
2875 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
2877 RBRefString
*old
, *new;
2879 if (!(entry
->flags
& RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY
))
2882 old
= g_atomic_pointer_get (&entry
->first_seen_str
);
2883 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->first_seen
));
2884 new = rb_refstring_new (val
);
2887 if (g_atomic_pointer_compare_and_exchange (&entry
->first_seen_str
, old
, new)) {
2889 rb_refstring_unref (old
);
2892 rb_refstring_unref (new);
2897 case RHYTHMDB_PROP_LAST_SEEN_STR
:
2899 RBRefString
*old
, *new;
2901 if (!(entry
->flags
& RHYTHMDB_ENTRY_LAST_SEEN_DIRTY
))
2904 old
= g_atomic_pointer_get (&entry
->last_seen_str
);
2905 /* only store last seen time as a string for hidden entries */
2906 if (entry
->flags
& RHYTHMDB_ENTRY_HIDDEN
) {
2907 val
= eel_strdup_strftime (format
, localtime ((glong
*)&entry
->last_seen
));
2908 new = rb_refstring_new (val
);
2914 if (g_atomic_pointer_compare_and_exchange (&entry
->first_seen_str
, old
, new)) {
2916 rb_refstring_unref (old
);
2919 rb_refstring_unref (new);
2930 * rhythmdb_entry_delete:
2932 * @entry: a #RhythmDBEntry.
2934 * Delete entry @entry from the database, sending notification of it's deletion.
2935 * This is usually used by sources where entries can disappear randomly, such
2936 * as a network source.
2939 rhythmdb_entry_delete (RhythmDB
*db
,
2940 RhythmDBEntry
*entry
)
2942 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
2944 g_return_if_fail (RHYTHMDB_IS (db
));
2945 g_return_if_fail (entry
!= NULL
);
2947 klass
->impl_entry_delete (db
, entry
);
2949 /* ref the entry before adding to hash, it is unreffed when removed */
2950 rhythmdb_entry_ref (entry
);
2951 g_mutex_lock (db
->priv
->change_mutex
);
2952 g_hash_table_insert (db
->priv
->deleted_entries
, entry
, g_thread_self ());
2953 g_mutex_unlock (db
->priv
->change_mutex
);
2955 /* deleting an entry makes the db dirty */
2956 db
->priv
->dirty
= TRUE
;
2960 rhythmdb_entry_move_to_trash_cb (GnomeVFSXferProgressInfo
*info
,
2963 /* Abort immediately if anything happens */
2964 if (info
->status
== GNOME_VFS_XFER_PROGRESS_STATUS_VFSERROR
)
2965 return GNOME_VFS_XFER_ERROR_ACTION_ABORT
;
2966 /* Don't overwrite files */
2967 if (info
->status
== GNOME_VFS_XFER_PROGRESS_STATUS_OVERWRITE
)
2973 rhythmdb_entry_move_to_trash_set_error (RhythmDB
*db
,
2974 RhythmDBEntry
*entry
,
2977 GValue value
= { 0, };
2980 res
= GNOME_VFS_ERROR_INTERNAL
;
2982 g_value_init (&value
, G_TYPE_STRING
);
2983 g_value_set_string (&value
, gnome_vfs_result_to_string (res
));
2984 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_PLAYBACK_ERROR
, &value
);
2985 g_value_unset (&value
);
2987 rb_debug ("Deleting %s failed: %s", rb_refstring_get (entry
->location
),
2988 gnome_vfs_result_to_string (res
));
2992 rhythmdb_entry_move_to_trash (RhythmDB
*db
,
2993 RhythmDBEntry
*entry
)
2996 GnomeVFSURI
*uri
, *trash
, *dest
;
2999 uri
= gnome_vfs_uri_new (rb_refstring_get (entry
->location
));
3001 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3005 res
= gnome_vfs_find_directory (uri
,
3006 GNOME_VFS_DIRECTORY_KIND_TRASH
,
3010 if (res
!= GNOME_VFS_OK
|| trash
== NULL
) {
3011 /* If the file doesn't exist, or trash isn't support,
3012 * remove it from the db */
3013 if (res
== GNOME_VFS_ERROR_NOT_FOUND
||
3014 res
== GNOME_VFS_ERROR_NOT_SUPPORTED
) {
3015 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
3017 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3020 gnome_vfs_uri_unref (uri
);
3024 /* Is the file already in the Trash? If so it should be hidden */
3025 if (gnome_vfs_uri_is_parent (trash
, uri
, TRUE
)) {
3026 GValue value
= { 0, };
3027 g_value_init (&value
, G_TYPE_BOOLEAN
);
3028 g_value_set_boolean (&value
, TRUE
);
3029 rhythmdb_entry_set (db
, entry
, RHYTHMDB_PROP_HIDDEN
, &value
);
3030 rhythmdb_commit (db
);
3032 gnome_vfs_uri_unref (trash
);
3033 gnome_vfs_uri_unref (uri
);
3037 shortname
= gnome_vfs_uri_extract_short_name (uri
);
3038 if (shortname
== NULL
) {
3039 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3040 rhythmdb_commit (db
);
3041 gnome_vfs_uri_unref (uri
);
3042 gnome_vfs_uri_unref (trash
);
3046 /* Compute the destination URI */
3047 dest
= gnome_vfs_uri_append_path (trash
, shortname
);
3048 gnome_vfs_uri_unref (trash
);
3051 rhythmdb_entry_move_to_trash_set_error (db
, entry
, -1);
3052 rhythmdb_commit (db
);
3053 gnome_vfs_uri_unref (uri
);
3057 /* RB can't tell that a file's moved, so no unique names */
3058 res
= gnome_vfs_xfer_uri (uri
, dest
,
3059 GNOME_VFS_XFER_REMOVESOURCE
,
3060 GNOME_VFS_XFER_ERROR_MODE_ABORT
,
3061 GNOME_VFS_XFER_OVERWRITE_MODE_SKIP
,
3062 rhythmdb_entry_move_to_trash_cb
,
3065 if (res
== GNOME_VFS_OK
) {
3066 rhythmdb_entry_set_visibility (db
, entry
, FALSE
);
3068 rhythmdb_entry_move_to_trash_set_error (db
, entry
, res
);
3070 rhythmdb_commit (db
);
3072 gnome_vfs_uri_unref (dest
);
3073 gnome_vfs_uri_unref (uri
);
3077 * rhythmdb_entry_delete_by_type:
3079 * @type: type of entried to delete.
3081 * Delete all entries from the database of the given type.
3082 * This is usually used by non-permanent sources when they disappear, such as
3083 * removable media being removed, or a network share becoming unavailable.
3086 rhythmdb_entry_delete_by_type (RhythmDB
*db
,
3087 RhythmDBEntryType type
)
3089 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3091 if (klass
->impl_entry_delete_by_type
) {
3092 klass
->impl_entry_delete_by_type (db
, type
);
3094 g_warning ("delete_by_type not implemented");
3099 rhythmdb_nice_elt_name_from_propid (RhythmDB
*db
,
3100 RhythmDBPropType propid
)
3102 return db
->priv
->column_xml_names
[propid
];
3106 rhythmdb_propid_from_nice_elt_name (RhythmDB
*db
,
3107 const xmlChar
*name
)
3110 if (g_hash_table_lookup_extended (db
->priv
->propname_map
, name
,
3112 return GPOINTER_TO_INT (ret
);
3118 * rhythmdb_entry_lookup_by_location:
3120 * @uri: the URI of the entry to lookup.
3122 * Looks up the entry with location @uri.
3124 * Returns: the entry with location @uri, or NULL if no such entry exists.
3127 rhythmdb_entry_lookup_by_location (RhythmDB
*db
,
3132 rs
= rb_refstring_find (uri
);
3134 return rhythmdb_entry_lookup_by_location_refstring (db
, rs
);
3141 rhythmdb_entry_lookup_by_location_refstring (RhythmDB
*db
,
3144 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3146 return klass
->impl_lookup_by_location (db
, uri
);
3150 *rhythmdb_entry_foreach:
3152 * @func: the function to call with each entry.
3153 * @data: user data to pass to the function.
3155 * Calls the given function for each of the entries in the database.
3158 rhythmdb_entry_foreach (RhythmDB
*db
,
3162 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3164 klass
->impl_entry_foreach (db
, func
, data
);
3168 * rhythmdb_evaluate_query:
3171 * @entry a @RhythmDBEntry.
3173 * Evaluates the given entry against the given query.
3175 * Returns: whether the given entry matches the criteria of the given query.
3178 rhythmdb_evaluate_query (RhythmDB
*db
,
3180 RhythmDBEntry
*entry
)
3182 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3184 return klass
->impl_evaluate_query (db
, query
, entry
);
3188 rhythmdb_query_internal (RhythmDBQueryThreadData
*data
)
3190 RhythmDBEvent
*result
;
3191 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (data
->db
);
3193 rhythmdb_query_preprocess (data
->db
, data
->query
);
3195 rb_debug ("doing query");
3197 klass
->impl_do_full_query (data
->db
, data
->query
,
3201 rb_debug ("completed");
3202 rhythmdb_query_results_query_complete (data
->results
);
3204 result
= g_new0 (RhythmDBEvent
, 1);
3205 result
->db
= data
->db
;
3206 result
->type
= RHYTHMDB_EVENT_QUERY_COMPLETE
;
3207 result
->results
= data
->results
;
3208 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
3210 rhythmdb_query_free (data
->query
);
3214 query_thread_main (RhythmDBQueryThreadData
*data
)
3216 RhythmDBEvent
*result
;
3218 rb_debug ("entering query thread");
3220 rhythmdb_query_internal (data
);
3222 result
= g_new0 (RhythmDBEvent
, 1);
3223 result
->db
= data
->db
;
3224 result
->type
= RHYTHMDB_EVENT_THREAD_EXITED
;
3225 g_async_queue_push (data
->db
->priv
->event_queue
, result
);
3231 rhythmdb_do_full_query_async_parsed (RhythmDB
*db
,
3232 RhythmDBQueryResults
*results
,
3235 RhythmDBQueryThreadData
*data
;
3237 data
= g_new0 (RhythmDBQueryThreadData
, 1);
3239 data
->query
= rhythmdb_query_copy (query
);
3240 data
->results
= results
;
3241 data
->cancel
= FALSE
;
3243 rhythmdb_read_enter (db
);
3245 rhythmdb_query_results_set_query (results
, query
);
3247 g_object_ref (results
);
3249 g_atomic_int_inc (&db
->priv
->outstanding_threads
);
3250 g_async_queue_ref (db
->priv
->action_queue
);
3251 g_async_queue_ref (db
->priv
->event_queue
);
3252 g_thread_pool_push (db
->priv
->query_thread_pool
, data
, NULL
);
3256 rhythmdb_do_full_query_async (RhythmDB
*db
,
3257 RhythmDBQueryResults
*results
,
3263 va_start (args
, results
);
3265 query
= rhythmdb_query_parse_valist (db
, args
);
3267 rhythmdb_do_full_query_async_parsed (db
, results
, query
);
3269 rhythmdb_query_free (query
);
3275 rhythmdb_do_full_query_internal (RhythmDB
*db
,
3276 RhythmDBQueryResults
*results
,
3279 RhythmDBQueryThreadData
*data
;
3281 data
= g_new0 (RhythmDBQueryThreadData
, 1);
3283 data
->query
= rhythmdb_query_copy (query
);
3284 data
->results
= results
;
3285 data
->cancel
= FALSE
;
3287 rhythmdb_read_enter (db
);
3289 rhythmdb_query_results_set_query (results
, query
);
3290 g_object_ref (results
);
3292 rhythmdb_query_internal (data
);
3297 rhythmdb_do_full_query_parsed (RhythmDB
*db
,
3298 RhythmDBQueryResults
*results
,
3301 rhythmdb_do_full_query_internal (db
, results
, query
);
3305 rhythmdb_do_full_query (RhythmDB
*db
,
3306 RhythmDBQueryResults
*results
,
3312 va_start (args
, results
);
3314 query
= rhythmdb_query_parse_valist (db
, args
);
3316 rhythmdb_do_full_query_internal (db
, results
, query
);
3318 rhythmdb_query_free (query
);
3323 /* This should really be standard. */
3324 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
3327 rhythmdb_query_type_get_type (void)
3329 static GType etype
= 0;
3333 static const GEnumValue values
[] =
3336 ENUM_ENTRY (RHYTHMDB_QUERY_END
, "Query end marker"),
3337 ENUM_ENTRY (RHYTHMDB_QUERY_DISJUNCTION
, "Disjunctive marker"),
3338 ENUM_ENTRY (RHYTHMDB_QUERY_SUBQUERY
, "Subquery"),
3339 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_EQUALS
, "Property equivalence"),
3340 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LIKE
, "Fuzzy property matching"),
3341 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_LIKE
, "Inverted fuzzy property matching"),
3342 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_PREFIX
, "Starts with"),
3343 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_SUFFIX
, "Ends with"),
3344 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_GREATER
, "True if property1 >= property2"),
3345 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LESS
, "True if property1 <= property2"),
3346 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN
, "True if property1 is within property2 of the current time"),
3347 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN
, "True if property1 is not within property2 of the current time"),
3348 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_EQUALS
, "Year equivalence: true if date within year"),
3349 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_GREATER
, "True if date greater than year"),
3350 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_LESS
, "True if date less than year"),
3354 etype
= g_enum_register_static ("RhythmDBQueryType", values
);
3361 rhythmdb_prop_type_get_type (void)
3363 static GType etype
= 0;
3367 static const GEnumValue values
[] =
3369 /* We reuse the description to store extra data about
3370 * a property. The first part is just a generic
3371 * human-readable description. Next, there is
3372 * a string describing the GType of the property, in
3374 * Finally, there is the XML element name in brackets.
3376 ENUM_ENTRY (RHYTHMDB_PROP_TYPE
, "Type of entry (gpointer) [type]"),
3377 ENUM_ENTRY (RHYTHMDB_PROP_ENTRY_ID
, "Numeric ID (guint) [entry-id]"),
3378 ENUM_ENTRY (RHYTHMDB_PROP_TITLE
, "Title (gchararray) [title]"),
3379 ENUM_ENTRY (RHYTHMDB_PROP_GENRE
, "Genre (gchararray) [genre]"),
3380 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST
, "Artist (gchararray) [artist]"),
3381 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM
, "Album (gchararray) [album]"),
3382 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_NUMBER
, "Track Number (gulong) [track-number]"),
3383 ENUM_ENTRY (RHYTHMDB_PROP_DISC_NUMBER
, "Disc Number (gulong) [disc-number]"),
3384 ENUM_ENTRY (RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
, "Musicbrainz Track ID (gchararray) [mb-trackid]"),
3386 ENUM_ENTRY (RHYTHMDB_PROP_DURATION
, "Duration (gulong) [duration]"),
3387 ENUM_ENTRY (RHYTHMDB_PROP_FILE_SIZE
, "File Size (guint64) [file-size]"),
3388 ENUM_ENTRY (RHYTHMDB_PROP_LOCATION
, "Location (gchararray) [location]"),
3389 ENUM_ENTRY (RHYTHMDB_PROP_MOUNTPOINT
, "Mount point it's located in (gchararray) [mountpoint]"),
3390 ENUM_ENTRY (RHYTHMDB_PROP_MTIME
, "Modification time (gulong) [mtime]"),
3391 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN
, "Time the song was added to the library (gulong) [first-seen]"),
3392 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN
, "Last time the song was available (gulong) [last-seen]"),
3393 ENUM_ENTRY (RHYTHMDB_PROP_RATING
, "Rating (gdouble) [rating]"),
3394 ENUM_ENTRY (RHYTHMDB_PROP_PLAY_COUNT
, "Play Count (gulong) [play-count]"),
3395 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED
, "Last Played (gulong) [last-played]"),
3396 ENUM_ENTRY (RHYTHMDB_PROP_BITRATE
, "Bitrate (gulong) [bitrate]"),
3397 ENUM_ENTRY (RHYTHMDB_PROP_DATE
, "Date of release (gulong) [date]"),
3398 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_GAIN
, "Replaygain track gain (gdouble) [replaygain-track-gain]"),
3399 ENUM_ENTRY (RHYTHMDB_PROP_TRACK_PEAK
, "Replaygain track peak (gdouble) [replaygain-track-peak]"),
3400 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_GAIN
, "Replaygain album pain (gdouble) [replaygain-album-gain]"),
3401 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_PEAK
, "Replaygain album peak (gdouble) [replaygain-album-peak]"),
3402 ENUM_ENTRY (RHYTHMDB_PROP_MIMETYPE
, "Mime Type (gchararray) [mimetype]"),
3403 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_SORT_KEY
, "Title sort key (gchararray) [title-sort-key]"),
3404 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_SORT_KEY
, "Genre sort key (gchararray) [genre-sort-key]"),
3405 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_SORT_KEY
, "Artist sort key (gchararray) [artist-sort-key]"),
3406 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_SORT_KEY
, "Album sort key (gchararray) [album-sort-key]"),
3408 ENUM_ENTRY (RHYTHMDB_PROP_TITLE_FOLDED
, "Title folded (gchararray) [title-folded]"),
3409 ENUM_ENTRY (RHYTHMDB_PROP_GENRE_FOLDED
, "Genre folded (gchararray) [genre-folded]"),
3410 ENUM_ENTRY (RHYTHMDB_PROP_ARTIST_FOLDED
, "Artist folded (gchararray) [artist-folded]"),
3411 ENUM_ENTRY (RHYTHMDB_PROP_ALBUM_FOLDED
, "Album folded (gchararray) [album-folded]"),
3412 ENUM_ENTRY (RHYTHMDB_PROP_LAST_PLAYED_STR
, "Last Played (gchararray) [last-played-str]"),
3413 ENUM_ENTRY (RHYTHMDB_PROP_PLAYBACK_ERROR
, "Playback error string (gchararray) [playback-error]"),
3414 ENUM_ENTRY (RHYTHMDB_PROP_HIDDEN
, "Hidden (gboolean) [hidden]"),
3415 ENUM_ENTRY (RHYTHMDB_PROP_FIRST_SEEN_STR
, "Time Added to Library (gchararray) [first-seen-str]"),
3416 ENUM_ENTRY (RHYTHMDB_PROP_LAST_SEEN_STR
, "Last time the song was available (gchararray) [last-seen-str]"),
3417 ENUM_ENTRY (RHYTHMDB_PROP_SEARCH_MATCH
, "Search matching key (gchararray) [search-match]"),
3418 ENUM_ENTRY (RHYTHMDB_PROP_YEAR
, "Year of date (gulong) [year]"),
3420 ENUM_ENTRY (RHYTHMDB_PROP_STATUS
, "Status of file (gulong) [status]"),
3421 ENUM_ENTRY (RHYTHMDB_PROP_DESCRIPTION
, "Podcast description(gchararray) [description]"),
3422 ENUM_ENTRY (RHYTHMDB_PROP_SUBTITLE
, "Podcast subtitle (gchararray) [subtitle]"),
3423 ENUM_ENTRY (RHYTHMDB_PROP_SUMMARY
, "Podcast summary (gchararray) [summary]"),
3424 ENUM_ENTRY (RHYTHMDB_PROP_LANG
, "Podcast language (gchararray) [lang]"),
3425 ENUM_ENTRY (RHYTHMDB_PROP_COPYRIGHT
, "Podcast copyright (gchararray) [copyright]"),
3426 ENUM_ENTRY (RHYTHMDB_PROP_IMAGE
, "Podcast image(gchararray) [image]"),
3427 ENUM_ENTRY (RHYTHMDB_PROP_POST_TIME
, "Podcast time of post (gulong) [post-time]"),
3430 g_assert ((sizeof (values
) / sizeof (values
[0]) - 1) == RHYTHMDB_NUM_PROPERTIES
);
3431 etype
= g_enum_register_static ("RhythmDBPropType", values
);
3438 rhythmdb_emit_entry_deleted (RhythmDB
*db
,
3439 RhythmDBEntry
*entry
)
3441 g_signal_emit (G_OBJECT (db
), rhythmdb_signals
[ENTRY_DELETED
], 0, entry
);
3445 rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint
*ihint
,
3446 GValue
*return_accu
,
3447 const GValue
*handler_return
,
3450 if (handler_return
== NULL
)
3453 g_value_copy (handler_return
, return_accu
);
3454 return (g_value_get_boxed (return_accu
) == NULL
);
3458 * rhythmdb_entry_request_extra_metadata:
3460 * @entry: a #RhythmDBEntry
3461 * @property_name: the metadata predicate
3463 * Emits a request for extra metadata for the @entry.
3464 * The @property_name argument is emitted as the ::detail part of the
3465 * "entry_extra_metadata_request" signal. It should be a namespaced RDF
3466 * predicate e.g. from Dublin Core, MusicBrainz, or internal to Rhythmbox
3467 * (namespace "rb:"). Suitable predicates would be those that are expensive to
3468 * acquire or only apply to a limited range of entries.
3469 * Handlers capable of providing a particular predicate may ensure they only
3470 * see appropriate requests by supplying an appropriate ::detail part when
3471 * connecting to the signal. Upon a handler returning a non-%NULL value,
3472 * emission will be stopped and the value returned to the caller; if no
3473 * handlers return a non-%NULL value, the caller will receive %NULL. Priority
3474 * is determined by signal connection order, with %G_CONNECT_AFTER providing a
3475 * second, lower rank of priority.
3476 * A handler returning a value should do so in a #GValue allocated on the heap;
3477 * the accumulator will take ownership. The caller should unset and free the
3478 * #GValue if non-%NULL when finished with it.
3480 * Returns: an allocated, initialised, set #GValue, or NULL
3483 rhythmdb_entry_request_extra_metadata (RhythmDB
*db
,
3484 RhythmDBEntry
*entry
,
3485 const gchar
*property_name
)
3487 GValue
*value
= NULL
;
3489 g_signal_emit (G_OBJECT (db
),
3490 rhythmdb_signals
[ENTRY_EXTRA_METADATA_REQUEST
],
3491 g_quark_from_string (property_name
),
3499 * rhythmdb_emit_entry_extra_metadata_notify:
3501 * @entry: a #RhythmDBEntry
3502 * @property_name: the metadata predicate
3503 * @metadata: a #GValue
3505 * Emits a signal describing extra metadata for the @entry. The @property_name
3506 * argument is emitted as the ::detail part of the
3507 * "entry_extra_metadata_notify" signal and as the 'field' parameter. Handlers
3508 * can ensure they only get metadata they are interested in by supplying an
3509 * appropriate ::detail part when connecting to the signal. If handlers are
3510 * interested in the metadata they should ref or copy the contents of @metadata
3511 * and unref or free it when they are finished with it.
3514 rhythmdb_emit_entry_extra_metadata_notify (RhythmDB
*db
,
3515 RhythmDBEntry
*entry
,
3516 const gchar
*property_name
,
3517 const GValue
*metadata
)
3519 g_signal_emit (G_OBJECT (db
),
3520 rhythmdb_signals
[ENTRY_EXTRA_METADATA_NOTIFY
],
3521 g_quark_from_string (property_name
),
3528 unset_and_free_g_value (gpointer valpointer
)
3530 GValue
*value
= valpointer
;
3531 g_value_unset (value
);
3536 * rhythmdb_entry_extra_gather:
3538 * @entry: a #RhythmDBEntry
3540 * Gathers all metadata for the @entry. The returned GHashTable maps property
3541 * names and extra metadata names (described under
3542 * @rhythmdb_entry_request_extra_metadata) to GValues. Anything wanting to
3543 * provide extra metadata should connect to the "entry_extra_metadata_gather"
3546 * Returns: a GHashTable containing metadata for the entry. This must be freed
3547 * using g_hash_table_destroy.
3550 rhythmdb_entry_gather_metadata (RhythmDB
*db
,
3551 RhythmDBEntry
*entry
)
3553 GHashTable
*metadata
;
3557 metadata
= g_hash_table_new_full (g_str_hash
,
3560 unset_and_free_g_value
);
3562 /* add core properties */
3563 klass
= g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE
);
3564 for (i
= 0; i
< klass
->n_values
; i
++) {
3570 prop
= klass
->values
[i
].value
;
3572 /* only include easily marshallable types in the hash table */
3573 value_type
= rhythmdb_get_property_type (db
, prop
);
3574 switch (value_type
) {
3576 case G_TYPE_BOOLEAN
:
3585 value
= g_new0 (GValue
, 1);
3586 g_value_init (value
, value_type
);
3587 rhythmdb_entry_get (db
, entry
, prop
, value
);
3588 name
= (char *)rhythmdb_nice_elt_name_from_propid (db
, prop
);
3589 g_hash_table_insert (metadata
,
3590 (gpointer
) g_strdup (name
),
3593 g_type_class_unref (klass
);
3595 /* gather extra metadata */
3596 g_signal_emit (G_OBJECT (db
),
3597 rhythmdb_signals
[ENTRY_EXTRA_METADATA_GATHER
], 0,
3605 queue_is_empty (GAsyncQueue
*queue
)
3607 return g_async_queue_length (queue
) <= 0;
3614 * Returns: whether the #RhythmDB has events to process.
3617 rhythmdb_is_busy (RhythmDB
*db
)
3619 return (!db
->priv
->action_thread_running
|| !queue_is_empty (db
->priv
->event_queue
));
3623 * rhythmdb_compute_status_normal:
3624 * @n_songs: the number of tracks.
3625 * @duration: the total duration of the tracks.
3626 * @size: the total size of the tracks.
3627 * @singular: singular form of the format string to use for entries (eg "%d song")
3628 * @plural: plural form of the format string to use for entries (eg "%d songs")
3630 * Creates a string containing the "status" information about a list of tracks.
3631 * The singular and plural strings must be used in a direct ngettext call
3632 * elsewhere in order for them to be marked for translation correctly.
3634 * Returns: the string, which should be freed with g_free.
3637 rhythmdb_compute_status_normal (gint n_songs
,
3640 const char *singular
,
3643 long days
, hours
, minutes
, seconds
;
3644 char *songcount
= NULL
;
3646 char *size_str
= NULL
;
3648 const char *minutefmt
;
3649 const char *hourfmt
;
3652 songcount
= g_strdup_printf (ngettext (singular
, plural
, n_songs
), n_songs
);
3654 days
= duration
/ (60 * 60 * 24);
3655 hours
= (duration
/ (60 * 60)) - (days
* 24);
3656 minutes
= (duration
/ 60) - ((days
* 24 * 60) + (hours
* 60));
3657 seconds
= duration
% 60;
3659 minutefmt
= ngettext ("%ld minute", "%ld minutes", minutes
);
3660 hourfmt
= ngettext ("%ld hour", "%ld hours", hours
);
3661 dayfmt
= ngettext ("%ld day", "%ld days", days
);
3666 /* Translators: the format is "X days, X hours and X minutes" */
3667 fmt
= g_strdup_printf (_("%s, %s and %s"), dayfmt
, hourfmt
, minutefmt
);
3668 time
= g_strdup_printf (fmt
, days
, hours
, minutes
);
3672 /* Translators: the format is "X days and X hours" */
3673 fmt
= g_strdup_printf (_("%s and %s"), dayfmt
, hourfmt
);
3674 time
= g_strdup_printf (fmt
, days
, hours
);
3680 /* Translators: the format is "X days and X minutes" */
3681 fmt
= g_strdup_printf (_("%s and %s"), dayfmt
, minutefmt
);
3682 time
= g_strdup_printf (fmt
, days
, minutes
);
3685 time
= g_strdup_printf (dayfmt
, days
);
3691 /* Translators: the format is "X hours and X minutes" */
3692 fmt
= g_strdup_printf (_("%s and %s"), hourfmt
, minutefmt
);
3693 time
= g_strdup_printf (fmt
, hours
, minutes
);
3696 time
= g_strdup_printf (hourfmt
, hours
);
3700 time
= g_strdup_printf (minutefmt
, minutes
);
3704 size_str
= gnome_vfs_format_file_size_for_display (size
);
3706 if (size
> 0 && duration
> 0) {
3707 ret
= g_strdup_printf ("%s, %s, %s", songcount
, time
, size_str
);
3708 } else if (duration
> 0) {
3709 ret
= g_strdup_printf ("%s, %s", songcount
, time
);
3710 } else if (size
> 0) {
3711 ret
= g_strdup_printf ("%s, %s", songcount
, size_str
);
3713 ret
= g_strdup (songcount
);
3724 default_sync_metadata (RhythmDB
*db
,
3725 RhythmDBEntry
*entry
,
3730 GError
*local_error
= NULL
;
3732 uri
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_LOCATION
);
3733 rb_metadata_load (db
->priv
->metadata
,
3735 if (local_error
!= NULL
) {
3736 g_propagate_error (error
, local_error
);
3740 entry_to_rb_metadata (db
, entry
, db
->priv
->metadata
);
3742 rb_metadata_save (db
->priv
->metadata
, &local_error
);
3743 if (local_error
!= NULL
) {
3744 RhythmDBAction
*load_action
;
3746 /* reload the metadata, to revert the db changes */
3747 load_action
= g_new0 (RhythmDBAction
, 1);
3748 load_action
->type
= RHYTHMDB_ACTION_LOAD
;
3749 load_action
->uri
= rb_refstring_ref (entry
->location
);
3750 load_action
->entry_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3751 g_async_queue_push (db
->priv
->action_queue
, load_action
);
3753 g_propagate_error (error
, local_error
);
3758 * rhythmdb_entry_register_type:
3760 * @name: optional name for the entry type
3762 * Registers a new #RhythmDBEntryType. This should be called to create a new
3763 * entry type for non-permanent sources.
3765 * Returns: the new #RhythmDBEntryType.
3768 rhythmdb_entry_register_type (RhythmDB
*db
,
3771 RhythmDBEntryType type
;
3772 RhythmDBClass
*klass
= RHYTHMDB_GET_CLASS (db
);
3774 type
= g_new0 (RhythmDBEntryType_
, 1);
3775 type
->can_sync_metadata
= (RhythmDBEntryCanSyncFunc
)rb_false_function
;
3776 type
->sync_metadata
= default_sync_metadata
;
3778 type
->name
= g_strdup (name
);
3779 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
3780 g_hash_table_insert (db
->priv
->entry_type_map
, g_strdup (type
->name
), type
);
3781 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
3784 if (klass
->impl_entry_type_registered
)
3785 klass
->impl_entry_type_registered (db
, name
, type
);
3791 rhythmdb_entry_register_type_alias (RhythmDB
*db
,
3792 RhythmDBEntryType type
,
3795 char *dn
= g_strdup (name
);
3797 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
3798 g_hash_table_insert (db
->priv
->entry_type_map
, dn
, type
);
3799 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
3805 } RhythmDBEntryTypeForeachData
;
3808 rhythmdb_entry_type_foreach_cb (const char *name
,
3809 RhythmDBEntryType entry_type
,
3810 RhythmDBEntryTypeForeachData
*data
)
3813 if (strcmp (entry_type
->name
, name
))
3816 data
->func ((gpointer
) name
, entry_type
, data
->data
);
3820 * rhythmdb_entry_type_foreach:
3822 * @func: callback function to call for each registered entry type
3823 * @data: data to pass to the callback
3825 * Calls a function for each registered entry type.
3828 rhythmdb_entry_type_foreach (RhythmDB
*db
,
3832 RhythmDBEntryTypeForeachData d
;
3837 g_mutex_lock (db
->priv
->entry_type_mutex
);
3838 g_hash_table_foreach (db
->priv
->entry_type_map
,
3839 (GHFunc
) rhythmdb_entry_type_foreach_cb
,
3841 g_mutex_unlock (db
->priv
->entry_type_mutex
);
3845 * rhythmdb_entry_type_get_by_name:
3847 * @name: name of the type to look for
3849 * Locates a #RhythmDBEntryType by name. Returns
3850 * RHYTHMDB_ENTRY_TYPE_INVALID if no entry type
3851 * is registered with the specified name.
3853 * Returns: the #RhythmDBEntryType
3856 rhythmdb_entry_type_get_by_name (RhythmDB
*db
,
3861 g_mutex_lock (db
->priv
->entry_type_map_mutex
);
3862 if (db
->priv
->entry_type_map
) {
3863 t
= g_hash_table_lookup (db
->priv
->entry_type_map
, name
);
3865 g_mutex_unlock (db
->priv
->entry_type_map_mutex
);
3868 return (RhythmDBEntryType
) t
;
3870 return RHYTHMDB_ENTRY_TYPE_INVALID
;
3874 song_can_sync_metadata (RhythmDB
*db
,
3875 RhythmDBEntry
*entry
,
3878 const char *mimetype
;
3880 mimetype
= rhythmdb_entry_get_string (entry
, RHYTHMDB_PROP_MIMETYPE
);
3881 return rb_metadata_can_save (db
->priv
->metadata
, mimetype
);
3885 podcast_get_playback_uri (RhythmDBEntry
*entry
,
3888 return rhythmdb_entry_dup_string (entry
, RHYTHMDB_PROP_MOUNTPOINT
);
3892 podcast_data_destroy (RhythmDBEntry
*entry
,
3895 RhythmDBPodcastFields
*podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
3896 rb_refstring_unref (podcast
->description
);
3897 rb_refstring_unref (podcast
->subtitle
);
3898 rb_refstring_unref (podcast
->summary
);
3899 rb_refstring_unref (podcast
->lang
);
3900 rb_refstring_unref (podcast
->copyright
);
3901 rb_refstring_unref (podcast
->image
);
3904 static RhythmDBEntryType song_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3905 static RhythmDBEntryType ignore_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3906 static RhythmDBEntryType import_error_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3909 static RhythmDBEntryType podcast_post_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3910 static RhythmDBEntryType podcast_feed_type
= RHYTHMDB_ENTRY_TYPE_INVALID
;
3913 rhythmdb_register_core_entry_types (RhythmDB
*db
)
3916 song_type
= rhythmdb_entry_register_type (db
, "song");
3917 rhythmdb_entry_register_type_alias (db
, song_type
, "0");
3918 song_type
->save_to_disk
= TRUE
;
3919 song_type
->category
= RHYTHMDB_ENTRY_NORMAL
;
3920 song_type
->can_sync_metadata
= song_can_sync_metadata
;
3923 import_error_type
= rhythmdb_entry_register_type (db
, "import-error");
3924 import_error_type
->get_playback_uri
= (RhythmDBEntryStringFunc
)rb_null_function
;
3925 import_error_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
3928 ignore_type
= rhythmdb_entry_register_type (db
, "ignore");
3929 ignore_type
->save_to_disk
= TRUE
;
3930 ignore_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
3933 podcast_post_type
= rhythmdb_entry_register_type (db
, "podcast-post");
3934 podcast_post_type
->entry_type_data_size
= sizeof (RhythmDBPodcastFields
);
3935 podcast_post_type
->save_to_disk
= TRUE
;
3936 podcast_post_type
->category
= RHYTHMDB_ENTRY_NORMAL
;
3937 podcast_post_type
->pre_entry_destroy
= (RhythmDBEntryActionFunc
) podcast_data_destroy
;
3938 podcast_post_type
->get_playback_uri
= podcast_get_playback_uri
;
3941 podcast_feed_type
= rhythmdb_entry_register_type (db
, "podcast-feed");
3942 podcast_feed_type
->entry_type_data_size
= sizeof (RhythmDBPodcastFields
);
3943 podcast_feed_type
->save_to_disk
= TRUE
;
3944 podcast_feed_type
->category
= RHYTHMDB_ENTRY_VIRTUAL
;
3945 podcast_feed_type
->pre_entry_destroy
= (RhythmDBEntryActionFunc
) podcast_data_destroy
;
3949 rhythmdb_entry_song_get_type (void)
3955 rhythmdb_entry_ignore_get_type (void)
3961 rhythmdb_entry_import_error_get_type (void)
3963 return import_error_type
;
3967 rhythmdb_entry_podcast_post_get_type (void)
3969 return podcast_post_type
;
3973 rhythmdb_entry_podcast_feed_get_type (void)
3975 return podcast_feed_type
;
3979 rhythmdb_entry_set_mount_point (RhythmDB
*db
,
3980 RhythmDBEntry
*entry
,
3981 const gchar
*realuri
)
3984 GValue value
= {0, };
3986 mount_point
= rb_uri_get_mount_point (realuri
);
3987 if (mount_point
!= NULL
) {
3988 g_value_init (&value
, G_TYPE_STRING
);
3989 g_value_set_string_take_ownership (&value
, mount_point
);
3990 rhythmdb_entry_set_internal (db
, entry
, FALSE
,
3991 RHYTHMDB_PROP_MOUNTPOINT
,
3993 g_value_unset (&value
);
3998 rhythmdb_entry_set_visibility (RhythmDB
*db
,
3999 RhythmDBEntry
*entry
,
4002 GValue old_val
= {0, };
4003 gboolean old_visible
;
4005 g_return_if_fail (RHYTHMDB_IS (db
));
4006 g_return_if_fail (entry
!= NULL
);
4008 g_value_init (&old_val
, G_TYPE_BOOLEAN
);
4010 rhythmdb_entry_get (db
, entry
, RHYTHMDB_PROP_HIDDEN
, &old_val
);
4011 old_visible
= !g_value_get_boolean (&old_val
);
4013 if ((old_visible
&& !visible
) || (!old_visible
&& visible
)) {
4014 GValue new_val
= {0, };
4016 g_value_init (&new_val
, G_TYPE_BOOLEAN
);
4017 g_value_set_boolean (&new_val
, !visible
);
4018 rhythmdb_entry_set_internal (db
, entry
, TRUE
,
4019 RHYTHMDB_PROP_HIDDEN
, &new_val
);
4020 g_value_unset (&new_val
);
4022 g_value_unset (&old_val
);
4026 rhythmdb_idle_save (RhythmDB
*db
)
4028 if (db
->priv
->dirty
) {
4029 rb_debug ("database is dirty, doing regular save");
4030 rhythmdb_save_async (db
);
4037 rhythmdb_sync_library_location (RhythmDB
*db
)
4039 gboolean reload
= (db
->priv
->library_locations
!= NULL
);
4041 if (db
->priv
->library_location_notify_id
== 0) {
4042 db
->priv
->library_location_notify_id
=
4043 eel_gconf_notification_add (CONF_LIBRARY_LOCATION
,
4044 (GConfClientNotifyFunc
) library_location_changed_cb
,
4049 rb_debug ("ending monitor of old library directories");
4051 rhythmdb_stop_monitoring (db
);
4053 g_slist_foreach (db
->priv
->library_locations
, (GFunc
) g_free
, NULL
);
4054 g_slist_free (db
->priv
->library_locations
);
4055 db
->priv
->library_locations
= NULL
;
4058 if (eel_gconf_get_boolean (CONF_MONITOR_LIBRARY
)) {
4059 db
->priv
->library_locations
= eel_gconf_get_string_list (CONF_LIBRARY_LOCATION
);
4061 rhythmdb_start_monitoring (db
);
4066 library_location_changed_cb (GConfClient
*client
,
4071 rhythmdb_sync_library_location (db
);
4075 rhythmdb_entry_dup_string (RhythmDBEntry
*entry
,
4076 RhythmDBPropType propid
)
4080 g_return_val_if_fail (entry
!= NULL
, NULL
);
4082 s
= rhythmdb_entry_get_string (entry
, propid
);
4084 return g_strdup (s
);
4091 rhythmdb_entry_get_string (RhythmDBEntry
*entry
,
4092 RhythmDBPropType propid
)
4094 RhythmDBPodcastFields
*podcast
= NULL
;
4096 g_return_val_if_fail (entry
!= NULL
, NULL
);
4097 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
4099 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
4100 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
4101 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
4103 rhythmdb_entry_sync_mirrored (entry
, propid
);
4106 case RHYTHMDB_PROP_TITLE
:
4107 return rb_refstring_get (entry
->title
);
4108 case RHYTHMDB_PROP_ALBUM
:
4109 return rb_refstring_get (entry
->album
);
4110 case RHYTHMDB_PROP_ARTIST
:
4111 return rb_refstring_get (entry
->artist
);
4112 case RHYTHMDB_PROP_GENRE
:
4113 return rb_refstring_get (entry
->genre
);
4114 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
4115 return rb_refstring_get (entry
->musicbrainz_trackid
);
4116 case RHYTHMDB_PROP_MIMETYPE
:
4117 return rb_refstring_get (entry
->mimetype
);
4118 case RHYTHMDB_PROP_TITLE_SORT_KEY
:
4119 return rb_refstring_get_sort_key (entry
->title
);
4120 case RHYTHMDB_PROP_ALBUM_SORT_KEY
:
4121 return rb_refstring_get_sort_key (entry
->album
);
4122 case RHYTHMDB_PROP_ARTIST_SORT_KEY
:
4123 return rb_refstring_get_sort_key (entry
->artist
);
4124 case RHYTHMDB_PROP_GENRE_SORT_KEY
:
4125 return rb_refstring_get_sort_key (entry
->genre
);
4126 case RHYTHMDB_PROP_TITLE_FOLDED
:
4127 return rb_refstring_get_folded (entry
->title
);
4128 case RHYTHMDB_PROP_ALBUM_FOLDED
:
4129 return rb_refstring_get_folded (entry
->album
);
4130 case RHYTHMDB_PROP_ARTIST_FOLDED
:
4131 return rb_refstring_get_folded (entry
->artist
);
4132 case RHYTHMDB_PROP_GENRE_FOLDED
:
4133 return rb_refstring_get_folded (entry
->genre
);
4134 case RHYTHMDB_PROP_LOCATION
:
4135 return rb_refstring_get (entry
->location
);
4136 case RHYTHMDB_PROP_MOUNTPOINT
:
4137 return rb_refstring_get (entry
->mountpoint
);
4138 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
4139 return rb_refstring_get (entry
->last_played_str
);
4140 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
4141 return rb_refstring_get (entry
->playback_error
);
4142 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
4143 return rb_refstring_get (entry
->first_seen_str
);
4144 case RHYTHMDB_PROP_LAST_SEEN_STR
:
4145 return rb_refstring_get (entry
->last_seen_str
);
4146 case RHYTHMDB_PROP_SEARCH_MATCH
:
4147 return NULL
; /* synthetic property */
4148 /* Podcast properties */
4149 case RHYTHMDB_PROP_DESCRIPTION
:
4151 return rb_refstring_get (podcast
->description
);
4154 case RHYTHMDB_PROP_SUBTITLE
:
4156 return rb_refstring_get (podcast
->subtitle
);
4159 case RHYTHMDB_PROP_SUMMARY
:
4161 return rb_refstring_get (podcast
->summary
);
4164 case RHYTHMDB_PROP_LANG
:
4166 return rb_refstring_get (podcast
->lang
);
4169 case RHYTHMDB_PROP_COPYRIGHT
:
4171 return rb_refstring_get (podcast
->copyright
);
4174 case RHYTHMDB_PROP_IMAGE
:
4176 return rb_refstring_get (podcast
->image
);
4181 g_assert_not_reached ();
4187 rhythmdb_entry_get_refstring (RhythmDBEntry
*entry
,
4188 RhythmDBPropType propid
)
4190 g_return_val_if_fail (entry
!= NULL
, NULL
);
4191 g_return_val_if_fail (entry
->refcount
> 0, NULL
);
4193 rhythmdb_entry_sync_mirrored (entry
, propid
);
4196 case RHYTHMDB_PROP_TITLE
:
4197 return rb_refstring_ref (entry
->title
);
4198 case RHYTHMDB_PROP_ALBUM
:
4199 return rb_refstring_ref (entry
->album
);
4200 case RHYTHMDB_PROP_ARTIST
:
4201 return rb_refstring_ref (entry
->artist
);
4202 case RHYTHMDB_PROP_GENRE
:
4203 return rb_refstring_ref (entry
->genre
);
4204 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID
:
4205 return rb_refstring_ref (entry
->musicbrainz_trackid
);
4206 case RHYTHMDB_PROP_MIMETYPE
:
4207 return rb_refstring_ref (entry
->mimetype
);
4208 case RHYTHMDB_PROP_MOUNTPOINT
:
4209 return rb_refstring_ref (entry
->mountpoint
);
4210 case RHYTHMDB_PROP_LAST_PLAYED_STR
:
4211 return rb_refstring_ref (entry
->last_played_str
);
4212 case RHYTHMDB_PROP_FIRST_SEEN_STR
:
4213 return rb_refstring_ref (entry
->first_seen_str
);
4214 case RHYTHMDB_PROP_LAST_SEEN_STR
:
4215 return rb_refstring_ref (entry
->last_seen_str
);
4216 case RHYTHMDB_PROP_LOCATION
:
4217 return rb_refstring_ref (entry
->location
);
4218 case RHYTHMDB_PROP_PLAYBACK_ERROR
:
4219 return rb_refstring_ref (entry
->playback_error
);
4221 g_assert_not_reached ();
4227 rhythmdb_entry_get_boolean (RhythmDBEntry
*entry
,
4228 RhythmDBPropType propid
)
4230 g_return_val_if_fail (entry
!= NULL
, FALSE
);
4233 case RHYTHMDB_PROP_HIDDEN
:
4234 return ((entry
->flags
& RHYTHMDB_ENTRY_HIDDEN
) != 0);
4236 g_assert_not_reached ();
4242 rhythmdb_entry_get_uint64 (RhythmDBEntry
*entry
,
4243 RhythmDBPropType propid
)
4245 g_return_val_if_fail (entry
!= NULL
, 0);
4248 case RHYTHMDB_PROP_FILE_SIZE
:
4249 return entry
->file_size
;
4251 g_assert_not_reached ();
4257 rhythmdb_entry_get_entry_type (RhythmDBEntry
*entry
)
4259 g_return_val_if_fail (entry
!= NULL
, RHYTHMDB_ENTRY_TYPE_INVALID
);
4265 rhythmdb_entry_get_pointer (RhythmDBEntry
*entry
,
4266 RhythmDBPropType propid
)
4268 g_return_val_if_fail (entry
!= NULL
, NULL
);
4271 case RHYTHMDB_PROP_TYPE
:
4274 g_assert_not_reached ();
4280 rhythmdb_entry_get_ulong (RhythmDBEntry
*entry
,
4281 RhythmDBPropType propid
)
4283 RhythmDBPodcastFields
*podcast
= NULL
;
4285 g_return_val_if_fail (entry
!= NULL
, 0);
4287 if (entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_FEED
||
4288 entry
->type
== RHYTHMDB_ENTRY_TYPE_PODCAST_POST
)
4289 podcast
= RHYTHMDB_ENTRY_GET_TYPE_DATA (entry
, RhythmDBPodcastFields
);
4292 case RHYTHMDB_PROP_ENTRY_ID
:
4294 case RHYTHMDB_PROP_TRACK_NUMBER
:
4295 return entry
->tracknum
;
4296 case RHYTHMDB_PROP_DISC_NUMBER
:
4297 return entry
->discnum
;
4298 case RHYTHMDB_PROP_DURATION
:
4299 return entry
->duration
;
4300 case RHYTHMDB_PROP_MTIME
:
4301 return entry
->mtime
;
4302 case RHYTHMDB_PROP_FIRST_SEEN
:
4303 return entry
->first_seen
;
4304 case RHYTHMDB_PROP_LAST_SEEN
:
4305 return entry
->last_seen
;
4306 case RHYTHMDB_PROP_LAST_PLAYED
:
4307 return entry
->last_played
;
4308 case RHYTHMDB_PROP_PLAY_COUNT
:
4309 return entry
->play_count
;
4310 case RHYTHMDB_PROP_BITRATE
:
4311 return entry
->bitrate
;
4312 case RHYTHMDB_PROP_DATE
:
4313 if (g_date_valid (&entry
->date
))
4314 return g_date_get_julian (&entry
->date
);
4317 case RHYTHMDB_PROP_YEAR
:
4318 if (g_date_valid (&entry
->date
))
4319 return g_date_get_year (&entry
->date
);
4322 case RHYTHMDB_PROP_POST_TIME
:
4324 return podcast
->post_time
;
4327 case RHYTHMDB_PROP_STATUS
:
4329 return podcast
->status
;
4333 g_assert_not_reached ();
4339 rhythmdb_entry_get_double (RhythmDBEntry
*entry
,
4340 RhythmDBPropType propid
)
4342 g_return_val_if_fail (entry
!= NULL
, 0);
4345 case RHYTHMDB_PROP_TRACK_GAIN
:
4346 return entry
->track_gain
;
4347 case RHYTHMDB_PROP_TRACK_PEAK
:
4348 return entry
->track_peak
;
4349 case RHYTHMDB_PROP_ALBUM_GAIN
:
4350 return entry
->album_gain
;
4351 case RHYTHMDB_PROP_ALBUM_PEAK
:
4352 return entry
->album_peak
;
4353 case RHYTHMDB_PROP_RATING
:
4354 return entry
->rating
;
4356 g_assert_not_reached ();
4362 rhythmdb_entry_get_playback_uri (RhythmDBEntry
*entry
)
4364 RhythmDBEntryType type
;
4366 g_return_val_if_fail (entry
!= NULL
, NULL
);
4368 type
= rhythmdb_entry_get_entry_type (entry
);
4369 if (type
->get_playback_uri
)
4370 return (type
->get_playback_uri
) (entry
, type
->get_playback_uri_data
);
4372 return rhythmdb_entry_dup_string (entry
, RHYTHMDB_PROP_LOCATION
);
4376 rhythmdb_get_property_type (RhythmDB
*db
,
4379 g_assert (property_id
>= 0 && property_id
< RHYTHMDB_NUM_PROPERTIES
);
4380 return rhythmdb_property_type_map
[property_id
];
4384 rhythmdb_entry_get_type (void)
4386 static GType type
= 0;
4388 if (G_UNLIKELY (type
== 0)) {
4389 type
= g_boxed_type_register_static ("RhythmDBEntry",
4390 (GBoxedCopyFunc
)rhythmdb_entry_ref
,
4391 (GBoxedFreeFunc
)rhythmdb_entry_unref
);
4398 rhythmdb_entry_type_get_type (void)
4400 static GType type
= 0;
4402 if (G_UNLIKELY (type
== 0)) {
4403 type
= g_boxed_type_register_static ("RhythmDBEntryType",
4404 (GBoxedCopyFunc
)rb_copy_function
,
4405 (GBoxedFreeFunc
)rb_null_function
);