Location | Tool | Test ID | Function | Issue |
---|---|---|---|---|
rhythmdb.c:770:9 | clang-analyzer | Access to field 'message' results in a dereference of a null pointer (loaded from variable 'access_error') | ||
rhythmdb.c:770:9 | clang-analyzer | Access to field 'message' results in a dereference of a null pointer (loaded from variable 'access_error') | ||
rhythmdb.c:3705:2 | clang-analyzer | Access to field 'impl_entry_delete' results in a dereference of a null pointer (loaded from variable 'klass') | ||
rhythmdb.c:3705:2 | clang-analyzer | Access to field 'impl_entry_delete' results in a dereference of a null pointer (loaded from variable 'klass') |
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
2 *
3 * Copyright (C) 2003,2004 Colin Walters <walters@gnome.org>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * The Rhythmbox authors hereby grant permission for non-GPL compatible
11 * GStreamer plugins to be used and distributed together with GStreamer
12 * and Rhythmbox. This permission is above and beyond the permissions granted
13 * by the GPL license by which Rhythmbox is covered. If you modify this code
14 * you may extend this exception to your version of the code, but you are not
15 * obligated to do so. If you do not wish to do so, delete this exception
16 * statement from your version.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21 * GNU General Public License for more details.
22 *
23 * You should have received a copy of the GNU General Public License
24 * along with this program; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
26 *
27 */
28
29 /**
30 * SECTION:rhythmdb
31 * @short_description: Rhythmbox database functions
32 *
33 * RhythmDB is an in-memory database containing #RhythmDBEntry items. It
34 * runs queries represented as #GPtrArray<!-- -->s containing query criteria,
35 * feeding the results into #RhythmDBQueryResults implementations such as
36 * #RhythmDBQueryModel. From there, entries are grouped by particular property
37 * values to form #RhythmDBPropertyModel<!-- -->s.
38 *
39 * #RhythmDBEntry contains a fixed set of properties, defined by #RhythmDBPropType,
40 */
41
42 #include "config.h"
43
44 #define G_IMPLEMENT_INLINES 1
45 #define __RHYTHMDB_C__
46 #include "rhythmdb.h"
47 #undef G_IMPLEMENT_INLINES
48
49 #include <string.h>
50 #include <libxml/tree.h>
51 #include <glib.h>
52 #include <glib-object.h>
53 #include <glib/gi18n.h>
54 #include <gio/gio.h>
55 #include <gobject/gvaluecollector.h>
56 #include <gdk/gdk.h>
57
58
59 #include "rb-marshal.h"
60 #include "rb-file-helpers.h"
61 #include "rb-debug.h"
62 #include "rb-util.h"
63 #include "rb-cut-and-paste-code.h"
64 #include "rhythmdb-private.h"
65 #include "rhythmdb-property-model.h"
66 #include "rb-dialog.h"
67 #include "rb-string-value-map.h"
68 #include "rb-async-queue-watch.h"
69 #include "rb-podcast-entry-types.h"
70 #include "rb-gst-media-types.h"
71
72 #define PROP_ENTRY(p,t,n) { RHYTHMDB_PROP_ ## p, "RHYTHMDB_PROP_" #p "", t, n }
73
74 typedef struct _RhythmDBPropertyDef {
75 RhythmDBPropType prop_id;
76 const char *prop_name;
77 GType prop_type;
78 const char *elt_name;
79 } RhythmDBPropertyDef;
80
81 static const RhythmDBPropertyDef rhythmdb_properties[] = {
82 PROP_ENTRY(TYPE, G_TYPE_OBJECT, "type"),
83 PROP_ENTRY(ENTRY_ID, G_TYPE_ULONG, "entry-id"),
84 PROP_ENTRY(TITLE, G_TYPE_STRING, "title"),
85 PROP_ENTRY(GENRE, G_TYPE_STRING, "genre"),
86 PROP_ENTRY(ARTIST, G_TYPE_STRING, "artist"),
87 PROP_ENTRY(ALBUM, G_TYPE_STRING, "album"),
88 PROP_ENTRY(TRACK_NUMBER, G_TYPE_ULONG, "track-number"),
89 PROP_ENTRY(DISC_NUMBER, G_TYPE_ULONG, "disc-number"),
90 PROP_ENTRY(DURATION, G_TYPE_ULONG, "duration"),
91 PROP_ENTRY(FILE_SIZE, G_TYPE_UINT64, "file-size"),
92 PROP_ENTRY(LOCATION, G_TYPE_STRING, "location"),
93 PROP_ENTRY(MOUNTPOINT, G_TYPE_STRING, "mountpoint"),
94 PROP_ENTRY(MTIME, G_TYPE_ULONG, "mtime"),
95 PROP_ENTRY(FIRST_SEEN, G_TYPE_ULONG, "first-seen"),
96 PROP_ENTRY(LAST_SEEN, G_TYPE_ULONG, "last-seen"),
97 PROP_ENTRY(RATING, G_TYPE_DOUBLE, "rating"),
98 PROP_ENTRY(PLAY_COUNT, G_TYPE_ULONG, "play-count"),
99 PROP_ENTRY(LAST_PLAYED, G_TYPE_ULONG, "last-played"),
100 PROP_ENTRY(BITRATE, G_TYPE_ULONG, "bitrate"),
101 PROP_ENTRY(DATE, G_TYPE_ULONG, "date"),
102 PROP_ENTRY(TRACK_GAIN, G_TYPE_DOUBLE, "replaygain-track-gain"),
103 PROP_ENTRY(TRACK_PEAK, G_TYPE_DOUBLE, "replaygain-track-peak"),
104 PROP_ENTRY(ALBUM_GAIN, G_TYPE_DOUBLE, "replaygain-album-gain"),
105 PROP_ENTRY(ALBUM_PEAK, G_TYPE_DOUBLE, "replaygain-album-peak"),
106 PROP_ENTRY(MEDIA_TYPE, G_TYPE_STRING, "media-type"),
107 PROP_ENTRY(TITLE_SORT_KEY, G_TYPE_STRING, "title-sort-key"),
108 PROP_ENTRY(GENRE_SORT_KEY, G_TYPE_STRING, "genre-sort-key"),
109 PROP_ENTRY(ARTIST_SORT_KEY, G_TYPE_STRING, "artist-sort-key"),
110 PROP_ENTRY(ALBUM_SORT_KEY, G_TYPE_STRING, "album-sort-key"),
111 PROP_ENTRY(TITLE_FOLDED, G_TYPE_STRING, "title-folded"),
112 PROP_ENTRY(GENRE_FOLDED, G_TYPE_STRING, "genre-folded"),
113 PROP_ENTRY(ARTIST_FOLDED, G_TYPE_STRING, "artist-folded"),
114 PROP_ENTRY(ALBUM_FOLDED, G_TYPE_STRING, "album-folded"),
115 PROP_ENTRY(LAST_PLAYED_STR, G_TYPE_STRING, "last-played-str"),
116 PROP_ENTRY(HIDDEN, G_TYPE_BOOLEAN, "hidden"),
117 PROP_ENTRY(PLAYBACK_ERROR, G_TYPE_STRING, "playback-error"),
118 PROP_ENTRY(FIRST_SEEN_STR, G_TYPE_STRING, "first-seen-str"),
119 PROP_ENTRY(LAST_SEEN_STR, G_TYPE_STRING, "last-seen-str"),
120
121 PROP_ENTRY(SEARCH_MATCH, G_TYPE_STRING, "search-match"),
122 PROP_ENTRY(YEAR, G_TYPE_ULONG, "year"),
123 PROP_ENTRY(KEYWORD, G_TYPE_STRING, "keyword"),
124
125 PROP_ENTRY(STATUS, G_TYPE_ULONG, "status"),
126 PROP_ENTRY(DESCRIPTION, G_TYPE_STRING, "description"),
127 PROP_ENTRY(SUBTITLE, G_TYPE_STRING, "subtitle"),
128 PROP_ENTRY(SUMMARY, G_TYPE_STRING, "summary"),
129 PROP_ENTRY(LANG, G_TYPE_STRING, "lang"),
130 PROP_ENTRY(COPYRIGHT, G_TYPE_STRING, "copyright"),
131 PROP_ENTRY(IMAGE, G_TYPE_STRING, "image"),
132 PROP_ENTRY(POST_TIME, G_TYPE_ULONG, "post-time"),
133
134 PROP_ENTRY(MUSICBRAINZ_TRACKID, G_TYPE_STRING, "mb-trackid"),
135 PROP_ENTRY(MUSICBRAINZ_ARTISTID, G_TYPE_STRING, "mb-artistid"),
136 PROP_ENTRY(MUSICBRAINZ_ALBUMID, G_TYPE_STRING, "mb-albumid"),
137 PROP_ENTRY(MUSICBRAINZ_ALBUMARTISTID, G_TYPE_STRING, "mb-albumartistid"),
138 PROP_ENTRY(ARTIST_SORTNAME, G_TYPE_STRING, "mb-artistsortname"),
139 PROP_ENTRY(ALBUM_SORTNAME, G_TYPE_STRING, "album-sortname"),
140
141 PROP_ENTRY(ARTIST_SORTNAME_SORT_KEY, G_TYPE_STRING, "artist-sortname-sort-key"),
142 PROP_ENTRY(ARTIST_SORTNAME_FOLDED, G_TYPE_STRING, "artist-sortname-folded"),
143 PROP_ENTRY(ALBUM_SORTNAME_SORT_KEY, G_TYPE_STRING, "album-sortname-sort-key"),
144 PROP_ENTRY(ALBUM_SORTNAME_FOLDED, G_TYPE_STRING, "album-sortname-folded"),
145
146 PROP_ENTRY(COMMENT, G_TYPE_STRING, "comment"),
147
148 PROP_ENTRY(ALBUM_ARTIST, G_TYPE_STRING, "album-artist"),
149 PROP_ENTRY(ALBUM_ARTIST_SORT_KEY, G_TYPE_STRING, "album-artist-sort-key"),
150 PROP_ENTRY(ALBUM_ARTIST_FOLDED, G_TYPE_STRING, "album-artist-folded"),
151 PROP_ENTRY(ALBUM_ARTIST_SORTNAME, G_TYPE_STRING, "album-artist-sortname"),
152 PROP_ENTRY(ALBUM_ARTIST_SORTNAME_SORT_KEY, G_TYPE_STRING, "album-artist-sortname-sort-key"),
153 PROP_ENTRY(ALBUM_ARTIST_SORTNAME_FOLDED, G_TYPE_STRING, "album-artist-sortname-folded"),
154
155 PROP_ENTRY(BPM, G_TYPE_DOUBLE, "beats-per-minute"),
156
157 { 0, 0, 0, 0 }
158 };
159
160 #define RB_PARSE_NICK_START (xmlChar *) "["
161 #define RB_PARSE_NICK_END (xmlChar *) "]"
162
163
164 #define RHYTHMDB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE, RhythmDBPrivate))
165 G_DEFINE_ABSTRACT_TYPE(RhythmDB, rhythmdb, G_TYPE_OBJECT)
166
167 /* file attributes requested in RHYTHMDB_ACTION_STAT and RHYTHMDB_ACTION_LOAD */
168 #define RHYTHMDB_FILE_INFO_ATTRIBUTES \
169 G_FILE_ATTRIBUTE_STANDARD_SIZE "," \
170 G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME "," \
171 G_FILE_ATTRIBUTE_STANDARD_TYPE "," \
172 G_FILE_ATTRIBUTE_TIME_MODIFIED
173
174 /* file attributes requested in RHYTHMDB_ACTION_ENUM_DIR */
175 #define RHYTHMDB_FILE_CHILD_INFO_ATTRIBUTES \
176 RHYTHMDB_FILE_INFO_ATTRIBUTES "," \
177 G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN "," \
178 G_FILE_ATTRIBUTE_STANDARD_NAME
179
180 /*
181 * Filters for MIME/media types to ignore.
182 * The only complication here is that there are some application/ types that
183 * are used for audio/video files. Otherwise, we'd ignore everything except
184 * audio/ and video/.
185 */
186 struct media_type_filter {
187 const char *prefix;
188 gboolean ignore;
189 } media_type_filters[] = {
190 { "image/", TRUE },
191 { "text/", TRUE },
192 { "application/ogg", FALSE },
193 { "application/x-id3", FALSE },
194 { "application/x-apetag", FALSE },
195 { "application/x-3gp", FALSE },
196 { "application/x-annodex", FALSE },
197 { "application/", TRUE },
198 };
199
200 /*
201 * File size below which we will simply ignore files that can't be identified.
202 * This is mostly here so we ignore the various text files that are packaged
203 * with many netlabel releases and other downloads.
204 */
205 #define REALLY_SMALL_FILE_SIZE (4096)
206
207
208 typedef struct
209 {
210 RhythmDB *db;
211 GPtrArray *query;
212 guint propid;
213 RhythmDBQueryResults *results;
214 gboolean cancel;
215 } RhythmDBQueryThreadData;
216
217 typedef struct
218 {
219 RhythmDB *db;
220 RhythmDBEntryType *type;
221 RhythmDBEntryType *ignore_type;
222 RhythmDBEntryType *error_type;
223 } RhythmDBAddThreadData;
224
225 typedef struct
226 {
227 enum {
228 RHYTHMDB_ACTION_STAT,
229 RHYTHMDB_ACTION_LOAD,
230 RHYTHMDB_ACTION_ENUM_DIR,
231 RHYTHMDB_ACTION_SYNC,
232 RHYTHMDB_ACTION_QUIT,
233 } type;
234 RBRefString *uri;
235 union {
236 struct {
237 RhythmDBEntryType *entry_type;
238 RhythmDBEntryType *ignore_type;
239 RhythmDBEntryType *error_type;
240 } types;
241 GSList *changes;
242 } data;
243 } RhythmDBAction;
244
245 static void rhythmdb_dispose (GObject *object);
246 static void rhythmdb_finalize (GObject *object);
247 static void rhythmdb_set_property (GObject *object,
248 guint prop_id,
249 const GValue *value,
250 GParamSpec *pspec);
251 static void rhythmdb_get_property (GObject *object,
252 guint prop_id,
253 GValue *value,
254 GParamSpec *pspec);
255 static void rhythmdb_thread_create (RhythmDB *db,
256 GThreadPool *pool,
257 GThreadFunc func,
258 gpointer data);
259 static void rhythmdb_read_enter (RhythmDB *db);
260 static void rhythmdb_read_leave (RhythmDB *db);
261 static void rhythmdb_process_one_event (RhythmDBEvent *event, RhythmDB *db);
262 static gpointer action_thread_main (RhythmDB *db);
263 static gpointer query_thread_main (RhythmDBQueryThreadData *data);
264 static void rhythmdb_entry_set_mount_point (RhythmDB *db,
265 RhythmDBEntry *entry,
266 const gchar *realuri);
267
268 static gboolean rhythmdb_idle_save (RhythmDB *db);
269 static void db_settings_changed_cb (GSettings *settings, const char *key, RhythmDB *db);
270 static void rhythmdb_sync_library_location (RhythmDB *db);
271 static void rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
272 guint propid);
273 static gboolean rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
274 GValue *return_accu,
275 const GValue *handler_return,
276 gpointer data);
277
278 static void rhythmdb_event_free (RhythmDB *db, RhythmDBEvent *event);
279 static void rhythmdb_add_to_stat_list (RhythmDB *db,
280 const char *uri,
281 RhythmDBEntry *entry,
282 RhythmDBEntryType *type,
283 RhythmDBEntryType *ignore_type,
284 RhythmDBEntryType *error_type);
285 static void free_entry_changes (GSList *entry_changes);
286
287 static void perform_next_mount (RhythmDB *db);
288
289 enum
290 {
291 PROP_0,
292 PROP_NAME,
293 PROP_DRY_RUN,
294 PROP_NO_UPDATE,
295 };
296
297 enum
298 {
299 ENTRY_ADDED,
300 ENTRY_CHANGED,
301 ENTRY_DELETED,
302 ENTRY_KEYWORD_ADDED,
303 ENTRY_KEYWORD_REMOVED,
304 ENTRY_EXTRA_METADATA_REQUEST,
305 ENTRY_EXTRA_METADATA_NOTIFY,
306 ENTRY_EXTRA_METADATA_GATHER,
307 LOAD_COMPLETE,
308 SAVE_COMPLETE,
309 SAVE_ERROR,
310 READ_ONLY,
311 CREATE_MOUNT_OP,
312 LAST_SIGNAL
313 };
314
315 static guint rhythmdb_signals[LAST_SIGNAL] = { 0 };
316
317 static void
318 rhythmdb_class_init (RhythmDBClass *klass)
319 {
320 GObjectClass *object_class = G_OBJECT_CLASS (klass);
321
322 object_class->dispose = rhythmdb_dispose;
323 object_class->finalize = rhythmdb_finalize;
324
325 object_class->set_property = rhythmdb_set_property;
326 object_class->get_property = rhythmdb_get_property;
327
328 /**
329 * RhythmDB:name:
330 *
331 * Database name. Not sure whta this is used for.
332 */
333 g_object_class_install_property (object_class,
334 PROP_NAME,
335 g_param_spec_string ("name",
336 "name",
337 "name",
338 NULL,
339 G_PARAM_READWRITE));
340 /**
341 * RhythmDB:dry-run:
342 *
343 * If %TRUE, no metadata changes will be written back to media fies.
344 */
345 g_object_class_install_property (object_class,
346 PROP_DRY_RUN,
347 g_param_spec_boolean ("dry-run",
348 "dry run",
349 "Whether or not changes should be saved",
350 FALSE,
351 G_PARAM_READWRITE));
352 /**
353 * RhythmDB:no-update:
354 *
355 * If %TRUE, the database will not be updated.
356 */
357 g_object_class_install_property (object_class,
358 PROP_NO_UPDATE,
359 g_param_spec_boolean ("no-update",
360 "no update",
361 "Whether or not to update the database",
362 FALSE,
363 G_PARAM_READWRITE));
364 /**
365 * RhythmDB::entry-added:
366 * @db: the #RhythmDB
367 * @entry: the newly added #RhythmDBEntry
368 *
369 * Emitted when a new entry is added to the database.
370 */
371 rhythmdb_signals[ENTRY_ADDED] =
372 g_signal_new ("entry_added",
373 RHYTHMDB_TYPE,
374 G_SIGNAL_RUN_LAST,
375 G_STRUCT_OFFSET (RhythmDBClass, entry_added),
376 NULL, NULL,
377 g_cclosure_marshal_VOID__BOXED,
378 G_TYPE_NONE,
379 1, RHYTHMDB_TYPE_ENTRY);
380
381 /**
382 * RhythmDB::entry-deleted:
383 * @db: the #RhythmDB
384 * @entry: the deleted #RhythmDBEntry
385 *
386 * Emitted when an entry is deleted from the database.
387 */
388 rhythmdb_signals[ENTRY_DELETED] =
389 g_signal_new ("entry_deleted",
390 RHYTHMDB_TYPE,
391 G_SIGNAL_RUN_LAST,
392 G_STRUCT_OFFSET (RhythmDBClass, entry_deleted),
393 NULL, NULL,
394 g_cclosure_marshal_VOID__BOXED,
395 G_TYPE_NONE,
396 1, RHYTHMDB_TYPE_ENTRY);
397
398 /**
399 * RhythmDB::entry-changed:
400 * @db: the #RhythmDB
401 * @entry: the changed #RhythmDBEntry
402 * @changes: a #GArray of #RhythmDBEntryChange structures describing the changes
403 *
404 * Emitted when a database entry is modified. The @changes list
405 * contains a structure for each entry property that has been modified.
406 */
407 rhythmdb_signals[ENTRY_CHANGED] =
408 g_signal_new ("entry_changed",
409 RHYTHMDB_TYPE,
410 G_SIGNAL_RUN_LAST,
411 G_STRUCT_OFFSET (RhythmDBClass, entry_changed),
412 NULL, NULL,
413 rb_marshal_VOID__BOXED_BOXED,
414 G_TYPE_NONE, 2,
415 RHYTHMDB_TYPE_ENTRY, G_TYPE_ARRAY);
416
417 /**
418 * RhythmDB::entry-keyword-added:
419 * @db: the #RhythmDB
420 * @entry: the #RhythmDBEntry to which a keyword has been added
421 * @keyword: the keyword that was added
422 *
423 * Emitted when a keyword is added to an entry.
424 */
425 rhythmdb_signals[ENTRY_KEYWORD_ADDED] =
426 g_signal_new ("entry_keyword_added",
427 RHYTHMDB_TYPE,
428 G_SIGNAL_RUN_LAST,
429 G_STRUCT_OFFSET (RhythmDBClass, entry_added),
430 NULL, NULL,
431 rb_marshal_VOID__BOXED_BOXED,
432 G_TYPE_NONE,
433 2, RHYTHMDB_TYPE_ENTRY, RB_TYPE_REFSTRING);
434
435 /**
436 * RhythmDB::entry-keyword-removed:
437 * @db: the #RhythmDB
438 * @entry: the #RhythmDBEntry from which a keyword has been removed
439 * @keyword: the keyword that was removed
440 *
441 * Emitted when a keyword is removed from an entry.
442 */
443 rhythmdb_signals[ENTRY_KEYWORD_REMOVED] =
444 g_signal_new ("entry_keyword_removed",
445 RHYTHMDB_TYPE,
446 G_SIGNAL_RUN_LAST,
447 G_STRUCT_OFFSET (RhythmDBClass, entry_deleted),
448 NULL, NULL,
449 rb_marshal_VOID__BOXED_BOXED,
450 G_TYPE_NONE,
451 2, RHYTHMDB_TYPE_ENTRY, RB_TYPE_REFSTRING);
452
453 /**
454 * RhythmDB::entry-extra-metadata-request:
455 * @db: the #RhythmDB
456 * @entry: the #RhythmDBEntry for which extra metadata is being requested
457 *
458 * This signal is emitted to allow extra (transient) metadata to be supplied
459 * for the given entry. The detail of the signal invocation describes the
460 * specific metadata value being requested. If the object handling the signal
461 * can provide the requested item, but it isn't immediately available, it can
462 * initiate an attempt to retrieve it. If successful, it would call
463 * @rhythmdb_emit_entry_extra_metadata_notify when the metadata is available.
464 *
465 * Return value: the extra metadata value
466 */
467 rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST] =
468 g_signal_new ("entry_extra_metadata_request",
469 G_OBJECT_CLASS_TYPE (object_class),
470 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
471 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_request),
472 rhythmdb_entry_extra_metadata_accumulator, NULL,
473 rb_marshal_BOXED__BOXED,
474 G_TYPE_VALUE, 1,
475 RHYTHMDB_TYPE_ENTRY);
476
477 /**
478 * RhythmDB::entry-extra-metadata-notify:
479 * @db: the #RhythmDB
480 * @entry: the #RhythmDBEntry for which extra metadata has been supplied
481 * @field: the extra metadata field being supplied
482 * @metadata: the extra metadata value
483 *
484 * This signal is emitted when an extra metadata value is provided for a specific
485 * entry independantly of an extra metadata request.
486 */
487 rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY] =
488 g_signal_new ("entry_extra_metadata_notify",
489 G_OBJECT_CLASS_TYPE (object_class),
490 G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
491 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_notify),
492 NULL, NULL,
493 rb_marshal_VOID__BOXED_STRING_BOXED,
494 G_TYPE_NONE, 3,
495 RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_VALUE);
496
497 /**
498 * RhythmDB::entry-extra-metadata-gather:
499 * @db: the #RhythmDB
500 * @entry: the #RhythmDBEntry for which to gather metadata
501 * @data: a #RBStringValueMap to hold the gathered metadata
502 *
503 * Emitted to gather all available extra metadata for a database entry.
504 * Handlers for this signal should insert any metadata they can provide
505 * into the string-value map. Only immediately available metadata
506 * items should be returned. If one or more metadata items is not
507 * immediately available, the handler should not initiate an attempt to
508 * retrieve them.
509 */
510 rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER] =
511 g_signal_new ("entry_extra_metadata_gather",
512 G_OBJECT_CLASS_TYPE (object_class),
513 G_SIGNAL_RUN_LAST,
514 G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_gather),
515 NULL, NULL,
516 rb_marshal_VOID__BOXED_OBJECT,
517 G_TYPE_NONE, 2,
518 RHYTHMDB_TYPE_ENTRY, RB_TYPE_STRING_VALUE_MAP);
519
520 /**
521 * RhythmDB::load-complete:
522 * @db: the #RhythmDB
523 *
524 * Emitted when the database is fully loaded.
525 */
526 rhythmdb_signals[LOAD_COMPLETE] =
527 g_signal_new ("load_complete",
528 RHYTHMDB_TYPE,
529 G_SIGNAL_RUN_LAST,
530 G_STRUCT_OFFSET (RhythmDBClass, load_complete),
531 NULL, NULL,
532 g_cclosure_marshal_VOID__VOID,
533 G_TYPE_NONE,
534 0);
535
536 /**
537 * RhythmDB::save-complete:
538 * @db: the #RhythmDB
539 *
540 * Emitted when the database has been saved.
541 */
542 rhythmdb_signals[SAVE_COMPLETE] =
543 g_signal_new ("save_complete",
544 RHYTHMDB_TYPE,
545 G_SIGNAL_RUN_LAST,
546 G_STRUCT_OFFSET (RhythmDBClass, save_complete),
547 NULL, NULL,
548 g_cclosure_marshal_VOID__VOID,
549 G_TYPE_NONE,
550 0);
551
552 /**
553 * RhythmDB::save-error:
554 * @db: the #RhythmDB
555 * @uri: URI of the database file
556 * @error: the error that occurred
557 *
558 * Emitted when an error occurs while saving the database.
559 */
560 rhythmdb_signals[SAVE_ERROR] =
561 g_signal_new ("save-error",
562 G_OBJECT_CLASS_TYPE (object_class),
563 G_SIGNAL_RUN_LAST,
564 G_STRUCT_OFFSET (RhythmDBClass, save_error),
565 NULL, NULL,
566 rb_marshal_VOID__STRING_POINTER,
567 G_TYPE_NONE,
568 2,
569 G_TYPE_STRING,
570 G_TYPE_POINTER);
571
572 /**
573 * RhythmDB::read-only:
574 * @db: the #RhythmDB
575 * @readonly: %TRUE if the database is read-only
576 *
577 * Emitted when the database becomes temporarily read-only, or becomes
578 * writeable after being read-only.
579 */
580 rhythmdb_signals[READ_ONLY] =
581 g_signal_new ("read-only",
582 G_OBJECT_CLASS_TYPE (object_class),
583 G_SIGNAL_RUN_LAST,
584 G_STRUCT_OFFSET (RhythmDBClass, read_only),
585 NULL, NULL,
586 g_cclosure_marshal_VOID__BOOLEAN,
587 G_TYPE_NONE,
588 1,
589 G_TYPE_BOOLEAN);
590
591 /**
592 * RhythmDB::create-mount-op:
593 * @db: the #RhythmDB
594 *
595 * Emitted to request creation of a #GMountOperation to use to mount a volume.
596 *
597 * Returns: (transfer full): a #GMountOperation (usually actually a #GtkMountOperation)
598 */
599 rhythmdb_signals[CREATE_MOUNT_OP] =
600 g_signal_new ("create-mount-op",
601 G_OBJECT_CLASS_TYPE (object_class),
602 G_SIGNAL_RUN_LAST,
603 0, /* no need for an internal handler */
604 rb_signal_accumulator_object_handled, NULL,
605 rb_marshal_OBJECT__VOID,
606 G_TYPE_MOUNT_OPERATION,
607 0);
608
609 g_type_class_add_private (klass, sizeof (RhythmDBPrivate));
610 }
611
612 static void
613 rhythmdb_push_event (RhythmDB *db, RhythmDBEvent *event)
614 {
615 g_async_queue_push (db->priv->event_queue, event);
616 g_main_context_wakeup (g_main_context_default ());
617 }
618
619 static gboolean
620 metadata_field_from_prop (RhythmDBPropType prop,
621 RBMetaDataField *field)
622 {
623 switch (prop) {
624 case RHYTHMDB_PROP_TITLE:
625 *field = RB_METADATA_FIELD_TITLE;
626 return TRUE;
627 case RHYTHMDB_PROP_ARTIST:
628 *field = RB_METADATA_FIELD_ARTIST;
629 return TRUE;
630 case RHYTHMDB_PROP_ALBUM:
631 *field = RB_METADATA_FIELD_ALBUM;
632 return TRUE;
633 case RHYTHMDB_PROP_GENRE:
634 *field = RB_METADATA_FIELD_GENRE;
635 return TRUE;
636 case RHYTHMDB_PROP_COMMENT:
637 *field = RB_METADATA_FIELD_COMMENT;
638 return TRUE;
639 case RHYTHMDB_PROP_TRACK_NUMBER:
640 *field = RB_METADATA_FIELD_TRACK_NUMBER;
641 return TRUE;
642 case RHYTHMDB_PROP_DISC_NUMBER:
643 *field = RB_METADATA_FIELD_DISC_NUMBER;
644 return TRUE;
645 case RHYTHMDB_PROP_DATE:
646 *field = RB_METADATA_FIELD_DATE;
647 return TRUE;
648 case RHYTHMDB_PROP_BPM:
649 *field = RB_METADATA_FIELD_BPM;
650 return TRUE;
651 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
652 *field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
653 return TRUE;
654 case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID:
655 *field = RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID;
656 return TRUE;
657 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID:
658 *field = RB_METADATA_FIELD_MUSICBRAINZ_ALBUMID;
659 return TRUE;
660 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID:
661 *field = RB_METADATA_FIELD_MUSICBRAINZ_ALBUMARTISTID;
662 return TRUE;
663 case RHYTHMDB_PROP_ARTIST_SORTNAME:
664 *field = RB_METADATA_FIELD_ARTIST_SORTNAME;
665 return TRUE;
666 case RHYTHMDB_PROP_ALBUM_SORTNAME:
667 *field = RB_METADATA_FIELD_ALBUM_SORTNAME;
668 return TRUE;
669 case RHYTHMDB_PROP_ALBUM_ARTIST:
670 *field = RB_METADATA_FIELD_ALBUM_ARTIST;
671 return TRUE;
672 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
673 *field = RB_METADATA_FIELD_ALBUM_ARTIST_SORTNAME;
674 return TRUE;
675 default:
676 return FALSE;
677 }
678 }
679
680 static void
681 rhythmdb_init (RhythmDB *db)
682 {
683 guint i;
684 GEnumClass *prop_class;
685
686 db->priv = RHYTHMDB_GET_PRIVATE (db);
687
688 db->priv->settings = g_settings_new ("org.gnome.rhythmbox.rhythmdb");
689 g_signal_connect_object (db->priv->settings, "changed", G_CALLBACK (db_settings_changed_cb), db, 0);
690
691 db->priv->action_queue = g_async_queue_new ();
692 db->priv->event_queue = g_async_queue_new ();
693 db->priv->delayed_write_queue = g_async_queue_new ();
694 db->priv->event_queue_watch_id = rb_async_queue_watch_new (db->priv->event_queue,
695 G_PRIORITY_LOW, /* really? */
696 (RBAsyncQueueWatchFunc) rhythmdb_process_one_event,
697 db,
698 NULL,
699 NULL);
700
701 db->priv->restored_queue = g_async_queue_new ();
702
703 db->priv->query_thread_pool = g_thread_pool_new ((GFunc)query_thread_main,
704 NULL,
705 -1, FALSE, NULL);
706
707 db->priv->metadata = rb_metadata_new ();
708
709 prop_class = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
710
711 g_assert (prop_class->n_values == RHYTHMDB_NUM_PROPERTIES);
712
713 g_type_class_unref (prop_class);
714
715 db->priv->propname_map = g_hash_table_new (g_str_hash, g_str_equal);
716
717 for (i = 0; i < RHYTHMDB_NUM_PROPERTIES; i++) {
718 const xmlChar *name = rhythmdb_nice_elt_name_from_propid (db, i);
719 g_hash_table_insert (db->priv->propname_map, (gpointer) name, GINT_TO_POINTER (i));
720 }
721
722 db->priv->entry_type_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
723
724 rhythmdb_register_song_entry_types (db);
725 rb_podcast_register_entry_types (db);
726
727 db->priv->changed_entries = g_hash_table_new_full (NULL,
728 NULL,
729 (GDestroyNotify) rhythmdb_entry_unref,
730 NULL);
731 db->priv->added_entries = g_hash_table_new_full (NULL,
732 NULL,
733 (GDestroyNotify) rhythmdb_entry_unref,
734 NULL);
735 db->priv->deleted_entries = g_hash_table_new_full (NULL,
736 NULL,
737 (GDestroyNotify) rhythmdb_entry_unref,
738 NULL);
739
740 db->priv->can_save = TRUE;
741 db->priv->exiting = g_cancellable_new ();
742 db->priv->saving = FALSE;
743 db->priv->dirty = FALSE;
744
745 db->priv->empty_string = rb_refstring_new ("");
746 db->priv->octet_stream_str = rb_refstring_new ("application/octet-stream");
747
748 db->priv->next_entry_id = 1;
749
750 rhythmdb_init_monitoring (db);
751
752 rhythmdb_dbus_register (db);
753 }
754
755 static GError *
756 make_access_failed_error (const char *uri, GError *access_error)
757 {
758 char *unescaped;
759 char *utf8ised;
760 GError *error;
761
762 /* make sure the URI we put in the error message is valid utf8 */
763 unescaped = g_uri_unescape_string (uri, NULL);
764 utf8ised = rb_make_valid_utf8 (unescaped, '?');
765
766 error = g_error_new (RHYTHMDB_ERROR,
767 RHYTHMDB_ERROR_ACCESS_FAILED,
768 _("Couldn't access %s: %s"),
769 utf8ised,
770 access_error->message);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
771 rb_debug ("got error on %s: %s", uri, error->message);
772 g_free (unescaped);
773 g_free (utf8ised);
774 return error;
775 }
776
777 static gboolean
778 rhythmdb_ignore_media_type (const char *media_type)
779 {
780 int i;
781
782 for (i = 0; i < G_N_ELEMENTS (media_type_filters); i++) {
783 if (g_str_has_prefix (media_type, media_type_filters[i].prefix)) {
784 return media_type_filters[i].ignore;
785 }
786 }
787 return FALSE;
788 }
789
790 typedef struct {
791 RhythmDB *db;
792 GList *stat_list;
793 } RhythmDBStatThreadData;
794
795 static gpointer
796 stat_thread_main (RhythmDBStatThreadData *data)
797 {
798 GList *i;
799 GError *error = NULL;
800 RhythmDBEvent *result;
801
802 data->db->priv->stat_thread_count = g_list_length (data->stat_list);
803 data->db->priv->stat_thread_done = 0;
804
805 rb_debug ("entering stat thread: %d to process", data->db->priv->stat_thread_count);
806 for (i = data->stat_list; i != NULL; i = i->next) {
807 RhythmDBEvent *event = (RhythmDBEvent *)i->data;
808 GFile *file;
809
810 /* if we've been cancelled, just free the event. this will
811 * clean up the list and then we'll exit the thread.
812 */
813 if (g_cancellable_is_cancelled (data->db->priv->exiting)) {
814 rhythmdb_event_free (data->db, event);
815 continue;
816 }
817
818 if (data->db->priv->stat_thread_done > 0 &&
819 data->db->priv->stat_thread_done % 1000 == 0) {
820 rb_debug ("%d file info queries done",
821 data->db->priv->stat_thread_done);
822 }
823
824 file = g_file_new_for_uri (rb_refstring_get (event->uri));
825 event->real_uri = rb_refstring_ref (event->uri); /* what? */
826 event->file_info = g_file_query_info (file,
827 G_FILE_ATTRIBUTE_TIME_MODIFIED, /* anything else? */
828 G_FILE_QUERY_INFO_NONE,
829 data->db->priv->exiting,
830 &error);
831 if (error != NULL) {
832 event->error = make_access_failed_error (rb_refstring_get (event->uri), error);
833 g_clear_error (&error);
834
835 if (event->file_info != NULL) {
836 g_object_unref (event->file_info);
837 event->file_info = NULL;
838 }
839 }
840
841 g_async_queue_push (data->db->priv->event_queue, event);
842 g_object_unref (file);
843 g_atomic_int_inc (&data->db->priv->stat_thread_done);
844 }
845
846 g_list_free (data->stat_list);
847
848 data->db->priv->stat_thread_running = FALSE;
849
850 rb_debug ("exiting stat thread");
851 result = g_slice_new0 (RhythmDBEvent);
852 result->db = data->db; /* need to unref? */
853 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
854 rhythmdb_push_event (data->db, result);
855
856 g_free (data);
857 return NULL;
858 }
859
860 static void
861 perform_next_mount_cb (GObject *file, GAsyncResult *res, RhythmDB *db)
862 {
863 GError *error = NULL;
864
865 g_file_mount_enclosing_volume_finish (G_FILE (file), res, &error);
866 if (error != NULL) {
867 char *uri;
868
869 uri = g_file_get_uri (G_FILE (file));
870 rb_debug ("Unable to mount %s: %s", uri, error->message);
871 g_free (uri);
872 g_clear_error (&error);
873 }
874 g_object_unref (file);
875
876 perform_next_mount (db);
877 }
878
879 static void
880 perform_next_mount (RhythmDB *db)
881 {
882 GList *l;
883 char *mountpoint;
884 GMountOperation *mount_op = NULL;
885
886 if (db->priv->mount_list == NULL) {
887 rb_debug ("finished mounting");
888 return;
889 }
890
891 l = db->priv->mount_list;
892 db->priv->mount_list = db->priv->mount_list->next;
893 mountpoint = l->data;
894 g_list_free1 (l);
895
896 rb_debug ("mounting %s", (char *)mountpoint);
897 g_signal_emit (G_OBJECT (db), rhythmdb_signals[CREATE_MOUNT_OP], 0, &mount_op);
898 g_file_mount_enclosing_volume (g_file_new_for_uri (mountpoint),
899 G_MOUNT_MOUNT_NONE,
900 mount_op,
901 db->priv->exiting,
902 (GAsyncReadyCallback) perform_next_mount_cb,
903 db);
904 }
905
906 /**
907 * rhythmdb_start_action_thread:
908 * @db: the #RhythmDB
909 *
910 * Starts the #RhythmDB processing thread. Needs to be called during startup.
911 */
912 void
913 rhythmdb_start_action_thread (RhythmDB *db)
914 {
915 g_mutex_lock (&db->priv->stat_mutex);
916 db->priv->action_thread_running = TRUE;
917 rhythmdb_thread_create (db, NULL, (GThreadFunc) action_thread_main, db);
918
919 if (db->priv->stat_list != NULL) {
920 RhythmDBStatThreadData *data;
921 data = g_new0 (RhythmDBStatThreadData, 1);
922 data->db = g_object_ref (db);
923 data->stat_list = db->priv->stat_list;
924 db->priv->stat_list = NULL;
925
926 db->priv->stat_thread_running = TRUE;
927 rhythmdb_thread_create (db, NULL, (GThreadFunc) stat_thread_main, data);
928 }
929
930 perform_next_mount (db);
931
932 g_mutex_unlock (&db->priv->stat_mutex);
933 }
934
935 static void
936 rhythmdb_action_free (RhythmDB *db,
937 RhythmDBAction *action)
938 {
939 rb_refstring_unref (action->uri);
940 if (action->type == RHYTHMDB_ACTION_SYNC) {
941 free_entry_changes (action->data.changes);
942 }
943 g_slice_free (RhythmDBAction, action);
944 }
945
946 static void
947 rhythmdb_event_free (RhythmDB *db,
948 RhythmDBEvent *result)
949 {
950 switch (result->type) {
951 case RHYTHMDB_EVENT_THREAD_EXITED:
952 g_object_unref (db);
953 g_assert (g_atomic_int_dec_and_test (&db->priv->outstanding_threads) >= 0);
954 g_async_queue_unref (db->priv->action_queue);
955 g_async_queue_unref (db->priv->event_queue);
956 break;
957 case RHYTHMDB_EVENT_STAT:
958 case RHYTHMDB_EVENT_METADATA_LOAD:
959 case RHYTHMDB_EVENT_DB_LOAD:
960 case RHYTHMDB_EVENT_DB_SAVED:
961 case RHYTHMDB_EVENT_QUERY_COMPLETE:
962 break;
963 case RHYTHMDB_EVENT_ENTRY_SET:
964 g_value_unset (&result->change.new);
965 break;
966 }
967 if (result->error)
968 g_error_free (result->error);
969 rb_refstring_unref (result->uri);
970 rb_refstring_unref (result->real_uri);
971 if (result->file_info)
972 g_object_unref (result->file_info);
973 if (result->metadata)
974 g_object_unref (result->metadata);
975 if (result->results)
976 g_object_unref (result->results);
977 if (result->entry != NULL) {
978 rhythmdb_entry_unref (result->entry);
979 }
980 g_slice_free (RhythmDBEvent, result);
981 }
982
983 static void
984 _shutdown_foreach_swapped (RhythmDBEvent *event, RhythmDB *db)
985 {
986 rhythmdb_event_free (db, event);
987 }
988
989 /**
990 * rhythmdb_shutdown:
991 * @db: the #RhythmDB
992 *
993 * Ceases all #RhythmDB operations, including stopping all directory monitoring, and
994 * removing all actions and events currently queued.
995 */
996 void
997 rhythmdb_shutdown (RhythmDB *db)
998 {
999 RhythmDBEvent *result;
1000 RhythmDBAction *action;
1001
1002 g_return_if_fail (RHYTHMDB_IS (db));
1003
1004 g_cancellable_cancel (db->priv->exiting);
1005
1006 /* force the action thread to wake up and exit */
1007 action = g_slice_new0 (RhythmDBAction);
1008 action->type = RHYTHMDB_ACTION_QUIT;
1009 g_async_queue_push (db->priv->action_queue, action);
1010
1011 /* abort all async io operations */
1012 g_mutex_lock (&db->priv->stat_mutex);
1013 g_list_foreach (db->priv->outstanding_stats, (GFunc)_shutdown_foreach_swapped, db);
1014 g_list_free (db->priv->outstanding_stats);
1015 db->priv->outstanding_stats = NULL;
1016 g_mutex_unlock (&db->priv->stat_mutex);
1017
1018 rb_debug ("%d outstanding threads", g_atomic_int_get (&db->priv->outstanding_threads));
1019 while (g_atomic_int_get (&db->priv->outstanding_threads) > 0) {
1020 result = g_async_queue_pop (db->priv->event_queue);
1021 rhythmdb_event_free (db, result);
1022 }
1023
1024 /* FIXME */
1025 while ((result = g_async_queue_try_pop (db->priv->event_queue)) != NULL)
1026 rhythmdb_event_free (db, result);
1027 while ((result = g_async_queue_try_pop (db->priv->delayed_write_queue)) != NULL)
1028 rhythmdb_event_free (db, result);
1029
1030 while ((action = g_async_queue_try_pop (db->priv->action_queue)) != NULL) {
1031 rhythmdb_action_free (db, action);
1032 }
1033 }
1034
1035 static void
1036 rhythmdb_dispose (GObject *object)
1037 {
1038 RhythmDB *db;
1039
1040 g_return_if_fail (object != NULL);
1041 g_return_if_fail (RHYTHMDB_IS (object));
1042
1043 rb_debug ("disposing rhythmdb");
1044 db = RHYTHMDB (object);
1045
1046 g_return_if_fail (db->priv != NULL);
1047
1048 rhythmdb_dispose_monitoring (db);
1049 rhythmdb_dbus_unregister (db);
1050
1051 if (db->priv->event_queue_watch_id != 0) {
1052 g_source_remove (db->priv->event_queue_watch_id);
1053 db->priv->event_queue_watch_id = 0;
1054 }
1055
1056 if (db->priv->save_timeout_id != 0) {
1057 g_source_remove (db->priv->save_timeout_id);
1058 db->priv->save_timeout_id = 0;
1059 }
1060
1061 if (db->priv->emit_entry_signals_id != 0) {
1062 g_source_remove (db->priv->emit_entry_signals_id);
1063 db->priv->emit_entry_signals_id = 0;
1064
1065 g_list_foreach (db->priv->added_entries_to_emit, (GFunc)rhythmdb_entry_unref, NULL);
1066 g_list_foreach (db->priv->deleted_entries_to_emit, (GFunc)rhythmdb_entry_unref, NULL);
1067 if (db->priv->changed_entries_to_emit != NULL) {
1068 g_hash_table_destroy (db->priv->changed_entries_to_emit);
1069 }
1070 }
1071
1072 if (db->priv->metadata != NULL) {
1073 g_object_unref (db->priv->metadata);
1074 db->priv->metadata = NULL;
1075 }
1076
1077 if (db->priv->exiting != NULL) {
1078 g_object_unref (db->priv->exiting);
1079 db->priv->exiting = NULL;
1080 }
1081
1082 if (db->priv->settings != NULL) {
1083 g_object_unref (db->priv->settings);
1084 db->priv->settings = NULL;
1085 }
1086
1087 G_OBJECT_CLASS (rhythmdb_parent_class)->dispose (object);
1088 }
1089
1090 static void
1091 rhythmdb_finalize (GObject *object)
1092 {
1093 RhythmDB *db;
1094
1095 g_return_if_fail (object != NULL);
1096 g_return_if_fail (RHYTHMDB_IS (object));
1097
1098 rb_debug ("finalizing rhythmdb");
1099 db = RHYTHMDB (object);
1100
1101 g_return_if_fail (db->priv != NULL);
1102
1103 rhythmdb_finalize_monitoring (db);
1104 g_strfreev (db->priv->library_locations);
1105 db->priv->library_locations = NULL;
1106
1107 g_thread_pool_free (db->priv->query_thread_pool, FALSE, TRUE);
1108 g_async_queue_unref (db->priv->action_queue);
1109 g_async_queue_unref (db->priv->event_queue);
1110 g_async_queue_unref (db->priv->restored_queue);
1111 g_async_queue_unref (db->priv->delayed_write_queue);
1112
1113 g_list_free (db->priv->stat_list);
1114
1115 g_hash_table_destroy (db->priv->propname_map);
1116
1117 g_hash_table_destroy (db->priv->added_entries);
1118 g_hash_table_destroy (db->priv->deleted_entries);
1119 g_hash_table_destroy (db->priv->changed_entries);
1120
1121 rb_refstring_unref (db->priv->empty_string);
1122 rb_refstring_unref (db->priv->octet_stream_str);
1123
1124 g_hash_table_destroy (db->priv->entry_type_map);
1125
1126 g_free (db->priv->name);
1127
1128 G_OBJECT_CLASS (rhythmdb_parent_class)->finalize (object);
1129 }
1130
1131 static void
1132 rhythmdb_set_property (GObject *object,
1133 guint prop_id,
1134 const GValue *value,
1135 GParamSpec *pspec)
1136 {
1137 RhythmDB *db = RHYTHMDB (object);
1138
1139 switch (prop_id) {
1140 case PROP_NAME:
1141 g_free (db->priv->name);
1142 db->priv->name = g_value_dup_string (value);
1143 break;
1144 case PROP_DRY_RUN:
1145 db->priv->dry_run = g_value_get_boolean (value);
1146 break;
1147 case PROP_NO_UPDATE:
1148 db->priv->no_update = g_value_get_boolean (value);
1149 break;
1150 default:
1151 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1152 break;
1153 }
1154 }
1155
1156 static void
1157 rhythmdb_get_property (GObject *object,
1158 guint prop_id,
1159 GValue *value,
1160 GParamSpec *pspec)
1161 {
1162 RhythmDB *source = RHYTHMDB (object);
1163
1164 switch (prop_id) {
1165 case PROP_NAME:
1166 g_value_set_string (value, source->priv->name);
1167 break;
1168 case PROP_DRY_RUN:
1169 g_value_set_boolean (value, source->priv->dry_run);
1170 break;
1171 case PROP_NO_UPDATE:
1172 g_value_set_boolean (value, source->priv->no_update);
1173 break;
1174 default:
1175 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
1176 break;
1177 }
1178 }
1179
1180 static void
1181 rhythmdb_thread_create (RhythmDB *db,
1182 GThreadPool *pool,
1183 GThreadFunc func,
1184 gpointer data)
1185 {
1186 g_object_ref (db);
1187 g_atomic_int_inc (&db->priv->outstanding_threads);
1188 g_async_queue_ref (db->priv->action_queue);
1189 g_async_queue_ref (db->priv->event_queue);
1190
1191 if (pool)
1192 g_thread_pool_push (pool, data, NULL);
1193 else
1194 g_thread_new ("rhythmdb-thread", (GThreadFunc) func, data);
1195 }
1196
1197 static gboolean
1198 rhythmdb_get_readonly (RhythmDB *db)
1199 {
1200 return (g_atomic_int_get (&db->priv->read_counter) > 0);
1201 }
1202
1203 static void
1204 rhythmdb_read_enter (RhythmDB *db)
1205 {
1206 gint count;
1207 g_return_if_fail (g_atomic_int_get (&db->priv->read_counter) >= 0);
1208 g_assert (rb_is_main_thread ());
1209
1210 count = g_atomic_int_add (&db->priv->read_counter, 1);
1211 rb_debug ("counter: %d", count+1);
1212 if (count == 0)
1213 g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY],
1214 0, TRUE);
1215 }
1216
1217 static void
1218 rhythmdb_read_leave (RhythmDB *db)
1219 {
1220 gint count;
1221 g_return_if_fail (rhythmdb_get_readonly (db));
1222 g_assert (rb_is_main_thread ());
1223
1224 count = g_atomic_int_add (&db->priv->read_counter, -1);
1225 rb_debug ("counter: %d", count-1);
1226 if (count == 1) {
1227
1228 g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY],
1229 0, FALSE);
1230
1231 /* move any delayed writes back to the main event queue */
1232 if (g_async_queue_length (db->priv->delayed_write_queue) > 0) {
1233 RhythmDBEvent *event;
1234 while ((event = g_async_queue_try_pop (db->priv->delayed_write_queue)) != NULL)
1235 g_async_queue_push (db->priv->event_queue, event);
1236
1237 g_main_context_wakeup (g_main_context_default ());
1238 }
1239
1240 }
1241 }
1242
1243 static void
1244 rhythmdb_entry_change_free (RhythmDBEntryChange *change)
1245 {
1246 g_value_unset (&change->old);
1247 g_value_unset (&change->new);
1248 g_slice_free (RhythmDBEntryChange, change);
1249 }
1250
1251 static RhythmDBEntryChange *
1252 rhythmdb_entry_change_copy (RhythmDBEntryChange *change)
1253 {
1254 RhythmDBEntryChange *c = g_slice_new0 (RhythmDBEntryChange);
1255
1256 c->prop = change->prop;
1257 g_value_init (&c->old, G_VALUE_TYPE (&change->old));
1258 g_value_init (&c->new, G_VALUE_TYPE (&change->new));
1259 g_value_copy (&change->old, &c->old);
1260 g_value_copy (&change->new, &c->new);
1261 return c;
1262 }
1263
1264 static void
1265 free_entry_changes (GSList *entry_changes)
1266 {
1267 GSList *t;
1268 for (t = entry_changes; t; t = t->next) {
1269 RhythmDBEntryChange *change = t->data;
1270 rhythmdb_entry_change_free (change);
1271 }
1272 g_slist_free (entry_changes);
1273 }
1274
1275 static GSList *
1276 copy_entry_changes (GSList *entry_changes)
1277 {
1278 GSList *r = NULL;
1279 GSList *t;
1280 for (t = entry_changes; t; t = t->next) {
1281 RhythmDBEntryChange *change = t->data;
1282 r = g_slist_prepend (r, rhythmdb_entry_change_copy (change));
1283 }
1284
1285 return g_slist_reverse (r);
1286 }
1287
1288 static gboolean
1289 rhythmdb_emit_entry_signals_idle (RhythmDB *db)
1290 {
1291 GList *added_entries;
1292 GList *deleted_entries;
1293 GHashTable *changed_entries;
1294 GList *l;
1295 GHashTableIter iter;
1296 RhythmDBEntry *entry;
1297 GSList *entry_changes;
1298
1299 /* get lists of entries to emit, reset source id value */
1300 g_mutex_lock (&db->priv->change_mutex);
1301
1302 added_entries = db->priv->added_entries_to_emit;
1303 db->priv->added_entries_to_emit = NULL;
1304
1305 deleted_entries = db->priv->deleted_entries_to_emit;
1306 db->priv->deleted_entries_to_emit = NULL;
1307
1308 changed_entries = db->priv->changed_entries_to_emit;
1309 db->priv->changed_entries_to_emit = NULL;
1310
1311 db->priv->emit_entry_signals_id = 0;
1312
1313 g_mutex_unlock (&db->priv->change_mutex);
1314
1315 GDK_THREADS_ENTER ();
1316
1317 /* emit changed entries */
1318 if (changed_entries != NULL) {
1319 g_hash_table_iter_init (&iter, changed_entries);
1320 while (g_hash_table_iter_next (&iter, (gpointer *)&entry, (gpointer *)&entry_changes)) {
1321 GArray *emit_changes;
1322 GSList *c;
1323
1324 emit_changes = g_array_sized_new (FALSE, TRUE, sizeof (GValue), g_slist_length (entry_changes));
1325 g_array_set_clear_func (emit_changes, (GDestroyNotify) g_value_unset);
1326 for (c = entry_changes; c != NULL; c = c->next) {
1327 GValue v = {0,};
1328 g_value_init (&v, RHYTHMDB_TYPE_ENTRY_CHANGE);
1329 g_value_take_boxed (&v, c->data);
1330 g_array_append_val (emit_changes, v);
1331 }
1332 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_CHANGED], 0, entry, emit_changes);
1333 g_array_unref (emit_changes);
1334 g_hash_table_iter_remove (&iter);
1335 }
1336 }
1337
1338 /* emit added entries */
1339 for (l = added_entries; l; l = g_list_next (l)) {
1340 entry = (RhythmDBEntry *)l->data;
1341 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_ADDED], 0, entry);
1342 rhythmdb_entry_unref (entry);
1343 }
1344
1345 /* emit deleted entries */
1346 for (l = deleted_entries; l; l = g_list_next (l)) {
1347 entry = (RhythmDBEntry *)l->data;
1348 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry);
1349 rhythmdb_entry_unref (entry);
1350 }
1351
1352 GDK_THREADS_LEAVE ();
1353
1354 if (changed_entries != NULL) {
1355 g_hash_table_destroy (changed_entries);
1356 }
1357 g_list_free (added_entries);
1358 g_list_free (deleted_entries);
1359 return FALSE;
1360 }
1361
1362 static gboolean
1363 process_added_entries_cb (RhythmDBEntry *entry,
1364 GThread *thread,
1365 RhythmDB *db)
1366 {
1367 if (thread != g_thread_self ())
1368 return FALSE;
1369
1370 if (entry->type == RHYTHMDB_ENTRY_TYPE_SONG) {
1371 const gchar *uri;
1372
1373 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
1374 if (uri == NULL)
1375 return TRUE;
1376
1377 /*
1378 * hmm, do we really need to take the stat mutex to check if the action thread is running?
1379 * maybe it should be atomicised?
1380 */
1381 /*
1382 * current plan:
1383 * - only stat things with mountpoint == NULL here
1384 * - collect other mountpoints
1385 * just before starting action/stat threads:
1386 * - find remote mountpoints that aren't mounted, try to mount them
1387 * - for local mountpoints that are mounted, add to stat list
1388 * - for everything else, hide entries on those mountpoints
1389 */
1390 g_mutex_lock (&db->priv->stat_mutex);
1391 if (db->priv->action_thread_running == FALSE) {
1392 const char *mountpoint;
1393
1394 mountpoint = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT);
1395 if (mountpoint == NULL) {
1396 /* entry is on a core filesystem, always check it */
1397 rhythmdb_add_to_stat_list (db, uri, entry,
1398 RHYTHMDB_ENTRY_TYPE_SONG,
1399 RHYTHMDB_ENTRY_TYPE_IGNORE,
1400 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR);
1401 } else if (rb_string_list_contains (db->priv->active_mounts, mountpoint)) {
1402 /* mountpoint is mounted - check the file if it's local */
1403 if (rb_uri_is_local (mountpoint)) {
1404 rhythmdb_add_to_stat_list (db,
1405 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION),
1406 entry,
1407 NULL,
1408 RHYTHMDB_ENTRY_TYPE_IGNORE,
1409 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR);
1410 } else {
1411 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_MOUNTED);
1412 }
1413 } else {
1414 /* mountpoint is not mounted */
1415 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_UNMOUNTED);
1416
1417 if (rb_string_list_contains (db->priv->mount_list, mountpoint) == FALSE) {
1418 db->priv->mount_list = g_list_prepend (db->priv->mount_list, g_strdup (mountpoint));
1419 }
1420 }
1421 }
1422 g_mutex_unlock (&db->priv->stat_mutex);
1423 }
1424
1425 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) == 0);
1426 entry->flags |= RHYTHMDB_ENTRY_INSERTED;
1427
1428 rhythmdb_entry_ref (entry);
1429 db->priv->added_entries_to_emit = g_list_prepend (db->priv->added_entries_to_emit, entry);
1430
1431 return TRUE;
1432 }
1433
1434 static gboolean
1435 process_deleted_entries_cb (RhythmDBEntry *entry,
1436 GThread *thread,
1437 RhythmDB *db)
1438 {
1439 if (thread != g_thread_self ())
1440 return FALSE;
1441
1442 rhythmdb_entry_ref (entry);
1443 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) != 0);
1444 entry->flags &= ~(RHYTHMDB_ENTRY_INSERTED);
1445 db->priv->deleted_entries_to_emit = g_list_prepend (db->priv->deleted_entries_to_emit, entry);
1446
1447 return TRUE;
1448 }
1449
1450 static gboolean
1451 process_changed_entries_cb (RhythmDBEntry *entry,
1452 GSList *changes,
1453 RhythmDB *db)
1454 {
1455 GSList *existing;
1456 if (db->priv->changed_entries_to_emit == NULL) {
1457 /* the value destroy function is just g_slist_free because we
1458 * steal the actual change structures to build the value array.
1459 */
1460 db->priv->changed_entries_to_emit = g_hash_table_new_full (NULL,
1461 NULL,
1462 (GDestroyNotify) rhythmdb_entry_unref,
1463 (GDestroyNotify) g_slist_free);
1464 }
1465
1466 /* if the entry is already in the change map from a previous commit, add the
1467 * new changes to the end of the existing list.
1468 */
1469 existing = g_hash_table_lookup (db->priv->changed_entries_to_emit, entry);
1470 if (existing != NULL) {
1471 changes = g_slist_concat (existing, changes);
1472
1473 /* steal the hash entry so it doesn't free the changes; also means we
1474 * don't need to add a reference on the entry.
1475 */
1476 g_hash_table_steal (db->priv->changed_entries_to_emit, entry);
1477 } else {
1478 rhythmdb_entry_ref (entry);
1479 }
1480
1481 g_hash_table_insert (db->priv->changed_entries_to_emit, entry, changes);
1482 return TRUE;
1483 }
1484
1485 static void
1486 sync_entry_changed (RhythmDBEntry *entry,
1487 GSList *changes,
1488 RhythmDB *db)
1489 {
1490 GSList *t;
1491
1492 for (t = changes; t; t = t->next) {
1493 RBMetaDataField field;
1494 RhythmDBEntryChange *change = t->data;
1495
1496 if (metadata_field_from_prop (change->prop, &field)) {
1497 RhythmDBAction *action;
1498
1499 if (!rhythmdb_entry_can_sync_metadata (entry)) {
1500 g_warning ("trying to sync properties of non-editable file");
1501 break;
1502 }
1503
1504 action = g_slice_new0 (RhythmDBAction);
1505 action->type = RHYTHMDB_ACTION_SYNC;
1506 action->uri = rb_refstring_ref (entry->location);
1507 action->data.changes = copy_entry_changes (changes);
1508 g_async_queue_push (db->priv->action_queue, action);
1509 break;
1510 }
1511 }
1512 }
1513
1514
1515 static void
1516 rhythmdb_commit_internal (RhythmDB *db,
1517 gboolean sync_changes,
1518 GThread *thread)
1519 {
1520 g_mutex_lock (&db->priv->change_mutex);
1521
1522 if (sync_changes) {
1523 g_hash_table_foreach (db->priv->changed_entries, (GHFunc) sync_entry_changed, db);
1524 }
1525
1526 /* update the sets of entry changed/added/deleted signals to emit */
1527 g_hash_table_foreach_remove (db->priv->changed_entries, (GHRFunc) process_changed_entries_cb, db);
1528 g_hash_table_foreach_remove (db->priv->added_entries, (GHRFunc) process_added_entries_cb, db);
1529 g_hash_table_foreach_remove (db->priv->deleted_entries, (GHRFunc) process_deleted_entries_cb, db);
1530
1531 /* if there are some signals to emit, add a new idle callback if required */
1532 if (db->priv->added_entries_to_emit || db->priv->deleted_entries_to_emit || db->priv->changed_entries_to_emit) {
1533 if (db->priv->emit_entry_signals_id == 0)
1534 db->priv->emit_entry_signals_id = g_idle_add ((GSourceFunc) rhythmdb_emit_entry_signals_idle, db);
1535 }
1536
1537 g_mutex_unlock (&db->priv->change_mutex);
1538 }
1539
1540 typedef struct {
1541 RhythmDB *db;
1542 gboolean sync;
1543 GThread *thread;
1544 } RhythmDBTimeoutCommitData;
1545
1546 static gboolean
1547 timeout_rhythmdb_commit (RhythmDBTimeoutCommitData *data)
1548 {
1549 rhythmdb_commit_internal (data->db, data->sync, data->thread);
1550 g_object_unref (data->db);
1551 g_free (data);
1552 return FALSE;
1553 }
1554
1555 static void
1556 rhythmdb_add_timeout_commit (RhythmDB *db,
1557 gboolean sync)
1558 {
1559 RhythmDBTimeoutCommitData *data;
1560
1561 g_assert (rb_is_main_thread ());
1562
1563 data = g_new0 (RhythmDBTimeoutCommitData, 1);
1564 data->db = g_object_ref (db);
1565 data->sync = sync;
1566 data->thread = g_thread_self ();
1567 g_timeout_add (100, (GSourceFunc)timeout_rhythmdb_commit, data);
1568 }
1569
1570 /**
1571 * rhythmdb_commit:
1572 * @db: a #RhythmDB.
1573 *
1574 * Apply all database changes, and send notification of changes and new entries.
1575 * This needs to be called after any changes have been made, such as a group of
1576 * rhythmdb_entry_set() calls, or a new entry has been added.
1577 */
1578 void
1579 rhythmdb_commit (RhythmDB *db)
1580 {
1581 rhythmdb_commit_internal (db, TRUE, g_thread_self ());
1582 }
1583
1584 /**
1585 * rhythmdb_error_quark:
1586 *
1587 * Returns the #GQuark used for #RhythmDBError information
1588 *
1589 * Return value: error quark
1590 */
1591 GQuark
1592 rhythmdb_error_quark (void)
1593 {
1594 static GQuark quark;
1595 if (!quark)
1596 quark = g_quark_from_static_string ("rhythmdb_error");
1597
1598 return quark;
1599 }
1600
1601 /* structure alignment magic, stolen from glib */
1602 #define STRUCT_ALIGNMENT (2 * sizeof (gsize))
1603 #define ALIGN_STRUCT(offset) \
1604 ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT)
1605
1606 /**
1607 * rhythmdb_entry_allocate:
1608 * @db: a #RhythmDB.
1609 * @type: type of entry to allocate
1610 *
1611 * Allocate and initialise memory for a new #RhythmDBEntry of the type @type.
1612 * The entry's initial properties needs to be set with rhythmdb_entry_set (),
1613 * the entry added to the database with rhythmdb_entry_insert(), and committed with
1614 * rhythmdb_commit().
1615 *
1616 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
1617 *
1618 * Returns: the newly allocated #RhythmDBEntry
1619 */
1620 RhythmDBEntry *
1621 rhythmdb_entry_allocate (RhythmDB *db,
1622 RhythmDBEntryType *type)
1623 {
1624 RhythmDBEntry *ret;
1625 guint type_data_size = 0;
1626 gsize size = sizeof (RhythmDBEntry);
1627
1628 g_object_get (type, "type-data-size", &type_data_size, NULL);
1629 if (type_data_size > 0) {
1630 size = ALIGN_STRUCT (sizeof (RhythmDBEntry)) + type_data_size;
1631 }
1632 ret = g_malloc0 (size);
1633 ret->id = (guint) g_atomic_int_add (&db->priv->next_entry_id, 1);
1634
1635 ret->type = type;
1636 ret->title = rb_refstring_ref (db->priv->empty_string);
1637 ret->genre = rb_refstring_ref (db->priv->empty_string);
1638 ret->artist = rb_refstring_ref (db->priv->empty_string);
1639 ret->album = rb_refstring_ref (db->priv->empty_string);
1640 ret->comment = rb_refstring_ref (db->priv->empty_string);
1641 ret->album_artist = rb_refstring_ref (db->priv->empty_string);
1642 ret->musicbrainz_trackid = rb_refstring_ref (db->priv->empty_string);
1643 ret->musicbrainz_artistid = rb_refstring_ref (db->priv->empty_string);
1644 ret->musicbrainz_albumid = rb_refstring_ref (db->priv->empty_string);
1645 ret->musicbrainz_albumartistid = rb_refstring_ref (db->priv->empty_string);
1646 ret->artist_sortname = rb_refstring_ref (db->priv->empty_string);
1647 ret->album_sortname = rb_refstring_ref (db->priv->empty_string);
1648 ret->album_artist_sortname = rb_refstring_ref (db->priv->empty_string);
1649 ret->media_type = rb_refstring_ref (db->priv->octet_stream_str);
1650
1651 ret->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY |
1652 RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY |
1653 RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
1654
1655 /* The refcount is initially 0, we want to set it to 1 */
1656 ret->refcount = 1;
1657
1658 rhythmdb_entry_created (ret);
1659
1660 return ret;
1661 }
1662
1663 /**
1664 * rhythmdb_entry_get_type_data:
1665 * @entry: a #RhythmDBEntry
1666 * @expected_size: expected size of the type-specific data.
1667 *
1668 * Retrieves a pointer to the entry's type-specific data, checking that
1669 * the size of the data structure matches what is expected.
1670 * Callers should use the RHYTHMDB_ENTRY_GET_TYPE_DATA macro for
1671 * a slightly more friendly interface to this functionality.
1672 *
1673 * Return value: (transfer none): type-specific data pointer
1674 */
1675 gpointer
1676 rhythmdb_entry_get_type_data (RhythmDBEntry *entry,
1677 guint expected_size)
1678 {
1679 g_return_val_if_fail (entry != NULL, NULL);
1680 int type_data_size = 0;
1681 gsize offset;
1682
1683 g_object_get (entry->type, "type-data-size", &type_data_size, NULL);
1684
1685 g_assert (expected_size == type_data_size);
1686 offset = ALIGN_STRUCT (sizeof (RhythmDBEntry));
1687
1688 return (gpointer) (((guint8 *)entry) + offset);
1689 }
1690
1691 /**
1692 * rhythmdb_entry_insert:
1693 * @db: a #RhythmDB.
1694 * @entry: the entry to insert.
1695 *
1696 * Inserts a newly-created entry into the database.
1697 *
1698 * Note that you must call rhythmdb_commit() at some point after invoking
1699 * this function.
1700 */
1701 void
1702 rhythmdb_entry_insert (RhythmDB *db,
1703 RhythmDBEntry *entry)
1704 {
1705 g_return_if_fail (RHYTHMDB_IS (db));
1706 g_return_if_fail (entry != NULL);
1707
1708 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) == 0);
1709 g_return_if_fail (entry->location != NULL);
1710
1711 /* ref the entry before adding to hash, it is unreffed when removed */
1712 rhythmdb_entry_ref (entry);
1713 g_mutex_lock (&db->priv->change_mutex);
1714 g_hash_table_insert (db->priv->added_entries, entry, g_thread_self ());
1715 g_mutex_unlock (&db->priv->change_mutex);
1716 }
1717
1718 /**
1719 * rhythmdb_entry_new:
1720 * @db: a #RhythmDB.
1721 * @type: type of entry to create
1722 * @uri: the location of the entry, this be unique amongst all entries.
1723 *
1724 * Creates a new entry of type @type and location @uri, and inserts
1725 * it into the database. You must call rhythmdb_commit() at some point
1726 * after invoking this function.
1727 *
1728 * This may return NULL if entry creation fails. This can occur if there is
1729 * already an entry with the given uri.
1730 *
1731 * Returns: the newly created #RhythmDBEntry
1732 */
1733 RhythmDBEntry *
1734 rhythmdb_entry_new (RhythmDB *db,
1735 RhythmDBEntryType *type,
1736 const char *uri)
1737 {
1738 RhythmDBEntry *ret;
1739 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
1740
1741 ret = rhythmdb_entry_lookup_by_location (db, uri);
1742 if (ret) {
1743 g_warning ("attempting to create entry that already exists: %s", uri);
1744 return NULL;
1745 }
1746
1747 ret = rhythmdb_entry_allocate (db, type);
1748 ret->location = rb_refstring_new (uri);
1749 klass->impl_entry_new (db, ret);
1750 rb_debug ("emitting entry added");
1751 rhythmdb_entry_insert (db, ret);
1752
1753 return ret;
1754 }
1755
1756 /**
1757 * rhythmdb_entry_example_new:
1758 * @db: a #RhythmDB.
1759 * @type: type of entry to create
1760 * @uri: the location of the entry, this be unique amongst all entries.
1761 *
1762 * Creates a new sample entry of type @type and location @uri, it does not insert
1763 * it into the database. This is indended for use as a example entry.
1764 *
1765 * This may return NULL if entry creation fails.
1766 *
1767 * Returns: the newly created #RhythmDBEntry
1768 */
1769 RhythmDBEntry *
1770 rhythmdb_entry_example_new (RhythmDB *db,
1771 RhythmDBEntryType *type,
1772 const char *uri)
1773 {
1774 RhythmDBEntry *ret;
1775
1776 ret = rhythmdb_entry_allocate (db, type);
1777 if (uri)
1778 ret->location = rb_refstring_new (uri);
1779
1780 if (type == RHYTHMDB_ENTRY_TYPE_SONG) {
1781 rb_refstring_unref (ret->artist);
1782 /* Translators: this is an example artist name. It should
1783 * not be translated literally, but could be replaced with
1784 * a local artist name if desired. Ensure the album name
1785 * and song title are also replaced in this case.
1786 */
1787 ret->artist = rb_refstring_new (_("The Beatles"));
1788 rb_refstring_unref (ret->album);
1789 /* Translators: this is an example album name. If the
1790 * example artist name is localised, this should be replaced
1791 * with the name of an album by that artist.
1792 */
1793 ret->album = rb_refstring_new (_("Help!"));
1794 rb_refstring_unref (ret->title);
1795 /* Translators: this is an example song title. If the example
1796 * artist and album names are localised, this should be replaced
1797 * with the name of the seventh song from the localised album.
1798 */
1799 ret->title = rb_refstring_new (_("Ticket To Ride"));
1800 ret->tracknum = 7;
1801 } else {
1802 }
1803
1804 return ret;
1805 }
1806
1807 /**
1808 * rhythmdb_entry_ref:
1809 * @entry: a #RhythmDBEntry.
1810 *
1811 * Increase the reference count of the entry.
1812 *
1813 * Returns: the entry
1814 */
1815 RhythmDBEntry *
1816 rhythmdb_entry_ref (RhythmDBEntry *entry)
1817 {
1818 g_return_val_if_fail (entry != NULL, NULL);
1819 g_return_val_if_fail (entry->refcount > 0, NULL);
1820
1821 g_atomic_int_inc (&entry->refcount);
1822
1823 return entry;
1824 }
1825
1826 static void
1827 rhythmdb_entry_finalize (RhythmDBEntry *entry)
1828 {
1829 rhythmdb_entry_pre_destroy (entry);
1830
1831 rb_refstring_unref (entry->location);
1832 rb_refstring_unref (entry->playback_error);
1833 rb_refstring_unref (entry->title);
1834 rb_refstring_unref (entry->genre);
1835 rb_refstring_unref (entry->artist);
1836 rb_refstring_unref (entry->album);
1837 rb_refstring_unref (entry->comment);
1838 rb_refstring_unref (entry->musicbrainz_trackid);
1839 rb_refstring_unref (entry->musicbrainz_artistid);
1840 rb_refstring_unref (entry->musicbrainz_albumid);
1841 rb_refstring_unref (entry->musicbrainz_albumartistid);
1842 rb_refstring_unref (entry->artist_sortname);
1843 rb_refstring_unref (entry->album_sortname);
1844 rb_refstring_unref (entry->media_type);
1845
1846 g_free (entry);
1847 }
1848
1849 /**
1850 * rhythmdb_entry_unref:
1851 * @entry: a #RhythmDBEntry.
1852 *
1853 * Decrease the reference count of the entry, and destroys it if there are
1854 * no references left.
1855 */
1856 void
1857 rhythmdb_entry_unref (RhythmDBEntry *entry)
1858 {
1859 gboolean is_zero;
1860
1861 g_return_if_fail (entry != NULL);
1862 g_return_if_fail (entry->refcount > 0);
1863
1864 is_zero = g_atomic_int_dec_and_test (&entry->refcount);
1865 if (G_UNLIKELY (is_zero)) {
1866 rhythmdb_entry_finalize (entry);
1867 }
1868 }
1869
1870 static void
1871 set_metadata_string_with_default (RhythmDB *db,
1872 RBMetaData *metadata,
1873 RhythmDBEntry *entry,
1874 RBMetaDataField field,
1875 RhythmDBPropType prop,
1876 const char *default_value)
1877 {
1878 GValue val = {0, };
1879
1880 if (!(rb_metadata_get (metadata,
1881 field,
1882 &val))) {
1883 g_value_init (&val, G_TYPE_STRING);
1884 g_value_set_static_string (&val, default_value);
1885 } else {
1886 const gchar *str = g_value_get_string (&val);
1887 if (str == NULL || str[0] == '\0')
1888 g_value_set_static_string (&val, default_value);
1889 }
1890 rhythmdb_entry_set_internal (db, entry, TRUE, prop, &val);
1891 g_value_unset (&val);
1892 }
1893
1894 static void
1895 set_props_from_metadata (RhythmDB *db,
1896 RhythmDBEntry *entry,
1897 GFileInfo *fileinfo,
1898 RBMetaData *metadata)
1899 {
1900 const char *media_type;
1901 GValue val = {0,};
1902
1903 g_value_init (&val, G_TYPE_STRING);
1904 media_type = rb_metadata_get_media_type (metadata);
1905 if (media_type) {
1906 g_value_set_string (&val, media_type);
1907 rhythmdb_entry_set_internal (db, entry, TRUE,
1908 RHYTHMDB_PROP_MEDIA_TYPE, &val);
1909 }
1910 g_value_unset (&val);
1911
1912 /* track number */
1913 if (!rb_metadata_get (metadata,
1914 RB_METADATA_FIELD_TRACK_NUMBER,
1915 &val)) {
1916 g_value_init (&val, G_TYPE_ULONG);
1917 g_value_set_ulong (&val, 0);
1918 }
1919 rhythmdb_entry_set_internal (db, entry, TRUE,
1920 RHYTHMDB_PROP_TRACK_NUMBER, &val);
1921 g_value_unset (&val);
1922
1923 /* disc number */
1924 if (!rb_metadata_get (metadata,
1925 RB_METADATA_FIELD_DISC_NUMBER,
1926 &val)) {
1927 g_value_init (&val, G_TYPE_ULONG);
1928 g_value_set_ulong (&val, 0);
1929 }
1930 rhythmdb_entry_set_internal (db, entry, TRUE,
1931 RHYTHMDB_PROP_DISC_NUMBER, &val);
1932 g_value_unset (&val);
1933
1934 /* duration */
1935 if (rb_metadata_get (metadata,
1936 RB_METADATA_FIELD_DURATION,
1937 &val)) {
1938 rhythmdb_entry_set_internal (db, entry, TRUE,
1939 RHYTHMDB_PROP_DURATION, &val);
1940 g_value_unset (&val);
1941 }
1942
1943 /* bitrate */
1944 if (rb_metadata_get (metadata,
1945 RB_METADATA_FIELD_BITRATE,
1946 &val)) {
1947 rhythmdb_entry_set_internal (db, entry, TRUE,
1948 RHYTHMDB_PROP_BITRATE, &val);
1949 g_value_unset (&val);
1950 }
1951
1952 /* date */
1953 if (rb_metadata_get (metadata,
1954 RB_METADATA_FIELD_DATE,
1955 &val)) {
1956 rhythmdb_entry_set_internal (db, entry, TRUE,
1957 RHYTHMDB_PROP_DATE, &val);
1958 g_value_unset (&val);
1959 }
1960
1961 /* musicbrainz trackid */
1962 set_metadata_string_with_default (db, metadata, entry,
1963 RB_METADATA_FIELD_MUSICBRAINZ_TRACKID,
1964 RHYTHMDB_PROP_MUSICBRAINZ_TRACKID,
1965 "");
1966
1967 /* musicbrainz artistid */
1968 set_metadata_string_with_default (db, metadata, entry,
1969 RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID,
1970 RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID,
1971 "");
1972
1973 /* musicbrainz albumid */
1974 set_metadata_string_with_default (db, metadata, entry,
1975 RB_METADATA_FIELD_MUSICBRAINZ_ALBUMID,
1976 RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID,
1977 "");
1978
1979 /* musicbrainz albumartistid */
1980 set_metadata_string_with_default (db, metadata, entry,
1981 RB_METADATA_FIELD_MUSICBRAINZ_ALBUMARTISTID,
1982 RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID,
1983 "");
1984
1985 /* filesize */
1986 g_value_init (&val, G_TYPE_UINT64);
1987 g_value_set_uint64 (&val, g_file_info_get_attribute_uint64 (fileinfo, G_FILE_ATTRIBUTE_STANDARD_SIZE));
1988 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_FILE_SIZE, &val);
1989 g_value_unset (&val);
1990
1991 /* title */
1992 if (!rb_metadata_get (metadata,
1993 RB_METADATA_FIELD_TITLE,
1994 &val) || g_value_get_string (&val)[0] == '\0') {
1995 const char *fname;
1996 fname = g_file_info_get_display_name (fileinfo);
1997 if (G_VALUE_HOLDS_STRING (&val))
1998 g_value_reset (&val);
1999 else
2000 g_value_init (&val, G_TYPE_STRING);
2001 g_value_set_string (&val, fname);
2002 }
2003 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_TITLE, &val);
2004 g_value_unset (&val);
2005
2006 /* genre */
2007 set_metadata_string_with_default (db, metadata, entry,
2008 RB_METADATA_FIELD_GENRE,
2009 RHYTHMDB_PROP_GENRE,
2010 _("Unknown"));
2011
2012 /* artist */
2013 set_metadata_string_with_default (db, metadata, entry,
2014 RB_METADATA_FIELD_ARTIST,
2015 RHYTHMDB_PROP_ARTIST,
2016 _("Unknown"));
2017
2018 /* beats per minute */
2019 if (rb_metadata_get (metadata,
2020 RB_METADATA_FIELD_BPM,
2021 &val)) {
2022 rhythmdb_entry_set_internal (db, entry, TRUE,
2023 RHYTHMDB_PROP_BPM, &val);
2024 g_value_unset (&val);
2025 }
2026
2027 /* album */
2028 set_metadata_string_with_default (db, metadata, entry,
2029 RB_METADATA_FIELD_ALBUM,
2030 RHYTHMDB_PROP_ALBUM,
2031 _("Unknown"));
2032 /* artist sortname */
2033 set_metadata_string_with_default (db, metadata, entry,
2034 RB_METADATA_FIELD_ARTIST_SORTNAME,
2035 RHYTHMDB_PROP_ARTIST_SORTNAME,
2036 "");
2037
2038 /* album sortname */
2039 set_metadata_string_with_default (db, metadata, entry,
2040 RB_METADATA_FIELD_ALBUM_SORTNAME,
2041 RHYTHMDB_PROP_ALBUM_SORTNAME,
2042 "");
2043
2044 /* comment */
2045 set_metadata_string_with_default (db, metadata, entry,
2046 RB_METADATA_FIELD_COMMENT,
2047 RHYTHMDB_PROP_COMMENT,
2048 "");
2049 /* album artist */
2050 set_metadata_string_with_default (db, metadata, entry,
2051 RB_METADATA_FIELD_ALBUM_ARTIST,
2052 RHYTHMDB_PROP_ALBUM_ARTIST,
2053 "");
2054
2055 /* album artist sortname */
2056 set_metadata_string_with_default (db, metadata, entry,
2057 RB_METADATA_FIELD_ALBUM_ARTIST_SORTNAME,
2058 RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME,
2059 "");
2060 }
2061
2062 static void
2063 rhythmdb_process_stat_event (RhythmDB *db,
2064 RhythmDBEvent *event)
2065 {
2066 RhythmDBEntry *entry;
2067 RhythmDBAction *action;
2068 GFileType file_type;
2069
2070 if (event->entry != NULL) {
2071 entry = event->entry;
2072 } else {
2073 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
2074 }
2075
2076 /* handle errors:
2077 * - if a non-ignore entry exists, process ghostliness (hide/delete)
2078 * - otherwise, create an import error entry? hmm.
2079 */
2080 if (event->error) {
2081 if (entry != NULL) {
2082 rb_debug ("error accessing %s: %s",
2083 rb_refstring_get (event->real_uri),
2084 event->error->message);
2085 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_NOT_FOUND);
2086 rhythmdb_commit (db);
2087 }
2088 return;
2089 }
2090
2091 g_assert (event->file_info != NULL);
2092
2093 /* figure out what to do based on the file type */
2094 file_type = g_file_info_get_attribute_uint32 (event->file_info,
2095 G_FILE_ATTRIBUTE_STANDARD_TYPE);
2096 switch (file_type) {
2097 case G_FILE_TYPE_UNKNOWN:
2098 case G_FILE_TYPE_REGULAR:
2099 if (entry != NULL) {
2100 guint64 new_mtime;
2101 guint64 new_size;
2102
2103 /* update the existing entry, as long as the entry type matches */
2104 if ((event->entry_type != NULL) &&
2105 (entry->type != event->entry_type) &&
2106 (entry->type != event->ignore_type) &&
2107 (entry->type != event->error_type)) {
2108 if (event->entry_type == RHYTHMDB_ENTRY_TYPE_SONG &&
2109 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) {
2110 rb_debug ("Ignoring stat event for '%s', it's already loaded as a podcast",
2111 rb_refstring_get (event->real_uri));
2112 break;
2113 }
2114 g_warning ("attempt to use location %s in multiple entry types (%s and %s)",
2115 rb_refstring_get (event->real_uri),
2116 rhythmdb_entry_type_get_name (event->entry_type),
2117 rhythmdb_entry_type_get_name (entry->type));
2118 }
2119
2120 if (entry->type == event->ignore_type)
2121 rb_debug ("ignoring %p", entry);
2122
2123 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_CHECKED);
2124
2125 /* compare modification time and size to the values in the database.
2126 * if either has changed, we'll re-read the file.
2127 */
2128 new_mtime = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
2129 new_size = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
2130 if (entry->mtime == new_mtime && (new_size == 0 || entry->file_size == new_size)) {
2131 rb_debug ("not modified: %s", rb_refstring_get (event->real_uri));
2132 } else {
2133 rb_debug ("changed: %s", rb_refstring_get (event->real_uri));
2134 action = g_slice_new0 (RhythmDBAction);
2135 action->type = RHYTHMDB_ACTION_LOAD;
2136 action->uri = rb_refstring_ref (event->real_uri);
2137 action->data.types.entry_type = event->entry_type;
2138 action->data.types.ignore_type = event->ignore_type;
2139 action->data.types.error_type = event->error_type;
2140 g_async_queue_push (db->priv->action_queue, action);
2141 }
2142 } else {
2143 /* push a LOAD action */
2144 action = g_slice_new0 (RhythmDBAction);
2145 action->type = RHYTHMDB_ACTION_LOAD;
2146 action->uri = rb_refstring_ref (event->real_uri);
2147 action->data.types.entry_type = event->entry_type;
2148 action->data.types.ignore_type = event->ignore_type;
2149 action->data.types.error_type = event->error_type;
2150 rb_debug ("queuing a RHYTHMDB_ACTION_LOAD: %s", rb_refstring_get (action->uri));
2151 g_async_queue_push (db->priv->action_queue, action);
2152 }
2153 break;
2154
2155 case G_FILE_TYPE_DIRECTORY:
2156 rb_debug ("processing directory %s", rb_refstring_get (event->real_uri));
2157 /* push an ENUM_DIR action */
2158 action = g_slice_new0 (RhythmDBAction);
2159 action->type = RHYTHMDB_ACTION_ENUM_DIR;
2160 action->uri = rb_refstring_ref (event->real_uri);
2161 action->data.types.entry_type = event->entry_type;
2162 action->data.types.ignore_type = event->ignore_type;
2163 action->data.types.error_type = event->error_type;
2164 rb_debug ("queuing a RHYTHMDB_ACTION_ENUM_DIR: %s", rb_refstring_get (action->uri));
2165 g_async_queue_push (db->priv->action_queue, action);
2166 break;
2167
2168 case G_FILE_TYPE_SYMBOLIC_LINK:
2169 case G_FILE_TYPE_SHORTCUT:
2170 /* this shouldn't happen, but maybe we should handle it anyway? */
2171 rb_debug ("ignoring stat results for %s: is link", rb_refstring_get (event->real_uri));
2172 break;
2173
2174 case G_FILE_TYPE_SPECIAL:
2175 case G_FILE_TYPE_MOUNTABLE: /* hmm. */
2176 rb_debug ("ignoring stat results for %s: is special", rb_refstring_get (event->real_uri));
2177 break;
2178 }
2179
2180 rhythmdb_commit (db);
2181 }
2182
2183 typedef struct
2184 {
2185 RhythmDB *db;
2186 char *uri;
2187 char *msg;
2188 } RhythmDBLoadErrorData;
2189
2190 static void
2191 rhythmdb_add_import_error_entry (RhythmDB *db,
2192 RhythmDBEvent *event,
2193 RhythmDBEntryType *error_entry_type)
2194 {
2195 RhythmDBEntry *entry;
2196 GValue value = {0,};
2197
2198 if (error_entry_type == NULL) {
2199 /* we don't have an error entry type, so we can't add an import error */
2200 return;
2201 }
2202 rb_debug ("adding import error type %s for %s: %s",
2203 rhythmdb_entry_type_get_name (error_entry_type),
2204 rb_refstring_get (event->real_uri),
2205 event->error ? event->error->message : "<no error>");
2206
2207 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
2208 if (entry) {
2209 RhythmDBEntryType *entry_type = rhythmdb_entry_get_entry_type (entry);
2210 if (entry_type != event->error_type &&
2211 entry_type != event->ignore_type) {
2212 /* FIXME we've successfully read this file before.. so what should we do? */
2213 rb_debug ("%s already exists in the library.. ignoring import error?", rb_refstring_get (event->real_uri));
2214 return;
2215 }
2216
2217 if (entry_type != error_entry_type) {
2218 /* delete the existing entry, then create a new one below */
2219 rhythmdb_entry_delete (db, entry);
2220 entry = NULL;
2221 } else if (error_entry_type == event->error_type && event->error) {
2222 /* we've already got an error for this file, so just update it */
2223 g_value_init (&value, G_TYPE_STRING);
2224 g_value_set_string (&value, event->error->message);
2225 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
2226 g_value_unset (&value);
2227 } else {
2228 /* no need to update the ignored file entry */
2229 }
2230
2231 if (entry && event->file_info) {
2232 /* mtime */
2233 guint64 new_mtime = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
2234 g_value_init (&value, G_TYPE_ULONG);
2235 g_value_set_ulong (&value, new_mtime); /* hmm, cast */
2236 rhythmdb_entry_set(db, entry, RHYTHMDB_PROP_MTIME, &value);
2237 g_value_unset (&value);
2238 }
2239
2240 rhythmdb_add_timeout_commit (db, FALSE);
2241 }
2242
2243 if (entry == NULL) {
2244 /* create a new import error or ignore entry */
2245 entry = rhythmdb_entry_new (db, error_entry_type, rb_refstring_get (event->real_uri));
2246 if (entry == NULL)
2247 return;
2248
2249 /* if we have missing plugin details, store them in the
2250 * comment field so we can collect them later, and set a
2251 * suitable error message
2252 */
2253 if (event->metadata != NULL && rb_metadata_has_missing_plugins (event->metadata)) {
2254 char **missing_plugins;
2255 char **plugin_descriptions;
2256 char *comment;
2257 char *list;
2258 const char *msg;
2259
2260 /* Translators: the parameter here is a list of GStreamer plugins.
2261 * The plugin names are already translated.
2262 */
2263 msg = _("Additional GStreamer plugins are required to play this file: %s");
2264
2265 if (rb_metadata_has_audio (event->metadata) == TRUE &&
2266 rb_metadata_has_video (event->metadata) == FALSE &&
2267 rb_metadata_has_missing_plugins (event->metadata) == TRUE) {
2268 rb_metadata_get_missing_plugins (event->metadata, &missing_plugins, &plugin_descriptions);
2269 comment = g_strjoinv ("\n", missing_plugins);
2270 rb_debug ("storing missing plugin details: %s", comment);
2271
2272 g_value_init (&value, G_TYPE_STRING);
2273 g_value_take_string (&value, comment);
2274 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_COMMENT, &value);
2275 g_value_unset (&value);
2276
2277 g_value_init (&value, G_TYPE_STRING);
2278 list = g_strjoinv (", ", plugin_descriptions);
2279 g_value_take_string (&value, g_strdup_printf (msg, list));
2280 g_free (list);
2281 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
2282 g_value_unset (&value);
2283
2284 g_strfreev (missing_plugins);
2285 g_strfreev (plugin_descriptions);
2286
2287 } else if (rb_metadata_has_missing_plugins (event->metadata)) {
2288 rb_debug ("ignoring missing plugins for non-audio file");
2289 }
2290 } else if (error_entry_type == event->error_type && event->error && event->error->message) {
2291 g_value_init (&value, G_TYPE_STRING);
2292 if (g_utf8_validate (event->error->message, -1, NULL))
2293 g_value_set_string (&value, event->error->message);
2294 else
2295 g_value_set_static_string (&value, _("invalid unicode in error message"));
2296 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
2297 g_value_unset (&value);
2298 }
2299
2300 /* mtime */
2301 if (event->file_info) {
2302 guint64 new_mtime = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
2303 g_value_init (&value, G_TYPE_ULONG);
2304 g_value_set_ulong (&value, new_mtime); /* hmm, cast */
2305 rhythmdb_entry_set(db, entry, RHYTHMDB_PROP_MTIME, &value);
2306 g_value_unset (&value);
2307 }
2308
2309 /* record the mount point so we can delete entries for unmounted volumes */
2310 rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri));
2311
2312 rhythmdb_entry_set_visibility (db, entry, TRUE);
2313
2314 rhythmdb_add_timeout_commit (db, FALSE);
2315 }
2316 }
2317
2318 static gboolean
2319 rhythmdb_process_metadata_load (RhythmDB *db, RhythmDBEvent *event)
2320 {
2321 RhythmDBEntry *entry;
2322 GValue value = {0,};
2323 GTimeVal time;
2324 gboolean monitor;
2325
2326 if (event->entry_type == NULL)
2327 event->entry_type = RHYTHMDB_ENTRY_TYPE_SONG;
2328
2329 if (event->metadata != NULL) {
2330 /* always ignore anything with video in it */
2331 if (rb_metadata_has_video (event->metadata)) {
2332 rhythmdb_add_import_error_entry (db, event, event->ignore_type);
2333 return TRUE;
2334 }
2335
2336 /* if we identified the media type, we can ignore anything
2337 * that matches one of the media types we don't care about,
2338 * as well as anything that doesn't contain audio.
2339 */
2340 const char *media_type = rb_metadata_get_media_type (event->metadata);
2341 if (media_type != NULL && media_type[0] != '\0') {
2342 if (rhythmdb_ignore_media_type (media_type) ||
2343 rb_metadata_has_audio (event->metadata) == FALSE) {
2344 rhythmdb_add_import_error_entry (db, event, event->ignore_type);
2345 return TRUE;
2346 }
2347 }
2348 }
2349
2350 /* also ignore really small files we can't identify */
2351 if (event->error && event->error->code == RB_METADATA_ERROR_UNRECOGNIZED) {
2352 guint64 file_size;
2353
2354 file_size = g_file_info_get_attribute_uint64 (event->file_info,
2355 G_FILE_ATTRIBUTE_STANDARD_SIZE);
2356 if (file_size == 0) {
2357 /* except for empty files */
2358 g_clear_error (&event->error);
2359 g_set_error (&event->error,
2360 RB_METADATA_ERROR,
2361 RB_METADATA_ERROR_EMPTY_FILE,
2362 _("Empty file"));
2363 } else if (file_size < REALLY_SMALL_FILE_SIZE) {
2364 rhythmdb_add_import_error_entry (db, event, event->ignore_type);
2365 return TRUE;
2366 }
2367 }
2368
2369 if (event->error) {
2370 rhythmdb_add_import_error_entry (db, event, event->error_type);
2371 return TRUE;
2372 }
2373
2374 g_get_current_time (&time);
2375
2376 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri);
2377
2378 if (entry != NULL) {
2379 RhythmDBEntryType *etype;
2380 etype = rhythmdb_entry_get_entry_type (entry);
2381 if (etype == event->error_type || etype == event->ignore_type) {
2382 /* switching from IGNORE/ERROR to SONG, recreate the entry */
2383 rhythmdb_entry_delete (db, entry);
2384 rhythmdb_add_timeout_commit (db, FALSE);
2385 entry = NULL;
2386 }
2387 }
2388
2389 if (entry == NULL) {
2390
2391 entry = rhythmdb_entry_new (db, event->entry_type, rb_refstring_get (event->real_uri));
2392 if (entry == NULL) {
2393 rb_debug ("entry already exists");
2394 return TRUE;
2395 }
2396
2397 /* initialize the last played date to 0=never */
2398 g_value_init (&value, G_TYPE_ULONG);
2399 g_value_set_ulong (&value, 0);
2400 rhythmdb_entry_set (db, entry,
2401 RHYTHMDB_PROP_LAST_PLAYED, &value);
2402 g_value_unset (&value);
2403
2404 /* initialize the rating */
2405 g_value_init (&value, G_TYPE_DOUBLE);
2406 g_value_set_double (&value, 0);
2407 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &value);
2408 g_value_unset (&value);
2409
2410 /* first seen */
2411 g_value_init (&value, G_TYPE_ULONG);
2412 g_value_set_ulong (&value, time.tv_sec);
2413 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &value);
2414 g_value_unset (&value);
2415 }
2416
2417 if ((event->entry_type != NULL) && (entry->type != event->entry_type)) {
2418 g_warning ("attempt to use same location in multiple entry types");
2419 return TRUE;
2420 }
2421
2422 /* mtime */
2423 if (event->file_info) {
2424 guint64 mtime;
2425
2426 mtime = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED);
2427
2428 g_value_init (&value, G_TYPE_ULONG);
2429 g_value_set_ulong (&value, (gulong)mtime);
2430 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_MTIME, &value);
2431 g_value_unset (&value);
2432 }
2433
2434 if (event->entry_type != event->ignore_type &&
2435 event->entry_type != event->error_type) {
2436 set_props_from_metadata (db, entry, event->file_info, event->metadata);
2437 }
2438
2439 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_CHECKED);
2440
2441 /* Remember the mount point of the volume the song is on */
2442 rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri));
2443
2444 /* monitor the file for changes */
2445 /* FIXME: watch for errors */
2446 monitor = g_settings_get_boolean (db->priv->settings, "monitor-library");
2447 if (monitor && event->entry_type == RHYTHMDB_ENTRY_TYPE_SONG)
2448 rhythmdb_monitor_uri_path (db, rb_refstring_get (entry->location), NULL);
2449
2450 rhythmdb_commit_internal (db, FALSE, g_thread_self ());
2451
2452 return TRUE;
2453 }
2454
2455 static void
2456 rhythmdb_process_queued_entry_set_event (RhythmDB *db,
2457 RhythmDBEvent *event)
2458 {
2459 rhythmdb_entry_set_internal (db, event->entry,
2460 event->signal_change,
2461 event->change.prop,
2462 &event->change.new);
2463 /* Don't run rhythmdb_commit right now in case there
2464 * we can run a single commit for several queued
2465 * entry_set
2466 */
2467 rhythmdb_add_timeout_commit (db, TRUE);
2468 }
2469
2470 static void
2471 rhythmdb_process_one_event (RhythmDBEvent *event, RhythmDB *db)
2472 {
2473 gboolean free = TRUE;
2474
2475 /* if the database is read-only, we can't process those events
2476 * since they call rhythmdb_entry_set. Doing it this way
2477 * is safe if we assume all calls to read_enter/read_leave
2478 * are done from the main thread (the thread this function
2479 * runs in).
2480 */
2481 if (rhythmdb_get_readonly (db) &&
2482 ((event->type == RHYTHMDB_EVENT_STAT)
2483 || (event->type == RHYTHMDB_EVENT_METADATA_LOAD)
2484 || (event->type == RHYTHMDB_EVENT_ENTRY_SET))) {
2485 rb_debug ("Database is read-only, delaying event processing");
2486 g_async_queue_push (db->priv->delayed_write_queue, event);
2487 return;
2488 }
2489
2490 switch (event->type) {
2491 case RHYTHMDB_EVENT_STAT:
2492 rb_debug ("processing RHYTHMDB_EVENT_STAT");
2493 rhythmdb_process_stat_event (db, event);
2494 break;
2495 case RHYTHMDB_EVENT_METADATA_LOAD:
2496 rb_debug ("processing RHYTHMDB_EVENT_METADATA_LOAD");
2497 free = rhythmdb_process_metadata_load (db, event);
2498 break;
2499 case RHYTHMDB_EVENT_ENTRY_SET:
2500 rb_debug ("processing RHYTHMDB_EVENT_ENTRY_SET");
2501 rhythmdb_process_queued_entry_set_event (db, event);
2502 break;
2503 case RHYTHMDB_EVENT_DB_LOAD:
2504 rb_debug ("processing RHYTHMDB_EVENT_DB_LOAD");
2505 g_signal_emit (G_OBJECT (db), rhythmdb_signals[LOAD_COMPLETE], 0);
2506
2507 /* save the db every five minutes */
2508 if (db->priv->save_timeout_id > 0) {
2509 g_source_remove (db->priv->save_timeout_id);
2510 }
2511 db->priv->save_timeout_id = g_timeout_add_seconds_full (G_PRIORITY_LOW,
2512 5 * 60,
2513 (GSourceFunc) rhythmdb_idle_save,
2514 db,
2515 NULL);
2516 break;
2517 case RHYTHMDB_EVENT_THREAD_EXITED:
2518 rb_debug ("processing RHYTHMDB_EVENT_THREAD_EXITED");
2519 break;
2520 case RHYTHMDB_EVENT_DB_SAVED:
2521 rb_debug ("processing RHYTHMDB_EVENT_DB_SAVED");
2522 rhythmdb_read_leave (db);
2523 break;
2524 case RHYTHMDB_EVENT_QUERY_COMPLETE:
2525 rb_debug ("processing RHYTHMDB_EVENT_QUERY_COMPLETE");
2526 rhythmdb_read_leave (db);
2527 break;
2528 }
2529 if (free)
2530 rhythmdb_event_free (db, event);
2531 }
2532
2533
2534 static void
2535 rhythmdb_file_info_query (RhythmDB *db, GFile *file, RhythmDBEvent *event)
2536 {
2537 event->file_info = g_file_query_info (file,
2538 RHYTHMDB_FILE_INFO_ATTRIBUTES,
2539 G_FILE_QUERY_INFO_NONE,
2540 db->priv->exiting,
2541 &event->error);
2542 }
2543
2544 static void
2545 wrap_access_failed_error (RhythmDBEvent *event)
2546 {
2547 GError *wrapped;
2548
2549 wrapped = make_access_failed_error (rb_refstring_get (event->real_uri), event->error);
2550 g_error_free (event->error);
2551 event->error = wrapped;
2552 }
2553
2554 static void
2555 rhythmdb_execute_stat_mount_ready_cb (GObject *source, GAsyncResult *result, RhythmDBEvent *event)
2556 {
2557 GError *error = NULL;
2558
2559 g_file_mount_enclosing_volume_finish (G_FILE (source), result, &error);
2560 if (error != NULL) {
2561 event->error = make_access_failed_error (rb_refstring_get (event->real_uri), error);
2562 g_error_free (error);
2563
2564 g_object_unref (event->file_info);
2565 event->file_info = NULL;
2566 } else {
2567 rhythmdb_file_info_query (event->db, G_FILE (source), event);
2568 }
2569
2570 g_mutex_lock (&event->db->priv->stat_mutex);
2571 event->db->priv->outstanding_stats = g_list_remove (event->db->priv->outstanding_stats, event);
2572 g_mutex_unlock (&event->db->priv->stat_mutex);
2573
2574 g_object_unref (source);
2575 rhythmdb_push_event (event->db, event);
2576 }
2577
2578
2579 static void
2580 rhythmdb_execute_stat (RhythmDB *db,
2581 const char *uri,
2582 RhythmDBEvent *event)
2583 {
2584 GFile *file;
2585
2586 event->real_uri = rb_refstring_new (uri);
2587 file = g_file_new_for_uri (uri);
2588
2589 g_mutex_lock (&db->priv->stat_mutex);
2590 db->priv->outstanding_stats = g_list_prepend (db->priv->outstanding_stats, event);
2591 g_mutex_unlock (&db->priv->stat_mutex);
2592
2593 rhythmdb_file_info_query (db, file, event);
2594
2595 if (event->error != NULL) {
2596 /* if we can't get at it because the location isn't mounted, mount it and try again */
2597 if (g_error_matches (event->error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED)) {
2598 GMountOperation *mount_op = NULL;
2599
2600 g_error_free (event->error);
2601 event->error = NULL;
2602
2603 g_signal_emit (G_OBJECT (event->db), rhythmdb_signals[CREATE_MOUNT_OP], 0, &mount_op);
2604 if (mount_op != NULL) {
2605 g_file_mount_enclosing_volume (file,
2606 G_MOUNT_MOUNT_NONE,
2607 mount_op,
2608 event->db->priv->exiting,
2609 (GAsyncReadyCallback)rhythmdb_execute_stat_mount_ready_cb,
2610 event);
2611 return;
2612 }
2613 }
2614
2615 /* if it's some other error, or we couldn't attempt to mount the location, report the error */
2616 wrap_access_failed_error (event);
2617
2618 if (event->file_info != NULL) {
2619 g_object_unref (event->file_info);
2620 event->file_info = NULL;
2621 }
2622 }
2623
2624 /* either way, we're done now */
2625
2626 g_mutex_lock (&event->db->priv->stat_mutex);
2627 event->db->priv->outstanding_stats = g_list_remove (event->db->priv->outstanding_stats, event);
2628 g_mutex_unlock (&event->db->priv->stat_mutex);
2629
2630 rhythmdb_push_event (event->db, event);
2631 g_object_unref (file);
2632 }
2633
2634 static void
2635 rhythmdb_execute_load (RhythmDB *db,
2636 const char *uri,
2637 RhythmDBEvent *event)
2638 {
2639 GError *error = NULL;
2640 char *resolved;
2641
2642 resolved = rb_uri_resolve_symlink (uri, &error);
2643 if (resolved != NULL) {
2644 GFile *file;
2645
2646 file = g_file_new_for_uri (uri);
2647 event->file_info = g_file_query_info (file,
2648 RHYTHMDB_FILE_INFO_ATTRIBUTES,
2649 G_FILE_QUERY_INFO_NONE,
2650 NULL,
2651 &error);
2652 event->real_uri = rb_refstring_new (resolved);
2653
2654 g_free (resolved);
2655 g_object_unref (file);
2656 } else {
2657 event->real_uri = rb_refstring_new (uri);
2658 }
2659
2660 if (error != NULL) {
2661 event->error = make_access_failed_error (uri, error);
2662 if (event->file_info) {
2663 g_object_unref (event->file_info);
2664 event->file_info = NULL;
2665 }
2666 } else if (event->type == RHYTHMDB_EVENT_METADATA_LOAD) {
2667 event->metadata = rb_metadata_new ();
2668 rb_metadata_load (event->metadata,
2669 rb_refstring_get (event->real_uri),
2670 &event->error);
2671 }
2672
2673 rhythmdb_push_event (db, event);
2674 }
2675
2676 static void
2677 rhythmdb_execute_enum_dir (RhythmDB *db,
2678 RhythmDBAction *action)
2679 {
2680 GFile *dir;
2681 GFileEnumerator *dir_enum;
2682 GError *error = NULL;
2683
2684 dir = g_file_new_for_uri (rb_refstring_get (action->uri));
2685 dir_enum = g_file_enumerate_children (dir,
2686 RHYTHMDB_FILE_CHILD_INFO_ATTRIBUTES,
2687 G_FILE_QUERY_INFO_NONE,
2688 db->priv->exiting,
2689 &error);
2690 if (error != NULL) {
2691 /* don't need to worry about mounting here, as the mount should have
2692 * occurred on the stat.
2693 */
2694
2695 /* um.. what now? */
2696 rb_debug ("unable to enumerate children of %s: %s",
2697 rb_refstring_get (action->uri),
2698 error->message);
2699 g_error_free (error);
2700 g_object_unref (dir);
2701 return;
2702 }
2703
2704 while (1) {
2705 RhythmDBEvent *result;
2706 GFileInfo *file_info;
2707 GFile *child;
2708 char *child_uri;
2709
2710 file_info = g_file_enumerator_next_file (dir_enum, db->priv->exiting, &error);
2711 if (file_info == NULL) {
2712 if (error == NULL) {
2713 /* done */
2714 break;
2715 }
2716
2717 g_warning ("error getting next file: %s", error->message);
2718 g_clear_error (&error);
2719 continue;
2720 }
2721
2722 if (g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) {
2723 rb_debug ("ignoring hidden file %s", g_file_info_get_name (file_info));
2724 g_object_unref (file_info);
2725 continue;
2726 }
2727
2728 child = g_file_get_child (dir, g_file_info_get_name (file_info));
2729 child_uri = g_file_get_uri (child);
2730
2731 result = g_slice_new0 (RhythmDBEvent);
2732 result->db = db;
2733 result->type = RHYTHMDB_EVENT_STAT;
2734 result->entry_type = action->data.types.entry_type;
2735 result->error_type = action->data.types.error_type;
2736 result->ignore_type = action->data.types.ignore_type;
2737 result->real_uri = rb_refstring_new (child_uri);
2738 result->file_info = file_info;
2739 result->error = error;
2740
2741 rhythmdb_push_event (db, result);
2742 g_free (child_uri);
2743 }
2744
2745 g_file_enumerator_close (dir_enum, db->priv->exiting, &error);
2746 if (error != NULL) {
2747 /* hmm.. */
2748 rb_debug ("error closing file enumerator: %s", error->message);
2749 g_error_free (error);
2750 }
2751
2752 g_object_unref (dir);
2753 g_object_unref (dir_enum);
2754 }
2755
2756 /**
2757 * rhythmdb_entry_get:
2758 * @db: the #RhythmDB
2759 * @entry: a #RhythmDBEntry.
2760 * @propid: the id of the property to get.
2761 * @val: return location for the property value.
2762 *
2763 * Gets a property of an entry, storing it in the given #GValue.
2764 */
2765 void
2766 rhythmdb_entry_get (RhythmDB *db,
2767 RhythmDBEntry *entry,
2768 RhythmDBPropType propid,
2769 GValue *val)
2770 {
2771 g_return_if_fail (RHYTHMDB_IS (db));
2772 g_return_if_fail (entry != NULL);
2773 g_return_if_fail (entry->refcount > 0);
2774
2775 rhythmdb_entry_sync_mirrored (entry, propid);
2776
2777 g_assert (G_VALUE_TYPE (val) == rhythmdb_get_property_type (db, propid));
2778 switch (rhythmdb_properties[propid].prop_type) {
2779 case G_TYPE_STRING:
2780 g_value_set_string (val, rhythmdb_entry_get_string (entry, propid));
2781 break;
2782 case G_TYPE_BOOLEAN:
2783 g_value_set_boolean (val, rhythmdb_entry_get_boolean (entry, propid));
2784 break;
2785 case G_TYPE_ULONG:
2786 g_value_set_ulong (val, rhythmdb_entry_get_ulong (entry, propid));
2787 break;
2788 case G_TYPE_UINT64:
2789 g_value_set_uint64 (val, rhythmdb_entry_get_uint64 (entry, propid));
2790 break;
2791 case G_TYPE_DOUBLE:
2792 g_value_set_double (val, rhythmdb_entry_get_double (entry, propid));
2793 break;
2794 case G_TYPE_OBJECT:
2795 g_value_set_object (val, rhythmdb_entry_get_object (entry, propid));
2796 break;
2797 default:
2798 g_assert_not_reached ();
2799 break;
2800 }
2801 }
2802
2803 typedef struct
2804 {
2805 RhythmDB *db;
2806 char *uri;
2807 GError *error;
2808 } RhythmDBSaveErrorData;
2809
2810 static gboolean
2811 emit_save_error_idle (RhythmDBSaveErrorData *data)
2812 {
2813 g_signal_emit (G_OBJECT (data->db), rhythmdb_signals[SAVE_ERROR], 0, data->uri, data->error);
2814 g_object_unref (G_OBJECT (data->db));
2815 g_free (data->uri);
2816 g_error_free (data->error);
2817 g_free (data);
2818 return FALSE;
2819 }
2820
2821 static gpointer
2822 action_thread_main (RhythmDB *db)
2823 {
2824 RhythmDBEvent *result;
2825
2826 while (!g_cancellable_is_cancelled (db->priv->exiting)) {
2827 RhythmDBAction *action;
2828
2829 action = g_async_queue_pop (db->priv->action_queue);
2830
2831 /* hrm, do we need this check at all? */
2832 if (!g_cancellable_is_cancelled (db->priv->exiting)) {
2833 switch (action->type) {
2834 case RHYTHMDB_ACTION_STAT:
2835 result = g_slice_new0 (RhythmDBEvent);
2836 result->db = db;
2837 result->type = RHYTHMDB_EVENT_STAT;
2838 result->entry_type = action->data.types.entry_type;
2839 result->error_type = action->data.types.error_type;
2840 result->ignore_type = action->data.types.ignore_type;
2841
2842 rb_debug ("executing RHYTHMDB_ACTION_STAT for \"%s\"", rb_refstring_get (action->uri));
2843
2844 rhythmdb_execute_stat (db, rb_refstring_get (action->uri), result);
2845 break;
2846
2847 case RHYTHMDB_ACTION_LOAD:
2848 result = g_slice_new0 (RhythmDBEvent);
2849 result->db = db;
2850 result->type = RHYTHMDB_EVENT_METADATA_LOAD;
2851 result->entry_type = action->data.types.entry_type;
2852 result->error_type = action->data.types.error_type;
2853 result->ignore_type = action->data.types.ignore_type;
2854
2855 rb_debug ("executing RHYTHMDB_ACTION_LOAD for \"%s\"", rb_refstring_get (action->uri));
2856
2857 rhythmdb_execute_load (db, rb_refstring_get (action->uri), result);
2858 break;
2859
2860 case RHYTHMDB_ACTION_ENUM_DIR:
2861 rb_debug ("executing RHYTHMDB_ACTION_ENUM_DIR for \"%s\"", rb_refstring_get (action->uri));
2862 rhythmdb_execute_enum_dir (db, action);
2863 break;
2864
2865 case RHYTHMDB_ACTION_SYNC:
2866 {
2867 GError *error = NULL;
2868 RhythmDBEntry *entry;
2869
2870 if (db->priv->dry_run) {
2871 rb_debug ("dry run is enabled, not syncing metadata");
2872 break;
2873 }
2874
2875 entry = rhythmdb_entry_lookup_by_location_refstring (db, action->uri);
2876 if (!entry)
2877 break;
2878
2879 rhythmdb_entry_sync_metadata (entry, action->data.changes, &error);
2880
2881 if (error != NULL) {
2882 RhythmDBSaveErrorData *data;
2883
2884 data = g_new0 (RhythmDBSaveErrorData, 1);
2885 g_object_ref (db);
2886 data->db = db;
2887 data->uri = g_strdup (rb_refstring_get (action->uri));
2888 data->error = error;
2889 g_idle_add ((GSourceFunc)emit_save_error_idle, data);
2890 break;
2891 }
2892 break;
2893 }
2894
2895 case RHYTHMDB_ACTION_QUIT:
2896 /* don't do any real work here, since we may not process it */
2897 rb_debug ("received QUIT action");
2898 break;
2899
2900 default:
2901 g_assert_not_reached ();
2902 break;
2903 }
2904 }
2905
2906 rhythmdb_action_free (db, action);
2907 }
2908
2909 rb_debug ("exiting action thread");
2910 result = g_slice_new0 (RhythmDBEvent);
2911 result->db = db;
2912 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
2913 rhythmdb_push_event (db, result);
2914
2915 return NULL;
2916 }
2917
2918 /**
2919 * rhythmdb_add_uri:
2920 * @db: a #RhythmDB.
2921 * @uri: the URI to add an entry/entries for
2922 *
2923 * Adds the file(s) pointed to by @uri to the database, as entries of type
2924 * RHYTHMDB_ENTRY_TYPE_SONG. If the URI is that of a file, it will be added.
2925 * If the URI is that of a directory, everything under it will be added recursively.
2926 */
2927 void
2928 rhythmdb_add_uri (RhythmDB *db,
2929 const char *uri)
2930 {
2931 rhythmdb_add_uri_with_types (db,
2932 uri,
2933 RHYTHMDB_ENTRY_TYPE_SONG,
2934 RHYTHMDB_ENTRY_TYPE_IGNORE,
2935 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR);
2936 }
2937
2938 static void
2939 rhythmdb_add_to_stat_list (RhythmDB *db,
2940 const char *uri,
2941 RhythmDBEntry *entry,
2942 RhythmDBEntryType *type,
2943 RhythmDBEntryType *ignore_type,
2944 RhythmDBEntryType *error_type)
2945 {
2946 RhythmDBEvent *result;
2947
2948 result = g_slice_new0 (RhythmDBEvent);
2949 result->db = db;
2950 result->type = RHYTHMDB_EVENT_STAT;
2951 result->entry_type = type;
2952 result->ignore_type = ignore_type;
2953 result->error_type = error_type;
2954
2955 if (entry != NULL) {
2956 result->entry = rhythmdb_entry_ref (entry);
2957 }
2958
2959 /* do we really need to check for duplicate requests here? .. nah. */
2960 result->uri = rb_refstring_new (uri);
2961 db->priv->stat_list = g_list_prepend (db->priv->stat_list, result);
2962 }
2963
2964
2965 /**
2966 * rhythmdb_add_uri_with_types:
2967 * @db: a #RhythmDB.
2968 * @uri: the URI to add
2969 * @type: the #RhythmDBEntryType to use for new entries
2970 * @ignore_type: the #RhythmDBEntryType to use for ignored files
2971 * @error_type: the #RhythmDBEntryType to use for import errors
2972 *
2973 * Adds the file(s) pointed to by @uri to the database, as entries
2974 * of the specified type. If the URI points to a file, it will be added.
2975 * The the URI identifies a directory, everything under it will be added
2976 * recursively.
2977 */
2978 void
2979 rhythmdb_add_uri_with_types (RhythmDB *db,
2980 const char *uri,
2981 RhythmDBEntryType *type,
2982 RhythmDBEntryType *ignore_type,
2983 RhythmDBEntryType *error_type)
2984 {
2985 rb_debug ("queueing stat for \"%s\"", uri);
2986 g_assert (uri && *uri);
2987
2988 /*
2989 * before the action thread is started, we queue up stat actions,
2990 * as we're still creating and running queries, as well as loading
2991 * the database. when we start the action thread, we'll kick off
2992 * a thread to process all the stat events too.
2993 *
2994 * when the action thread is already running, stat actions go through
2995 * the normal action queue and are processed by the action thread.
2996 */
2997 g_mutex_lock (&db->priv->stat_mutex);
2998 if (db->priv->action_thread_running) {
2999 RhythmDBAction *action;
3000 g_mutex_unlock (&db->priv->stat_mutex);
3001
3002 action = g_slice_new0 (RhythmDBAction);
3003 action->type = RHYTHMDB_ACTION_STAT;
3004 action->uri = rb_refstring_new (uri);
3005 action->data.types.entry_type = type;
3006 action->data.types.ignore_type = ignore_type;
3007 action->data.types.error_type = error_type;
3008
3009 g_async_queue_push (db->priv->action_queue, action);
3010 } else {
3011 RhythmDBEntry *entry;
3012
3013 entry = rhythmdb_entry_lookup_by_location (db, uri);
3014 rhythmdb_add_to_stat_list (db, uri, entry, type, ignore_type, error_type);
3015
3016 g_mutex_unlock (&db->priv->stat_mutex);
3017 }
3018 }
3019
3020
3021 static gboolean
3022 rhythmdb_sync_library_idle (RhythmDB *db)
3023 {
3024 rhythmdb_sync_library_location (db);
3025 g_object_unref (db);
3026 return FALSE;
3027 }
3028
3029 static gboolean
3030 rhythmdb_load_error_cb (GError *error)
3031 {
3032 GDK_THREADS_ENTER ();
3033 rb_error_dialog (NULL,
3034 _("Could not load the music database:"),
3035 "%s", error->message);
3036 g_error_free (error);
3037
3038 GDK_THREADS_LEAVE ();
3039 return FALSE;
3040 }
3041
3042 static gpointer
3043 rhythmdb_load_thread_main (RhythmDB *db)
3044 {
3045 RhythmDBEvent *result;
3046 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3047 GError *error = NULL;
3048
3049 db->priv->active_mounts = rhythmdb_get_active_mounts (db);
3050
3051 rb_profile_start ("loading db");
3052 g_mutex_lock (&db->priv->saving_mutex);
3053 if (klass->impl_load (db, db->priv->exiting, &error) == FALSE) {
3054 rb_debug ("db load failed: disabling saving");
3055 db->priv->can_save = FALSE;
3056
3057 if (error) {
3058 g_idle_add ((GSourceFunc) rhythmdb_load_error_cb, error);
3059 }
3060 }
3061 g_mutex_unlock (&db->priv->saving_mutex);
3062
3063 rb_list_deep_free (db->priv->active_mounts);
3064 db->priv->active_mounts = NULL;
3065
3066 g_object_ref (db);
3067 g_timeout_add_seconds (10, (GSourceFunc) rhythmdb_sync_library_idle, db);
3068
3069 rb_debug ("queuing db load complete signal");
3070 result = g_slice_new0 (RhythmDBEvent);
3071 result->type = RHYTHMDB_EVENT_DB_LOAD;
3072 g_async_queue_push (db->priv->event_queue, result);
3073
3074 rb_debug ("exiting");
3075 result = g_slice_new0 (RhythmDBEvent);
3076 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
3077 rhythmdb_push_event (db, result);
3078
3079 return NULL;
3080 }
3081
3082 /**
3083 * rhythmdb_load:
3084 * @db: a #RhythmDB.
3085 *
3086 * Load the database from disk.
3087 */
3088 void
3089 rhythmdb_load (RhythmDB *db)
3090 {
3091 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_load_thread_main, db);
3092 }
3093
3094 static gpointer
3095 rhythmdb_save_thread_main (RhythmDB *db)
3096 {
3097 RhythmDBClass *klass;
3098 RhythmDBEvent *result;
3099
3100 rb_debug ("entering save thread");
3101
3102 g_mutex_lock (&db->priv->saving_mutex);
3103
3104 db->priv->save_count++;
3105 g_cond_broadcast (&db->priv->saving_condition);
3106
3107 if (!(db->priv->dirty && db->priv->can_save)) {
3108 rb_debug ("no save needed, ignoring");
3109 g_mutex_unlock (&db->priv->saving_mutex);
3110 goto out;
3111 }
3112
3113 while (db->priv->saving)
3114 g_cond_wait (&db->priv->saving_condition, &db->priv->saving_mutex);
3115
3116 db->priv->saving = TRUE;
3117
3118 rb_debug ("saving rhythmdb");
3119
3120 klass = RHYTHMDB_GET_CLASS (db);
3121 klass->impl_save (db);
3122
3123 db->priv->saving = FALSE;
3124 db->priv->dirty = FALSE;
3125
3126 g_mutex_unlock (&db->priv->saving_mutex);
3127
3128 g_cond_broadcast (&db->priv->saving_condition);
3129
3130 out:
3131 result = g_slice_new0 (RhythmDBEvent);
3132 result->db = db;
3133 result->type = RHYTHMDB_EVENT_DB_SAVED;
3134 g_async_queue_push (db->priv->event_queue, result);
3135
3136 result = g_slice_new0 (RhythmDBEvent);
3137 result->db = db;
3138 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
3139 rhythmdb_push_event (db, result);
3140 return NULL;
3141 }
3142
3143 /**
3144 * rhythmdb_save_async:
3145 * @db: a #RhythmDB.
3146 *
3147 * Save the database to disk, asynchronously.
3148 */
3149 void
3150 rhythmdb_save_async (RhythmDB *db)
3151 {
3152 rb_debug ("saving the rhythmdb in the background");
3153
3154 rhythmdb_read_enter (db);
3155
3156 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_save_thread_main, db);
3157 }
3158
3159 /**
3160 * rhythmdb_save:
3161 * @db: a #RhythmDB.
3162 *
3163 * Save the database to disk, not returning until it has been saved.
3164 */
3165 void
3166 rhythmdb_save (RhythmDB *db)
3167 {
3168 int new_save_count;
3169
3170 rb_debug("saving the rhythmdb and blocking");
3171
3172 g_mutex_lock (&db->priv->saving_mutex);
3173 new_save_count = db->priv->save_count + 1;
3174
3175 rhythmdb_save_async (db);
3176
3177 /* wait until this save request is being processed */
3178 while (db->priv->save_count < new_save_count) {
3179 g_cond_wait (&db->priv->saving_condition, &db->priv->saving_mutex);
3180 }
3181
3182 /* wait until it's done */
3183 while (db->priv->saving) {
3184 g_cond_wait (&db->priv->saving_condition, &db->priv->saving_mutex);
3185 }
3186
3187 rb_debug ("done");
3188
3189 g_mutex_unlock (&db->priv->saving_mutex);
3190 }
3191
3192 /**
3193 * rhythmdb_entry_set:
3194 * @db:# a RhythmDB.
3195 * @entry: a #RhythmDBEntry.
3196 * @propid: the id of the property to set.
3197 * @value: the property value.
3198 *
3199 * This function can be called by any code which wishes to change a
3200 * song property and send a notification. It may be called when the
3201 * database is read-only; in this case the change will be queued for
3202 * an unspecified time in the future. The implication of this is that
3203 * rhythmdb_entry_get() may not reflect the changes immediately. However,
3204 * if this property is exposed in the user interface, you should still
3205 * make the change in the widget. Then when the database returns to a
3206 * writable state, your change will take effect in the database too,
3207 * and a notification will be sent at that point.
3208 *
3209 * Note that you must call rhythmdb_commit() at some point after invoking
3210 * this function, and that even after the commit, your change may not
3211 * have taken effect.
3212 */
3213 void
3214 rhythmdb_entry_set (RhythmDB *db,
3215 RhythmDBEntry *entry,
3216 guint propid,
3217 const GValue *value)
3218 {
3219 g_return_if_fail (RHYTHMDB_IS (db));
3220 g_return_if_fail (entry != NULL);
3221
3222 if ((entry->flags & RHYTHMDB_ENTRY_INSERTED) != 0) {
3223 if (!rhythmdb_get_readonly (db) && rb_is_main_thread ()) {
3224 rhythmdb_entry_set_internal (db, entry, TRUE, propid, value);
3225 } else {
3226 RhythmDBEvent *result;
3227
3228 result = g_slice_new0 (RhythmDBEvent);
3229 result->db = db;
3230 result->type = RHYTHMDB_EVENT_ENTRY_SET;
3231
3232 rb_debug ("queuing RHYTHMDB_ACTION_ENTRY_SET");
3233
3234 result->entry = rhythmdb_entry_ref (entry);
3235 result->change.prop = propid;
3236 result->signal_change = TRUE;
3237 g_value_init (&result->change.new, G_VALUE_TYPE (value));
3238 g_value_copy (value, &result->change.new);
3239 rhythmdb_push_event (db, result);
3240 }
3241 } else {
3242 rhythmdb_entry_set_internal (db, entry, FALSE, propid, value);
3243 }
3244 }
3245
3246 static void
3247 record_entry_change (RhythmDB *db,
3248 RhythmDBEntry *entry,
3249 guint propid,
3250 const GValue *old_value,
3251 const GValue *new_value)
3252 {
3253 RhythmDBEntryChange *changedata;
3254 GSList *changelist;
3255
3256 changedata = g_slice_new0 (RhythmDBEntryChange);
3257 changedata->prop = propid;
3258
3259 g_value_init (&changedata->old, G_VALUE_TYPE (old_value));
3260 g_value_init (&changedata->new, G_VALUE_TYPE (new_value));
3261 g_value_copy (old_value, &changedata->old);
3262 g_value_copy (new_value, &changedata->new);
3263
3264 g_mutex_lock (&db->priv->change_mutex);
3265 /* ref the entry before adding to hash, it is unreffed when removed */
3266 rhythmdb_entry_ref (entry);
3267 changelist = g_hash_table_lookup (db->priv->changed_entries, entry);
3268 changelist = g_slist_append (changelist, changedata);
3269 g_hash_table_insert (db->priv->changed_entries, entry, changelist);
3270 g_mutex_unlock (&db->priv->change_mutex);
3271 }
3272
3273 void
3274 rhythmdb_entry_set_internal (RhythmDB *db,
3275 RhythmDBEntry *entry,
3276 gboolean notify_if_inserted,
3277 guint propid,
3278 const GValue *value)
3279 {
3280 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3281 gboolean handled;
3282 RhythmDBPodcastFields *podcast = NULL;
3283 GValue conv_value = {0,};
3284 GValue old_value = {0,};
3285 gboolean nop;
3286
3287 g_return_if_fail (entry != NULL);
3288
3289 /* convert the value if necessary */
3290 if (G_VALUE_TYPE (value) != rhythmdb_get_property_type (db, propid)) {
3291 g_value_init (&conv_value, rhythmdb_get_property_type (db, propid));
3292 if (g_value_transform (value, &conv_value) == FALSE) {
3293 g_warning ("Unable to convert new value for property %s from %s to %s",
3294 rhythmdb_nice_elt_name_from_propid (db, propid),
3295 g_type_name (G_VALUE_TYPE (value)),
3296 g_type_name (rhythmdb_get_property_type (db, propid)));
3297 g_assert_not_reached ();
3298 }
3299 value = &conv_value;
3300 }
3301
3302 /* compare the value with what's already there */
3303 g_value_init (&old_value, G_VALUE_TYPE (value));
3304 rhythmdb_entry_get (db, entry, propid, &old_value);
3305 switch (G_VALUE_TYPE (value)) {
3306 case G_TYPE_STRING:
3307 #ifndef G_DISABLE_ASSERT
3308 /* the playback error is allowed to be NULL */
3309 if (propid != RHYTHMDB_PROP_PLAYBACK_ERROR || g_value_get_string (value))
3310 g_assert (g_utf8_validate (g_value_get_string (value), -1, NULL));
3311 #endif
3312 if (g_value_get_string (value) && g_value_get_string (&old_value)) {
3313 nop = (strcmp (g_value_get_string (value), g_value_get_string (&old_value)) == 0);
3314 } else {
3315 nop = FALSE;
3316 }
3317 break;
3318 case G_TYPE_BOOLEAN:
3319 nop = (g_value_get_boolean (value) == g_value_get_boolean (&old_value));
3320 break;
3321 case G_TYPE_ULONG:
3322 nop = (g_value_get_ulong (value) == g_value_get_ulong (&old_value));
3323 break;
3324 case G_TYPE_UINT64:
3325 nop = (g_value_get_uint64 (value) == g_value_get_uint64 (&old_value));
3326 break;
3327 case G_TYPE_DOUBLE:
3328 nop = (g_value_get_double (value) == g_value_get_double (&old_value));
3329 break;
3330 case G_TYPE_OBJECT:
3331 nop = (g_value_get_object (value) == g_value_get_object (&old_value));
3332 break;
3333 default:
3334 g_assert_not_reached ();
3335 break;
3336 }
3337
3338 if (nop == FALSE && (entry->flags & RHYTHMDB_ENTRY_INSERTED) && notify_if_inserted) {
3339 record_entry_change (db, entry, propid, &old_value, value);
3340 }
3341 g_value_unset (&old_value);
3342
3343 if (nop) {
3344 if (value == &conv_value) {
3345 g_value_unset (&conv_value);
3346 }
3347 return;
3348 }
3349
3350 handled = klass->impl_entry_set (db, entry, propid, value);
3351
3352 if (!handled) {
3353 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
3354 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST ||
3355 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH)
3356 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
3357
3358 switch (propid) {
3359 case RHYTHMDB_PROP_TYPE:
3360 case RHYTHMDB_PROP_ENTRY_ID:
3361 g_assert_not_reached ();
3362 break;
3363 case RHYTHMDB_PROP_TITLE:
3364 if (entry->title != NULL) {
3365 rb_refstring_unref (entry->title);
3366 }
3367 entry->title = rb_refstring_new (g_value_get_string (value));
3368 break;
3369 case RHYTHMDB_PROP_ALBUM:
3370 if (entry->album != NULL) {
3371 rb_refstring_unref (entry->album);
3372 }
3373 entry->album = rb_refstring_new (g_value_get_string (value));
3374 break;
3375 case RHYTHMDB_PROP_ARTIST:
3376 if (entry->artist != NULL) {
3377 rb_refstring_unref (entry->artist);
3378 }
3379 entry->artist = rb_refstring_new (g_value_get_string (value));
3380 break;
3381 case RHYTHMDB_PROP_GENRE:
3382 if (entry->genre != NULL) {
3383 rb_refstring_unref (entry->genre);
3384 }
3385 entry->genre = rb_refstring_new (g_value_get_string (value));
3386 break;
3387 case RHYTHMDB_PROP_COMMENT:
3388 if (entry->comment != NULL) {
3389 rb_refstring_unref (entry->comment);
3390 }
3391 entry->comment = rb_refstring_new (g_value_get_string (value));
3392 break;
3393 case RHYTHMDB_PROP_TRACK_NUMBER:
3394 entry->tracknum = g_value_get_ulong (value);
3395 break;
3396 case RHYTHMDB_PROP_DISC_NUMBER:
3397 entry->discnum = g_value_get_ulong (value);
3398 break;
3399 case RHYTHMDB_PROP_DURATION:
3400 entry->duration = g_value_get_ulong (value);
3401 break;
3402 case RHYTHMDB_PROP_BITRATE:
3403 entry->bitrate = g_value_get_ulong (value);
3404 break;
3405 case RHYTHMDB_PROP_DATE:
3406 {
3407 gulong julian;
3408 julian = g_value_get_ulong (value);
3409 if (julian > 0)
3410 g_date_set_julian (&entry->date, julian);
3411 else
3412 g_date_clear (&entry->date, 1);
3413 break;
3414 }
3415 case RHYTHMDB_PROP_TRACK_GAIN:
3416 g_warning ("RHYTHMDB_PROP_TRACK_GAIN no longer supported");
3417 break;
3418 case RHYTHMDB_PROP_TRACK_PEAK:
3419 g_warning ("RHYTHMDB_PROP_TRACK_PEAK no longer supported");
3420 break;
3421 case RHYTHMDB_PROP_ALBUM_GAIN:
3422 g_warning ("RHYTHMDB_PROP_ALBUM_GAIN no longer supported");
3423 break;
3424 case RHYTHMDB_PROP_ALBUM_PEAK:
3425 g_warning ("RHYTHMDB_PROP_ALBUM_PEAK no longer supported");
3426 break;
3427 case RHYTHMDB_PROP_LOCATION:
3428 rb_refstring_unref (entry->location);
3429 entry->location = rb_refstring_new (g_value_get_string (value));
3430 break;
3431 case RHYTHMDB_PROP_PLAYBACK_ERROR:
3432 rb_refstring_unref (entry->playback_error);
3433 if (g_value_get_string (value))
3434 entry->playback_error = rb_refstring_new (g_value_get_string (value));
3435 else
3436 entry->playback_error = NULL;
3437 break;
3438 case RHYTHMDB_PROP_MOUNTPOINT:
3439 if (entry->mountpoint != NULL) {
3440 rb_refstring_unref (entry->mountpoint);
3441 }
3442 entry->mountpoint = rb_refstring_new (g_value_get_string (value));
3443 break;
3444 case RHYTHMDB_PROP_FILE_SIZE:
3445 entry->file_size = g_value_get_uint64 (value);
3446 break;
3447 case RHYTHMDB_PROP_MEDIA_TYPE:
3448 if (entry->media_type != NULL) {
3449 rb_refstring_unref (entry->media_type);
3450 }
3451 entry->media_type = rb_refstring_new (g_value_get_string (value));
3452 break;
3453 case RHYTHMDB_PROP_MTIME:
3454 entry->mtime = g_value_get_ulong (value);
3455 break;
3456 case RHYTHMDB_PROP_FIRST_SEEN:
3457 entry->first_seen = g_value_get_ulong (value);
3458 entry->flags |= RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY;
3459 break;
3460 case RHYTHMDB_PROP_LAST_SEEN:
3461 entry->last_seen = g_value_get_ulong (value);
3462 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
3463 break;
3464 case RHYTHMDB_PROP_RATING:
3465 entry->rating = g_value_get_double (value);
3466 break;
3467 case RHYTHMDB_PROP_PLAY_COUNT:
3468 entry->play_count = g_value_get_ulong (value);
3469 break;
3470 case RHYTHMDB_PROP_LAST_PLAYED:
3471 entry->last_played = g_value_get_ulong (value);
3472 entry->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY;
3473 break;
3474 case RHYTHMDB_PROP_BPM:
3475 entry->bpm = g_value_get_double (value);
3476 break;
3477 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
3478 rb_refstring_unref (entry->musicbrainz_trackid);
3479 entry->musicbrainz_trackid = rb_refstring_new (g_value_get_string (value));
3480 break;
3481 case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID:
3482 rb_refstring_unref (entry->musicbrainz_artistid);
3483 entry->musicbrainz_artistid = rb_refstring_new (g_value_get_string (value));
3484 break;
3485 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID:
3486 rb_refstring_unref (entry->musicbrainz_albumid);
3487 entry->musicbrainz_albumid = rb_refstring_new (g_value_get_string (value));
3488 break;
3489 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID:
3490 rb_refstring_unref (entry->musicbrainz_albumartistid);
3491 entry->musicbrainz_albumartistid = rb_refstring_new (g_value_get_string (value));
3492 break;
3493 case RHYTHMDB_PROP_ARTIST_SORTNAME:
3494 rb_refstring_unref (entry->artist_sortname);
3495 entry->artist_sortname = rb_refstring_new (g_value_get_string (value));
3496 break;
3497 case RHYTHMDB_PROP_ALBUM_SORTNAME:
3498 rb_refstring_unref (entry->album_sortname);
3499 entry->album_sortname = rb_refstring_new (g_value_get_string (value));
3500 break;
3501 case RHYTHMDB_PROP_ALBUM_ARTIST:
3502 rb_refstring_unref (entry->album_artist);
3503 entry->album_artist = rb_refstring_new (g_value_get_string (value));
3504 break;
3505 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
3506 rb_refstring_unref (entry->album_artist_sortname);
3507 entry->album_artist_sortname = rb_refstring_new (g_value_get_string (value));
3508 break;
3509 case RHYTHMDB_PROP_HIDDEN:
3510 if (g_value_get_boolean (value)) {
3511 entry->flags |= RHYTHMDB_ENTRY_HIDDEN;
3512 } else {
3513 entry->flags &= ~RHYTHMDB_ENTRY_HIDDEN;
3514 }
3515 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY;
3516 break;
3517 case RHYTHMDB_PROP_STATUS:
3518 g_assert (podcast);
3519 podcast->status = g_value_get_ulong (value);
3520 break;
3521 case RHYTHMDB_PROP_DESCRIPTION:
3522 g_assert (podcast);
3523 rb_refstring_unref (podcast->description);
3524 podcast->description = rb_refstring_new (g_value_get_string (value));
3525 break;
3526 case RHYTHMDB_PROP_SUBTITLE:
3527 g_assert (podcast);
3528 rb_refstring_unref (podcast->subtitle);
3529 podcast->subtitle = rb_refstring_new (g_value_get_string (value));
3530 break;
3531 case RHYTHMDB_PROP_SUMMARY:
3532 g_assert (podcast);
3533 rb_refstring_unref (podcast->summary);
3534 podcast->summary = rb_refstring_new (g_value_get_string (value));
3535 break;
3536 case RHYTHMDB_PROP_LANG:
3537 g_assert (podcast);
3538 if (podcast->lang != NULL) {
3539 rb_refstring_unref (podcast->lang);
3540 }
3541 podcast->lang = rb_refstring_new (g_value_get_string (value));
3542 break;
3543 case RHYTHMDB_PROP_COPYRIGHT:
3544 g_assert (podcast);
3545 if (podcast->copyright != NULL) {
3546 rb_refstring_unref (podcast->copyright);
3547 }
3548 podcast->copyright = rb_refstring_new (g_value_get_string (value));
3549 break;
3550 case RHYTHMDB_PROP_IMAGE:
3551 g_assert (podcast);
3552 if (podcast->image != NULL) {
3553 rb_refstring_unref (podcast->image);
3554 }
3555 podcast->image = rb_refstring_new (g_value_get_string (value));
3556 break;
3557 case RHYTHMDB_PROP_POST_TIME:
3558 g_assert (podcast);
3559 podcast->post_time = g_value_get_ulong (value);
3560 break;
3561 case RHYTHMDB_NUM_PROPERTIES:
3562 g_assert_not_reached ();
3563 break;
3564 }
3565 }
3566
3567 if (value == &conv_value) {
3568 g_value_unset (&conv_value);
3569 }
3570
3571 /* set the dirty state */
3572 db->priv->dirty = TRUE;
3573 }
3574
3575 /**
3576 * rhythmdb_entry_sync_mirrored:
3577 * @db: a #RhythmDB.
3578 * @type: a #RhythmDBEntry.
3579 * @propid: the property to sync the mirrored version of.
3580 *
3581 * Synchronise "mirrored" properties, such as the string version of the last-played
3582 * time. This should be called when a property is directly modified, passing the
3583 * original property.
3584 *
3585 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree).
3586 */
3587 static void
3588 rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
3589 guint propid)
3590 {
3591 static const char *never;
3592 char *val;
3593
3594 if (never == NULL)
3595 never = _("Never");
3596
3597 switch (propid) {
3598 case RHYTHMDB_PROP_LAST_PLAYED_STR:
3599 {
3600 RBRefString *old, *new;
3601
3602 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY))
3603 break;
3604
3605 old = g_atomic_pointer_get (&entry->last_played_str);
3606 if (entry->last_played == 0) {
3607 new = rb_refstring_new (never);
3608 } else {
3609 val = rb_utf_friendly_time (entry->last_played);
3610 new = rb_refstring_new (val);
3611 g_free (val);
3612 }
3613
3614 if (g_atomic_pointer_compare_and_exchange (&entry->last_played_str, old, new)) {
3615 if (old != NULL) {
3616 rb_refstring_unref (old);
3617 }
3618 } else {
3619 rb_refstring_unref (new);
3620 }
3621
3622 break;
3623 }
3624 case RHYTHMDB_PROP_FIRST_SEEN_STR:
3625 {
3626 RBRefString *old, *new;
3627
3628 if (!(entry->flags & RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY))
3629 break;
3630
3631 old = g_atomic_pointer_get (&entry->first_seen_str);
3632 if (entry->first_seen == 0) {
3633 new = rb_refstring_new (never);
3634 } else {
3635 val = rb_utf_friendly_time (entry->first_seen);
3636 new = rb_refstring_new (val);
3637 g_free (val);
3638 }
3639
3640 if (g_atomic_pointer_compare_and_exchange (&entry->first_seen_str, old, new)) {
3641 if (old != NULL) {
3642 rb_refstring_unref (old);
3643 }
3644 } else {
3645 rb_refstring_unref (new);
3646 }
3647
3648 break;
3649 }
3650 case RHYTHMDB_PROP_LAST_SEEN_STR:
3651 {
3652 RBRefString *old, *new;
3653
3654 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_SEEN_DIRTY))
3655 break;
3656
3657 old = g_atomic_pointer_get (&entry->last_seen_str);
3658 /* only store last seen time as a string for hidden entries */
3659 if (entry->flags & RHYTHMDB_ENTRY_HIDDEN) {
3660 val = rb_utf_friendly_time (entry->last_seen);
3661 new = rb_refstring_new (val);
3662 g_free (val);
3663 } else {
3664 new = NULL;
3665 }
3666
3667 if (g_atomic_pointer_compare_and_exchange (&entry->last_seen_str, old, new)) {
3668 if (old != NULL) {
3669 rb_refstring_unref (old);
3670 }
3671 } else {
3672 rb_refstring_unref (new);
3673 }
3674
3675 break;
3676 }
3677 default:
3678 break;
3679 }
3680 }
3681
3682 /**
3683 * rhythmdb_entry_delete:
3684 * @db: a #RhythmDB.
3685 * @entry: a #RhythmDBEntry.
3686 *
3687 * Delete entry @entry from the database, sending notification of its deletion.
3688 * This is usually used by sources where entries can disappear randomly, such
3689 * as a network source.
3690 */
3691 void
3692 rhythmdb_entry_delete (RhythmDB *db,
3693 RhythmDBEntry *entry)
3694 {
3695 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3696
3697 g_return_if_fail (RHYTHMDB_IS (db));
3698 g_return_if_fail (entry != NULL);
3699
3700 rb_debug ("deleting entry %p", entry);
3701
3702 /* ref the entry before adding to hash, it is unreffed when removed */
3703 rhythmdb_entry_ref (entry);
3704
3705 klass->impl_entry_delete (db, entry);
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
(emitted by clang-analyzer)TODO: a detailed trace is available in the data model (not yet rendered in this report)
3706
3707 g_mutex_lock (&db->priv->change_mutex);
3708 g_hash_table_insert (db->priv->deleted_entries, entry, g_thread_self ());
3709 g_mutex_unlock (&db->priv->change_mutex);
3710
3711 /* deleting an entry makes the db dirty */
3712 db->priv->dirty = TRUE;
3713 }
3714
3715 /**
3716 * rhythmdb_entry_move_to_trash:
3717 * @db: the #RhythmDB
3718 * @entry: #RhythmDBEntry to trash
3719 *
3720 * Trashes the file represented by #entry. If possible, the file is
3721 * moved to the user's trash directory and the entry is set to hidden,
3722 * otherwise the error will be stored as the playback error for the entry.
3723 */
3724 void
3725 rhythmdb_entry_move_to_trash (RhythmDB *db,
3726 RhythmDBEntry *entry)
3727 {
3728 const char *uri;
3729 GFile *file;
3730 GError *error = NULL;
3731
3732 uri = rb_refstring_get (entry->location);
3733 file = g_file_new_for_uri (uri);
3734
3735 g_file_trash (file, NULL, &error);
3736 if (error != NULL) {
3737 GValue value = { 0, };
3738
3739 g_value_init (&value, G_TYPE_STRING);
3740 g_value_set_string (&value, error->message);
3741 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value);
3742 g_value_unset (&value);
3743
3744 rb_debug ("trashing %s failed: %s",
3745 uri,
3746 error->message);
3747 g_error_free (error);
3748
3749 } else {
3750 rhythmdb_entry_set_visibility (db, entry, FALSE);
3751 }
3752 g_object_unref (file);
3753 }
3754
3755 /**
3756 * rhythmdb_entry_delete_by_type:
3757 * @db: a #RhythmDB.
3758 * @type: type of entried to delete.
3759 *
3760 * Delete all entries from the database of the given type.
3761 * This is usually used by non-permanent sources when they disappear, such as
3762 * removable media being removed, or a network share becoming unavailable.
3763 */
3764 void
3765 rhythmdb_entry_delete_by_type (RhythmDB *db,
3766 RhythmDBEntryType *type)
3767 {
3768 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3769
3770 if (klass->impl_entry_delete_by_type) {
3771 klass->impl_entry_delete_by_type (db, type);
3772 } else {
3773 g_warning ("delete_by_type not implemented");
3774 }
3775 }
3776
3777 /**
3778 * rhythmdb_nice_elt_name_from_propid:
3779 * @db: the #RhythmDB
3780 * @propid: property ID
3781 *
3782 * Returns a short non-translated name for the property #propid.
3783 * This name is suitable for use as an XML tag name, for example.
3784 *
3785 * Return value: property ID name, must not be freed
3786 */
3787 const xmlChar *
3788 rhythmdb_nice_elt_name_from_propid (RhythmDB *db,
3789 RhythmDBPropType propid)
3790 {
3791 return (xmlChar *)rhythmdb_properties[propid].elt_name;
3792 }
3793
3794 /**
3795 * rhythmdb_propid_from_nice_elt_name:
3796 * @db: the #RhythmDB
3797 * @name: a property ID name
3798 *
3799 * Converts a property name returned by @rhythmdb_propid_from_nice_elt_name
3800 * back to a #RhythmDBPropType. If the name does not match a property ID,
3801 * -1 will be returned instead.
3802 *
3803 * Return value: a #RhythmDBPropType, or -1
3804 */
3805 int
3806 rhythmdb_propid_from_nice_elt_name (RhythmDB *db,
3807 const xmlChar *name)
3808 {
3809 gpointer ret, orig;
3810 if (g_hash_table_lookup_extended (db->priv->propname_map, name,
3811 &orig, &ret)) {
3812 return GPOINTER_TO_INT (ret);
3813 }
3814 return -1;
3815 }
3816
3817 /**
3818 * rhythmdb_entry_lookup_by_location:
3819 * @db: a #RhythmDB.
3820 * @uri: the URI of the entry to lookup.
3821 *
3822 * Looks up the entry with location @uri.
3823 *
3824 * Returns: the entry with location @uri, or NULL if no such entry exists.
3825 */
3826 RhythmDBEntry *
3827 rhythmdb_entry_lookup_by_location (RhythmDB *db,
3828 const char *uri)
3829 {
3830 RBRefString *rs;
3831
3832 rs = rb_refstring_find (uri);
3833 if (rs != NULL) {
3834 return rhythmdb_entry_lookup_by_location_refstring (db, rs);
3835 } else {
3836 return NULL;
3837 }
3838 }
3839
3840 /**
3841 * rhythmdb_entry_lookup_by_location_refstring:
3842 * @db: the #RhythmDB
3843 * @uri: #RBRefString for the entry location
3844 *
3845 * Looks up the entry with location @uri.
3846 *
3847 * Returns: the entry with location @uri, or NULL if no such entry exists.
3848 */
3849 RhythmDBEntry *
3850 rhythmdb_entry_lookup_by_location_refstring (RhythmDB *db,
3851 RBRefString *uri)
3852 {
3853 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3854
3855 return klass->impl_lookup_by_location (db, uri);
3856 }
3857
3858 /**
3859 * rhythmdb_entry_lookup_by_id:
3860 * @db: a #RhythmDB.
3861 * @id: entry ID
3862 *
3863 * Looks up the entry with id @id.
3864 *
3865 * Returns: the entry with id @id, or NULL if no such entry exists.
3866 */
3867 RhythmDBEntry *
3868 rhythmdb_entry_lookup_by_id (RhythmDB *db,
3869 gint id)
3870 {
3871 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3872
3873 return klass->impl_lookup_by_id (db, id);
3874 }
3875
3876 /**
3877 * rhythmdb_entry_lookup_from_string:
3878 * @db: a #RhythmDB.
3879 * @str: string
3880 * @is_id: whether the string is an entry ID or a location.
3881 *
3882 * Locates an entry using a string containing either an entry ID
3883 * or a location.
3884 *
3885 * Returns: the entry matching the string, or NULL if no such entry exists.
3886 */
3887 RhythmDBEntry *
3888 rhythmdb_entry_lookup_from_string (RhythmDB *db,
3889 const char *str,
3890 gboolean is_id)
3891 {
3892 if (is_id) {
3893 gint id;
3894
3895 id = strtoul (str, NULL, 10);
3896 if (id == 0)
3897 return NULL;
3898
3899 return rhythmdb_entry_lookup_by_id (db, id);
3900 } else {
3901 return rhythmdb_entry_lookup_by_location (db, str);
3902 }
3903 }
3904
3905 /**
3906 * rhythmdb_entry_foreach:
3907 * @db: a #RhythmDB.
3908 * @func: (scope call): the function to call with each entry.
3909 * @data: user data to pass to the function.
3910 *
3911 * Calls the given function for each of the entries in the database.
3912 */
3913 void
3914 rhythmdb_entry_foreach (RhythmDB *db,
3915 GFunc func,
3916 gpointer data)
3917 {
3918 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3919
3920 klass->impl_entry_foreach (db, func, data);
3921 }
3922
3923 /**
3924 * rhythmdb_entry_count:
3925 * @db: a #RhythmDB.
3926 *
3927 * Returns the number of entries in the database.
3928 *
3929 * Return value: number of entries
3930 */
3931 gint64
3932 rhythmdb_entry_count (RhythmDB *db)
3933 {
3934 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3935
3936 return klass->impl_entry_count (db);
3937 }
3938
3939 /**
3940 * rhythmdb_entry_foreach_by_type:
3941 * @db: a #RhythmDB.
3942 * @entry_type: the type of entry to retrieve
3943 * @func: (scope call): the function to call with each entry
3944 * @data: user data to pass to the function.
3945 *
3946 * Calls the given function for each of the entries in the database
3947 * of a given type.
3948 */
3949 void
3950 rhythmdb_entry_foreach_by_type (RhythmDB *db,
3951 RhythmDBEntryType *entry_type,
3952 GFunc func,
3953 gpointer data)
3954 {
3955 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3956
3957 klass->impl_entry_foreach_by_type (db, entry_type, func, data);
3958 }
3959
3960 /**
3961 * rhythmdb_entry_count_by_type:
3962 * @db: a #RhythmDB.
3963 * @entry_type: a #RhythmDBEntryType.
3964 *
3965 * Returns the number of entries in the database of a particular type.
3966 *
3967 * Return value: entry count
3968 */
3969 gint64
3970 rhythmdb_entry_count_by_type (RhythmDB *db,
3971 RhythmDBEntryType *entry_type)
3972 {
3973 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3974
3975 return klass->impl_entry_count_by_type (db, entry_type);
3976 }
3977
3978
3979 /**
3980 * rhythmdb_evaluate_query:
3981 * @db: a #RhythmDB.
3982 * @query: a query.
3983 * @entry: a @RhythmDBEntry.
3984 *
3985 * Evaluates the given entry against the given query.
3986 *
3987 * Returns: whether the given entry matches the criteria of the given query.
3988 */
3989 gboolean
3990 rhythmdb_evaluate_query (RhythmDB *db,
3991 GPtrArray *query,
3992 RhythmDBEntry *entry)
3993 {
3994 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
3995
3996 return klass->impl_evaluate_query (db, query, entry);
3997 }
3998
3999 static void
4000 rhythmdb_query_internal (RhythmDBQueryThreadData *data)
4001 {
4002 RhythmDBEvent *result;
4003 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (data->db);
4004
4005 rhythmdb_query_preprocess (data->db, data->query);
4006
4007 rb_debug ("doing query");
4008
4009 klass->impl_do_full_query (data->db, data->query,
4010 data->results,
4011 &data->cancel);
4012
4013 rb_debug ("completed");
4014 rhythmdb_query_results_query_complete (data->results);
4015
4016 result = g_slice_new0 (RhythmDBEvent);
4017 result->db = data->db;
4018 result->type = RHYTHMDB_EVENT_QUERY_COMPLETE;
4019 result->results = data->results;
4020 rhythmdb_push_event (data->db, result);
4021
4022 rhythmdb_query_free (data->query);
4023 }
4024
4025 static gpointer
4026 query_thread_main (RhythmDBQueryThreadData *data)
4027 {
4028 RhythmDBEvent *result;
4029
4030 rb_debug ("entering query thread");
4031
4032 rhythmdb_query_internal (data);
4033
4034 result = g_slice_new0 (RhythmDBEvent);
4035 result->db = data->db;
4036 result->type = RHYTHMDB_EVENT_THREAD_EXITED;
4037 rhythmdb_push_event (data->db, result);
4038 g_free (data);
4039 return NULL;
4040 }
4041
4042 /**
4043 * rhythmdb_do_full_query_async_parsed:
4044 * @db: the #RhythmDB
4045 * @results: a #RhythmDBQueryResults instance to feed results to
4046 * @query: the query to run
4047 *
4048 * Asynchronously runs a parsed query across the database, feeding matching
4049 * entries to @results in chunks. This can only be called from the
4050 * main thread.
4051 *
4052 * Since @results is always a @RhythmDBQueryModel,
4053 * use the RhythmDBQueryModel::complete signal to identify when the
4054 * query is complete.
4055 */
4056 void
4057 rhythmdb_do_full_query_async_parsed (RhythmDB *db,
4058 RhythmDBQueryResults *results,
4059 GPtrArray *query)
4060 {
4061 RhythmDBQueryThreadData *data;
4062
4063 data = g_new0 (RhythmDBQueryThreadData, 1);
4064 data->db = db;
4065 data->query = rhythmdb_query_copy (query);
4066 data->results = results;
4067 data->cancel = FALSE;
4068
4069 rhythmdb_read_enter (db);
4070
4071 rhythmdb_query_results_set_query (results, query);
4072
4073 g_object_ref (results);
4074 g_object_ref (db);
4075 g_atomic_int_inc (&db->priv->outstanding_threads);
4076 g_async_queue_ref (db->priv->action_queue);
4077 g_async_queue_ref (db->priv->event_queue);
4078 g_thread_pool_push (db->priv->query_thread_pool, data, NULL);
4079 }
4080
4081 /**
4082 * rhythmdb_do_full_query_async:
4083 * @db: the #RhythmDB
4084 * @results: a #RhythmDBQueryResults to feed results to
4085 * @Varargs: query parameters
4086 *
4087 * Asynchronously runs a query specified in the function arguments
4088 * across the database, feeding matching entries to @results in chunks.
4089 * This can only be called from the main thread.
4090 *
4091 * Since @results is always a @RhythmDBQueryModel,
4092 * use the RhythmDBQueryModel::complete signal to identify when the
4093 * query is complete.
4094 *
4095 * FIXME: example
4096 */
4097 void
4098 rhythmdb_do_full_query_async (RhythmDB *db,
4099 RhythmDBQueryResults *results,
4100 ...)
4101 {
4102 GPtrArray *query;
4103 va_list args;
4104
4105 va_start (args, results);
4106
4107 query = rhythmdb_query_parse_valist (db, args);
4108
4109 rhythmdb_do_full_query_async_parsed (db, results, query);
4110
4111 rhythmdb_query_free (query);
4112
4113 va_end (args);
4114 }
4115
4116 static void
4117 rhythmdb_do_full_query_internal (RhythmDB *db,
4118 RhythmDBQueryResults *results,
4119 GPtrArray *query)
4120 {
4121 RhythmDBQueryThreadData *data;
4122
4123 data = g_new0 (RhythmDBQueryThreadData, 1);
4124 data->db = db;
4125 data->query = rhythmdb_query_copy (query);
4126 data->results = results;
4127 data->cancel = FALSE;
4128
4129 rhythmdb_read_enter (db);
4130
4131 rhythmdb_query_results_set_query (results, query);
4132 g_object_ref (results);
4133
4134 rhythmdb_query_internal (data);
4135 g_free (data);
4136 }
4137
4138 /**
4139 * rhythmdb_do_full_query_parsed:
4140 * @db: the #RhythmDB
4141 * @results: a #RhythmDBQueryResults instance to feed results to
4142 * @query: a parsed query
4143 *
4144 * Synchronously evaluates the parsed query @query, feeding results
4145 * to @results in chunks. Does not return until the query is complete.
4146 */
4147 void
4148 rhythmdb_do_full_query_parsed (RhythmDB *db,
4149 RhythmDBQueryResults *results,
4150 GPtrArray *query)
4151 {
4152 rhythmdb_do_full_query_internal (db, results, query);
4153 }
4154
4155 /**
4156 * rhythmdb_do_full_query:
4157 * @db: the #RhythmDB
4158 * @results: a #RhythmDBQueryResults instance to feed results to
4159 * @Varargs: query parameters
4160 *
4161 * Synchronously evaluates @query, feeding results to @results in
4162 * chunks. Does not return until the query is complete.
4163 * This can only be called from the main thread.
4164 *
4165 * FIXME: example
4166 */
4167 void
4168 rhythmdb_do_full_query (RhythmDB *db,
4169 RhythmDBQueryResults *results,
4170 ...)
4171 {
4172 GPtrArray *query;
4173 va_list args;
4174
4175 va_start (args, results);
4176
4177 query = rhythmdb_query_parse_valist (db, args);
4178
4179 rhythmdb_do_full_query_internal (db, results, query);
4180
4181 rhythmdb_query_free (query);
4182
4183 va_end (args);
4184 }
4185
4186 /* This should really be standard. */
4187 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC }
4188
4189 GType
4190 rhythmdb_query_type_get_type (void)
4191 {
4192 static GType etype = 0;
4193
4194 if (etype == 0)
4195 {
4196 static const GEnumValue values[] =
4197 {
4198
4199 ENUM_ENTRY (RHYTHMDB_QUERY_END, "query-end"),
4200 ENUM_ENTRY (RHYTHMDB_QUERY_DISJUNCTION, "disjunctive-marker"),
4201 ENUM_ENTRY (RHYTHMDB_QUERY_SUBQUERY, "subquery"),
4202 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_EQUALS, "equals"),
4203 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_EQUAL, "not-equal"),
4204 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LIKE, "fuzzy-match"),
4205 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_LIKE, "inverted-fuzzy-match"),
4206 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_PREFIX, "starts-with"),
4207 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_SUFFIX, "ends-with"),
4208 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_GREATER, "greater-than"),
4209 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LESS, "less-than"),
4210 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN, "within-current-time"),
4211 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN, "not-within-current-time"),
4212 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_EQUALS, "year-equals"),
4213 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL, "year-not-equals"),
4214 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_GREATER, "year-greater-than"),
4215 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_LESS, "year-less-than"),
4216 { 0, 0, 0 }
4217 };
4218
4219 etype = g_enum_register_static ("RhythmDBQueryType", values);
4220 }
4221
4222 return etype;
4223 }
4224
4225 GType
4226 rhythmdb_prop_type_get_type (void)
4227 {
4228 static GType etype = 0;
4229
4230 if (etype == 0)
4231 {
4232 int i;
4233 static GEnumValue values[G_N_ELEMENTS(rhythmdb_properties)];
4234 g_assert(G_N_ELEMENTS(rhythmdb_properties)-1 == RHYTHMDB_NUM_PROPERTIES);
4235 for (i = 0; i < G_N_ELEMENTS(rhythmdb_properties)-1; i++) {
4236 g_assert (i == rhythmdb_properties[i].prop_id);
4237 values[i].value = rhythmdb_properties[i].prop_id;
4238 values[i].value_name = rhythmdb_properties[i].prop_name;
4239 values[i].value_nick = rhythmdb_properties[i].elt_name;
4240 }
4241 etype = g_enum_register_static ("RhythmDBPropType", values);
4242 }
4243
4244 return etype;
4245 }
4246
4247 void
4248 rhythmdb_emit_entry_deleted (RhythmDB *db,
4249 RhythmDBEntry *entry)
4250 {
4251 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry);
4252 }
4253
4254 static gboolean
4255 rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
4256 GValue *return_accu,
4257 const GValue *handler_return,
4258 gpointer data)
4259 {
4260 if (handler_return == NULL)
4261 return TRUE;
4262
4263 g_value_copy (handler_return, return_accu);
4264 return (g_value_get_boxed (return_accu) == NULL);
4265 }
4266
4267 /**
4268 * rhythmdb_entry_request_extra_metadata:
4269 * @db: a #RhythmDB
4270 * @entry: a #RhythmDBEntry
4271 * @property_name: the metadata predicate
4272 *
4273 * Emits a request for extra metadata for the @entry.
4274 * The @property_name argument is emitted as the ::detail part of the
4275 * "entry_extra_metadata_request" signal. It should be a namespaced RDF
4276 * predicate e.g. from Dublin Core, MusicBrainz, or internal to Rhythmbox
4277 * (namespace "rb:"). Suitable predicates would be those that are expensive to
4278 * acquire or only apply to a limited range of entries.
4279 * Handlers capable of providing a particular predicate may ensure they only
4280 * see appropriate requests by supplying an appropriate ::detail part when
4281 * connecting to the signal. Upon a handler returning a non-%NULL value,
4282 * emission will be stopped and the value returned to the caller; if no
4283 * handlers return a non-%NULL value, the caller will receive %NULL. Priority
4284 * is determined by signal connection order, with %G_CONNECT_AFTER providing a
4285 * second, lower rank of priority.
4286 * A handler returning a value should do so in a #GValue allocated on the heap;
4287 * the accumulator will take ownership. The caller should unset and free the
4288 * #GValue if non-%NULL when finished with it.
4289 *
4290 * Returns: an allocated, initialised, set #GValue, or NULL
4291 */
4292 GValue *
4293 rhythmdb_entry_request_extra_metadata (RhythmDB *db,
4294 RhythmDBEntry *entry,
4295 const gchar *property_name)
4296 {
4297 GValue *value = NULL;
4298
4299 g_signal_emit (G_OBJECT (db),
4300 rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST],
4301 g_quark_from_string (property_name),
4302 entry,
4303 &value);
4304
4305 return value;
4306 }
4307
4308 /**
4309 * rhythmdb_emit_entry_extra_metadata_notify:
4310 * @db: a #RhythmDB
4311 * @entry: a #RhythmDBEntry
4312 * @property_name: the metadata predicate
4313 * @metadata: a #GValue
4314 *
4315 * Emits a signal describing extra metadata for the @entry. The @property_name
4316 * argument is emitted as the ::detail part of the
4317 * "entry_extra_metadata_notify" signal and as the 'field' parameter. Handlers
4318 * can ensure they only get metadata they are interested in by supplying an
4319 * appropriate ::detail part when connecting to the signal. If handlers are
4320 * interested in the metadata they should ref or copy the contents of @metadata
4321 * and unref or free it when they are finished with it.
4322 */
4323 void
4324 rhythmdb_emit_entry_extra_metadata_notify (RhythmDB *db,
4325 RhythmDBEntry *entry,
4326 const gchar *property_name,
4327 const GValue *metadata)
4328 {
4329 g_signal_emit (G_OBJECT (db),
4330 rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY],
4331 g_quark_from_string (property_name),
4332 entry,
4333 property_name,
4334 metadata);
4335 }
4336
4337 /**
4338 * rhythmdb_entry_gather_metadata:
4339 * @db: a #RhythmDB
4340 * @entry: a #RhythmDBEntry
4341 *
4342 * Gathers all metadata for the @entry. The returned GHashTable maps property
4343 * names and extra metadata names (described under
4344 * @rhythmdb_entry_request_extra_metadata) to GValues. Anything wanting to
4345 * provide extra metadata should connect to the "entry_extra_metadata_gather"
4346 * signal.
4347 *
4348 * Returns: (transfer full): a RBStringValueMap containing metadata for the entry.
4349 * This must be freed using g_object_unref.
4350 */
4351 RBStringValueMap *
4352 rhythmdb_entry_gather_metadata (RhythmDB *db,
4353 RhythmDBEntry *entry)
4354 {
4355 RBStringValueMap *metadata;
4356 GEnumClass *klass;
4357 guint i;
4358
4359 metadata = rb_string_value_map_new ();
4360
4361 /* add core properties */
4362 klass = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
4363 for (i = 0; i < klass->n_values; i++) {
4364 GValue value = {0,};
4365 gint prop;
4366 GType value_type;
4367 const char *name;
4368
4369 prop = klass->values[i].value;
4370
4371 /* only include easily marshallable types in the hash table */
4372 value_type = rhythmdb_get_property_type (db, prop);
4373 switch (value_type) {
4374 case G_TYPE_STRING:
4375 case G_TYPE_BOOLEAN:
4376 case G_TYPE_ULONG:
4377 case G_TYPE_UINT64:
4378 case G_TYPE_DOUBLE:
4379 break;
4380 default:
4381 continue;
4382 }
4383
4384 /* skip deprecated properties */
4385 switch (prop) {
4386 case RHYTHMDB_PROP_TRACK_GAIN:
4387 case RHYTHMDB_PROP_TRACK_PEAK:
4388 case RHYTHMDB_PROP_ALBUM_GAIN:
4389 case RHYTHMDB_PROP_ALBUM_PEAK:
4390 continue;
4391 default:
4392 break;
4393 }
4394
4395 g_value_init (&value, value_type);
4396 rhythmdb_entry_get (db, entry, prop, &value);
4397 name = (char *)rhythmdb_nice_elt_name_from_propid (db, prop);
4398 rb_string_value_map_set (metadata, name, &value);
4399 g_value_unset (&value);
4400 }
4401 g_type_class_unref (klass);
4402
4403 /* gather extra metadata */
4404 g_signal_emit (G_OBJECT (db),
4405 rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER], 0,
4406 entry,
4407 metadata);
4408
4409 return metadata;
4410 }
4411
4412 static gboolean
4413 queue_is_empty (GAsyncQueue *queue)
4414 {
4415 return g_async_queue_length (queue) <= 0;
4416 }
4417
4418 /**
4419 * rhythmdb_is_busy:
4420 * @db: a #RhythmDB.
4421 *
4422 * Checks if the database has events to process. This probably isn't
4423 * very useful.
4424 *
4425 * Returns: whether the #RhythmDB has events to process.
4426 */
4427 gboolean
4428 rhythmdb_is_busy (RhythmDB *db)
4429 {
4430 return (!db->priv->action_thread_running ||
4431 db->priv->stat_thread_running ||
4432 !queue_is_empty (db->priv->event_queue) ||
4433 !queue_is_empty (db->priv->action_queue) ||
4434 (db->priv->outstanding_stats != NULL));
4435 }
4436
4437 /**
4438 * rhythmdb_get_progress_info:
4439 * @db: a #RhythmDB.
4440 * @text: used to return progress text
4441 * @progress: used to return progress fraction
4442 *
4443 * Provides progress information for rhythmdb operations, if any are running.
4444 */
4445 void
4446 rhythmdb_get_progress_info (RhythmDB *db, char **text, float *progress)
4447 {
4448 if (db->priv->stat_thread_running && db->priv->stat_thread_count > 0) {
4449 g_free (*text);
4450 *text = g_strdup_printf (_("Checking (%d/%d)"),
4451 db->priv->stat_thread_done,
4452 db->priv->stat_thread_count);
4453 *progress = ((float)db->priv->stat_thread_done /
4454 (float)db->priv->stat_thread_count);
4455 }
4456 }
4457
4458 /**
4459 * rhythmdb_compute_status_normal:
4460 * @n_songs: the number of tracks.
4461 * @duration: the total duration of the tracks.
4462 * @size: the total size of the tracks.
4463 * @singular: singular form of the format string to use for entries (eg "%d song")
4464 * @plural: plural form of the format string to use for entries (eg "%d songs")
4465 *
4466 * Creates a string containing the "status" information about a list of tracks.
4467 * The singular and plural strings must be used in a direct ngettext call
4468 * elsewhere in order for them to be marked for translation correctly.
4469 *
4470 * Returns: the string, which should be freed with g_free.
4471 */
4472 char *
4473 rhythmdb_compute_status_normal (gint n_songs,
4474 glong duration,
4475 guint64 size,
4476 const char *singular,
4477 const char *plural)
4478 {
4479 long days, hours, minutes;
4480 char *songcount = NULL;
4481 char *time = NULL;
4482 char *size_str = NULL;
4483 char *ret;
4484 const char *minutefmt;
4485 const char *hourfmt;
4486 const char *dayfmt;
4487
4488 songcount = g_strdup_printf (ngettext (singular, plural, n_songs), n_songs);
4489
4490 days = duration / (60 * 60 * 24);
4491 hours = (duration / (60 * 60)) - (days * 24);
4492 minutes = (duration / 60) - ((days * 24 * 60) + (hours * 60));
4493
4494 minutefmt = ngettext ("%ld minute", "%ld minutes", minutes);
4495 hourfmt = ngettext ("%ld hour", "%ld hours", hours);
4496 dayfmt = ngettext ("%ld day", "%ld days", days);
4497 if (days > 0) {
4498 if (hours > 0)
4499 if (minutes > 0) {
4500 char *fmt;
4501 /* Translators: the format is "X days, X hours and X minutes" */
4502 fmt = g_strdup_printf (_("%s, %s and %s"), dayfmt, hourfmt, minutefmt);
4503 time = g_strdup_printf (fmt, days, hours, minutes);
4504 g_free (fmt);
4505 } else {
4506 char *fmt;
4507 /* Translators: the format is "X days and X hours" */
4508 fmt = g_strdup_printf (_("%s and %s"), dayfmt, hourfmt);
4509 time = g_strdup_printf (fmt, days, hours);
4510 g_free (fmt);
4511 }
4512 else
4513 if (minutes > 0) {
4514 char *fmt;
4515 /* Translators: the format is "X days and X minutes" */
4516 fmt = g_strdup_printf (_("%s and %s"), dayfmt, minutefmt);
4517 time = g_strdup_printf (fmt, days, minutes);
4518 g_free (fmt);
4519 } else {
4520 time = g_strdup_printf (dayfmt, days);
4521 }
4522 } else {
4523 if (hours > 0) {
4524 if (minutes > 0) {
4525 char *fmt;
4526 /* Translators: the format is "X hours and X minutes" */
4527 fmt = g_strdup_printf (_("%s and %s"), hourfmt, minutefmt);
4528 time = g_strdup_printf (fmt, hours, minutes);
4529 g_free (fmt);
4530 } else {
4531 time = g_strdup_printf (hourfmt, hours);
4532 }
4533
4534 } else {
4535 time = g_strdup_printf (minutefmt, minutes);
4536 }
4537 }
4538
4539 size_str = g_format_size (size);
4540
4541 if (size > 0 && duration > 0) {
4542 ret = g_strdup_printf ("%s, %s, %s", songcount, time, size_str);
4543 } else if (duration > 0) {
4544 ret = g_strdup_printf ("%s, %s", songcount, time);
4545 } else if (size > 0) {
4546 ret = g_strdup_printf ("%s, %s", songcount, size_str);
4547 } else {
4548 ret = g_strdup (songcount);
4549 }
4550
4551 g_free (songcount);
4552 g_free (time);
4553 g_free (size_str);
4554
4555 return ret;
4556 }
4557
4558 /**
4559 * rhythmdb_register_entry_type:
4560 * @db: the #RhythmDB
4561 * @entry_type: the new entry type to register
4562 *
4563 * Registers a new entry type. An entry type must be registered before
4564 * any entries can be created for it.
4565 */
4566 void
4567 rhythmdb_register_entry_type (RhythmDB *db, RhythmDBEntryType *entry_type)
4568 {
4569 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
4570 char *name = NULL;
4571
4572 g_object_get (entry_type, "name", &name, NULL);
4573 g_assert (name != NULL);
4574 g_mutex_lock (&db->priv->entry_type_map_mutex);
4575 g_hash_table_insert (db->priv->entry_type_map, name, g_object_ref (entry_type));
4576 g_mutex_unlock (&db->priv->entry_type_map_mutex);
4577
4578 if (klass->impl_entry_type_registered)
4579 klass->impl_entry_type_registered (db, entry_type);
4580 }
4581
4582 /**
4583 * rhythmdb_entry_type_foreach:
4584 * @db: a #RhythmDB
4585 * @func: callback function to call for each registered entry type
4586 * @data: data to pass to the callback
4587 *
4588 * Calls a function for each registered entry type.
4589 */
4590 void
4591 rhythmdb_entry_type_foreach (RhythmDB *db,
4592 GHFunc func,
4593 gpointer data)
4594 {
4595 g_mutex_lock (&db->priv->entry_type_mutex);
4596 g_hash_table_foreach (db->priv->entry_type_map, func, data);
4597 g_mutex_unlock (&db->priv->entry_type_mutex);
4598 }
4599
4600 /**
4601 * rhythmdb_entry_type_get_by_name:
4602 * @db: a #RhythmDB
4603 * @name: name of the type to look for
4604 *
4605 * Locates a #RhythmDBEntryType by name. Returns NULL if no entry
4606 * type is registered with the specified name.
4607 *
4608 * Returns: (transfer none): the #RhythmDBEntryType
4609 */
4610 RhythmDBEntryType *
4611 rhythmdb_entry_type_get_by_name (RhythmDB *db,
4612 const char *name)
4613 {
4614 gpointer t = NULL;
4615
4616 g_mutex_lock (&db->priv->entry_type_map_mutex);
4617 if (db->priv->entry_type_map) {
4618 t = g_hash_table_lookup (db->priv->entry_type_map, name);
4619 }
4620 g_mutex_unlock (&db->priv->entry_type_map_mutex);
4621
4622 return (RhythmDBEntryType *) t;
4623 }
4624
4625 static void
4626 rhythmdb_entry_set_mount_point (RhythmDB *db,
4627 RhythmDBEntry *entry,
4628 const gchar *realuri)
4629 {
4630 gchar *mount_point;
4631 GValue value = {0, };
4632
4633 mount_point = rb_uri_get_mount_point (realuri);
4634 if (mount_point != NULL) {
4635 g_value_init (&value, G_TYPE_STRING);
4636 g_value_take_string (&value, mount_point);
4637 rhythmdb_entry_set_internal (db, entry, FALSE,
4638 RHYTHMDB_PROP_MOUNTPOINT,
4639 &value);
4640 g_value_unset (&value);
4641 }
4642 }
4643
4644 void
4645 rhythmdb_entry_set_visibility (RhythmDB *db,
4646 RhythmDBEntry *entry,
4647 gboolean visible)
4648 {
4649 GValue old_val = {0, };
4650 gboolean old_visible;
4651
4652 g_return_if_fail (RHYTHMDB_IS (db));
4653 g_return_if_fail (entry != NULL);
4654
4655 g_value_init (&old_val, G_TYPE_BOOLEAN);
4656
4657 rhythmdb_entry_get (db, entry, RHYTHMDB_PROP_HIDDEN, &old_val);
4658 old_visible = !g_value_get_boolean (&old_val);
4659
4660 if ((old_visible && !visible) || (!old_visible && visible)) {
4661 GValue new_val = {0, };
4662
4663 g_value_init (&new_val, G_TYPE_BOOLEAN);
4664 g_value_set_boolean (&new_val, !visible);
4665 rhythmdb_entry_set_internal (db, entry, TRUE,
4666 RHYTHMDB_PROP_HIDDEN, &new_val);
4667 g_value_unset (&new_val);
4668 }
4669 g_value_unset (&old_val);
4670 }
4671
4672 static gboolean
4673 rhythmdb_idle_save (RhythmDB *db)
4674 {
4675 if (db->priv->dirty) {
4676 rb_debug ("database is dirty, doing regular save");
4677 rhythmdb_save_async (db);
4678 }
4679
4680 return TRUE;
4681 }
4682
4683 static void
4684 rhythmdb_sync_library_location (RhythmDB *db)
4685 {
4686 if (db->priv->library_locations != NULL &&
4687 g_strv_length (db->priv->library_locations) > 0) {
4688 rb_debug ("ending monitor of old library directories");
4689
4690 rhythmdb_stop_monitoring (db);
4691
4692 g_strfreev (db->priv->library_locations);
4693 db->priv->library_locations = NULL;
4694 }
4695
4696 if (g_settings_get_boolean (db->priv->settings, "monitor-library")) {
4697 rb_debug ("starting library monitoring");
4698 db->priv->library_locations = g_settings_get_strv (db->priv->settings, "locations");
4699
4700 rhythmdb_start_monitoring (db);
4701 }
4702 }
4703
4704 static void
4705 db_settings_changed_cb (GSettings *settings, const char *key, RhythmDB *db)
4706 {
4707 if (g_strcmp0 (key, "locations") == 0 || g_strcmp0 (key, "monitor-library") == 0) {
4708 rhythmdb_sync_library_location (db);
4709 }
4710 }
4711
4712 char *
4713 rhythmdb_entry_dup_string (RhythmDBEntry *entry,
4714 RhythmDBPropType propid)
4715 {
4716 const char *s;
4717
4718 g_return_val_if_fail (entry != NULL, NULL);
4719
4720 s = rhythmdb_entry_get_string (entry, propid);
4721 if (s != NULL) {
4722 return g_strdup (s);
4723 } else {
4724 return NULL;
4725 }
4726 }
4727
4728 /**
4729 * rhythmdb_entry_get_string:
4730 * @entry: a #RhythmDBEntry
4731 * @propid: the #RhythmDBPropType to return
4732 *
4733 * Returns the value of a string property of #entry.
4734 *
4735 * Return value: property value, must not be freed
4736 */
4737 const char *
4738 rhythmdb_entry_get_string (RhythmDBEntry *entry,
4739 RhythmDBPropType propid)
4740 {
4741 RhythmDBPodcastFields *podcast = NULL;
4742
4743 g_return_val_if_fail (entry != NULL, NULL);
4744 g_return_val_if_fail (entry->refcount > 0, NULL);
4745
4746 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
4747 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST ||
4748 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH)
4749 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
4750
4751 rhythmdb_entry_sync_mirrored (entry, propid);
4752
4753 switch (propid) {
4754 case RHYTHMDB_PROP_TITLE:
4755 return rb_refstring_get (entry->title);
4756 case RHYTHMDB_PROP_ALBUM:
4757 return rb_refstring_get (entry->album);
4758 case RHYTHMDB_PROP_ARTIST:
4759 return rb_refstring_get (entry->artist);
4760 case RHYTHMDB_PROP_GENRE:
4761 return rb_refstring_get (entry->genre);
4762 case RHYTHMDB_PROP_COMMENT:
4763 return rb_refstring_get (entry->comment);
4764 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4765 return rb_refstring_get (entry->musicbrainz_trackid);
4766 case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID:
4767 return rb_refstring_get (entry->musicbrainz_artistid);
4768 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID:
4769 return rb_refstring_get (entry->musicbrainz_albumid);
4770 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID:
4771 return rb_refstring_get (entry->musicbrainz_albumartistid);
4772 case RHYTHMDB_PROP_ARTIST_SORTNAME:
4773 return rb_refstring_get (entry->artist_sortname);
4774 case RHYTHMDB_PROP_ALBUM_SORTNAME:
4775 return rb_refstring_get (entry->album_sortname);
4776 case RHYTHMDB_PROP_ALBUM_ARTIST:
4777 return rb_refstring_get (entry->album_artist);
4778 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
4779 return rb_refstring_get (entry->album_artist_sortname);
4780 case RHYTHMDB_PROP_MEDIA_TYPE:
4781 return rb_refstring_get (entry->media_type);
4782 case RHYTHMDB_PROP_TITLE_SORT_KEY:
4783 return rb_refstring_get_sort_key (entry->title);
4784 case RHYTHMDB_PROP_ALBUM_SORT_KEY:
4785 return rb_refstring_get_sort_key (entry->album);
4786 case RHYTHMDB_PROP_ARTIST_SORT_KEY:
4787 return rb_refstring_get_sort_key (entry->artist);
4788 case RHYTHMDB_PROP_GENRE_SORT_KEY:
4789 return rb_refstring_get_sort_key (entry->genre);
4790 case RHYTHMDB_PROP_ARTIST_SORTNAME_SORT_KEY:
4791 return rb_refstring_get_sort_key (entry->artist_sortname);
4792 case RHYTHMDB_PROP_ALBUM_SORTNAME_SORT_KEY:
4793 return rb_refstring_get_sort_key (entry->album_sortname);
4794 case RHYTHMDB_PROP_ALBUM_ARTIST_SORT_KEY:
4795 return rb_refstring_get_sort_key (entry->album_artist);
4796 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME_SORT_KEY:
4797 return rb_refstring_get_sort_key (entry->album_artist_sortname);
4798 case RHYTHMDB_PROP_TITLE_FOLDED:
4799 return rb_refstring_get_folded (entry->title);
4800 case RHYTHMDB_PROP_ALBUM_FOLDED:
4801 return rb_refstring_get_folded (entry->album);
4802 case RHYTHMDB_PROP_ARTIST_FOLDED:
4803 return rb_refstring_get_folded (entry->artist);
4804 case RHYTHMDB_PROP_GENRE_FOLDED:
4805 return rb_refstring_get_folded (entry->genre);
4806 case RHYTHMDB_PROP_ARTIST_SORTNAME_FOLDED:
4807 return rb_refstring_get_folded (entry->artist_sortname);
4808 case RHYTHMDB_PROP_ALBUM_SORTNAME_FOLDED:
4809 return rb_refstring_get_folded (entry->album_sortname);
4810 case RHYTHMDB_PROP_ALBUM_ARTIST_FOLDED:
4811 return rb_refstring_get_folded (entry->album_artist);
4812 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME_FOLDED:
4813 return rb_refstring_get_folded (entry->album_artist_sortname);
4814 case RHYTHMDB_PROP_LOCATION:
4815 return rb_refstring_get (entry->location);
4816 case RHYTHMDB_PROP_MOUNTPOINT:
4817 return rb_refstring_get (entry->mountpoint);
4818 case RHYTHMDB_PROP_LAST_PLAYED_STR:
4819 return rb_refstring_get (entry->last_played_str);
4820 case RHYTHMDB_PROP_PLAYBACK_ERROR:
4821 return rb_refstring_get (entry->playback_error);
4822 case RHYTHMDB_PROP_FIRST_SEEN_STR:
4823 return rb_refstring_get (entry->first_seen_str);
4824 case RHYTHMDB_PROP_LAST_SEEN_STR:
4825 return rb_refstring_get (entry->last_seen_str);
4826
4827 /* synthetic properties */
4828 case RHYTHMDB_PROP_SEARCH_MATCH:
4829 return NULL;
4830 case RHYTHMDB_PROP_KEYWORD:
4831 return NULL;
4832
4833 /* Podcast properties */
4834 case RHYTHMDB_PROP_DESCRIPTION:
4835 if (podcast)
4836 return rb_refstring_get (podcast->description);
4837 else
4838 return NULL;
4839 case RHYTHMDB_PROP_SUBTITLE:
4840 if (podcast)
4841 return rb_refstring_get (podcast->subtitle);
4842 else
4843 return NULL;
4844 case RHYTHMDB_PROP_SUMMARY:
4845 if (podcast)
4846 return rb_refstring_get (podcast->summary);
4847 else
4848 return NULL;
4849 case RHYTHMDB_PROP_LANG:
4850 if (podcast)
4851 return rb_refstring_get (podcast->lang);
4852 else
4853 return NULL;
4854 case RHYTHMDB_PROP_COPYRIGHT:
4855 if (podcast)
4856 return rb_refstring_get (podcast->copyright);
4857 else
4858 return NULL;
4859 case RHYTHMDB_PROP_IMAGE:
4860 if (podcast)
4861 return rb_refstring_get (podcast->image);
4862 else
4863 return NULL;
4864
4865 default:
4866 g_assert_not_reached ();
4867 return NULL;
4868 }
4869 }
4870
4871 /**
4872 * rhythmdb_entry_get_refstring:
4873 * @entry: a #RhythmDBEntry
4874 * @propid: the property to return
4875 *
4876 * Returns an #RBRefString containing a string property of @entry.
4877 *
4878 * Return value: a #RBRefString, must be unreffed by caller.
4879 */
4880 RBRefString *
4881 rhythmdb_entry_get_refstring (RhythmDBEntry *entry,
4882 RhythmDBPropType propid)
4883 {
4884 g_return_val_if_fail (entry != NULL, NULL);
4885 g_return_val_if_fail (entry->refcount > 0, NULL);
4886
4887 rhythmdb_entry_sync_mirrored (entry, propid);
4888
4889 switch (propid) {
4890 case RHYTHMDB_PROP_TITLE:
4891 return rb_refstring_ref (entry->title);
4892 case RHYTHMDB_PROP_ALBUM:
4893 return rb_refstring_ref (entry->album);
4894 case RHYTHMDB_PROP_ARTIST:
4895 return rb_refstring_ref (entry->artist);
4896 case RHYTHMDB_PROP_ALBUM_ARTIST:
4897 return rb_refstring_ref (entry->album_artist);
4898 case RHYTHMDB_PROP_GENRE:
4899 return rb_refstring_ref (entry->genre);
4900 case RHYTHMDB_PROP_COMMENT:
4901 return rb_refstring_ref (entry->comment);
4902 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
4903 return rb_refstring_ref (entry->musicbrainz_trackid);
4904 case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID:
4905 return rb_refstring_ref (entry->musicbrainz_artistid);
4906 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID:
4907 return rb_refstring_ref (entry->musicbrainz_albumid);
4908 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID:
4909 return rb_refstring_ref (entry->musicbrainz_albumartistid);
4910 case RHYTHMDB_PROP_ARTIST_SORTNAME:
4911 return rb_refstring_ref (entry->artist_sortname);
4912 case RHYTHMDB_PROP_ALBUM_SORTNAME:
4913 return rb_refstring_ref (entry->album_sortname);
4914 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
4915 return rb_refstring_ref (entry->album_artist_sortname);
4916 case RHYTHMDB_PROP_MEDIA_TYPE:
4917 return rb_refstring_ref (entry->media_type);
4918 case RHYTHMDB_PROP_MOUNTPOINT:
4919 return rb_refstring_ref (entry->mountpoint);
4920 case RHYTHMDB_PROP_LAST_PLAYED_STR:
4921 return rb_refstring_ref (entry->last_played_str);
4922 case RHYTHMDB_PROP_FIRST_SEEN_STR:
4923 return rb_refstring_ref (entry->first_seen_str);
4924 case RHYTHMDB_PROP_LAST_SEEN_STR:
4925 return rb_refstring_ref (entry->last_seen_str);
4926 case RHYTHMDB_PROP_LOCATION:
4927 return rb_refstring_ref (entry->location);
4928 case RHYTHMDB_PROP_PLAYBACK_ERROR:
4929 return rb_refstring_ref (entry->playback_error);
4930 default:
4931 g_assert_not_reached ();
4932 return NULL;
4933 }
4934 }
4935
4936 /**
4937 * rhythmdb_entry_get_boolean:
4938 * @entry: a #RhythmDBEntry
4939 * @propid: property to return
4940 *
4941 * Returns the value of a boolean property of @entry.
4942 *
4943 * Return value: property value
4944 */
4945 gboolean
4946 rhythmdb_entry_get_boolean (RhythmDBEntry *entry,
4947 RhythmDBPropType propid)
4948 {
4949 g_return_val_if_fail (entry != NULL, FALSE);
4950
4951 switch (propid) {
4952 case RHYTHMDB_PROP_HIDDEN:
4953 return ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0);
4954 default:
4955 g_assert_not_reached ();
4956 return FALSE;
4957 }
4958 }
4959
4960 /**
4961 * rhythmdb_entry_get_uint64:
4962 * @entry: a #RhythmDBEntry
4963 * @propid: property to return
4964 *
4965 * Returns the value of a 64bit unsigned integer property.
4966 *
4967 * Return value: property value
4968 */
4969 guint64
4970 rhythmdb_entry_get_uint64 (RhythmDBEntry *entry,
4971 RhythmDBPropType propid)
4972 {
4973 g_return_val_if_fail (entry != NULL, 0);
4974
4975 switch (propid) {
4976 case RHYTHMDB_PROP_FILE_SIZE:
4977 return entry->file_size;
4978 default:
4979 g_assert_not_reached ();
4980 return 0;
4981 }
4982 }
4983
4984 /**
4985 * rhythmdb_entry_get_entry_type:
4986 * @entry: a #RhythmDBEntry
4987 *
4988 * Returns the #RhythmDBEntryType for @entry. This is used to access
4989 * entry type properties, to check that entries are of the same type,
4990 * and to call entry type methods.
4991 *
4992 * Return value: (transfer none): the #RhythmDBEntryType for @entry
4993 */
4994 RhythmDBEntryType *
4995 rhythmdb_entry_get_entry_type (RhythmDBEntry *entry)
4996 {
4997 g_return_val_if_fail (entry != NULL, NULL);
4998
4999 return entry->type;
5000 }
5001
5002 /**
5003 * rhythmdb_entry_get_object:
5004 * @entry: a #RhythmDBEntry
5005 * @propid: the property to return
5006 *
5007 * Returns the value of an object property of @entry.
5008 *
5009 * Return value: (transfer none): property value
5010 */
5011 GObject *
5012 rhythmdb_entry_get_object (RhythmDBEntry *entry,
5013 RhythmDBPropType propid)
5014 {
5015 g_return_val_if_fail (entry != NULL, NULL);
5016
5017 switch (propid) {
5018 case RHYTHMDB_PROP_TYPE:
5019 return G_OBJECT (entry->type);
5020 default:
5021 g_assert_not_reached ();
5022 return NULL;
5023 }
5024 }
5025
5026 /**
5027 * rhythmdb_entry_get_ulong:
5028 * @entry: a #RhythmDBEntry
5029 * @propid: property to return
5030 *
5031 * Returns the value of an unsigned long integer property of @entry.
5032 *
5033 * Return value: property value
5034 */
5035 gulong
5036 rhythmdb_entry_get_ulong (RhythmDBEntry *entry,
5037 RhythmDBPropType propid)
5038 {
5039 RhythmDBPodcastFields *podcast = NULL;
5040
5041 g_return_val_if_fail (entry != NULL, 0);
5042
5043 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED ||
5044 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST ||
5045 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH)
5046 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields);
5047
5048 switch (propid) {
5049 case RHYTHMDB_PROP_ENTRY_ID:
5050 return entry->id;
5051 case RHYTHMDB_PROP_TRACK_NUMBER:
5052 return entry->tracknum;
5053 case RHYTHMDB_PROP_DISC_NUMBER:
5054 return entry->discnum;
5055 case RHYTHMDB_PROP_DURATION:
5056 return entry->duration;
5057 case RHYTHMDB_PROP_MTIME:
5058 return entry->mtime;
5059 case RHYTHMDB_PROP_FIRST_SEEN:
5060 return entry->first_seen;
5061 case RHYTHMDB_PROP_LAST_SEEN:
5062 return entry->last_seen;
5063 case RHYTHMDB_PROP_LAST_PLAYED:
5064 return entry->last_played;
5065 case RHYTHMDB_PROP_PLAY_COUNT:
5066 return entry->play_count;
5067 case RHYTHMDB_PROP_BITRATE:
5068 return entry->bitrate;
5069 case RHYTHMDB_PROP_DATE:
5070 if (g_date_valid (&entry->date))
5071 return g_date_get_julian (&entry->date);
5072 else
5073 return 0;
5074 case RHYTHMDB_PROP_YEAR:
5075 if (g_date_valid (&entry->date))
5076 return g_date_get_year (&entry->date);
5077 else
5078 return 0;
5079 case RHYTHMDB_PROP_POST_TIME:
5080 if (podcast)
5081 return podcast->post_time;
5082 else
5083 return 0;
5084 case RHYTHMDB_PROP_STATUS:
5085 if (podcast)
5086 return podcast->status;
5087 else
5088 return 0;
5089 default:
5090 g_assert_not_reached ();
5091 return 0;
5092 }
5093 }
5094
5095 /**
5096 * rhythmdb_entry_get_double:
5097 * @entry: a #RhythmDBEntry
5098 * @propid: the property to return
5099 *
5100 * Returns the value of a double-precision floating point property of @value.
5101 *
5102 * Return value: property value
5103 */
5104 double
5105 rhythmdb_entry_get_double (RhythmDBEntry *entry,
5106 RhythmDBPropType propid)
5107 {
5108 g_return_val_if_fail (entry != NULL, 0);
5109
5110 switch (propid) {
5111 case RHYTHMDB_PROP_TRACK_GAIN:
5112 g_warning ("RHYTHMDB_PROP_TRACK_GAIN no longer supported");
5113 return 0.0;
5114 case RHYTHMDB_PROP_TRACK_PEAK:
5115 g_warning ("RHYTHMDB_PROP_TRACK_PEAK no longer supported");
5116 return 1.0;
5117 case RHYTHMDB_PROP_ALBUM_GAIN:
5118 g_warning ("RHYTHMDB_PROP_ALBUM_GAIN no longer supported");
5119 return 0.0;
5120 case RHYTHMDB_PROP_ALBUM_PEAK:
5121 g_warning ("RHYTHMDB_PROP_ALBUM_PEAK no longer supported");
5122 return 1.0;
5123 case RHYTHMDB_PROP_RATING:
5124 return entry->rating;
5125 case RHYTHMDB_PROP_BPM:
5126 return entry->bpm;
5127 default:
5128 g_assert_not_reached ();
5129 return 0.0;
5130 }
5131 }
5132
5133
5134 /**
5135 * rhythmdb_entry_keyword_add:
5136 * @db: the #RhythmDB
5137 * @entry: a #RhythmDBEntry.
5138 * @keyword: the keyword to add.
5139 *
5140 * Adds a keyword to an entry.
5141 *
5142 * Returns: whether the keyword was already on the entry
5143 */
5144 gboolean
5145 rhythmdb_entry_keyword_add (RhythmDB *db,
5146 RhythmDBEntry *entry,
5147 RBRefString *keyword)
5148 {
5149 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
5150 gboolean ret;
5151
5152 ret = klass->impl_entry_keyword_add (db, entry, keyword);
5153 if (!ret) {
5154 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_KEYWORD_ADDED], 0, entry, keyword);
5155 }
5156 return ret;
5157 }
5158
5159 /**
5160 * rhythmdb_entry_keyword_remove:
5161 * @db: the #RhythmDB
5162 * @entry: a #RhythmDBEntry.
5163 * @keyword: the keyword to remove.
5164 *
5165 * Removed a keyword from an entry.
5166 *
5167 * Returns: whether the keyword had previously been added to the entry.
5168 */
5169 gboolean
5170 rhythmdb_entry_keyword_remove (RhythmDB *db,
5171 RhythmDBEntry *entry,
5172 RBRefString *keyword)
5173 {
5174 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
5175 gboolean ret;
5176
5177 ret = klass->impl_entry_keyword_remove (db, entry, keyword);
5178 if (ret) {
5179 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_KEYWORD_REMOVED], 0, entry, keyword);
5180 }
5181 return ret;
5182 }
5183
5184 /**
5185 * rhythmdb_entry_keyword_has:
5186 * @db: the #RhythmDB
5187 * @entry: a #RhythmDBEntry.
5188 * @keyword: the keyword to check for.
5189 *
5190 * Checks whether a keyword is has been added to an entry.
5191 *
5192 * Returns: whether the keyword had been added to the entry.
5193 */
5194 gboolean
5195 rhythmdb_entry_keyword_has (RhythmDB *db,
5196 RhythmDBEntry *entry,
5197 RBRefString *keyword)
5198 {
5199 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
5200
5201 return klass->impl_entry_keyword_has (db, entry, keyword);
5202 }
5203
5204 /**
5205 * rhythmdb_entry_keywords_get:
5206 * @db: the #RhythmDB
5207 * @entry: a #RhythmDBEntry.
5208 *
5209 * Gets the list ofkeywords that have been added to an entry.
5210 *
5211 * Returns: (element-type RBRefString) (transfer full): the list of keywords
5212 * that have been added to the entry.
5213 */
5214 GList*
5215 rhythmdb_entry_keywords_get (RhythmDB *db,
5216 RhythmDBEntry *entry)
5217 {
5218 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db);
5219
5220 return klass->impl_entry_keywords_get (db, entry);
5221 }
5222
5223 /**
5224 * rhythmdb_entry_write_metadata_changes:
5225 * @db: the #RhythmDB
5226 * @entry: the #RhythmDBEntry to update
5227 * @changes: a list of changes to write
5228 * @error: returns error information
5229 *
5230 * This can be called from a #RhythmDBEntryType sync_metadata function
5231 * when the appropriate action is to write the metadata changes
5232 * to the file at the entry's location.
5233 */
5234 void
5235 rhythmdb_entry_write_metadata_changes (RhythmDB *db,
5236 RhythmDBEntry *entry,
5237 GSList *changes,
5238 GError **error)
5239 {
5240 const char *uri;
5241 GError *local_error = NULL;
5242 GSList *t;
5243
5244 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION);
5245 rb_metadata_reset (db->priv->metadata);
5246
5247 for (t = changes; t; t = t->next) {
5248 RBMetaDataField field;
5249 GValue val = {0,};
5250 RhythmDBEntryChange *change = (RhythmDBEntryChange *)t->data;
5251
5252 if (metadata_field_from_prop (change->prop, &field) == FALSE) {
5253 continue;
5254 }
5255
5256 g_value_init (&val, rhythmdb_get_property_type (db, change->prop));
5257 rhythmdb_entry_get (db, entry, change->prop, &val);
5258 rb_metadata_set (db->priv->metadata, field, &val);
5259 g_value_unset (&val);
5260 }
5261
5262 rb_metadata_save (db->priv->metadata, uri, &local_error);
5263 if (local_error != NULL) {
5264 RhythmDBAction *load_action;
5265
5266 /* reload the metadata, to revert the db changes */
5267 rb_debug ("error saving metadata for %s: %s; reloading metadata to revert",
5268 rb_refstring_get (entry->location),
5269 local_error->message);
5270 load_action = g_slice_new0 (RhythmDBAction);
5271 load_action->type = RHYTHMDB_ACTION_LOAD;
5272 load_action->uri = rb_refstring_ref (entry->location);
5273 /* XXX entry types? */
5274 g_async_queue_push (db->priv->action_queue, load_action);
5275
5276 g_propagate_error (error, local_error);
5277 }
5278 }
5279
5280 /**
5281 * rhythmdb_get_property_type:
5282 * @db: the #RhythmDB
5283 * @property_id: a property ID (#RhythmDBPropType)
5284 *
5285 * Returns the #GType for the value of the property.
5286 *
5287 * Return value: property value type
5288 */
5289 GType
5290 rhythmdb_get_property_type (RhythmDB *db,
5291 guint property_id)
5292 {
5293 g_assert (property_id >= 0 && property_id < RHYTHMDB_NUM_PROPERTIES);
5294 return rhythmdb_properties[property_id].prop_type;
5295 }
5296
5297 /**
5298 * rhythmdb_entry_get_type:
5299 *
5300 * Returns the #GType for #RhythmDBEntry. The #GType for #RhythmDBEntry is a
5301 * boxed type, where copying the value references the entry and freeing it
5302 * unrefs it.
5303 *
5304 * Return value: value type
5305 */
5306 GType
5307 rhythmdb_entry_get_type (void)
5308 {
5309 static GType type = 0;
5310
5311 if (G_UNLIKELY (type == 0)) {
5312 type = g_boxed_type_register_static ("RhythmDBEntry",
5313 (GBoxedCopyFunc)rhythmdb_entry_ref,
5314 (GBoxedFreeFunc)rhythmdb_entry_unref);
5315 }
5316
5317 return type;
5318 }
5319
5320 /**
5321 * rhythmdb_entry_change_get_type:
5322 *
5323 * Returns the #GType for #RhythmDBEntryChange. #RhythmDBEntryChange is stored as a
5324 * boxed value. Copying the value copies the full change, including old and new values.
5325 *
5326 * Return value: entry change value type
5327 */
5328 GType
5329 rhythmdb_entry_change_get_type (void)
5330 {
5331 static GType type = 0;
5332
5333 if (G_UNLIKELY (type == 0)) {
5334 type = g_boxed_type_register_static ("RhythmDBEntryChange",
5335 (GBoxedCopyFunc)rhythmdb_entry_change_copy,
5336 (GBoxedFreeFunc)rhythmdb_entry_change_free);
5337 }
5338 return type;
5339 }
5340
5341 /**
5342 * rhythmdb_entry_is_lossless:
5343 * @entry: a #RhythmDBEntry
5344 *
5345 * Checks if @entry represents a file that is losslessly encoded.
5346 * An entry is considered lossless if it has no bitrate value and
5347 * its media type is "audio/x-flac". Other lossless encoding types
5348 * may be added in the future.
5349 *
5350 * Return value: %TRUE if @entry is lossless
5351 */
5352 gboolean
5353 rhythmdb_entry_is_lossless (RhythmDBEntry *entry)
5354 {
5355 const char *media_type;
5356
5357 if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE) != 0)
5358 return FALSE;
5359
5360 media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE);
5361 return rb_gst_media_type_is_lossless (media_type);
5362 }
5363
5364 /**
5365 * rhythmdb_entry_create_ext_db_key:
5366 * @entry: a #RhythmDBEntry
5367 * @prop: the primary #RhythmDBPropType for metadata lookups
5368 *
5369 * Creates a #RBExtDBKey for finding external metadata
5370 * for a given property. This is mostly useful for finding album or
5371 * track related data.
5372 *
5373 * Return value: the new #RBExtDBKey
5374 */
5375 RBExtDBKey *
5376 rhythmdb_entry_create_ext_db_key (RhythmDBEntry *entry, RhythmDBPropType prop)
5377 {
5378 RBExtDBKey *key;
5379 const char *str;
5380
5381 switch (prop) {
5382 case RHYTHMDB_PROP_ALBUM:
5383 key = rb_ext_db_key_create_lookup ("album", rhythmdb_entry_get_string (entry, prop));
5384 rb_ext_db_key_add_field (key,
5385 "artist",
5386 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
5387 str = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST);
5388 if (g_strcmp0 (str, "") != 0 && g_strcmp0 (str, _("Unknown")) != 0) {
5389 rb_ext_db_key_add_field (key, "artist", str);
5390 }
5391
5392 str = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID);
5393 if (g_strcmp0 (str, "") != 0 && g_strcmp0 (str, _("Unknown")) != 0) {
5394 rb_ext_db_key_add_info (key, "musicbrainz-albumid", str);
5395 }
5396
5397 break;
5398
5399 case RHYTHMDB_PROP_TITLE:
5400 key = rb_ext_db_key_create_lookup ("title", rhythmdb_entry_get_string (entry, prop));
5401 /* maybe these should be info? */
5402 rb_ext_db_key_add_field (key, "artist", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST));
5403 rb_ext_db_key_add_field (key, "album", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM));
5404 break;
5405
5406 case RHYTHMDB_PROP_ARTIST:
5407 /* not really sure what this might be useful for */
5408 key = rb_ext_db_key_create_lookup ("artist", rhythmdb_entry_get_string (entry, prop));
5409 break;
5410
5411 default:
5412 g_assert_not_reached ();
5413 }
5414
5415 rb_ext_db_key_add_info (key, "location", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION));
5416 return key;
5417 }
5418
5419 /**
5420 * rhythmdb_entry_matches_ext_db_key:
5421 * @db: #RhythmDB instance
5422 * @entry: a #RhythmDBEntry
5423 * @key: a #RBExtDBKey
5424 *
5425 * Checks whether @key matches @entry.
5426 *
5427 * Return value: %TRUE if the key matches the entry
5428 */
5429 gboolean
5430 rhythmdb_entry_matches_ext_db_key (RhythmDB *db, RhythmDBEntry *entry, RBExtDBKey *key)
5431 {
5432 char **fields;
5433 int i;
5434
5435 fields = rb_ext_db_key_get_field_names (key);
5436 for (i = 0; fields[i] != NULL; i++) {
5437 RhythmDBPropType prop;
5438 RhythmDBPropType extra_prop;
5439 const char *v;
5440
5441 prop = rhythmdb_propid_from_nice_elt_name (db, (const xmlChar *)fields[i]);
5442 if (prop == -1) {
5443 if (rb_ext_db_key_field_matches (key, fields[i], NULL) == FALSE) {
5444 g_strfreev (fields);
5445 return FALSE;
5446 }
5447 continue;
5448 }
5449
5450 /* check additional values for some fields */
5451 switch (prop) {
5452 case RHYTHMDB_PROP_ARTIST:
5453 extra_prop = RHYTHMDB_PROP_ALBUM_ARTIST;
5454 break;
5455 default:
5456 extra_prop = -1;
5457 break;
5458 }
5459
5460 if (extra_prop != -1) {
5461 v = rhythmdb_entry_get_string (entry, extra_prop);
5462 if (rb_ext_db_key_field_matches (key, fields[i], v))
5463 continue;
5464 }
5465
5466 v = rhythmdb_entry_get_string (entry, prop);
5467 if (rb_ext_db_key_field_matches (key, fields[i], v) == FALSE) {
5468 g_strfreev (fields);
5469 return FALSE;
5470 }
5471 }
5472
5473 g_strfreev (fields);
5474 return TRUE;
5475 }