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