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