hythmbox-2.98/rhythmdb/rhythmdb.c

Location Tool Test ID Function Issue
rhythmdb.c:770:9 clang-analyzer Access to field 'message' results in a dereference of a null pointer (loaded from variable 'access_error')
rhythmdb.c:770:9 clang-analyzer Access to field 'message' results in a dereference of a null pointer (loaded from variable 'access_error')
rhythmdb.c:3705:2 clang-analyzer Access to field 'impl_entry_delete' results in a dereference of a null pointer (loaded from variable 'klass')
rhythmdb.c:3705:2 clang-analyzer Access to field 'impl_entry_delete' results in a dereference of a null pointer (loaded from variable 'klass')
   1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
   2  *
   3  *  Copyright (C) 2003,2004 Colin Walters <walters@gnome.org>
   4  *
   5  *  This program is free software; you can redistribute it and/or modify
   6  *  it under the terms of the GNU General Public License as published by
   7  *  the Free Software Foundation; either version 2 of the License, or
   8  *  (at your option) any later version.
   9  *
  10  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
  11  *  GStreamer plugins to be used and distributed together with GStreamer
  12  *  and Rhythmbox. This permission is above and beyond the permissions granted
  13  *  by the GPL license by which Rhythmbox is covered. If you modify this code
  14  *  you may extend this exception to your version of the code, but you are not
  15  *  obligated to do so. If you do not wish to do so, delete this exception
  16  *  statement from your version.
  17  *
  18  *  This program is distributed in the hope that it will be useful,
  19  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
  20  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  21  *  GNU General Public License for more details.
  22  *
  23  *  You should have received a copy of the GNU General Public License
  24  *  along with this program; if not, write to the Free Software
  25  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
  26  *
  27  */
  28 
  29 /**
  30  * SECTION:rhythmdb
  31  * @short_description: Rhythmbox database functions
  32  *
  33  * RhythmDB is an in-memory database containing #RhythmDBEntry items.  It
  34  * runs queries represented as #GPtrArray<!-- -->s containing query criteria,
  35  * feeding the results into #RhythmDBQueryResults implementations such as
  36  * #RhythmDBQueryModel.  From there, entries are grouped by particular property
  37  * values to form #RhythmDBPropertyModel<!-- -->s.
  38  *
  39  * #RhythmDBEntry contains a fixed set of properties, defined by #RhythmDBPropType,
  40  */
  41 
  42 #include "config.h"
  43 
  44 #define	G_IMPLEMENT_INLINES 1
  45 #define	__RHYTHMDB_C__
  46 #include "rhythmdb.h"
  47 #undef G_IMPLEMENT_INLINES
  48 
  49 #include <string.h>
  50 #include <libxml/tree.h>
  51 #include <glib.h>
  52 #include <glib-object.h>
  53 #include <glib/gi18n.h>
  54 #include <gio/gio.h>
  55 #include <gobject/gvaluecollector.h>
  56 #include <gdk/gdk.h>
  57 
  58 
  59 #include "rb-marshal.h"
  60 #include "rb-file-helpers.h"
  61 #include "rb-debug.h"
  62 #include "rb-util.h"
  63 #include "rb-cut-and-paste-code.h"
  64 #include "rhythmdb-private.h"
  65 #include "rhythmdb-property-model.h"
  66 #include "rb-dialog.h"
  67 #include "rb-string-value-map.h"
  68 #include "rb-async-queue-watch.h"
  69 #include "rb-podcast-entry-types.h"
  70 #include "rb-gst-media-types.h"
  71 
  72 #define PROP_ENTRY(p,t,n) { RHYTHMDB_PROP_ ## p, "RHYTHMDB_PROP_" #p "", t, n }
  73 
  74 typedef struct _RhythmDBPropertyDef {
  75 	RhythmDBPropType prop_id;
  76 	const char *prop_name;
  77 	GType prop_type;
  78 	const char *elt_name;
  79 } RhythmDBPropertyDef;
  80 
  81 static const RhythmDBPropertyDef rhythmdb_properties[] = {
  82 	PROP_ENTRY(TYPE, G_TYPE_OBJECT, "type"),
  83 	PROP_ENTRY(ENTRY_ID, G_TYPE_ULONG, "entry-id"),
  84 	PROP_ENTRY(TITLE, G_TYPE_STRING, "title"),
  85 	PROP_ENTRY(GENRE, G_TYPE_STRING, "genre"),
  86 	PROP_ENTRY(ARTIST, G_TYPE_STRING, "artist"),
  87 	PROP_ENTRY(ALBUM, G_TYPE_STRING, "album"),
  88 	PROP_ENTRY(TRACK_NUMBER, G_TYPE_ULONG, "track-number"),
  89 	PROP_ENTRY(DISC_NUMBER, G_TYPE_ULONG, "disc-number"),
  90 	PROP_ENTRY(DURATION, G_TYPE_ULONG, "duration"),
  91 	PROP_ENTRY(FILE_SIZE, G_TYPE_UINT64, "file-size"),
  92 	PROP_ENTRY(LOCATION, G_TYPE_STRING, "location"),
  93 	PROP_ENTRY(MOUNTPOINT, G_TYPE_STRING, "mountpoint"),
  94 	PROP_ENTRY(MTIME, G_TYPE_ULONG, "mtime"),
  95 	PROP_ENTRY(FIRST_SEEN, G_TYPE_ULONG, "first-seen"),
  96 	PROP_ENTRY(LAST_SEEN, G_TYPE_ULONG, "last-seen"),
  97 	PROP_ENTRY(RATING, G_TYPE_DOUBLE, "rating"),
  98 	PROP_ENTRY(PLAY_COUNT, G_TYPE_ULONG, "play-count"),
  99 	PROP_ENTRY(LAST_PLAYED, G_TYPE_ULONG, "last-played"),
 100 	PROP_ENTRY(BITRATE, G_TYPE_ULONG, "bitrate"),
 101 	PROP_ENTRY(DATE, G_TYPE_ULONG, "date"),
 102 	PROP_ENTRY(TRACK_GAIN, G_TYPE_DOUBLE, "replaygain-track-gain"),
 103 	PROP_ENTRY(TRACK_PEAK, G_TYPE_DOUBLE, "replaygain-track-peak"),
 104 	PROP_ENTRY(ALBUM_GAIN, G_TYPE_DOUBLE, "replaygain-album-gain"),
 105 	PROP_ENTRY(ALBUM_PEAK, G_TYPE_DOUBLE, "replaygain-album-peak"),
 106 	PROP_ENTRY(MEDIA_TYPE, G_TYPE_STRING, "media-type"),
 107 	PROP_ENTRY(TITLE_SORT_KEY, G_TYPE_STRING, "title-sort-key"),
 108 	PROP_ENTRY(GENRE_SORT_KEY, G_TYPE_STRING, "genre-sort-key"),
 109 	PROP_ENTRY(ARTIST_SORT_KEY, G_TYPE_STRING, "artist-sort-key"),
 110 	PROP_ENTRY(ALBUM_SORT_KEY, G_TYPE_STRING, "album-sort-key"),
 111 	PROP_ENTRY(TITLE_FOLDED, G_TYPE_STRING, "title-folded"),
 112 	PROP_ENTRY(GENRE_FOLDED, G_TYPE_STRING, "genre-folded"),
 113 	PROP_ENTRY(ARTIST_FOLDED, G_TYPE_STRING, "artist-folded"),
 114 	PROP_ENTRY(ALBUM_FOLDED, G_TYPE_STRING, "album-folded"),
 115 	PROP_ENTRY(LAST_PLAYED_STR, G_TYPE_STRING, "last-played-str"),
 116 	PROP_ENTRY(HIDDEN, G_TYPE_BOOLEAN, "hidden"),
 117 	PROP_ENTRY(PLAYBACK_ERROR, G_TYPE_STRING, "playback-error"),
 118 	PROP_ENTRY(FIRST_SEEN_STR, G_TYPE_STRING, "first-seen-str"),
 119 	PROP_ENTRY(LAST_SEEN_STR, G_TYPE_STRING, "last-seen-str"),
 120 
 121 	PROP_ENTRY(SEARCH_MATCH, G_TYPE_STRING, "search-match"),
 122 	PROP_ENTRY(YEAR, G_TYPE_ULONG, "year"),
 123 	PROP_ENTRY(KEYWORD, G_TYPE_STRING, "keyword"),
 124 
 125 	PROP_ENTRY(STATUS, G_TYPE_ULONG, "status"),
 126 	PROP_ENTRY(DESCRIPTION, G_TYPE_STRING, "description"),
 127 	PROP_ENTRY(SUBTITLE, G_TYPE_STRING, "subtitle"),
 128 	PROP_ENTRY(SUMMARY, G_TYPE_STRING, "summary"),
 129 	PROP_ENTRY(LANG, G_TYPE_STRING, "lang"),
 130 	PROP_ENTRY(COPYRIGHT, G_TYPE_STRING, "copyright"),
 131 	PROP_ENTRY(IMAGE, G_TYPE_STRING, "image"),
 132 	PROP_ENTRY(POST_TIME, G_TYPE_ULONG, "post-time"),
 133 
 134 	PROP_ENTRY(MUSICBRAINZ_TRACKID, G_TYPE_STRING, "mb-trackid"),
 135 	PROP_ENTRY(MUSICBRAINZ_ARTISTID, G_TYPE_STRING, "mb-artistid"),
 136 	PROP_ENTRY(MUSICBRAINZ_ALBUMID, G_TYPE_STRING, "mb-albumid"),
 137 	PROP_ENTRY(MUSICBRAINZ_ALBUMARTISTID, G_TYPE_STRING, "mb-albumartistid"),
 138 	PROP_ENTRY(ARTIST_SORTNAME, G_TYPE_STRING, "mb-artistsortname"),
 139 	PROP_ENTRY(ALBUM_SORTNAME, G_TYPE_STRING, "album-sortname"),
 140 
 141 	PROP_ENTRY(ARTIST_SORTNAME_SORT_KEY, G_TYPE_STRING, "artist-sortname-sort-key"),
 142 	PROP_ENTRY(ARTIST_SORTNAME_FOLDED, G_TYPE_STRING, "artist-sortname-folded"),
 143 	PROP_ENTRY(ALBUM_SORTNAME_SORT_KEY, G_TYPE_STRING, "album-sortname-sort-key"),
 144 	PROP_ENTRY(ALBUM_SORTNAME_FOLDED, G_TYPE_STRING, "album-sortname-folded"),
 145 
 146 	PROP_ENTRY(COMMENT, G_TYPE_STRING, "comment"),
 147 
 148 	PROP_ENTRY(ALBUM_ARTIST, G_TYPE_STRING, "album-artist"),
 149 	PROP_ENTRY(ALBUM_ARTIST_SORT_KEY, G_TYPE_STRING, "album-artist-sort-key"),
 150 	PROP_ENTRY(ALBUM_ARTIST_FOLDED, G_TYPE_STRING, "album-artist-folded"),
 151 	PROP_ENTRY(ALBUM_ARTIST_SORTNAME, G_TYPE_STRING, "album-artist-sortname"),
 152 	PROP_ENTRY(ALBUM_ARTIST_SORTNAME_SORT_KEY, G_TYPE_STRING, "album-artist-sortname-sort-key"),
 153 	PROP_ENTRY(ALBUM_ARTIST_SORTNAME_FOLDED, G_TYPE_STRING, "album-artist-sortname-folded"),
 154 
 155 	PROP_ENTRY(BPM, G_TYPE_DOUBLE, "beats-per-minute"),
 156 
 157 	{ 0, 0, 0, 0 }
 158 };
 159 
 160 #define RB_PARSE_NICK_START (xmlChar *) "["
 161 #define RB_PARSE_NICK_END (xmlChar *) "]"
 162 
 163 
 164 #define RHYTHMDB_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RHYTHMDB_TYPE, RhythmDBPrivate))
 165 G_DEFINE_ABSTRACT_TYPE(RhythmDB, rhythmdb, G_TYPE_OBJECT)
 166 
 167 /* file attributes requested in RHYTHMDB_ACTION_STAT and RHYTHMDB_ACTION_LOAD */
 168 #define RHYTHMDB_FILE_INFO_ATTRIBUTES			\
 169 	G_FILE_ATTRIBUTE_STANDARD_SIZE ","		\
 170 	G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME ","	\
 171 	G_FILE_ATTRIBUTE_STANDARD_TYPE ","		\
 172 	G_FILE_ATTRIBUTE_TIME_MODIFIED
 173 
 174 /* file attributes requested in RHYTHMDB_ACTION_ENUM_DIR */
 175 #define RHYTHMDB_FILE_CHILD_INFO_ATTRIBUTES		\
 176 	RHYTHMDB_FILE_INFO_ATTRIBUTES ","		\
 177 	G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN ","		\
 178 	G_FILE_ATTRIBUTE_STANDARD_NAME
 179 
 180 /*
 181  * Filters for MIME/media types to ignore.
 182  * The only complication here is that there are some application/ types that
 183  * are used for audio/video files.  Otherwise, we'd ignore everything except
 184  * audio/ and video/.
 185  */
 186 struct media_type_filter {
 187 	const char *prefix;
 188 	gboolean ignore;
 189 } media_type_filters[] = {
 190 	{ "image/", TRUE },
 191 	{ "text/", TRUE },
 192 	{ "application/ogg", FALSE },
 193 	{ "application/x-id3", FALSE },
 194 	{ "application/x-apetag", FALSE },
 195 	{ "application/x-3gp", FALSE },
 196 	{ "application/x-annodex", FALSE },
 197 	{ "application/", TRUE },
 198 };
 199 
 200 /*
 201  * File size below which we will simply ignore files that can't be identified.
 202  * This is mostly here so we ignore the various text files that are packaged
 203  * with many netlabel releases and other downloads.
 204  */
 205 #define REALLY_SMALL_FILE_SIZE	(4096)
 206 
 207 
 208 typedef struct
 209 {
 210 	RhythmDB *db;
 211 	GPtrArray *query;
 212 	guint propid;
 213 	RhythmDBQueryResults *results;
 214 	gboolean cancel;
 215 } RhythmDBQueryThreadData;
 216 
 217 typedef struct
 218 {
 219 	RhythmDB *db;
 220 	RhythmDBEntryType *type;
 221 	RhythmDBEntryType *ignore_type;
 222 	RhythmDBEntryType *error_type;
 223 } RhythmDBAddThreadData;
 224 
 225 typedef struct
 226 {
 227 	enum {
 228 		RHYTHMDB_ACTION_STAT,
 229 		RHYTHMDB_ACTION_LOAD,
 230 		RHYTHMDB_ACTION_ENUM_DIR,
 231 		RHYTHMDB_ACTION_SYNC,
 232 		RHYTHMDB_ACTION_QUIT,
 233 	} type;
 234 	RBRefString *uri;
 235 	union {
 236 		struct {
 237 			RhythmDBEntryType *entry_type;
 238 			RhythmDBEntryType *ignore_type;
 239 			RhythmDBEntryType *error_type;
 240 		} types;
 241 		GSList *changes;
 242 	} data;
 243 } RhythmDBAction;
 244 
 245 static void rhythmdb_dispose (GObject *object);
 246 static void rhythmdb_finalize (GObject *object);
 247 static void rhythmdb_set_property (GObject *object,
 248 					guint prop_id,
 249 					const GValue *value,
 250 					GParamSpec *pspec);
 251 static void rhythmdb_get_property (GObject *object,
 252 					guint prop_id,
 253 					GValue *value,
 254 					GParamSpec *pspec);
 255 static void rhythmdb_thread_create (RhythmDB *db,
 256 				    GThreadPool *pool,
 257 				    GThreadFunc func,
 258 				    gpointer data);
 259 static void rhythmdb_read_enter (RhythmDB *db);
 260 static void rhythmdb_read_leave (RhythmDB *db);
 261 static void rhythmdb_process_one_event (RhythmDBEvent *event, RhythmDB *db);
 262 static gpointer action_thread_main (RhythmDB *db);
 263 static gpointer query_thread_main (RhythmDBQueryThreadData *data);
 264 static void rhythmdb_entry_set_mount_point (RhythmDB *db,
 265  					    RhythmDBEntry *entry,
 266  					    const gchar *realuri);
 267 
 268 static gboolean rhythmdb_idle_save (RhythmDB *db);
 269 static void db_settings_changed_cb (GSettings *settings, const char *key, RhythmDB *db);
 270 static void rhythmdb_sync_library_location (RhythmDB *db);
 271 static void rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry,
 272 					  guint propid);
 273 static gboolean rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint,
 274 							   GValue *return_accu,
 275 							   const GValue *handler_return,
 276 							   gpointer data);
 277 
 278 static void rhythmdb_event_free (RhythmDB *db, RhythmDBEvent *event);
 279 static void rhythmdb_add_to_stat_list (RhythmDB *db,
 280 				       const char *uri,
 281 				       RhythmDBEntry *entry,
 282 				       RhythmDBEntryType *type,
 283 				       RhythmDBEntryType *ignore_type,
 284 				       RhythmDBEntryType *error_type);
 285 static void free_entry_changes (GSList *entry_changes);
 286 
 287 static void perform_next_mount (RhythmDB *db);
 288 
 289 enum
 290 {
 291 	PROP_0,
 292 	PROP_NAME,
 293 	PROP_DRY_RUN,
 294 	PROP_NO_UPDATE,
 295 };
 296 
 297 enum
 298 {
 299 	ENTRY_ADDED,
 300 	ENTRY_CHANGED,
 301 	ENTRY_DELETED,
 302 	ENTRY_KEYWORD_ADDED,
 303 	ENTRY_KEYWORD_REMOVED,
 304 	ENTRY_EXTRA_METADATA_REQUEST,
 305 	ENTRY_EXTRA_METADATA_NOTIFY,
 306 	ENTRY_EXTRA_METADATA_GATHER,
 307 	LOAD_COMPLETE,
 308 	SAVE_COMPLETE,
 309 	SAVE_ERROR,
 310 	READ_ONLY,
 311 	CREATE_MOUNT_OP,
 312 	LAST_SIGNAL
 313 };
 314 
 315 static guint rhythmdb_signals[LAST_SIGNAL] = { 0 };
 316 
 317 static void
 318 rhythmdb_class_init (RhythmDBClass *klass)
 319 {
 320 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
 321 
 322 	object_class->dispose = rhythmdb_dispose;
 323 	object_class->finalize = rhythmdb_finalize;
 324 
 325 	object_class->set_property = rhythmdb_set_property;
 326 	object_class->get_property = rhythmdb_get_property;
 327 
 328 	/**
 329 	 * RhythmDB:name:
 330 	 *
 331 	 * Database name.  Not sure whta this is used for.
 332 	 */
 333 	g_object_class_install_property (object_class,
 334 					 PROP_NAME,
 335 					 g_param_spec_string ("name",
 336 							      "name",
 337 							      "name",
 338 							      NULL,
 339 							      G_PARAM_READWRITE));
 340 	/**
 341 	 * RhythmDB:dry-run:
 342 	 *
 343 	 * If %TRUE, no metadata changes will be written back to media fies.
 344 	 */
 345 	g_object_class_install_property (object_class,
 346 					 PROP_DRY_RUN,
 347 					 g_param_spec_boolean ("dry-run",
 348 							       "dry run",
 349 							       "Whether or not changes should be saved",
 350 							       FALSE,
 351 							       G_PARAM_READWRITE));
 352 	/**
 353 	 * RhythmDB:no-update:
 354 	 *
 355 	 * If %TRUE, the database will not be updated.
 356 	 */
 357 	g_object_class_install_property (object_class,
 358 					 PROP_NO_UPDATE,
 359 					 g_param_spec_boolean ("no-update",
 360 							       "no update",
 361 							       "Whether or not to update the database",
 362 							       FALSE,
 363 							       G_PARAM_READWRITE));
 364 	/**
 365 	 * RhythmDB::entry-added:
 366 	 * @db: the #RhythmDB
 367 	 * @entry: the newly added #RhythmDBEntry
 368 	 *
 369 	 * Emitted when a new entry is added to the database.
 370 	 */
 371 	rhythmdb_signals[ENTRY_ADDED] =
 372 		g_signal_new ("entry_added",
 373 			      RHYTHMDB_TYPE,
 374 			      G_SIGNAL_RUN_LAST,
 375 			      G_STRUCT_OFFSET (RhythmDBClass, entry_added),
 376 			      NULL, NULL,
 377 			      g_cclosure_marshal_VOID__BOXED,
 378 			      G_TYPE_NONE,
 379 			      1, RHYTHMDB_TYPE_ENTRY);
 380 
 381 	/**
 382 	 * RhythmDB::entry-deleted:
 383 	 * @db: the #RhythmDB
 384 	 * @entry: the deleted #RhythmDBEntry
 385 	 *
 386 	 * Emitted when an entry is deleted from the database.
 387 	 */
 388 	rhythmdb_signals[ENTRY_DELETED] =
 389 		g_signal_new ("entry_deleted",
 390 			      RHYTHMDB_TYPE,
 391 			      G_SIGNAL_RUN_LAST,
 392 			      G_STRUCT_OFFSET (RhythmDBClass, entry_deleted),
 393 			      NULL, NULL,
 394 			      g_cclosure_marshal_VOID__BOXED,
 395 			      G_TYPE_NONE,
 396 			      1, RHYTHMDB_TYPE_ENTRY);
 397 
 398 	/**
 399 	 * RhythmDB::entry-changed:
 400 	 * @db: the #RhythmDB
 401 	 * @entry: the changed #RhythmDBEntry
 402 	 * @changes: a #GArray of #RhythmDBEntryChange structures describing the changes
 403 	 *
 404 	 * Emitted when a database entry is modified.  The @changes list
 405 	 * contains a structure for each entry property that has been modified.
 406 	 */
 407 	rhythmdb_signals[ENTRY_CHANGED] =
 408 		g_signal_new ("entry_changed",
 409 			      RHYTHMDB_TYPE,
 410 			      G_SIGNAL_RUN_LAST,
 411 			      G_STRUCT_OFFSET (RhythmDBClass, entry_changed),
 412 			      NULL, NULL,
 413 			      rb_marshal_VOID__BOXED_BOXED,
 414 			      G_TYPE_NONE, 2,
 415 			      RHYTHMDB_TYPE_ENTRY, G_TYPE_ARRAY);
 416 
 417 	/**
 418 	 * RhythmDB::entry-keyword-added:
 419 	 * @db: the #RhythmDB
 420 	 * @entry: the #RhythmDBEntry to which a keyword has been added
 421 	 * @keyword: the keyword that was added
 422 	 *
 423 	 * Emitted when a keyword is added to an entry.
 424 	 */
 425 	rhythmdb_signals[ENTRY_KEYWORD_ADDED] =
 426 		g_signal_new ("entry_keyword_added",
 427 			      RHYTHMDB_TYPE,
 428 			      G_SIGNAL_RUN_LAST,
 429 			      G_STRUCT_OFFSET (RhythmDBClass, entry_added),
 430 			      NULL, NULL,
 431 			      rb_marshal_VOID__BOXED_BOXED,
 432 			      G_TYPE_NONE,
 433 			      2, RHYTHMDB_TYPE_ENTRY, RB_TYPE_REFSTRING);
 434 
 435 	/**
 436 	 * RhythmDB::entry-keyword-removed:
 437 	 * @db: the #RhythmDB
 438 	 * @entry: the #RhythmDBEntry from which a keyword has been removed
 439 	 * @keyword: the keyword that was removed
 440 	 *
 441 	 * Emitted when a keyword is removed from an entry.
 442 	 */
 443 	rhythmdb_signals[ENTRY_KEYWORD_REMOVED] =
 444 		g_signal_new ("entry_keyword_removed",
 445 			      RHYTHMDB_TYPE,
 446 			      G_SIGNAL_RUN_LAST,
 447 			      G_STRUCT_OFFSET (RhythmDBClass, entry_deleted),
 448 			      NULL, NULL,
 449 			      rb_marshal_VOID__BOXED_BOXED,
 450 			      G_TYPE_NONE,
 451 			      2, RHYTHMDB_TYPE_ENTRY, RB_TYPE_REFSTRING);
 452 
 453 	/**
 454 	 * RhythmDB::entry-extra-metadata-request:
 455 	 * @db: the #RhythmDB
 456 	 * @entry: the #RhythmDBEntry for which extra metadata is being requested
 457 	 *
 458 	 * This signal is emitted to allow extra (transient) metadata to be supplied
 459 	 * for the given entry.  The detail of the signal invocation describes the
 460 	 * specific metadata value being requested.  If the object handling the signal
 461 	 * can provide the requested item, but it isn't immediately available, it can
 462 	 * initiate an attempt to retrieve it.  If successful, it would call
 463 	 * @rhythmdb_emit_entry_extra_metadata_notify when the metadata is available.
 464 	 *
 465 	 * Return value: the extra metadata value
 466 	 */
 467 	rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST] =
 468 		g_signal_new ("entry_extra_metadata_request",
 469 			      G_OBJECT_CLASS_TYPE (object_class),
 470 			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
 471 			      G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_request),
 472 			      rhythmdb_entry_extra_metadata_accumulator, NULL,
 473 			      rb_marshal_BOXED__BOXED,
 474 			      G_TYPE_VALUE, 1,
 475 			      RHYTHMDB_TYPE_ENTRY);
 476 
 477 	/**
 478 	 * RhythmDB::entry-extra-metadata-notify:
 479 	 * @db: the #RhythmDB
 480 	 * @entry: the #RhythmDBEntry for which extra metadata has been supplied
 481 	 * @field: the extra metadata field being supplied
 482 	 * @metadata: the extra metadata value
 483 	 *
 484 	 * This signal is emitted when an extra metadata value is provided for a specific
 485 	 * entry independantly of an extra metadata request.
 486 	 */
 487 	rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY] =
 488 		g_signal_new ("entry_extra_metadata_notify",
 489 			      G_OBJECT_CLASS_TYPE (object_class),
 490 			      G_SIGNAL_RUN_LAST | G_SIGNAL_DETAILED,
 491 			      G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_notify),
 492 			      NULL, NULL,
 493 			      rb_marshal_VOID__BOXED_STRING_BOXED,
 494 			      G_TYPE_NONE, 3,
 495 			      RHYTHMDB_TYPE_ENTRY, G_TYPE_STRING, G_TYPE_VALUE);
 496 
 497 	/**
 498 	 * RhythmDB::entry-extra-metadata-gather:
 499 	 * @db: the #RhythmDB
 500 	 * @entry: the #RhythmDBEntry for which to gather metadata
 501 	 * @data: a #RBStringValueMap to hold the gathered metadata
 502 	 *
 503 	 * Emitted to gather all available extra metadata for a database entry.
 504 	 * Handlers for this signal should insert any metadata they can provide
 505 	 * into the string-value map.  Only immediately available metadata
 506 	 * items should be returned.  If one or more metadata items is not
 507 	 * immediately available, the handler should not initiate an attempt to
 508 	 * retrieve them.
 509 	 */
 510 	rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER] =
 511 		g_signal_new ("entry_extra_metadata_gather",
 512 			      G_OBJECT_CLASS_TYPE (object_class),
 513 			      G_SIGNAL_RUN_LAST,
 514 			      G_STRUCT_OFFSET (RhythmDBClass, entry_extra_metadata_gather),
 515 			      NULL, NULL,
 516 			      rb_marshal_VOID__BOXED_OBJECT,
 517 			      G_TYPE_NONE, 2,
 518 			      RHYTHMDB_TYPE_ENTRY, RB_TYPE_STRING_VALUE_MAP);
 519 
 520 	/**
 521 	 * RhythmDB::load-complete:
 522 	 * @db: the #RhythmDB
 523 	 *
 524 	 * Emitted when the database is fully loaded.
 525 	 */
 526 	rhythmdb_signals[LOAD_COMPLETE] =
 527 		g_signal_new ("load_complete",
 528 			      RHYTHMDB_TYPE,
 529 			      G_SIGNAL_RUN_LAST,
 530 			      G_STRUCT_OFFSET (RhythmDBClass, load_complete),
 531 			      NULL, NULL,
 532 			      g_cclosure_marshal_VOID__VOID,
 533 			      G_TYPE_NONE,
 534 			      0);
 535 
 536 	/**
 537 	 * RhythmDB::save-complete:
 538 	 * @db: the #RhythmDB
 539 	 *
 540 	 * Emitted when the database has been saved.
 541 	 */
 542 	rhythmdb_signals[SAVE_COMPLETE] =
 543 		g_signal_new ("save_complete",
 544 			      RHYTHMDB_TYPE,
 545 			      G_SIGNAL_RUN_LAST,
 546 			      G_STRUCT_OFFSET (RhythmDBClass, save_complete),
 547 			      NULL, NULL,
 548 			      g_cclosure_marshal_VOID__VOID,
 549 			      G_TYPE_NONE,
 550 			      0);
 551 
 552 	/**
 553 	 * RhythmDB::save-error:
 554 	 * @db: the #RhythmDB
 555 	 * @uri: URI of the database file
 556 	 * @error: the error that occurred
 557 	 *
 558 	 * Emitted when an error occurs while saving the database.
 559 	 */
 560 	rhythmdb_signals[SAVE_ERROR] =
 561 		g_signal_new ("save-error",
 562 			      G_OBJECT_CLASS_TYPE (object_class),
 563 			      G_SIGNAL_RUN_LAST,
 564 			      G_STRUCT_OFFSET (RhythmDBClass, save_error),
 565 			      NULL, NULL,
 566 			      rb_marshal_VOID__STRING_POINTER,
 567 			      G_TYPE_NONE,
 568 			      2,
 569 			      G_TYPE_STRING,
 570 			      G_TYPE_POINTER);
 571 
 572 	/**
 573 	 * RhythmDB::read-only:
 574 	 * @db: the #RhythmDB
 575 	 * @readonly: %TRUE if the database is read-only
 576 	 *
 577 	 * Emitted when the database becomes temporarily read-only, or becomes
 578 	 * writeable after being read-only.
 579 	 */
 580 	rhythmdb_signals[READ_ONLY] =
 581 		g_signal_new ("read-only",
 582 			      G_OBJECT_CLASS_TYPE (object_class),
 583 			      G_SIGNAL_RUN_LAST,
 584 			      G_STRUCT_OFFSET (RhythmDBClass, read_only),
 585 			      NULL, NULL,
 586 			      g_cclosure_marshal_VOID__BOOLEAN,
 587 			      G_TYPE_NONE,
 588 			      1,
 589 			      G_TYPE_BOOLEAN);
 590 
 591 	/**
 592 	 * RhythmDB::create-mount-op:
 593 	 * @db: the #RhythmDB
 594 	 *
 595 	 * Emitted to request creation of a #GMountOperation to use to mount a volume.
 596 	 *
 597 	 * Returns: (transfer full): a #GMountOperation (usually actually a #GtkMountOperation)
 598 	 */
 599 	rhythmdb_signals[CREATE_MOUNT_OP] =
 600 		g_signal_new ("create-mount-op",
 601 			      G_OBJECT_CLASS_TYPE (object_class),
 602 			      G_SIGNAL_RUN_LAST,
 603 			      0,		/* no need for an internal handler */
 604 			      rb_signal_accumulator_object_handled, NULL,
 605 			      rb_marshal_OBJECT__VOID,
 606 			      G_TYPE_MOUNT_OPERATION,
 607 			      0);
 608 
 609 	g_type_class_add_private (klass, sizeof (RhythmDBPrivate));
 610 }
 611 
 612 static void
 613 rhythmdb_push_event (RhythmDB *db, RhythmDBEvent *event)
 614 {
 615 	g_async_queue_push (db->priv->event_queue, event);
 616 	g_main_context_wakeup (g_main_context_default ());
 617 }
 618 
 619 static gboolean
 620 metadata_field_from_prop (RhythmDBPropType prop,
 621 			  RBMetaDataField *field)
 622 {
 623 	switch (prop) {
 624 	case RHYTHMDB_PROP_TITLE:
 625 		*field = RB_METADATA_FIELD_TITLE;
 626 		return TRUE;
 627 	case RHYTHMDB_PROP_ARTIST:
 628 		*field = RB_METADATA_FIELD_ARTIST;
 629 		return TRUE;
 630 	case RHYTHMDB_PROP_ALBUM:
 631 		*field = RB_METADATA_FIELD_ALBUM;
 632 		return TRUE;
 633 	case RHYTHMDB_PROP_GENRE:
 634 		*field = RB_METADATA_FIELD_GENRE;
 635 		return TRUE;
 636 	case RHYTHMDB_PROP_COMMENT:
 637 		*field = RB_METADATA_FIELD_COMMENT;
 638 		return TRUE;
 639 	case RHYTHMDB_PROP_TRACK_NUMBER:
 640 		*field = RB_METADATA_FIELD_TRACK_NUMBER;
 641 		return TRUE;
 642 	case RHYTHMDB_PROP_DISC_NUMBER:
 643 		*field = RB_METADATA_FIELD_DISC_NUMBER;
 644 		return TRUE;
 645 	case RHYTHMDB_PROP_DATE:
 646 		*field = RB_METADATA_FIELD_DATE;
 647 		return TRUE;
 648 	case RHYTHMDB_PROP_BPM:
 649 		*field = RB_METADATA_FIELD_BPM;
 650 		return TRUE;
 651 	case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID:
 652 		*field = RB_METADATA_FIELD_MUSICBRAINZ_TRACKID;
 653 		return TRUE;
 654 	case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID:
 655 		*field = RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID;
 656 		return TRUE;
 657 	case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID:
 658 		*field = RB_METADATA_FIELD_MUSICBRAINZ_ALBUMID;
 659 		return TRUE;
 660 	case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID:
 661 		*field = RB_METADATA_FIELD_MUSICBRAINZ_ALBUMARTISTID;
 662 		return TRUE;
 663 	case RHYTHMDB_PROP_ARTIST_SORTNAME:
 664 		*field = RB_METADATA_FIELD_ARTIST_SORTNAME;
 665 		return TRUE;
 666 	case RHYTHMDB_PROP_ALBUM_SORTNAME:
 667 		*field = RB_METADATA_FIELD_ALBUM_SORTNAME;
 668 		return TRUE;
 669 	case RHYTHMDB_PROP_ALBUM_ARTIST:
 670 		*field = RB_METADATA_FIELD_ALBUM_ARTIST;
 671 		return TRUE;
 672 	case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME:
 673 		*field = RB_METADATA_FIELD_ALBUM_ARTIST_SORTNAME;
 674 		return TRUE;
 675 	default:
 676 		return FALSE;
 677 	}
 678 }
 679 
 680 static void
 681 rhythmdb_init (RhythmDB *db)
 682 {
 683 	guint i;
 684 	GEnumClass *prop_class;
 685 
 686 	db->priv = RHYTHMDB_GET_PRIVATE (db);
 687 
 688 	db->priv->settings = g_settings_new ("org.gnome.rhythmbox.rhythmdb");
 689 	g_signal_connect_object (db->priv->settings, "changed", G_CALLBACK (db_settings_changed_cb), db, 0);
 690 
 691 	db->priv->action_queue = g_async_queue_new ();
 692 	db->priv->event_queue = g_async_queue_new ();
 693 	db->priv->delayed_write_queue = g_async_queue_new ();
 694 	db->priv->event_queue_watch_id = rb_async_queue_watch_new (db->priv->event_queue,
 695 								   G_PRIORITY_LOW,		/* really? */
 696 								   (RBAsyncQueueWatchFunc) rhythmdb_process_one_event,
 697 								   db,
 698 								   NULL,
 699 								   NULL);
 700 
 701 	db->priv->restored_queue = g_async_queue_new ();
 702 
 703 	db->priv->query_thread_pool = g_thread_pool_new ((GFunc)query_thread_main,
 704 							 NULL,
 705 							 -1, FALSE, NULL);
 706 
 707 	db->priv->metadata = rb_metadata_new ();
 708 
 709 	prop_class = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE);
 710 
 711 	g_assert (prop_class->n_values == RHYTHMDB_NUM_PROPERTIES);
 712 
 713 	g_type_class_unref (prop_class);
 714 
 715 	db->priv->propname_map = g_hash_table_new (g_str_hash, g_str_equal);
 716 
 717 	for (i = 0; i < RHYTHMDB_NUM_PROPERTIES; i++) {
 718 		const xmlChar *name = rhythmdb_nice_elt_name_from_propid (db, i);
 719 		g_hash_table_insert (db->priv->propname_map, (gpointer) name, GINT_TO_POINTER (i));
 720 	}
 721 
 722 	db->priv->entry_type_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_object_unref);
 723 
 724 	rhythmdb_register_song_entry_types (db);
 725 	rb_podcast_register_entry_types (db);
 726 
 727 	db->priv->changed_entries = g_hash_table_new_full (NULL,
 728 							   NULL,
 729 							   (GDestroyNotify) rhythmdb_entry_unref,
 730 							   NULL);
 731 	db->priv->added_entries = g_hash_table_new_full (NULL,
 732 							 NULL,
 733 							 (GDestroyNotify) rhythmdb_entry_unref,
 734 							 NULL);
 735 	db->priv->deleted_entries = g_hash_table_new_full (NULL,
 736 							   NULL,
 737 							   (GDestroyNotify) rhythmdb_entry_unref,
 738 							   NULL);
 739 
 740 	db->priv->can_save = TRUE;
 741 	db->priv->exiting = g_cancellable_new ();
 742 	db->priv->saving = FALSE;
 743 	db->priv->dirty = FALSE;
 744 
 745 	db->priv->empty_string = rb_refstring_new ("");
 746 	db->priv->octet_stream_str = rb_refstring_new ("application/octet-stream");
 747 
 748 	db->priv->next_entry_id = 1;
 749 
 750 	rhythmdb_init_monitoring (db);
 751 
 752 	rhythmdb_dbus_register (db);
 753 }
 754 
 755 static GError *
 756 make_access_failed_error (const char *uri, GError *access_error)
 757 {
 758 	char *unescaped;
 759 	char *utf8ised;
 760 	GError *error;
 761 
 762 	/* make sure the URI we put in the error message is valid utf8 */
 763 	unescaped = g_uri_unescape_string (uri, NULL);
 764 	utf8ised = rb_make_valid_utf8 (unescaped, '?');
 765 
 766 	error = g_error_new (RHYTHMDB_ERROR,
 767 			     RHYTHMDB_ERROR_ACCESS_FAILED,
 768 			     _("Couldn't access %s: %s"),
 769 			     utf8ised,
 770 			     access_error->message);
Access to field 'message' results in a dereference of a null pointer (loaded from variable 'access_error')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Access to field 'message' results in a dereference of a null pointer (loaded from variable 'access_error')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

771 rb_debug ("got error on %s: %s", uri, error->message); 772 g_free (unescaped); 773 g_free (utf8ised); 774 return error; 775 } 776 777 static gboolean 778 rhythmdb_ignore_media_type (const char *media_type) 779 { 780 int i; 781 782 for (i = 0; i < G_N_ELEMENTS (media_type_filters); i++) { 783 if (g_str_has_prefix (media_type, media_type_filters[i].prefix)) { 784 return media_type_filters[i].ignore; 785 } 786 } 787 return FALSE; 788 } 789 790 typedef struct { 791 RhythmDB *db; 792 GList *stat_list; 793 } RhythmDBStatThreadData; 794 795 static gpointer 796 stat_thread_main (RhythmDBStatThreadData *data) 797 { 798 GList *i; 799 GError *error = NULL; 800 RhythmDBEvent *result; 801 802 data->db->priv->stat_thread_count = g_list_length (data->stat_list); 803 data->db->priv->stat_thread_done = 0; 804 805 rb_debug ("entering stat thread: %d to process", data->db->priv->stat_thread_count); 806 for (i = data->stat_list; i != NULL; i = i->next) { 807 RhythmDBEvent *event = (RhythmDBEvent *)i->data; 808 GFile *file; 809 810 /* if we've been cancelled, just free the event. this will 811 * clean up the list and then we'll exit the thread. 812 */ 813 if (g_cancellable_is_cancelled (data->db->priv->exiting)) { 814 rhythmdb_event_free (data->db, event); 815 continue; 816 } 817 818 if (data->db->priv->stat_thread_done > 0 && 819 data->db->priv->stat_thread_done % 1000 == 0) { 820 rb_debug ("%d file info queries done", 821 data->db->priv->stat_thread_done); 822 } 823 824 file = g_file_new_for_uri (rb_refstring_get (event->uri)); 825 event->real_uri = rb_refstring_ref (event->uri); /* what? */ 826 event->file_info = g_file_query_info (file, 827 G_FILE_ATTRIBUTE_TIME_MODIFIED, /* anything else? */ 828 G_FILE_QUERY_INFO_NONE, 829 data->db->priv->exiting, 830 &error); 831 if (error != NULL) { 832 event->error = make_access_failed_error (rb_refstring_get (event->uri), error); 833 g_clear_error (&error); 834 835 if (event->file_info != NULL) { 836 g_object_unref (event->file_info); 837 event->file_info = NULL; 838 } 839 } 840 841 g_async_queue_push (data->db->priv->event_queue, event); 842 g_object_unref (file); 843 g_atomic_int_inc (&data->db->priv->stat_thread_done); 844 } 845 846 g_list_free (data->stat_list); 847 848 data->db->priv->stat_thread_running = FALSE; 849 850 rb_debug ("exiting stat thread"); 851 result = g_slice_new0 (RhythmDBEvent); 852 result->db = data->db; /* need to unref? */ 853 result->type = RHYTHMDB_EVENT_THREAD_EXITED; 854 rhythmdb_push_event (data->db, result); 855 856 g_free (data); 857 return NULL; 858 } 859 860 static void 861 perform_next_mount_cb (GObject *file, GAsyncResult *res, RhythmDB *db) 862 { 863 GError *error = NULL; 864 865 g_file_mount_enclosing_volume_finish (G_FILE (file), res, &error); 866 if (error != NULL) { 867 char *uri; 868 869 uri = g_file_get_uri (G_FILE (file)); 870 rb_debug ("Unable to mount %s: %s", uri, error->message); 871 g_free (uri); 872 g_clear_error (&error); 873 } 874 g_object_unref (file); 875 876 perform_next_mount (db); 877 } 878 879 static void 880 perform_next_mount (RhythmDB *db) 881 { 882 GList *l; 883 char *mountpoint; 884 GMountOperation *mount_op = NULL; 885 886 if (db->priv->mount_list == NULL) { 887 rb_debug ("finished mounting"); 888 return; 889 } 890 891 l = db->priv->mount_list; 892 db->priv->mount_list = db->priv->mount_list->next; 893 mountpoint = l->data; 894 g_list_free1 (l); 895 896 rb_debug ("mounting %s", (char *)mountpoint); 897 g_signal_emit (G_OBJECT (db), rhythmdb_signals[CREATE_MOUNT_OP], 0, &mount_op); 898 g_file_mount_enclosing_volume (g_file_new_for_uri (mountpoint), 899 G_MOUNT_MOUNT_NONE, 900 mount_op, 901 db->priv->exiting, 902 (GAsyncReadyCallback) perform_next_mount_cb, 903 db); 904 } 905 906 /** 907 * rhythmdb_start_action_thread: 908 * @db: the #RhythmDB 909 * 910 * Starts the #RhythmDB processing thread. Needs to be called during startup. 911 */ 912 void 913 rhythmdb_start_action_thread (RhythmDB *db) 914 { 915 g_mutex_lock (&db->priv->stat_mutex); 916 db->priv->action_thread_running = TRUE; 917 rhythmdb_thread_create (db, NULL, (GThreadFunc) action_thread_main, db); 918 919 if (db->priv->stat_list != NULL) { 920 RhythmDBStatThreadData *data; 921 data = g_new0 (RhythmDBStatThreadData, 1); 922 data->db = g_object_ref (db); 923 data->stat_list = db->priv->stat_list; 924 db->priv->stat_list = NULL; 925 926 db->priv->stat_thread_running = TRUE; 927 rhythmdb_thread_create (db, NULL, (GThreadFunc) stat_thread_main, data); 928 } 929 930 perform_next_mount (db); 931 932 g_mutex_unlock (&db->priv->stat_mutex); 933 } 934 935 static void 936 rhythmdb_action_free (RhythmDB *db, 937 RhythmDBAction *action) 938 { 939 rb_refstring_unref (action->uri); 940 if (action->type == RHYTHMDB_ACTION_SYNC) { 941 free_entry_changes (action->data.changes); 942 } 943 g_slice_free (RhythmDBAction, action); 944 } 945 946 static void 947 rhythmdb_event_free (RhythmDB *db, 948 RhythmDBEvent *result) 949 { 950 switch (result->type) { 951 case RHYTHMDB_EVENT_THREAD_EXITED: 952 g_object_unref (db); 953 g_assert (g_atomic_int_dec_and_test (&db->priv->outstanding_threads) >= 0); 954 g_async_queue_unref (db->priv->action_queue); 955 g_async_queue_unref (db->priv->event_queue); 956 break; 957 case RHYTHMDB_EVENT_STAT: 958 case RHYTHMDB_EVENT_METADATA_LOAD: 959 case RHYTHMDB_EVENT_DB_LOAD: 960 case RHYTHMDB_EVENT_DB_SAVED: 961 case RHYTHMDB_EVENT_QUERY_COMPLETE: 962 break; 963 case RHYTHMDB_EVENT_ENTRY_SET: 964 g_value_unset (&result->change.new); 965 break; 966 } 967 if (result->error) 968 g_error_free (result->error); 969 rb_refstring_unref (result->uri); 970 rb_refstring_unref (result->real_uri); 971 if (result->file_info) 972 g_object_unref (result->file_info); 973 if (result->metadata) 974 g_object_unref (result->metadata); 975 if (result->results) 976 g_object_unref (result->results); 977 if (result->entry != NULL) { 978 rhythmdb_entry_unref (result->entry); 979 } 980 g_slice_free (RhythmDBEvent, result); 981 } 982 983 static void 984 _shutdown_foreach_swapped (RhythmDBEvent *event, RhythmDB *db) 985 { 986 rhythmdb_event_free (db, event); 987 } 988 989 /** 990 * rhythmdb_shutdown: 991 * @db: the #RhythmDB 992 * 993 * Ceases all #RhythmDB operations, including stopping all directory monitoring, and 994 * removing all actions and events currently queued. 995 */ 996 void 997 rhythmdb_shutdown (RhythmDB *db) 998 { 999 RhythmDBEvent *result; 1000 RhythmDBAction *action; 1001 1002 g_return_if_fail (RHYTHMDB_IS (db)); 1003 1004 g_cancellable_cancel (db->priv->exiting); 1005 1006 /* force the action thread to wake up and exit */ 1007 action = g_slice_new0 (RhythmDBAction); 1008 action->type = RHYTHMDB_ACTION_QUIT; 1009 g_async_queue_push (db->priv->action_queue, action); 1010 1011 /* abort all async io operations */ 1012 g_mutex_lock (&db->priv->stat_mutex); 1013 g_list_foreach (db->priv->outstanding_stats, (GFunc)_shutdown_foreach_swapped, db); 1014 g_list_free (db->priv->outstanding_stats); 1015 db->priv->outstanding_stats = NULL; 1016 g_mutex_unlock (&db->priv->stat_mutex); 1017 1018 rb_debug ("%d outstanding threads", g_atomic_int_get (&db->priv->outstanding_threads)); 1019 while (g_atomic_int_get (&db->priv->outstanding_threads) > 0) { 1020 result = g_async_queue_pop (db->priv->event_queue); 1021 rhythmdb_event_free (db, result); 1022 } 1023 1024 /* FIXME */ 1025 while ((result = g_async_queue_try_pop (db->priv->event_queue)) != NULL) 1026 rhythmdb_event_free (db, result); 1027 while ((result = g_async_queue_try_pop (db->priv->delayed_write_queue)) != NULL) 1028 rhythmdb_event_free (db, result); 1029 1030 while ((action = g_async_queue_try_pop (db->priv->action_queue)) != NULL) { 1031 rhythmdb_action_free (db, action); 1032 } 1033 } 1034 1035 static void 1036 rhythmdb_dispose (GObject *object) 1037 { 1038 RhythmDB *db; 1039 1040 g_return_if_fail (object != NULL); 1041 g_return_if_fail (RHYTHMDB_IS (object)); 1042 1043 rb_debug ("disposing rhythmdb"); 1044 db = RHYTHMDB (object); 1045 1046 g_return_if_fail (db->priv != NULL); 1047 1048 rhythmdb_dispose_monitoring (db); 1049 rhythmdb_dbus_unregister (db); 1050 1051 if (db->priv->event_queue_watch_id != 0) { 1052 g_source_remove (db->priv->event_queue_watch_id); 1053 db->priv->event_queue_watch_id = 0; 1054 } 1055 1056 if (db->priv->save_timeout_id != 0) { 1057 g_source_remove (db->priv->save_timeout_id); 1058 db->priv->save_timeout_id = 0; 1059 } 1060 1061 if (db->priv->emit_entry_signals_id != 0) { 1062 g_source_remove (db->priv->emit_entry_signals_id); 1063 db->priv->emit_entry_signals_id = 0; 1064 1065 g_list_foreach (db->priv->added_entries_to_emit, (GFunc)rhythmdb_entry_unref, NULL); 1066 g_list_foreach (db->priv->deleted_entries_to_emit, (GFunc)rhythmdb_entry_unref, NULL); 1067 if (db->priv->changed_entries_to_emit != NULL) { 1068 g_hash_table_destroy (db->priv->changed_entries_to_emit); 1069 } 1070 } 1071 1072 if (db->priv->metadata != NULL) { 1073 g_object_unref (db->priv->metadata); 1074 db->priv->metadata = NULL; 1075 } 1076 1077 if (db->priv->exiting != NULL) { 1078 g_object_unref (db->priv->exiting); 1079 db->priv->exiting = NULL; 1080 } 1081 1082 if (db->priv->settings != NULL) { 1083 g_object_unref (db->priv->settings); 1084 db->priv->settings = NULL; 1085 } 1086 1087 G_OBJECT_CLASS (rhythmdb_parent_class)->dispose (object); 1088 } 1089 1090 static void 1091 rhythmdb_finalize (GObject *object) 1092 { 1093 RhythmDB *db; 1094 1095 g_return_if_fail (object != NULL); 1096 g_return_if_fail (RHYTHMDB_IS (object)); 1097 1098 rb_debug ("finalizing rhythmdb"); 1099 db = RHYTHMDB (object); 1100 1101 g_return_if_fail (db->priv != NULL); 1102 1103 rhythmdb_finalize_monitoring (db); 1104 g_strfreev (db->priv->library_locations); 1105 db->priv->library_locations = NULL; 1106 1107 g_thread_pool_free (db->priv->query_thread_pool, FALSE, TRUE); 1108 g_async_queue_unref (db->priv->action_queue); 1109 g_async_queue_unref (db->priv->event_queue); 1110 g_async_queue_unref (db->priv->restored_queue); 1111 g_async_queue_unref (db->priv->delayed_write_queue); 1112 1113 g_list_free (db->priv->stat_list); 1114 1115 g_hash_table_destroy (db->priv->propname_map); 1116 1117 g_hash_table_destroy (db->priv->added_entries); 1118 g_hash_table_destroy (db->priv->deleted_entries); 1119 g_hash_table_destroy (db->priv->changed_entries); 1120 1121 rb_refstring_unref (db->priv->empty_string); 1122 rb_refstring_unref (db->priv->octet_stream_str); 1123 1124 g_hash_table_destroy (db->priv->entry_type_map); 1125 1126 g_free (db->priv->name); 1127 1128 G_OBJECT_CLASS (rhythmdb_parent_class)->finalize (object); 1129 } 1130 1131 static void 1132 rhythmdb_set_property (GObject *object, 1133 guint prop_id, 1134 const GValue *value, 1135 GParamSpec *pspec) 1136 { 1137 RhythmDB *db = RHYTHMDB (object); 1138 1139 switch (prop_id) { 1140 case PROP_NAME: 1141 g_free (db->priv->name); 1142 db->priv->name = g_value_dup_string (value); 1143 break; 1144 case PROP_DRY_RUN: 1145 db->priv->dry_run = g_value_get_boolean (value); 1146 break; 1147 case PROP_NO_UPDATE: 1148 db->priv->no_update = g_value_get_boolean (value); 1149 break; 1150 default: 1151 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 1152 break; 1153 } 1154 } 1155 1156 static void 1157 rhythmdb_get_property (GObject *object, 1158 guint prop_id, 1159 GValue *value, 1160 GParamSpec *pspec) 1161 { 1162 RhythmDB *source = RHYTHMDB (object); 1163 1164 switch (prop_id) { 1165 case PROP_NAME: 1166 g_value_set_string (value, source->priv->name); 1167 break; 1168 case PROP_DRY_RUN: 1169 g_value_set_boolean (value, source->priv->dry_run); 1170 break; 1171 case PROP_NO_UPDATE: 1172 g_value_set_boolean (value, source->priv->no_update); 1173 break; 1174 default: 1175 G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); 1176 break; 1177 } 1178 } 1179 1180 static void 1181 rhythmdb_thread_create (RhythmDB *db, 1182 GThreadPool *pool, 1183 GThreadFunc func, 1184 gpointer data) 1185 { 1186 g_object_ref (db); 1187 g_atomic_int_inc (&db->priv->outstanding_threads); 1188 g_async_queue_ref (db->priv->action_queue); 1189 g_async_queue_ref (db->priv->event_queue); 1190 1191 if (pool) 1192 g_thread_pool_push (pool, data, NULL); 1193 else 1194 g_thread_new ("rhythmdb-thread", (GThreadFunc) func, data); 1195 } 1196 1197 static gboolean 1198 rhythmdb_get_readonly (RhythmDB *db) 1199 { 1200 return (g_atomic_int_get (&db->priv->read_counter) > 0); 1201 } 1202 1203 static void 1204 rhythmdb_read_enter (RhythmDB *db) 1205 { 1206 gint count; 1207 g_return_if_fail (g_atomic_int_get (&db->priv->read_counter) >= 0); 1208 g_assert (rb_is_main_thread ()); 1209 1210 count = g_atomic_int_add (&db->priv->read_counter, 1); 1211 rb_debug ("counter: %d", count+1); 1212 if (count == 0) 1213 g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY], 1214 0, TRUE); 1215 } 1216 1217 static void 1218 rhythmdb_read_leave (RhythmDB *db) 1219 { 1220 gint count; 1221 g_return_if_fail (rhythmdb_get_readonly (db)); 1222 g_assert (rb_is_main_thread ()); 1223 1224 count = g_atomic_int_add (&db->priv->read_counter, -1); 1225 rb_debug ("counter: %d", count-1); 1226 if (count == 1) { 1227 1228 g_signal_emit (G_OBJECT (db), rhythmdb_signals[READ_ONLY], 1229 0, FALSE); 1230 1231 /* move any delayed writes back to the main event queue */ 1232 if (g_async_queue_length (db->priv->delayed_write_queue) > 0) { 1233 RhythmDBEvent *event; 1234 while ((event = g_async_queue_try_pop (db->priv->delayed_write_queue)) != NULL) 1235 g_async_queue_push (db->priv->event_queue, event); 1236 1237 g_main_context_wakeup (g_main_context_default ()); 1238 } 1239 1240 } 1241 } 1242 1243 static void 1244 rhythmdb_entry_change_free (RhythmDBEntryChange *change) 1245 { 1246 g_value_unset (&change->old); 1247 g_value_unset (&change->new); 1248 g_slice_free (RhythmDBEntryChange, change); 1249 } 1250 1251 static RhythmDBEntryChange * 1252 rhythmdb_entry_change_copy (RhythmDBEntryChange *change) 1253 { 1254 RhythmDBEntryChange *c = g_slice_new0 (RhythmDBEntryChange); 1255 1256 c->prop = change->prop; 1257 g_value_init (&c->old, G_VALUE_TYPE (&change->old)); 1258 g_value_init (&c->new, G_VALUE_TYPE (&change->new)); 1259 g_value_copy (&change->old, &c->old); 1260 g_value_copy (&change->new, &c->new); 1261 return c; 1262 } 1263 1264 static void 1265 free_entry_changes (GSList *entry_changes) 1266 { 1267 GSList *t; 1268 for (t = entry_changes; t; t = t->next) { 1269 RhythmDBEntryChange *change = t->data; 1270 rhythmdb_entry_change_free (change); 1271 } 1272 g_slist_free (entry_changes); 1273 } 1274 1275 static GSList * 1276 copy_entry_changes (GSList *entry_changes) 1277 { 1278 GSList *r = NULL; 1279 GSList *t; 1280 for (t = entry_changes; t; t = t->next) { 1281 RhythmDBEntryChange *change = t->data; 1282 r = g_slist_prepend (r, rhythmdb_entry_change_copy (change)); 1283 } 1284 1285 return g_slist_reverse (r); 1286 } 1287 1288 static gboolean 1289 rhythmdb_emit_entry_signals_idle (RhythmDB *db) 1290 { 1291 GList *added_entries; 1292 GList *deleted_entries; 1293 GHashTable *changed_entries; 1294 GList *l; 1295 GHashTableIter iter; 1296 RhythmDBEntry *entry; 1297 GSList *entry_changes; 1298 1299 /* get lists of entries to emit, reset source id value */ 1300 g_mutex_lock (&db->priv->change_mutex); 1301 1302 added_entries = db->priv->added_entries_to_emit; 1303 db->priv->added_entries_to_emit = NULL; 1304 1305 deleted_entries = db->priv->deleted_entries_to_emit; 1306 db->priv->deleted_entries_to_emit = NULL; 1307 1308 changed_entries = db->priv->changed_entries_to_emit; 1309 db->priv->changed_entries_to_emit = NULL; 1310 1311 db->priv->emit_entry_signals_id = 0; 1312 1313 g_mutex_unlock (&db->priv->change_mutex); 1314 1315 GDK_THREADS_ENTER (); 1316 1317 /* emit changed entries */ 1318 if (changed_entries != NULL) { 1319 g_hash_table_iter_init (&iter, changed_entries); 1320 while (g_hash_table_iter_next (&iter, (gpointer *)&entry, (gpointer *)&entry_changes)) { 1321 GArray *emit_changes; 1322 GSList *c; 1323 1324 emit_changes = g_array_sized_new (FALSE, TRUE, sizeof (GValue), g_slist_length (entry_changes)); 1325 g_array_set_clear_func (emit_changes, (GDestroyNotify) g_value_unset); 1326 for (c = entry_changes; c != NULL; c = c->next) { 1327 GValue v = {0,}; 1328 g_value_init (&v, RHYTHMDB_TYPE_ENTRY_CHANGE); 1329 g_value_take_boxed (&v, c->data); 1330 g_array_append_val (emit_changes, v); 1331 } 1332 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_CHANGED], 0, entry, emit_changes); 1333 g_array_unref (emit_changes); 1334 g_hash_table_iter_remove (&iter); 1335 } 1336 } 1337 1338 /* emit added entries */ 1339 for (l = added_entries; l; l = g_list_next (l)) { 1340 entry = (RhythmDBEntry *)l->data; 1341 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_ADDED], 0, entry); 1342 rhythmdb_entry_unref (entry); 1343 } 1344 1345 /* emit deleted entries */ 1346 for (l = deleted_entries; l; l = g_list_next (l)) { 1347 entry = (RhythmDBEntry *)l->data; 1348 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry); 1349 rhythmdb_entry_unref (entry); 1350 } 1351 1352 GDK_THREADS_LEAVE (); 1353 1354 if (changed_entries != NULL) { 1355 g_hash_table_destroy (changed_entries); 1356 } 1357 g_list_free (added_entries); 1358 g_list_free (deleted_entries); 1359 return FALSE; 1360 } 1361 1362 static gboolean 1363 process_added_entries_cb (RhythmDBEntry *entry, 1364 GThread *thread, 1365 RhythmDB *db) 1366 { 1367 if (thread != g_thread_self ()) 1368 return FALSE; 1369 1370 if (entry->type == RHYTHMDB_ENTRY_TYPE_SONG) { 1371 const gchar *uri; 1372 1373 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION); 1374 if (uri == NULL) 1375 return TRUE; 1376 1377 /* 1378 * hmm, do we really need to take the stat mutex to check if the action thread is running? 1379 * maybe it should be atomicised? 1380 */ 1381 /* 1382 * current plan: 1383 * - only stat things with mountpoint == NULL here 1384 * - collect other mountpoints 1385 * just before starting action/stat threads: 1386 * - find remote mountpoints that aren't mounted, try to mount them 1387 * - for local mountpoints that are mounted, add to stat list 1388 * - for everything else, hide entries on those mountpoints 1389 */ 1390 g_mutex_lock (&db->priv->stat_mutex); 1391 if (db->priv->action_thread_running == FALSE) { 1392 const char *mountpoint; 1393 1394 mountpoint = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MOUNTPOINT); 1395 if (mountpoint == NULL) { 1396 /* entry is on a core filesystem, always check it */ 1397 rhythmdb_add_to_stat_list (db, uri, entry, 1398 RHYTHMDB_ENTRY_TYPE_SONG, 1399 RHYTHMDB_ENTRY_TYPE_IGNORE, 1400 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR); 1401 } else if (rb_string_list_contains (db->priv->active_mounts, mountpoint)) { 1402 /* mountpoint is mounted - check the file if it's local */ 1403 if (rb_uri_is_local (mountpoint)) { 1404 rhythmdb_add_to_stat_list (db, 1405 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION), 1406 entry, 1407 NULL, 1408 RHYTHMDB_ENTRY_TYPE_IGNORE, 1409 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR); 1410 } else { 1411 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_MOUNTED); 1412 } 1413 } else { 1414 /* mountpoint is not mounted */ 1415 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_UNMOUNTED); 1416 1417 if (rb_string_list_contains (db->priv->mount_list, mountpoint) == FALSE) { 1418 db->priv->mount_list = g_list_prepend (db->priv->mount_list, g_strdup (mountpoint)); 1419 } 1420 } 1421 } 1422 g_mutex_unlock (&db->priv->stat_mutex); 1423 } 1424 1425 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) == 0); 1426 entry->flags |= RHYTHMDB_ENTRY_INSERTED; 1427 1428 rhythmdb_entry_ref (entry); 1429 db->priv->added_entries_to_emit = g_list_prepend (db->priv->added_entries_to_emit, entry); 1430 1431 return TRUE; 1432 } 1433 1434 static gboolean 1435 process_deleted_entries_cb (RhythmDBEntry *entry, 1436 GThread *thread, 1437 RhythmDB *db) 1438 { 1439 if (thread != g_thread_self ()) 1440 return FALSE; 1441 1442 rhythmdb_entry_ref (entry); 1443 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) != 0); 1444 entry->flags &= ~(RHYTHMDB_ENTRY_INSERTED); 1445 db->priv->deleted_entries_to_emit = g_list_prepend (db->priv->deleted_entries_to_emit, entry); 1446 1447 return TRUE; 1448 } 1449 1450 static gboolean 1451 process_changed_entries_cb (RhythmDBEntry *entry, 1452 GSList *changes, 1453 RhythmDB *db) 1454 { 1455 GSList *existing; 1456 if (db->priv->changed_entries_to_emit == NULL) { 1457 /* the value destroy function is just g_slist_free because we 1458 * steal the actual change structures to build the value array. 1459 */ 1460 db->priv->changed_entries_to_emit = g_hash_table_new_full (NULL, 1461 NULL, 1462 (GDestroyNotify) rhythmdb_entry_unref, 1463 (GDestroyNotify) g_slist_free); 1464 } 1465 1466 /* if the entry is already in the change map from a previous commit, add the 1467 * new changes to the end of the existing list. 1468 */ 1469 existing = g_hash_table_lookup (db->priv->changed_entries_to_emit, entry); 1470 if (existing != NULL) { 1471 changes = g_slist_concat (existing, changes); 1472 1473 /* steal the hash entry so it doesn't free the changes; also means we 1474 * don't need to add a reference on the entry. 1475 */ 1476 g_hash_table_steal (db->priv->changed_entries_to_emit, entry); 1477 } else { 1478 rhythmdb_entry_ref (entry); 1479 } 1480 1481 g_hash_table_insert (db->priv->changed_entries_to_emit, entry, changes); 1482 return TRUE; 1483 } 1484 1485 static void 1486 sync_entry_changed (RhythmDBEntry *entry, 1487 GSList *changes, 1488 RhythmDB *db) 1489 { 1490 GSList *t; 1491 1492 for (t = changes; t; t = t->next) { 1493 RBMetaDataField field; 1494 RhythmDBEntryChange *change = t->data; 1495 1496 if (metadata_field_from_prop (change->prop, &field)) { 1497 RhythmDBAction *action; 1498 1499 if (!rhythmdb_entry_can_sync_metadata (entry)) { 1500 g_warning ("trying to sync properties of non-editable file"); 1501 break; 1502 } 1503 1504 action = g_slice_new0 (RhythmDBAction); 1505 action->type = RHYTHMDB_ACTION_SYNC; 1506 action->uri = rb_refstring_ref (entry->location); 1507 action->data.changes = copy_entry_changes (changes); 1508 g_async_queue_push (db->priv->action_queue, action); 1509 break; 1510 } 1511 } 1512 } 1513 1514 1515 static void 1516 rhythmdb_commit_internal (RhythmDB *db, 1517 gboolean sync_changes, 1518 GThread *thread) 1519 { 1520 g_mutex_lock (&db->priv->change_mutex); 1521 1522 if (sync_changes) { 1523 g_hash_table_foreach (db->priv->changed_entries, (GHFunc) sync_entry_changed, db); 1524 } 1525 1526 /* update the sets of entry changed/added/deleted signals to emit */ 1527 g_hash_table_foreach_remove (db->priv->changed_entries, (GHRFunc) process_changed_entries_cb, db); 1528 g_hash_table_foreach_remove (db->priv->added_entries, (GHRFunc) process_added_entries_cb, db); 1529 g_hash_table_foreach_remove (db->priv->deleted_entries, (GHRFunc) process_deleted_entries_cb, db); 1530 1531 /* if there are some signals to emit, add a new idle callback if required */ 1532 if (db->priv->added_entries_to_emit || db->priv->deleted_entries_to_emit || db->priv->changed_entries_to_emit) { 1533 if (db->priv->emit_entry_signals_id == 0) 1534 db->priv->emit_entry_signals_id = g_idle_add ((GSourceFunc) rhythmdb_emit_entry_signals_idle, db); 1535 } 1536 1537 g_mutex_unlock (&db->priv->change_mutex); 1538 } 1539 1540 typedef struct { 1541 RhythmDB *db; 1542 gboolean sync; 1543 GThread *thread; 1544 } RhythmDBTimeoutCommitData; 1545 1546 static gboolean 1547 timeout_rhythmdb_commit (RhythmDBTimeoutCommitData *data) 1548 { 1549 rhythmdb_commit_internal (data->db, data->sync, data->thread); 1550 g_object_unref (data->db); 1551 g_free (data); 1552 return FALSE; 1553 } 1554 1555 static void 1556 rhythmdb_add_timeout_commit (RhythmDB *db, 1557 gboolean sync) 1558 { 1559 RhythmDBTimeoutCommitData *data; 1560 1561 g_assert (rb_is_main_thread ()); 1562 1563 data = g_new0 (RhythmDBTimeoutCommitData, 1); 1564 data->db = g_object_ref (db); 1565 data->sync = sync; 1566 data->thread = g_thread_self (); 1567 g_timeout_add (100, (GSourceFunc)timeout_rhythmdb_commit, data); 1568 } 1569 1570 /** 1571 * rhythmdb_commit: 1572 * @db: a #RhythmDB. 1573 * 1574 * Apply all database changes, and send notification of changes and new entries. 1575 * This needs to be called after any changes have been made, such as a group of 1576 * rhythmdb_entry_set() calls, or a new entry has been added. 1577 */ 1578 void 1579 rhythmdb_commit (RhythmDB *db) 1580 { 1581 rhythmdb_commit_internal (db, TRUE, g_thread_self ()); 1582 } 1583 1584 /** 1585 * rhythmdb_error_quark: 1586 * 1587 * Returns the #GQuark used for #RhythmDBError information 1588 * 1589 * Return value: error quark 1590 */ 1591 GQuark 1592 rhythmdb_error_quark (void) 1593 { 1594 static GQuark quark; 1595 if (!quark) 1596 quark = g_quark_from_static_string ("rhythmdb_error"); 1597 1598 return quark; 1599 } 1600 1601 /* structure alignment magic, stolen from glib */ 1602 #define STRUCT_ALIGNMENT (2 * sizeof (gsize)) 1603 #define ALIGN_STRUCT(offset) \ 1604 ((offset + (STRUCT_ALIGNMENT - 1)) & -STRUCT_ALIGNMENT) 1605 1606 /** 1607 * rhythmdb_entry_allocate: 1608 * @db: a #RhythmDB. 1609 * @type: type of entry to allocate 1610 * 1611 * Allocate and initialise memory for a new #RhythmDBEntry of the type @type. 1612 * The entry's initial properties needs to be set with rhythmdb_entry_set (), 1613 * the entry added to the database with rhythmdb_entry_insert(), and committed with 1614 * rhythmdb_commit(). 1615 * 1616 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree). 1617 * 1618 * Returns: the newly allocated #RhythmDBEntry 1619 */ 1620 RhythmDBEntry * 1621 rhythmdb_entry_allocate (RhythmDB *db, 1622 RhythmDBEntryType *type) 1623 { 1624 RhythmDBEntry *ret; 1625 guint type_data_size = 0; 1626 gsize size = sizeof (RhythmDBEntry); 1627 1628 g_object_get (type, "type-data-size", &type_data_size, NULL); 1629 if (type_data_size > 0) { 1630 size = ALIGN_STRUCT (sizeof (RhythmDBEntry)) + type_data_size; 1631 } 1632 ret = g_malloc0 (size); 1633 ret->id = (guint) g_atomic_int_add (&db->priv->next_entry_id, 1); 1634 1635 ret->type = type; 1636 ret->title = rb_refstring_ref (db->priv->empty_string); 1637 ret->genre = rb_refstring_ref (db->priv->empty_string); 1638 ret->artist = rb_refstring_ref (db->priv->empty_string); 1639 ret->album = rb_refstring_ref (db->priv->empty_string); 1640 ret->comment = rb_refstring_ref (db->priv->empty_string); 1641 ret->album_artist = rb_refstring_ref (db->priv->empty_string); 1642 ret->musicbrainz_trackid = rb_refstring_ref (db->priv->empty_string); 1643 ret->musicbrainz_artistid = rb_refstring_ref (db->priv->empty_string); 1644 ret->musicbrainz_albumid = rb_refstring_ref (db->priv->empty_string); 1645 ret->musicbrainz_albumartistid = rb_refstring_ref (db->priv->empty_string); 1646 ret->artist_sortname = rb_refstring_ref (db->priv->empty_string); 1647 ret->album_sortname = rb_refstring_ref (db->priv->empty_string); 1648 ret->album_artist_sortname = rb_refstring_ref (db->priv->empty_string); 1649 ret->media_type = rb_refstring_ref (db->priv->octet_stream_str); 1650 1651 ret->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY | 1652 RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY | 1653 RHYTHMDB_ENTRY_LAST_SEEN_DIRTY; 1654 1655 /* The refcount is initially 0, we want to set it to 1 */ 1656 ret->refcount = 1; 1657 1658 rhythmdb_entry_created (ret); 1659 1660 return ret; 1661 } 1662 1663 /** 1664 * rhythmdb_entry_get_type_data: 1665 * @entry: a #RhythmDBEntry 1666 * @expected_size: expected size of the type-specific data. 1667 * 1668 * Retrieves a pointer to the entry's type-specific data, checking that 1669 * the size of the data structure matches what is expected. 1670 * Callers should use the RHYTHMDB_ENTRY_GET_TYPE_DATA macro for 1671 * a slightly more friendly interface to this functionality. 1672 * 1673 * Return value: (transfer none): type-specific data pointer 1674 */ 1675 gpointer 1676 rhythmdb_entry_get_type_data (RhythmDBEntry *entry, 1677 guint expected_size) 1678 { 1679 g_return_val_if_fail (entry != NULL, NULL); 1680 int type_data_size = 0; 1681 gsize offset; 1682 1683 g_object_get (entry->type, "type-data-size", &type_data_size, NULL); 1684 1685 g_assert (expected_size == type_data_size); 1686 offset = ALIGN_STRUCT (sizeof (RhythmDBEntry)); 1687 1688 return (gpointer) (((guint8 *)entry) + offset); 1689 } 1690 1691 /** 1692 * rhythmdb_entry_insert: 1693 * @db: a #RhythmDB. 1694 * @entry: the entry to insert. 1695 * 1696 * Inserts a newly-created entry into the database. 1697 * 1698 * Note that you must call rhythmdb_commit() at some point after invoking 1699 * this function. 1700 */ 1701 void 1702 rhythmdb_entry_insert (RhythmDB *db, 1703 RhythmDBEntry *entry) 1704 { 1705 g_return_if_fail (RHYTHMDB_IS (db)); 1706 g_return_if_fail (entry != NULL); 1707 1708 g_assert ((entry->flags & RHYTHMDB_ENTRY_INSERTED) == 0); 1709 g_return_if_fail (entry->location != NULL); 1710 1711 /* ref the entry before adding to hash, it is unreffed when removed */ 1712 rhythmdb_entry_ref (entry); 1713 g_mutex_lock (&db->priv->change_mutex); 1714 g_hash_table_insert (db->priv->added_entries, entry, g_thread_self ()); 1715 g_mutex_unlock (&db->priv->change_mutex); 1716 } 1717 1718 /** 1719 * rhythmdb_entry_new: 1720 * @db: a #RhythmDB. 1721 * @type: type of entry to create 1722 * @uri: the location of the entry, this be unique amongst all entries. 1723 * 1724 * Creates a new entry of type @type and location @uri, and inserts 1725 * it into the database. You must call rhythmdb_commit() at some point 1726 * after invoking this function. 1727 * 1728 * This may return NULL if entry creation fails. This can occur if there is 1729 * already an entry with the given uri. 1730 * 1731 * Returns: the newly created #RhythmDBEntry 1732 */ 1733 RhythmDBEntry * 1734 rhythmdb_entry_new (RhythmDB *db, 1735 RhythmDBEntryType *type, 1736 const char *uri) 1737 { 1738 RhythmDBEntry *ret; 1739 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 1740 1741 ret = rhythmdb_entry_lookup_by_location (db, uri); 1742 if (ret) { 1743 g_warning ("attempting to create entry that already exists: %s", uri); 1744 return NULL; 1745 } 1746 1747 ret = rhythmdb_entry_allocate (db, type); 1748 ret->location = rb_refstring_new (uri); 1749 klass->impl_entry_new (db, ret); 1750 rb_debug ("emitting entry added"); 1751 rhythmdb_entry_insert (db, ret); 1752 1753 return ret; 1754 } 1755 1756 /** 1757 * rhythmdb_entry_example_new: 1758 * @db: a #RhythmDB. 1759 * @type: type of entry to create 1760 * @uri: the location of the entry, this be unique amongst all entries. 1761 * 1762 * Creates a new sample entry of type @type and location @uri, it does not insert 1763 * it into the database. This is indended for use as a example entry. 1764 * 1765 * This may return NULL if entry creation fails. 1766 * 1767 * Returns: the newly created #RhythmDBEntry 1768 */ 1769 RhythmDBEntry * 1770 rhythmdb_entry_example_new (RhythmDB *db, 1771 RhythmDBEntryType *type, 1772 const char *uri) 1773 { 1774 RhythmDBEntry *ret; 1775 1776 ret = rhythmdb_entry_allocate (db, type); 1777 if (uri) 1778 ret->location = rb_refstring_new (uri); 1779 1780 if (type == RHYTHMDB_ENTRY_TYPE_SONG) { 1781 rb_refstring_unref (ret->artist); 1782 /* Translators: this is an example artist name. It should 1783 * not be translated literally, but could be replaced with 1784 * a local artist name if desired. Ensure the album name 1785 * and song title are also replaced in this case. 1786 */ 1787 ret->artist = rb_refstring_new (_("The Beatles")); 1788 rb_refstring_unref (ret->album); 1789 /* Translators: this is an example album name. If the 1790 * example artist name is localised, this should be replaced 1791 * with the name of an album by that artist. 1792 */ 1793 ret->album = rb_refstring_new (_("Help!")); 1794 rb_refstring_unref (ret->title); 1795 /* Translators: this is an example song title. If the example 1796 * artist and album names are localised, this should be replaced 1797 * with the name of the seventh song from the localised album. 1798 */ 1799 ret->title = rb_refstring_new (_("Ticket To Ride")); 1800 ret->tracknum = 7; 1801 } else { 1802 } 1803 1804 return ret; 1805 } 1806 1807 /** 1808 * rhythmdb_entry_ref: 1809 * @entry: a #RhythmDBEntry. 1810 * 1811 * Increase the reference count of the entry. 1812 * 1813 * Returns: the entry 1814 */ 1815 RhythmDBEntry * 1816 rhythmdb_entry_ref (RhythmDBEntry *entry) 1817 { 1818 g_return_val_if_fail (entry != NULL, NULL); 1819 g_return_val_if_fail (entry->refcount > 0, NULL); 1820 1821 g_atomic_int_inc (&entry->refcount); 1822 1823 return entry; 1824 } 1825 1826 static void 1827 rhythmdb_entry_finalize (RhythmDBEntry *entry) 1828 { 1829 rhythmdb_entry_pre_destroy (entry); 1830 1831 rb_refstring_unref (entry->location); 1832 rb_refstring_unref (entry->playback_error); 1833 rb_refstring_unref (entry->title); 1834 rb_refstring_unref (entry->genre); 1835 rb_refstring_unref (entry->artist); 1836 rb_refstring_unref (entry->album); 1837 rb_refstring_unref (entry->comment); 1838 rb_refstring_unref (entry->musicbrainz_trackid); 1839 rb_refstring_unref (entry->musicbrainz_artistid); 1840 rb_refstring_unref (entry->musicbrainz_albumid); 1841 rb_refstring_unref (entry->musicbrainz_albumartistid); 1842 rb_refstring_unref (entry->artist_sortname); 1843 rb_refstring_unref (entry->album_sortname); 1844 rb_refstring_unref (entry->media_type); 1845 1846 g_free (entry); 1847 } 1848 1849 /** 1850 * rhythmdb_entry_unref: 1851 * @entry: a #RhythmDBEntry. 1852 * 1853 * Decrease the reference count of the entry, and destroys it if there are 1854 * no references left. 1855 */ 1856 void 1857 rhythmdb_entry_unref (RhythmDBEntry *entry) 1858 { 1859 gboolean is_zero; 1860 1861 g_return_if_fail (entry != NULL); 1862 g_return_if_fail (entry->refcount > 0); 1863 1864 is_zero = g_atomic_int_dec_and_test (&entry->refcount); 1865 if (G_UNLIKELY (is_zero)) { 1866 rhythmdb_entry_finalize (entry); 1867 } 1868 } 1869 1870 static void 1871 set_metadata_string_with_default (RhythmDB *db, 1872 RBMetaData *metadata, 1873 RhythmDBEntry *entry, 1874 RBMetaDataField field, 1875 RhythmDBPropType prop, 1876 const char *default_value) 1877 { 1878 GValue val = {0, }; 1879 1880 if (!(rb_metadata_get (metadata, 1881 field, 1882 &val))) { 1883 g_value_init (&val, G_TYPE_STRING); 1884 g_value_set_static_string (&val, default_value); 1885 } else { 1886 const gchar *str = g_value_get_string (&val); 1887 if (str == NULL || str[0] == '\0') 1888 g_value_set_static_string (&val, default_value); 1889 } 1890 rhythmdb_entry_set_internal (db, entry, TRUE, prop, &val); 1891 g_value_unset (&val); 1892 } 1893 1894 static void 1895 set_props_from_metadata (RhythmDB *db, 1896 RhythmDBEntry *entry, 1897 GFileInfo *fileinfo, 1898 RBMetaData *metadata) 1899 { 1900 const char *media_type; 1901 GValue val = {0,}; 1902 1903 g_value_init (&val, G_TYPE_STRING); 1904 media_type = rb_metadata_get_media_type (metadata); 1905 if (media_type) { 1906 g_value_set_string (&val, media_type); 1907 rhythmdb_entry_set_internal (db, entry, TRUE, 1908 RHYTHMDB_PROP_MEDIA_TYPE, &val); 1909 } 1910 g_value_unset (&val); 1911 1912 /* track number */ 1913 if (!rb_metadata_get (metadata, 1914 RB_METADATA_FIELD_TRACK_NUMBER, 1915 &val)) { 1916 g_value_init (&val, G_TYPE_ULONG); 1917 g_value_set_ulong (&val, 0); 1918 } 1919 rhythmdb_entry_set_internal (db, entry, TRUE, 1920 RHYTHMDB_PROP_TRACK_NUMBER, &val); 1921 g_value_unset (&val); 1922 1923 /* disc number */ 1924 if (!rb_metadata_get (metadata, 1925 RB_METADATA_FIELD_DISC_NUMBER, 1926 &val)) { 1927 g_value_init (&val, G_TYPE_ULONG); 1928 g_value_set_ulong (&val, 0); 1929 } 1930 rhythmdb_entry_set_internal (db, entry, TRUE, 1931 RHYTHMDB_PROP_DISC_NUMBER, &val); 1932 g_value_unset (&val); 1933 1934 /* duration */ 1935 if (rb_metadata_get (metadata, 1936 RB_METADATA_FIELD_DURATION, 1937 &val)) { 1938 rhythmdb_entry_set_internal (db, entry, TRUE, 1939 RHYTHMDB_PROP_DURATION, &val); 1940 g_value_unset (&val); 1941 } 1942 1943 /* bitrate */ 1944 if (rb_metadata_get (metadata, 1945 RB_METADATA_FIELD_BITRATE, 1946 &val)) { 1947 rhythmdb_entry_set_internal (db, entry, TRUE, 1948 RHYTHMDB_PROP_BITRATE, &val); 1949 g_value_unset (&val); 1950 } 1951 1952 /* date */ 1953 if (rb_metadata_get (metadata, 1954 RB_METADATA_FIELD_DATE, 1955 &val)) { 1956 rhythmdb_entry_set_internal (db, entry, TRUE, 1957 RHYTHMDB_PROP_DATE, &val); 1958 g_value_unset (&val); 1959 } 1960 1961 /* musicbrainz trackid */ 1962 set_metadata_string_with_default (db, metadata, entry, 1963 RB_METADATA_FIELD_MUSICBRAINZ_TRACKID, 1964 RHYTHMDB_PROP_MUSICBRAINZ_TRACKID, 1965 ""); 1966 1967 /* musicbrainz artistid */ 1968 set_metadata_string_with_default (db, metadata, entry, 1969 RB_METADATA_FIELD_MUSICBRAINZ_ARTISTID, 1970 RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID, 1971 ""); 1972 1973 /* musicbrainz albumid */ 1974 set_metadata_string_with_default (db, metadata, entry, 1975 RB_METADATA_FIELD_MUSICBRAINZ_ALBUMID, 1976 RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID, 1977 ""); 1978 1979 /* musicbrainz albumartistid */ 1980 set_metadata_string_with_default (db, metadata, entry, 1981 RB_METADATA_FIELD_MUSICBRAINZ_ALBUMARTISTID, 1982 RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID, 1983 ""); 1984 1985 /* filesize */ 1986 g_value_init (&val, G_TYPE_UINT64); 1987 g_value_set_uint64 (&val, g_file_info_get_attribute_uint64 (fileinfo, G_FILE_ATTRIBUTE_STANDARD_SIZE)); 1988 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_FILE_SIZE, &val); 1989 g_value_unset (&val); 1990 1991 /* title */ 1992 if (!rb_metadata_get (metadata, 1993 RB_METADATA_FIELD_TITLE, 1994 &val) || g_value_get_string (&val)[0] == '\0') { 1995 const char *fname; 1996 fname = g_file_info_get_display_name (fileinfo); 1997 if (G_VALUE_HOLDS_STRING (&val)) 1998 g_value_reset (&val); 1999 else 2000 g_value_init (&val, G_TYPE_STRING); 2001 g_value_set_string (&val, fname); 2002 } 2003 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_TITLE, &val); 2004 g_value_unset (&val); 2005 2006 /* genre */ 2007 set_metadata_string_with_default (db, metadata, entry, 2008 RB_METADATA_FIELD_GENRE, 2009 RHYTHMDB_PROP_GENRE, 2010 _("Unknown")); 2011 2012 /* artist */ 2013 set_metadata_string_with_default (db, metadata, entry, 2014 RB_METADATA_FIELD_ARTIST, 2015 RHYTHMDB_PROP_ARTIST, 2016 _("Unknown")); 2017 2018 /* beats per minute */ 2019 if (rb_metadata_get (metadata, 2020 RB_METADATA_FIELD_BPM, 2021 &val)) { 2022 rhythmdb_entry_set_internal (db, entry, TRUE, 2023 RHYTHMDB_PROP_BPM, &val); 2024 g_value_unset (&val); 2025 } 2026 2027 /* album */ 2028 set_metadata_string_with_default (db, metadata, entry, 2029 RB_METADATA_FIELD_ALBUM, 2030 RHYTHMDB_PROP_ALBUM, 2031 _("Unknown")); 2032 /* artist sortname */ 2033 set_metadata_string_with_default (db, metadata, entry, 2034 RB_METADATA_FIELD_ARTIST_SORTNAME, 2035 RHYTHMDB_PROP_ARTIST_SORTNAME, 2036 ""); 2037 2038 /* album sortname */ 2039 set_metadata_string_with_default (db, metadata, entry, 2040 RB_METADATA_FIELD_ALBUM_SORTNAME, 2041 RHYTHMDB_PROP_ALBUM_SORTNAME, 2042 ""); 2043 2044 /* comment */ 2045 set_metadata_string_with_default (db, metadata, entry, 2046 RB_METADATA_FIELD_COMMENT, 2047 RHYTHMDB_PROP_COMMENT, 2048 ""); 2049 /* album artist */ 2050 set_metadata_string_with_default (db, metadata, entry, 2051 RB_METADATA_FIELD_ALBUM_ARTIST, 2052 RHYTHMDB_PROP_ALBUM_ARTIST, 2053 ""); 2054 2055 /* album artist sortname */ 2056 set_metadata_string_with_default (db, metadata, entry, 2057 RB_METADATA_FIELD_ALBUM_ARTIST_SORTNAME, 2058 RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME, 2059 ""); 2060 } 2061 2062 static void 2063 rhythmdb_process_stat_event (RhythmDB *db, 2064 RhythmDBEvent *event) 2065 { 2066 RhythmDBEntry *entry; 2067 RhythmDBAction *action; 2068 GFileType file_type; 2069 2070 if (event->entry != NULL) { 2071 entry = event->entry; 2072 } else { 2073 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri); 2074 } 2075 2076 /* handle errors: 2077 * - if a non-ignore entry exists, process ghostliness (hide/delete) 2078 * - otherwise, create an import error entry? hmm. 2079 */ 2080 if (event->error) { 2081 if (entry != NULL) { 2082 rb_debug ("error accessing %s: %s", 2083 rb_refstring_get (event->real_uri), 2084 event->error->message); 2085 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_NOT_FOUND); 2086 rhythmdb_commit (db); 2087 } 2088 return; 2089 } 2090 2091 g_assert (event->file_info != NULL); 2092 2093 /* figure out what to do based on the file type */ 2094 file_type = g_file_info_get_attribute_uint32 (event->file_info, 2095 G_FILE_ATTRIBUTE_STANDARD_TYPE); 2096 switch (file_type) { 2097 case G_FILE_TYPE_UNKNOWN: 2098 case G_FILE_TYPE_REGULAR: 2099 if (entry != NULL) { 2100 guint64 new_mtime; 2101 guint64 new_size; 2102 2103 /* update the existing entry, as long as the entry type matches */ 2104 if ((event->entry_type != NULL) && 2105 (entry->type != event->entry_type) && 2106 (entry->type != event->ignore_type) && 2107 (entry->type != event->error_type)) { 2108 if (event->entry_type == RHYTHMDB_ENTRY_TYPE_SONG && 2109 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST) { 2110 rb_debug ("Ignoring stat event for '%s', it's already loaded as a podcast", 2111 rb_refstring_get (event->real_uri)); 2112 break; 2113 } 2114 g_warning ("attempt to use location %s in multiple entry types (%s and %s)", 2115 rb_refstring_get (event->real_uri), 2116 rhythmdb_entry_type_get_name (event->entry_type), 2117 rhythmdb_entry_type_get_name (entry->type)); 2118 } 2119 2120 if (entry->type == event->ignore_type) 2121 rb_debug ("ignoring %p", entry); 2122 2123 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_CHECKED); 2124 2125 /* compare modification time and size to the values in the database. 2126 * if either has changed, we'll re-read the file. 2127 */ 2128 new_mtime = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); 2129 new_size = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_STANDARD_SIZE); 2130 if (entry->mtime == new_mtime && (new_size == 0 || entry->file_size == new_size)) { 2131 rb_debug ("not modified: %s", rb_refstring_get (event->real_uri)); 2132 } else { 2133 rb_debug ("changed: %s", rb_refstring_get (event->real_uri)); 2134 action = g_slice_new0 (RhythmDBAction); 2135 action->type = RHYTHMDB_ACTION_LOAD; 2136 action->uri = rb_refstring_ref (event->real_uri); 2137 action->data.types.entry_type = event->entry_type; 2138 action->data.types.ignore_type = event->ignore_type; 2139 action->data.types.error_type = event->error_type; 2140 g_async_queue_push (db->priv->action_queue, action); 2141 } 2142 } else { 2143 /* push a LOAD action */ 2144 action = g_slice_new0 (RhythmDBAction); 2145 action->type = RHYTHMDB_ACTION_LOAD; 2146 action->uri = rb_refstring_ref (event->real_uri); 2147 action->data.types.entry_type = event->entry_type; 2148 action->data.types.ignore_type = event->ignore_type; 2149 action->data.types.error_type = event->error_type; 2150 rb_debug ("queuing a RHYTHMDB_ACTION_LOAD: %s", rb_refstring_get (action->uri)); 2151 g_async_queue_push (db->priv->action_queue, action); 2152 } 2153 break; 2154 2155 case G_FILE_TYPE_DIRECTORY: 2156 rb_debug ("processing directory %s", rb_refstring_get (event->real_uri)); 2157 /* push an ENUM_DIR action */ 2158 action = g_slice_new0 (RhythmDBAction); 2159 action->type = RHYTHMDB_ACTION_ENUM_DIR; 2160 action->uri = rb_refstring_ref (event->real_uri); 2161 action->data.types.entry_type = event->entry_type; 2162 action->data.types.ignore_type = event->ignore_type; 2163 action->data.types.error_type = event->error_type; 2164 rb_debug ("queuing a RHYTHMDB_ACTION_ENUM_DIR: %s", rb_refstring_get (action->uri)); 2165 g_async_queue_push (db->priv->action_queue, action); 2166 break; 2167 2168 case G_FILE_TYPE_SYMBOLIC_LINK: 2169 case G_FILE_TYPE_SHORTCUT: 2170 /* this shouldn't happen, but maybe we should handle it anyway? */ 2171 rb_debug ("ignoring stat results for %s: is link", rb_refstring_get (event->real_uri)); 2172 break; 2173 2174 case G_FILE_TYPE_SPECIAL: 2175 case G_FILE_TYPE_MOUNTABLE: /* hmm. */ 2176 rb_debug ("ignoring stat results for %s: is special", rb_refstring_get (event->real_uri)); 2177 break; 2178 } 2179 2180 rhythmdb_commit (db); 2181 } 2182 2183 typedef struct 2184 { 2185 RhythmDB *db; 2186 char *uri; 2187 char *msg; 2188 } RhythmDBLoadErrorData; 2189 2190 static void 2191 rhythmdb_add_import_error_entry (RhythmDB *db, 2192 RhythmDBEvent *event, 2193 RhythmDBEntryType *error_entry_type) 2194 { 2195 RhythmDBEntry *entry; 2196 GValue value = {0,}; 2197 2198 if (error_entry_type == NULL) { 2199 /* we don't have an error entry type, so we can't add an import error */ 2200 return; 2201 } 2202 rb_debug ("adding import error type %s for %s: %s", 2203 rhythmdb_entry_type_get_name (error_entry_type), 2204 rb_refstring_get (event->real_uri), 2205 event->error ? event->error->message : "<no error>"); 2206 2207 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri); 2208 if (entry) { 2209 RhythmDBEntryType *entry_type = rhythmdb_entry_get_entry_type (entry); 2210 if (entry_type != event->error_type && 2211 entry_type != event->ignore_type) { 2212 /* FIXME we've successfully read this file before.. so what should we do? */ 2213 rb_debug ("%s already exists in the library.. ignoring import error?", rb_refstring_get (event->real_uri)); 2214 return; 2215 } 2216 2217 if (entry_type != error_entry_type) { 2218 /* delete the existing entry, then create a new one below */ 2219 rhythmdb_entry_delete (db, entry); 2220 entry = NULL; 2221 } else if (error_entry_type == event->error_type && event->error) { 2222 /* we've already got an error for this file, so just update it */ 2223 g_value_init (&value, G_TYPE_STRING); 2224 g_value_set_string (&value, event->error->message); 2225 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value); 2226 g_value_unset (&value); 2227 } else { 2228 /* no need to update the ignored file entry */ 2229 } 2230 2231 if (entry && event->file_info) { 2232 /* mtime */ 2233 guint64 new_mtime = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); 2234 g_value_init (&value, G_TYPE_ULONG); 2235 g_value_set_ulong (&value, new_mtime); /* hmm, cast */ 2236 rhythmdb_entry_set(db, entry, RHYTHMDB_PROP_MTIME, &value); 2237 g_value_unset (&value); 2238 } 2239 2240 rhythmdb_add_timeout_commit (db, FALSE); 2241 } 2242 2243 if (entry == NULL) { 2244 /* create a new import error or ignore entry */ 2245 entry = rhythmdb_entry_new (db, error_entry_type, rb_refstring_get (event->real_uri)); 2246 if (entry == NULL) 2247 return; 2248 2249 /* if we have missing plugin details, store them in the 2250 * comment field so we can collect them later, and set a 2251 * suitable error message 2252 */ 2253 if (event->metadata != NULL && rb_metadata_has_missing_plugins (event->metadata)) { 2254 char **missing_plugins; 2255 char **plugin_descriptions; 2256 char *comment; 2257 char *list; 2258 const char *msg; 2259 2260 /* Translators: the parameter here is a list of GStreamer plugins. 2261 * The plugin names are already translated. 2262 */ 2263 msg = _("Additional GStreamer plugins are required to play this file: %s"); 2264 2265 if (rb_metadata_has_audio (event->metadata) == TRUE && 2266 rb_metadata_has_video (event->metadata) == FALSE && 2267 rb_metadata_has_missing_plugins (event->metadata) == TRUE) { 2268 rb_metadata_get_missing_plugins (event->metadata, &missing_plugins, &plugin_descriptions); 2269 comment = g_strjoinv ("\n", missing_plugins); 2270 rb_debug ("storing missing plugin details: %s", comment); 2271 2272 g_value_init (&value, G_TYPE_STRING); 2273 g_value_take_string (&value, comment); 2274 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_COMMENT, &value); 2275 g_value_unset (&value); 2276 2277 g_value_init (&value, G_TYPE_STRING); 2278 list = g_strjoinv (", ", plugin_descriptions); 2279 g_value_take_string (&value, g_strdup_printf (msg, list)); 2280 g_free (list); 2281 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value); 2282 g_value_unset (&value); 2283 2284 g_strfreev (missing_plugins); 2285 g_strfreev (plugin_descriptions); 2286 2287 } else if (rb_metadata_has_missing_plugins (event->metadata)) { 2288 rb_debug ("ignoring missing plugins for non-audio file"); 2289 } 2290 } else if (error_entry_type == event->error_type && event->error && event->error->message) { 2291 g_value_init (&value, G_TYPE_STRING); 2292 if (g_utf8_validate (event->error->message, -1, NULL)) 2293 g_value_set_string (&value, event->error->message); 2294 else 2295 g_value_set_static_string (&value, _("invalid unicode in error message")); 2296 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value); 2297 g_value_unset (&value); 2298 } 2299 2300 /* mtime */ 2301 if (event->file_info) { 2302 guint64 new_mtime = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); 2303 g_value_init (&value, G_TYPE_ULONG); 2304 g_value_set_ulong (&value, new_mtime); /* hmm, cast */ 2305 rhythmdb_entry_set(db, entry, RHYTHMDB_PROP_MTIME, &value); 2306 g_value_unset (&value); 2307 } 2308 2309 /* record the mount point so we can delete entries for unmounted volumes */ 2310 rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri)); 2311 2312 rhythmdb_entry_set_visibility (db, entry, TRUE); 2313 2314 rhythmdb_add_timeout_commit (db, FALSE); 2315 } 2316 } 2317 2318 static gboolean 2319 rhythmdb_process_metadata_load (RhythmDB *db, RhythmDBEvent *event) 2320 { 2321 RhythmDBEntry *entry; 2322 GValue value = {0,}; 2323 GTimeVal time; 2324 gboolean monitor; 2325 2326 if (event->entry_type == NULL) 2327 event->entry_type = RHYTHMDB_ENTRY_TYPE_SONG; 2328 2329 if (event->metadata != NULL) { 2330 /* always ignore anything with video in it */ 2331 if (rb_metadata_has_video (event->metadata)) { 2332 rhythmdb_add_import_error_entry (db, event, event->ignore_type); 2333 return TRUE; 2334 } 2335 2336 /* if we identified the media type, we can ignore anything 2337 * that matches one of the media types we don't care about, 2338 * as well as anything that doesn't contain audio. 2339 */ 2340 const char *media_type = rb_metadata_get_media_type (event->metadata); 2341 if (media_type != NULL && media_type[0] != '\0') { 2342 if (rhythmdb_ignore_media_type (media_type) || 2343 rb_metadata_has_audio (event->metadata) == FALSE) { 2344 rhythmdb_add_import_error_entry (db, event, event->ignore_type); 2345 return TRUE; 2346 } 2347 } 2348 } 2349 2350 /* also ignore really small files we can't identify */ 2351 if (event->error && event->error->code == RB_METADATA_ERROR_UNRECOGNIZED) { 2352 guint64 file_size; 2353 2354 file_size = g_file_info_get_attribute_uint64 (event->file_info, 2355 G_FILE_ATTRIBUTE_STANDARD_SIZE); 2356 if (file_size == 0) { 2357 /* except for empty files */ 2358 g_clear_error (&event->error); 2359 g_set_error (&event->error, 2360 RB_METADATA_ERROR, 2361 RB_METADATA_ERROR_EMPTY_FILE, 2362 _("Empty file")); 2363 } else if (file_size < REALLY_SMALL_FILE_SIZE) { 2364 rhythmdb_add_import_error_entry (db, event, event->ignore_type); 2365 return TRUE; 2366 } 2367 } 2368 2369 if (event->error) { 2370 rhythmdb_add_import_error_entry (db, event, event->error_type); 2371 return TRUE; 2372 } 2373 2374 g_get_current_time (&time); 2375 2376 entry = rhythmdb_entry_lookup_by_location_refstring (db, event->real_uri); 2377 2378 if (entry != NULL) { 2379 RhythmDBEntryType *etype; 2380 etype = rhythmdb_entry_get_entry_type (entry); 2381 if (etype == event->error_type || etype == event->ignore_type) { 2382 /* switching from IGNORE/ERROR to SONG, recreate the entry */ 2383 rhythmdb_entry_delete (db, entry); 2384 rhythmdb_add_timeout_commit (db, FALSE); 2385 entry = NULL; 2386 } 2387 } 2388 2389 if (entry == NULL) { 2390 2391 entry = rhythmdb_entry_new (db, event->entry_type, rb_refstring_get (event->real_uri)); 2392 if (entry == NULL) { 2393 rb_debug ("entry already exists"); 2394 return TRUE; 2395 } 2396 2397 /* initialize the last played date to 0=never */ 2398 g_value_init (&value, G_TYPE_ULONG); 2399 g_value_set_ulong (&value, 0); 2400 rhythmdb_entry_set (db, entry, 2401 RHYTHMDB_PROP_LAST_PLAYED, &value); 2402 g_value_unset (&value); 2403 2404 /* initialize the rating */ 2405 g_value_init (&value, G_TYPE_DOUBLE); 2406 g_value_set_double (&value, 0); 2407 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_RATING, &value); 2408 g_value_unset (&value); 2409 2410 /* first seen */ 2411 g_value_init (&value, G_TYPE_ULONG); 2412 g_value_set_ulong (&value, time.tv_sec); 2413 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_FIRST_SEEN, &value); 2414 g_value_unset (&value); 2415 } 2416 2417 if ((event->entry_type != NULL) && (entry->type != event->entry_type)) { 2418 g_warning ("attempt to use same location in multiple entry types"); 2419 return TRUE; 2420 } 2421 2422 /* mtime */ 2423 if (event->file_info) { 2424 guint64 mtime; 2425 2426 mtime = g_file_info_get_attribute_uint64 (event->file_info, G_FILE_ATTRIBUTE_TIME_MODIFIED); 2427 2428 g_value_init (&value, G_TYPE_ULONG); 2429 g_value_set_ulong (&value, (gulong)mtime); 2430 rhythmdb_entry_set_internal (db, entry, TRUE, RHYTHMDB_PROP_MTIME, &value); 2431 g_value_unset (&value); 2432 } 2433 2434 if (event->entry_type != event->ignore_type && 2435 event->entry_type != event->error_type) { 2436 set_props_from_metadata (db, entry, event->file_info, event->metadata); 2437 } 2438 2439 rhythmdb_entry_update_availability (entry, RHYTHMDB_ENTRY_AVAIL_CHECKED); 2440 2441 /* Remember the mount point of the volume the song is on */ 2442 rhythmdb_entry_set_mount_point (db, entry, rb_refstring_get (event->real_uri)); 2443 2444 /* monitor the file for changes */ 2445 /* FIXME: watch for errors */ 2446 monitor = g_settings_get_boolean (db->priv->settings, "monitor-library"); 2447 if (monitor && event->entry_type == RHYTHMDB_ENTRY_TYPE_SONG) 2448 rhythmdb_monitor_uri_path (db, rb_refstring_get (entry->location), NULL); 2449 2450 rhythmdb_commit_internal (db, FALSE, g_thread_self ()); 2451 2452 return TRUE; 2453 } 2454 2455 static void 2456 rhythmdb_process_queued_entry_set_event (RhythmDB *db, 2457 RhythmDBEvent *event) 2458 { 2459 rhythmdb_entry_set_internal (db, event->entry, 2460 event->signal_change, 2461 event->change.prop, 2462 &event->change.new); 2463 /* Don't run rhythmdb_commit right now in case there 2464 * we can run a single commit for several queued 2465 * entry_set 2466 */ 2467 rhythmdb_add_timeout_commit (db, TRUE); 2468 } 2469 2470 static void 2471 rhythmdb_process_one_event (RhythmDBEvent *event, RhythmDB *db) 2472 { 2473 gboolean free = TRUE; 2474 2475 /* if the database is read-only, we can't process those events 2476 * since they call rhythmdb_entry_set. Doing it this way 2477 * is safe if we assume all calls to read_enter/read_leave 2478 * are done from the main thread (the thread this function 2479 * runs in). 2480 */ 2481 if (rhythmdb_get_readonly (db) && 2482 ((event->type == RHYTHMDB_EVENT_STAT) 2483 || (event->type == RHYTHMDB_EVENT_METADATA_LOAD) 2484 || (event->type == RHYTHMDB_EVENT_ENTRY_SET))) { 2485 rb_debug ("Database is read-only, delaying event processing"); 2486 g_async_queue_push (db->priv->delayed_write_queue, event); 2487 return; 2488 } 2489 2490 switch (event->type) { 2491 case RHYTHMDB_EVENT_STAT: 2492 rb_debug ("processing RHYTHMDB_EVENT_STAT"); 2493 rhythmdb_process_stat_event (db, event); 2494 break; 2495 case RHYTHMDB_EVENT_METADATA_LOAD: 2496 rb_debug ("processing RHYTHMDB_EVENT_METADATA_LOAD"); 2497 free = rhythmdb_process_metadata_load (db, event); 2498 break; 2499 case RHYTHMDB_EVENT_ENTRY_SET: 2500 rb_debug ("processing RHYTHMDB_EVENT_ENTRY_SET"); 2501 rhythmdb_process_queued_entry_set_event (db, event); 2502 break; 2503 case RHYTHMDB_EVENT_DB_LOAD: 2504 rb_debug ("processing RHYTHMDB_EVENT_DB_LOAD"); 2505 g_signal_emit (G_OBJECT (db), rhythmdb_signals[LOAD_COMPLETE], 0); 2506 2507 /* save the db every five minutes */ 2508 if (db->priv->save_timeout_id > 0) { 2509 g_source_remove (db->priv->save_timeout_id); 2510 } 2511 db->priv->save_timeout_id = g_timeout_add_seconds_full (G_PRIORITY_LOW, 2512 5 * 60, 2513 (GSourceFunc) rhythmdb_idle_save, 2514 db, 2515 NULL); 2516 break; 2517 case RHYTHMDB_EVENT_THREAD_EXITED: 2518 rb_debug ("processing RHYTHMDB_EVENT_THREAD_EXITED"); 2519 break; 2520 case RHYTHMDB_EVENT_DB_SAVED: 2521 rb_debug ("processing RHYTHMDB_EVENT_DB_SAVED"); 2522 rhythmdb_read_leave (db); 2523 break; 2524 case RHYTHMDB_EVENT_QUERY_COMPLETE: 2525 rb_debug ("processing RHYTHMDB_EVENT_QUERY_COMPLETE"); 2526 rhythmdb_read_leave (db); 2527 break; 2528 } 2529 if (free) 2530 rhythmdb_event_free (db, event); 2531 } 2532 2533 2534 static void 2535 rhythmdb_file_info_query (RhythmDB *db, GFile *file, RhythmDBEvent *event) 2536 { 2537 event->file_info = g_file_query_info (file, 2538 RHYTHMDB_FILE_INFO_ATTRIBUTES, 2539 G_FILE_QUERY_INFO_NONE, 2540 db->priv->exiting, 2541 &event->error); 2542 } 2543 2544 static void 2545 wrap_access_failed_error (RhythmDBEvent *event) 2546 { 2547 GError *wrapped; 2548 2549 wrapped = make_access_failed_error (rb_refstring_get (event->real_uri), event->error); 2550 g_error_free (event->error); 2551 event->error = wrapped; 2552 } 2553 2554 static void 2555 rhythmdb_execute_stat_mount_ready_cb (GObject *source, GAsyncResult *result, RhythmDBEvent *event) 2556 { 2557 GError *error = NULL; 2558 2559 g_file_mount_enclosing_volume_finish (G_FILE (source), result, &error); 2560 if (error != NULL) { 2561 event->error = make_access_failed_error (rb_refstring_get (event->real_uri), error); 2562 g_error_free (error); 2563 2564 g_object_unref (event->file_info); 2565 event->file_info = NULL; 2566 } else { 2567 rhythmdb_file_info_query (event->db, G_FILE (source), event); 2568 } 2569 2570 g_mutex_lock (&event->db->priv->stat_mutex); 2571 event->db->priv->outstanding_stats = g_list_remove (event->db->priv->outstanding_stats, event); 2572 g_mutex_unlock (&event->db->priv->stat_mutex); 2573 2574 g_object_unref (source); 2575 rhythmdb_push_event (event->db, event); 2576 } 2577 2578 2579 static void 2580 rhythmdb_execute_stat (RhythmDB *db, 2581 const char *uri, 2582 RhythmDBEvent *event) 2583 { 2584 GFile *file; 2585 2586 event->real_uri = rb_refstring_new (uri); 2587 file = g_file_new_for_uri (uri); 2588 2589 g_mutex_lock (&db->priv->stat_mutex); 2590 db->priv->outstanding_stats = g_list_prepend (db->priv->outstanding_stats, event); 2591 g_mutex_unlock (&db->priv->stat_mutex); 2592 2593 rhythmdb_file_info_query (db, file, event); 2594 2595 if (event->error != NULL) { 2596 /* if we can't get at it because the location isn't mounted, mount it and try again */ 2597 if (g_error_matches (event->error, G_IO_ERROR, G_IO_ERROR_NOT_MOUNTED)) { 2598 GMountOperation *mount_op = NULL; 2599 2600 g_error_free (event->error); 2601 event->error = NULL; 2602 2603 g_signal_emit (G_OBJECT (event->db), rhythmdb_signals[CREATE_MOUNT_OP], 0, &mount_op); 2604 if (mount_op != NULL) { 2605 g_file_mount_enclosing_volume (file, 2606 G_MOUNT_MOUNT_NONE, 2607 mount_op, 2608 event->db->priv->exiting, 2609 (GAsyncReadyCallback)rhythmdb_execute_stat_mount_ready_cb, 2610 event); 2611 return; 2612 } 2613 } 2614 2615 /* if it's some other error, or we couldn't attempt to mount the location, report the error */ 2616 wrap_access_failed_error (event); 2617 2618 if (event->file_info != NULL) { 2619 g_object_unref (event->file_info); 2620 event->file_info = NULL; 2621 } 2622 } 2623 2624 /* either way, we're done now */ 2625 2626 g_mutex_lock (&event->db->priv->stat_mutex); 2627 event->db->priv->outstanding_stats = g_list_remove (event->db->priv->outstanding_stats, event); 2628 g_mutex_unlock (&event->db->priv->stat_mutex); 2629 2630 rhythmdb_push_event (event->db, event); 2631 g_object_unref (file); 2632 } 2633 2634 static void 2635 rhythmdb_execute_load (RhythmDB *db, 2636 const char *uri, 2637 RhythmDBEvent *event) 2638 { 2639 GError *error = NULL; 2640 char *resolved; 2641 2642 resolved = rb_uri_resolve_symlink (uri, &error); 2643 if (resolved != NULL) { 2644 GFile *file; 2645 2646 file = g_file_new_for_uri (uri); 2647 event->file_info = g_file_query_info (file, 2648 RHYTHMDB_FILE_INFO_ATTRIBUTES, 2649 G_FILE_QUERY_INFO_NONE, 2650 NULL, 2651 &error); 2652 event->real_uri = rb_refstring_new (resolved); 2653 2654 g_free (resolved); 2655 g_object_unref (file); 2656 } else { 2657 event->real_uri = rb_refstring_new (uri); 2658 } 2659 2660 if (error != NULL) { 2661 event->error = make_access_failed_error (uri, error); 2662 if (event->file_info) { 2663 g_object_unref (event->file_info); 2664 event->file_info = NULL; 2665 } 2666 } else if (event->type == RHYTHMDB_EVENT_METADATA_LOAD) { 2667 event->metadata = rb_metadata_new (); 2668 rb_metadata_load (event->metadata, 2669 rb_refstring_get (event->real_uri), 2670 &event->error); 2671 } 2672 2673 rhythmdb_push_event (db, event); 2674 } 2675 2676 static void 2677 rhythmdb_execute_enum_dir (RhythmDB *db, 2678 RhythmDBAction *action) 2679 { 2680 GFile *dir; 2681 GFileEnumerator *dir_enum; 2682 GError *error = NULL; 2683 2684 dir = g_file_new_for_uri (rb_refstring_get (action->uri)); 2685 dir_enum = g_file_enumerate_children (dir, 2686 RHYTHMDB_FILE_CHILD_INFO_ATTRIBUTES, 2687 G_FILE_QUERY_INFO_NONE, 2688 db->priv->exiting, 2689 &error); 2690 if (error != NULL) { 2691 /* don't need to worry about mounting here, as the mount should have 2692 * occurred on the stat. 2693 */ 2694 2695 /* um.. what now? */ 2696 rb_debug ("unable to enumerate children of %s: %s", 2697 rb_refstring_get (action->uri), 2698 error->message); 2699 g_error_free (error); 2700 g_object_unref (dir); 2701 return; 2702 } 2703 2704 while (1) { 2705 RhythmDBEvent *result; 2706 GFileInfo *file_info; 2707 GFile *child; 2708 char *child_uri; 2709 2710 file_info = g_file_enumerator_next_file (dir_enum, db->priv->exiting, &error); 2711 if (file_info == NULL) { 2712 if (error == NULL) { 2713 /* done */ 2714 break; 2715 } 2716 2717 g_warning ("error getting next file: %s", error->message); 2718 g_clear_error (&error); 2719 continue; 2720 } 2721 2722 if (g_file_info_get_attribute_boolean (file_info, G_FILE_ATTRIBUTE_STANDARD_IS_HIDDEN)) { 2723 rb_debug ("ignoring hidden file %s", g_file_info_get_name (file_info)); 2724 g_object_unref (file_info); 2725 continue; 2726 } 2727 2728 child = g_file_get_child (dir, g_file_info_get_name (file_info)); 2729 child_uri = g_file_get_uri (child); 2730 2731 result = g_slice_new0 (RhythmDBEvent); 2732 result->db = db; 2733 result->type = RHYTHMDB_EVENT_STAT; 2734 result->entry_type = action->data.types.entry_type; 2735 result->error_type = action->data.types.error_type; 2736 result->ignore_type = action->data.types.ignore_type; 2737 result->real_uri = rb_refstring_new (child_uri); 2738 result->file_info = file_info; 2739 result->error = error; 2740 2741 rhythmdb_push_event (db, result); 2742 g_free (child_uri); 2743 } 2744 2745 g_file_enumerator_close (dir_enum, db->priv->exiting, &error); 2746 if (error != NULL) { 2747 /* hmm.. */ 2748 rb_debug ("error closing file enumerator: %s", error->message); 2749 g_error_free (error); 2750 } 2751 2752 g_object_unref (dir); 2753 g_object_unref (dir_enum); 2754 } 2755 2756 /** 2757 * rhythmdb_entry_get: 2758 * @db: the #RhythmDB 2759 * @entry: a #RhythmDBEntry. 2760 * @propid: the id of the property to get. 2761 * @val: return location for the property value. 2762 * 2763 * Gets a property of an entry, storing it in the given #GValue. 2764 */ 2765 void 2766 rhythmdb_entry_get (RhythmDB *db, 2767 RhythmDBEntry *entry, 2768 RhythmDBPropType propid, 2769 GValue *val) 2770 { 2771 g_return_if_fail (RHYTHMDB_IS (db)); 2772 g_return_if_fail (entry != NULL); 2773 g_return_if_fail (entry->refcount > 0); 2774 2775 rhythmdb_entry_sync_mirrored (entry, propid); 2776 2777 g_assert (G_VALUE_TYPE (val) == rhythmdb_get_property_type (db, propid)); 2778 switch (rhythmdb_properties[propid].prop_type) { 2779 case G_TYPE_STRING: 2780 g_value_set_string (val, rhythmdb_entry_get_string (entry, propid)); 2781 break; 2782 case G_TYPE_BOOLEAN: 2783 g_value_set_boolean (val, rhythmdb_entry_get_boolean (entry, propid)); 2784 break; 2785 case G_TYPE_ULONG: 2786 g_value_set_ulong (val, rhythmdb_entry_get_ulong (entry, propid)); 2787 break; 2788 case G_TYPE_UINT64: 2789 g_value_set_uint64 (val, rhythmdb_entry_get_uint64 (entry, propid)); 2790 break; 2791 case G_TYPE_DOUBLE: 2792 g_value_set_double (val, rhythmdb_entry_get_double (entry, propid)); 2793 break; 2794 case G_TYPE_OBJECT: 2795 g_value_set_object (val, rhythmdb_entry_get_object (entry, propid)); 2796 break; 2797 default: 2798 g_assert_not_reached (); 2799 break; 2800 } 2801 } 2802 2803 typedef struct 2804 { 2805 RhythmDB *db; 2806 char *uri; 2807 GError *error; 2808 } RhythmDBSaveErrorData; 2809 2810 static gboolean 2811 emit_save_error_idle (RhythmDBSaveErrorData *data) 2812 { 2813 g_signal_emit (G_OBJECT (data->db), rhythmdb_signals[SAVE_ERROR], 0, data->uri, data->error); 2814 g_object_unref (G_OBJECT (data->db)); 2815 g_free (data->uri); 2816 g_error_free (data->error); 2817 g_free (data); 2818 return FALSE; 2819 } 2820 2821 static gpointer 2822 action_thread_main (RhythmDB *db) 2823 { 2824 RhythmDBEvent *result; 2825 2826 while (!g_cancellable_is_cancelled (db->priv->exiting)) { 2827 RhythmDBAction *action; 2828 2829 action = g_async_queue_pop (db->priv->action_queue); 2830 2831 /* hrm, do we need this check at all? */ 2832 if (!g_cancellable_is_cancelled (db->priv->exiting)) { 2833 switch (action->type) { 2834 case RHYTHMDB_ACTION_STAT: 2835 result = g_slice_new0 (RhythmDBEvent); 2836 result->db = db; 2837 result->type = RHYTHMDB_EVENT_STAT; 2838 result->entry_type = action->data.types.entry_type; 2839 result->error_type = action->data.types.error_type; 2840 result->ignore_type = action->data.types.ignore_type; 2841 2842 rb_debug ("executing RHYTHMDB_ACTION_STAT for \"%s\"", rb_refstring_get (action->uri)); 2843 2844 rhythmdb_execute_stat (db, rb_refstring_get (action->uri), result); 2845 break; 2846 2847 case RHYTHMDB_ACTION_LOAD: 2848 result = g_slice_new0 (RhythmDBEvent); 2849 result->db = db; 2850 result->type = RHYTHMDB_EVENT_METADATA_LOAD; 2851 result->entry_type = action->data.types.entry_type; 2852 result->error_type = action->data.types.error_type; 2853 result->ignore_type = action->data.types.ignore_type; 2854 2855 rb_debug ("executing RHYTHMDB_ACTION_LOAD for \"%s\"", rb_refstring_get (action->uri)); 2856 2857 rhythmdb_execute_load (db, rb_refstring_get (action->uri), result); 2858 break; 2859 2860 case RHYTHMDB_ACTION_ENUM_DIR: 2861 rb_debug ("executing RHYTHMDB_ACTION_ENUM_DIR for \"%s\"", rb_refstring_get (action->uri)); 2862 rhythmdb_execute_enum_dir (db, action); 2863 break; 2864 2865 case RHYTHMDB_ACTION_SYNC: 2866 { 2867 GError *error = NULL; 2868 RhythmDBEntry *entry; 2869 2870 if (db->priv->dry_run) { 2871 rb_debug ("dry run is enabled, not syncing metadata"); 2872 break; 2873 } 2874 2875 entry = rhythmdb_entry_lookup_by_location_refstring (db, action->uri); 2876 if (!entry) 2877 break; 2878 2879 rhythmdb_entry_sync_metadata (entry, action->data.changes, &error); 2880 2881 if (error != NULL) { 2882 RhythmDBSaveErrorData *data; 2883 2884 data = g_new0 (RhythmDBSaveErrorData, 1); 2885 g_object_ref (db); 2886 data->db = db; 2887 data->uri = g_strdup (rb_refstring_get (action->uri)); 2888 data->error = error; 2889 g_idle_add ((GSourceFunc)emit_save_error_idle, data); 2890 break; 2891 } 2892 break; 2893 } 2894 2895 case RHYTHMDB_ACTION_QUIT: 2896 /* don't do any real work here, since we may not process it */ 2897 rb_debug ("received QUIT action"); 2898 break; 2899 2900 default: 2901 g_assert_not_reached (); 2902 break; 2903 } 2904 } 2905 2906 rhythmdb_action_free (db, action); 2907 } 2908 2909 rb_debug ("exiting action thread"); 2910 result = g_slice_new0 (RhythmDBEvent); 2911 result->db = db; 2912 result->type = RHYTHMDB_EVENT_THREAD_EXITED; 2913 rhythmdb_push_event (db, result); 2914 2915 return NULL; 2916 } 2917 2918 /** 2919 * rhythmdb_add_uri: 2920 * @db: a #RhythmDB. 2921 * @uri: the URI to add an entry/entries for 2922 * 2923 * Adds the file(s) pointed to by @uri to the database, as entries of type 2924 * RHYTHMDB_ENTRY_TYPE_SONG. If the URI is that of a file, it will be added. 2925 * If the URI is that of a directory, everything under it will be added recursively. 2926 */ 2927 void 2928 rhythmdb_add_uri (RhythmDB *db, 2929 const char *uri) 2930 { 2931 rhythmdb_add_uri_with_types (db, 2932 uri, 2933 RHYTHMDB_ENTRY_TYPE_SONG, 2934 RHYTHMDB_ENTRY_TYPE_IGNORE, 2935 RHYTHMDB_ENTRY_TYPE_IMPORT_ERROR); 2936 } 2937 2938 static void 2939 rhythmdb_add_to_stat_list (RhythmDB *db, 2940 const char *uri, 2941 RhythmDBEntry *entry, 2942 RhythmDBEntryType *type, 2943 RhythmDBEntryType *ignore_type, 2944 RhythmDBEntryType *error_type) 2945 { 2946 RhythmDBEvent *result; 2947 2948 result = g_slice_new0 (RhythmDBEvent); 2949 result->db = db; 2950 result->type = RHYTHMDB_EVENT_STAT; 2951 result->entry_type = type; 2952 result->ignore_type = ignore_type; 2953 result->error_type = error_type; 2954 2955 if (entry != NULL) { 2956 result->entry = rhythmdb_entry_ref (entry); 2957 } 2958 2959 /* do we really need to check for duplicate requests here? .. nah. */ 2960 result->uri = rb_refstring_new (uri); 2961 db->priv->stat_list = g_list_prepend (db->priv->stat_list, result); 2962 } 2963 2964 2965 /** 2966 * rhythmdb_add_uri_with_types: 2967 * @db: a #RhythmDB. 2968 * @uri: the URI to add 2969 * @type: the #RhythmDBEntryType to use for new entries 2970 * @ignore_type: the #RhythmDBEntryType to use for ignored files 2971 * @error_type: the #RhythmDBEntryType to use for import errors 2972 * 2973 * Adds the file(s) pointed to by @uri to the database, as entries 2974 * of the specified type. If the URI points to a file, it will be added. 2975 * The the URI identifies a directory, everything under it will be added 2976 * recursively. 2977 */ 2978 void 2979 rhythmdb_add_uri_with_types (RhythmDB *db, 2980 const char *uri, 2981 RhythmDBEntryType *type, 2982 RhythmDBEntryType *ignore_type, 2983 RhythmDBEntryType *error_type) 2984 { 2985 rb_debug ("queueing stat for \"%s\"", uri); 2986 g_assert (uri && *uri); 2987 2988 /* 2989 * before the action thread is started, we queue up stat actions, 2990 * as we're still creating and running queries, as well as loading 2991 * the database. when we start the action thread, we'll kick off 2992 * a thread to process all the stat events too. 2993 * 2994 * when the action thread is already running, stat actions go through 2995 * the normal action queue and are processed by the action thread. 2996 */ 2997 g_mutex_lock (&db->priv->stat_mutex); 2998 if (db->priv->action_thread_running) { 2999 RhythmDBAction *action; 3000 g_mutex_unlock (&db->priv->stat_mutex); 3001 3002 action = g_slice_new0 (RhythmDBAction); 3003 action->type = RHYTHMDB_ACTION_STAT; 3004 action->uri = rb_refstring_new (uri); 3005 action->data.types.entry_type = type; 3006 action->data.types.ignore_type = ignore_type; 3007 action->data.types.error_type = error_type; 3008 3009 g_async_queue_push (db->priv->action_queue, action); 3010 } else { 3011 RhythmDBEntry *entry; 3012 3013 entry = rhythmdb_entry_lookup_by_location (db, uri); 3014 rhythmdb_add_to_stat_list (db, uri, entry, type, ignore_type, error_type); 3015 3016 g_mutex_unlock (&db->priv->stat_mutex); 3017 } 3018 } 3019 3020 3021 static gboolean 3022 rhythmdb_sync_library_idle (RhythmDB *db) 3023 { 3024 rhythmdb_sync_library_location (db); 3025 g_object_unref (db); 3026 return FALSE; 3027 } 3028 3029 static gboolean 3030 rhythmdb_load_error_cb (GError *error) 3031 { 3032 GDK_THREADS_ENTER (); 3033 rb_error_dialog (NULL, 3034 _("Could not load the music database:"), 3035 "%s", error->message); 3036 g_error_free (error); 3037 3038 GDK_THREADS_LEAVE (); 3039 return FALSE; 3040 } 3041 3042 static gpointer 3043 rhythmdb_load_thread_main (RhythmDB *db) 3044 { 3045 RhythmDBEvent *result; 3046 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3047 GError *error = NULL; 3048 3049 db->priv->active_mounts = rhythmdb_get_active_mounts (db); 3050 3051 rb_profile_start ("loading db"); 3052 g_mutex_lock (&db->priv->saving_mutex); 3053 if (klass->impl_load (db, db->priv->exiting, &error) == FALSE) { 3054 rb_debug ("db load failed: disabling saving"); 3055 db->priv->can_save = FALSE; 3056 3057 if (error) { 3058 g_idle_add ((GSourceFunc) rhythmdb_load_error_cb, error); 3059 } 3060 } 3061 g_mutex_unlock (&db->priv->saving_mutex); 3062 3063 rb_list_deep_free (db->priv->active_mounts); 3064 db->priv->active_mounts = NULL; 3065 3066 g_object_ref (db); 3067 g_timeout_add_seconds (10, (GSourceFunc) rhythmdb_sync_library_idle, db); 3068 3069 rb_debug ("queuing db load complete signal"); 3070 result = g_slice_new0 (RhythmDBEvent); 3071 result->type = RHYTHMDB_EVENT_DB_LOAD; 3072 g_async_queue_push (db->priv->event_queue, result); 3073 3074 rb_debug ("exiting"); 3075 result = g_slice_new0 (RhythmDBEvent); 3076 result->type = RHYTHMDB_EVENT_THREAD_EXITED; 3077 rhythmdb_push_event (db, result); 3078 3079 return NULL; 3080 } 3081 3082 /** 3083 * rhythmdb_load: 3084 * @db: a #RhythmDB. 3085 * 3086 * Load the database from disk. 3087 */ 3088 void 3089 rhythmdb_load (RhythmDB *db) 3090 { 3091 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_load_thread_main, db); 3092 } 3093 3094 static gpointer 3095 rhythmdb_save_thread_main (RhythmDB *db) 3096 { 3097 RhythmDBClass *klass; 3098 RhythmDBEvent *result; 3099 3100 rb_debug ("entering save thread"); 3101 3102 g_mutex_lock (&db->priv->saving_mutex); 3103 3104 db->priv->save_count++; 3105 g_cond_broadcast (&db->priv->saving_condition); 3106 3107 if (!(db->priv->dirty && db->priv->can_save)) { 3108 rb_debug ("no save needed, ignoring"); 3109 g_mutex_unlock (&db->priv->saving_mutex); 3110 goto out; 3111 } 3112 3113 while (db->priv->saving) 3114 g_cond_wait (&db->priv->saving_condition, &db->priv->saving_mutex); 3115 3116 db->priv->saving = TRUE; 3117 3118 rb_debug ("saving rhythmdb"); 3119 3120 klass = RHYTHMDB_GET_CLASS (db); 3121 klass->impl_save (db); 3122 3123 db->priv->saving = FALSE; 3124 db->priv->dirty = FALSE; 3125 3126 g_mutex_unlock (&db->priv->saving_mutex); 3127 3128 g_cond_broadcast (&db->priv->saving_condition); 3129 3130 out: 3131 result = g_slice_new0 (RhythmDBEvent); 3132 result->db = db; 3133 result->type = RHYTHMDB_EVENT_DB_SAVED; 3134 g_async_queue_push (db->priv->event_queue, result); 3135 3136 result = g_slice_new0 (RhythmDBEvent); 3137 result->db = db; 3138 result->type = RHYTHMDB_EVENT_THREAD_EXITED; 3139 rhythmdb_push_event (db, result); 3140 return NULL; 3141 } 3142 3143 /** 3144 * rhythmdb_save_async: 3145 * @db: a #RhythmDB. 3146 * 3147 * Save the database to disk, asynchronously. 3148 */ 3149 void 3150 rhythmdb_save_async (RhythmDB *db) 3151 { 3152 rb_debug ("saving the rhythmdb in the background"); 3153 3154 rhythmdb_read_enter (db); 3155 3156 rhythmdb_thread_create (db, NULL, (GThreadFunc) rhythmdb_save_thread_main, db); 3157 } 3158 3159 /** 3160 * rhythmdb_save: 3161 * @db: a #RhythmDB. 3162 * 3163 * Save the database to disk, not returning until it has been saved. 3164 */ 3165 void 3166 rhythmdb_save (RhythmDB *db) 3167 { 3168 int new_save_count; 3169 3170 rb_debug("saving the rhythmdb and blocking"); 3171 3172 g_mutex_lock (&db->priv->saving_mutex); 3173 new_save_count = db->priv->save_count + 1; 3174 3175 rhythmdb_save_async (db); 3176 3177 /* wait until this save request is being processed */ 3178 while (db->priv->save_count < new_save_count) { 3179 g_cond_wait (&db->priv->saving_condition, &db->priv->saving_mutex); 3180 } 3181 3182 /* wait until it's done */ 3183 while (db->priv->saving) { 3184 g_cond_wait (&db->priv->saving_condition, &db->priv->saving_mutex); 3185 } 3186 3187 rb_debug ("done"); 3188 3189 g_mutex_unlock (&db->priv->saving_mutex); 3190 } 3191 3192 /** 3193 * rhythmdb_entry_set: 3194 * @db:# a RhythmDB. 3195 * @entry: a #RhythmDBEntry. 3196 * @propid: the id of the property to set. 3197 * @value: the property value. 3198 * 3199 * This function can be called by any code which wishes to change a 3200 * song property and send a notification. It may be called when the 3201 * database is read-only; in this case the change will be queued for 3202 * an unspecified time in the future. The implication of this is that 3203 * rhythmdb_entry_get() may not reflect the changes immediately. However, 3204 * if this property is exposed in the user interface, you should still 3205 * make the change in the widget. Then when the database returns to a 3206 * writable state, your change will take effect in the database too, 3207 * and a notification will be sent at that point. 3208 * 3209 * Note that you must call rhythmdb_commit() at some point after invoking 3210 * this function, and that even after the commit, your change may not 3211 * have taken effect. 3212 */ 3213 void 3214 rhythmdb_entry_set (RhythmDB *db, 3215 RhythmDBEntry *entry, 3216 guint propid, 3217 const GValue *value) 3218 { 3219 g_return_if_fail (RHYTHMDB_IS (db)); 3220 g_return_if_fail (entry != NULL); 3221 3222 if ((entry->flags & RHYTHMDB_ENTRY_INSERTED) != 0) { 3223 if (!rhythmdb_get_readonly (db) && rb_is_main_thread ()) { 3224 rhythmdb_entry_set_internal (db, entry, TRUE, propid, value); 3225 } else { 3226 RhythmDBEvent *result; 3227 3228 result = g_slice_new0 (RhythmDBEvent); 3229 result->db = db; 3230 result->type = RHYTHMDB_EVENT_ENTRY_SET; 3231 3232 rb_debug ("queuing RHYTHMDB_ACTION_ENTRY_SET"); 3233 3234 result->entry = rhythmdb_entry_ref (entry); 3235 result->change.prop = propid; 3236 result->signal_change = TRUE; 3237 g_value_init (&result->change.new, G_VALUE_TYPE (value)); 3238 g_value_copy (value, &result->change.new); 3239 rhythmdb_push_event (db, result); 3240 } 3241 } else { 3242 rhythmdb_entry_set_internal (db, entry, FALSE, propid, value); 3243 } 3244 } 3245 3246 static void 3247 record_entry_change (RhythmDB *db, 3248 RhythmDBEntry *entry, 3249 guint propid, 3250 const GValue *old_value, 3251 const GValue *new_value) 3252 { 3253 RhythmDBEntryChange *changedata; 3254 GSList *changelist; 3255 3256 changedata = g_slice_new0 (RhythmDBEntryChange); 3257 changedata->prop = propid; 3258 3259 g_value_init (&changedata->old, G_VALUE_TYPE (old_value)); 3260 g_value_init (&changedata->new, G_VALUE_TYPE (new_value)); 3261 g_value_copy (old_value, &changedata->old); 3262 g_value_copy (new_value, &changedata->new); 3263 3264 g_mutex_lock (&db->priv->change_mutex); 3265 /* ref the entry before adding to hash, it is unreffed when removed */ 3266 rhythmdb_entry_ref (entry); 3267 changelist = g_hash_table_lookup (db->priv->changed_entries, entry); 3268 changelist = g_slist_append (changelist, changedata); 3269 g_hash_table_insert (db->priv->changed_entries, entry, changelist); 3270 g_mutex_unlock (&db->priv->change_mutex); 3271 } 3272 3273 void 3274 rhythmdb_entry_set_internal (RhythmDB *db, 3275 RhythmDBEntry *entry, 3276 gboolean notify_if_inserted, 3277 guint propid, 3278 const GValue *value) 3279 { 3280 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3281 gboolean handled; 3282 RhythmDBPodcastFields *podcast = NULL; 3283 GValue conv_value = {0,}; 3284 GValue old_value = {0,}; 3285 gboolean nop; 3286 3287 g_return_if_fail (entry != NULL); 3288 3289 /* convert the value if necessary */ 3290 if (G_VALUE_TYPE (value) != rhythmdb_get_property_type (db, propid)) { 3291 g_value_init (&conv_value, rhythmdb_get_property_type (db, propid)); 3292 if (g_value_transform (value, &conv_value) == FALSE) { 3293 g_warning ("Unable to convert new value for property %s from %s to %s", 3294 rhythmdb_nice_elt_name_from_propid (db, propid), 3295 g_type_name (G_VALUE_TYPE (value)), 3296 g_type_name (rhythmdb_get_property_type (db, propid))); 3297 g_assert_not_reached (); 3298 } 3299 value = &conv_value; 3300 } 3301 3302 /* compare the value with what's already there */ 3303 g_value_init (&old_value, G_VALUE_TYPE (value)); 3304 rhythmdb_entry_get (db, entry, propid, &old_value); 3305 switch (G_VALUE_TYPE (value)) { 3306 case G_TYPE_STRING: 3307 #ifndef G_DISABLE_ASSERT 3308 /* the playback error is allowed to be NULL */ 3309 if (propid != RHYTHMDB_PROP_PLAYBACK_ERROR || g_value_get_string (value)) 3310 g_assert (g_utf8_validate (g_value_get_string (value), -1, NULL)); 3311 #endif 3312 if (g_value_get_string (value) && g_value_get_string (&old_value)) { 3313 nop = (strcmp (g_value_get_string (value), g_value_get_string (&old_value)) == 0); 3314 } else { 3315 nop = FALSE; 3316 } 3317 break; 3318 case G_TYPE_BOOLEAN: 3319 nop = (g_value_get_boolean (value) == g_value_get_boolean (&old_value)); 3320 break; 3321 case G_TYPE_ULONG: 3322 nop = (g_value_get_ulong (value) == g_value_get_ulong (&old_value)); 3323 break; 3324 case G_TYPE_UINT64: 3325 nop = (g_value_get_uint64 (value) == g_value_get_uint64 (&old_value)); 3326 break; 3327 case G_TYPE_DOUBLE: 3328 nop = (g_value_get_double (value) == g_value_get_double (&old_value)); 3329 break; 3330 case G_TYPE_OBJECT: 3331 nop = (g_value_get_object (value) == g_value_get_object (&old_value)); 3332 break; 3333 default: 3334 g_assert_not_reached (); 3335 break; 3336 } 3337 3338 if (nop == FALSE && (entry->flags & RHYTHMDB_ENTRY_INSERTED) && notify_if_inserted) { 3339 record_entry_change (db, entry, propid, &old_value, value); 3340 } 3341 g_value_unset (&old_value); 3342 3343 if (nop) { 3344 if (value == &conv_value) { 3345 g_value_unset (&conv_value); 3346 } 3347 return; 3348 } 3349 3350 handled = klass->impl_entry_set (db, entry, propid, value); 3351 3352 if (!handled) { 3353 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED || 3354 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST || 3355 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH) 3356 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields); 3357 3358 switch (propid) { 3359 case RHYTHMDB_PROP_TYPE: 3360 case RHYTHMDB_PROP_ENTRY_ID: 3361 g_assert_not_reached (); 3362 break; 3363 case RHYTHMDB_PROP_TITLE: 3364 if (entry->title != NULL) { 3365 rb_refstring_unref (entry->title); 3366 } 3367 entry->title = rb_refstring_new (g_value_get_string (value)); 3368 break; 3369 case RHYTHMDB_PROP_ALBUM: 3370 if (entry->album != NULL) { 3371 rb_refstring_unref (entry->album); 3372 } 3373 entry->album = rb_refstring_new (g_value_get_string (value)); 3374 break; 3375 case RHYTHMDB_PROP_ARTIST: 3376 if (entry->artist != NULL) { 3377 rb_refstring_unref (entry->artist); 3378 } 3379 entry->artist = rb_refstring_new (g_value_get_string (value)); 3380 break; 3381 case RHYTHMDB_PROP_GENRE: 3382 if (entry->genre != NULL) { 3383 rb_refstring_unref (entry->genre); 3384 } 3385 entry->genre = rb_refstring_new (g_value_get_string (value)); 3386 break; 3387 case RHYTHMDB_PROP_COMMENT: 3388 if (entry->comment != NULL) { 3389 rb_refstring_unref (entry->comment); 3390 } 3391 entry->comment = rb_refstring_new (g_value_get_string (value)); 3392 break; 3393 case RHYTHMDB_PROP_TRACK_NUMBER: 3394 entry->tracknum = g_value_get_ulong (value); 3395 break; 3396 case RHYTHMDB_PROP_DISC_NUMBER: 3397 entry->discnum = g_value_get_ulong (value); 3398 break; 3399 case RHYTHMDB_PROP_DURATION: 3400 entry->duration = g_value_get_ulong (value); 3401 break; 3402 case RHYTHMDB_PROP_BITRATE: 3403 entry->bitrate = g_value_get_ulong (value); 3404 break; 3405 case RHYTHMDB_PROP_DATE: 3406 { 3407 gulong julian; 3408 julian = g_value_get_ulong (value); 3409 if (julian > 0) 3410 g_date_set_julian (&entry->date, julian); 3411 else 3412 g_date_clear (&entry->date, 1); 3413 break; 3414 } 3415 case RHYTHMDB_PROP_TRACK_GAIN: 3416 g_warning ("RHYTHMDB_PROP_TRACK_GAIN no longer supported"); 3417 break; 3418 case RHYTHMDB_PROP_TRACK_PEAK: 3419 g_warning ("RHYTHMDB_PROP_TRACK_PEAK no longer supported"); 3420 break; 3421 case RHYTHMDB_PROP_ALBUM_GAIN: 3422 g_warning ("RHYTHMDB_PROP_ALBUM_GAIN no longer supported"); 3423 break; 3424 case RHYTHMDB_PROP_ALBUM_PEAK: 3425 g_warning ("RHYTHMDB_PROP_ALBUM_PEAK no longer supported"); 3426 break; 3427 case RHYTHMDB_PROP_LOCATION: 3428 rb_refstring_unref (entry->location); 3429 entry->location = rb_refstring_new (g_value_get_string (value)); 3430 break; 3431 case RHYTHMDB_PROP_PLAYBACK_ERROR: 3432 rb_refstring_unref (entry->playback_error); 3433 if (g_value_get_string (value)) 3434 entry->playback_error = rb_refstring_new (g_value_get_string (value)); 3435 else 3436 entry->playback_error = NULL; 3437 break; 3438 case RHYTHMDB_PROP_MOUNTPOINT: 3439 if (entry->mountpoint != NULL) { 3440 rb_refstring_unref (entry->mountpoint); 3441 } 3442 entry->mountpoint = rb_refstring_new (g_value_get_string (value)); 3443 break; 3444 case RHYTHMDB_PROP_FILE_SIZE: 3445 entry->file_size = g_value_get_uint64 (value); 3446 break; 3447 case RHYTHMDB_PROP_MEDIA_TYPE: 3448 if (entry->media_type != NULL) { 3449 rb_refstring_unref (entry->media_type); 3450 } 3451 entry->media_type = rb_refstring_new (g_value_get_string (value)); 3452 break; 3453 case RHYTHMDB_PROP_MTIME: 3454 entry->mtime = g_value_get_ulong (value); 3455 break; 3456 case RHYTHMDB_PROP_FIRST_SEEN: 3457 entry->first_seen = g_value_get_ulong (value); 3458 entry->flags |= RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY; 3459 break; 3460 case RHYTHMDB_PROP_LAST_SEEN: 3461 entry->last_seen = g_value_get_ulong (value); 3462 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY; 3463 break; 3464 case RHYTHMDB_PROP_RATING: 3465 entry->rating = g_value_get_double (value); 3466 break; 3467 case RHYTHMDB_PROP_PLAY_COUNT: 3468 entry->play_count = g_value_get_ulong (value); 3469 break; 3470 case RHYTHMDB_PROP_LAST_PLAYED: 3471 entry->last_played = g_value_get_ulong (value); 3472 entry->flags |= RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY; 3473 break; 3474 case RHYTHMDB_PROP_BPM: 3475 entry->bpm = g_value_get_double (value); 3476 break; 3477 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID: 3478 rb_refstring_unref (entry->musicbrainz_trackid); 3479 entry->musicbrainz_trackid = rb_refstring_new (g_value_get_string (value)); 3480 break; 3481 case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID: 3482 rb_refstring_unref (entry->musicbrainz_artistid); 3483 entry->musicbrainz_artistid = rb_refstring_new (g_value_get_string (value)); 3484 break; 3485 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID: 3486 rb_refstring_unref (entry->musicbrainz_albumid); 3487 entry->musicbrainz_albumid = rb_refstring_new (g_value_get_string (value)); 3488 break; 3489 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID: 3490 rb_refstring_unref (entry->musicbrainz_albumartistid); 3491 entry->musicbrainz_albumartistid = rb_refstring_new (g_value_get_string (value)); 3492 break; 3493 case RHYTHMDB_PROP_ARTIST_SORTNAME: 3494 rb_refstring_unref (entry->artist_sortname); 3495 entry->artist_sortname = rb_refstring_new (g_value_get_string (value)); 3496 break; 3497 case RHYTHMDB_PROP_ALBUM_SORTNAME: 3498 rb_refstring_unref (entry->album_sortname); 3499 entry->album_sortname = rb_refstring_new (g_value_get_string (value)); 3500 break; 3501 case RHYTHMDB_PROP_ALBUM_ARTIST: 3502 rb_refstring_unref (entry->album_artist); 3503 entry->album_artist = rb_refstring_new (g_value_get_string (value)); 3504 break; 3505 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME: 3506 rb_refstring_unref (entry->album_artist_sortname); 3507 entry->album_artist_sortname = rb_refstring_new (g_value_get_string (value)); 3508 break; 3509 case RHYTHMDB_PROP_HIDDEN: 3510 if (g_value_get_boolean (value)) { 3511 entry->flags |= RHYTHMDB_ENTRY_HIDDEN; 3512 } else { 3513 entry->flags &= ~RHYTHMDB_ENTRY_HIDDEN; 3514 } 3515 entry->flags |= RHYTHMDB_ENTRY_LAST_SEEN_DIRTY; 3516 break; 3517 case RHYTHMDB_PROP_STATUS: 3518 g_assert (podcast); 3519 podcast->status = g_value_get_ulong (value); 3520 break; 3521 case RHYTHMDB_PROP_DESCRIPTION: 3522 g_assert (podcast); 3523 rb_refstring_unref (podcast->description); 3524 podcast->description = rb_refstring_new (g_value_get_string (value)); 3525 break; 3526 case RHYTHMDB_PROP_SUBTITLE: 3527 g_assert (podcast); 3528 rb_refstring_unref (podcast->subtitle); 3529 podcast->subtitle = rb_refstring_new (g_value_get_string (value)); 3530 break; 3531 case RHYTHMDB_PROP_SUMMARY: 3532 g_assert (podcast); 3533 rb_refstring_unref (podcast->summary); 3534 podcast->summary = rb_refstring_new (g_value_get_string (value)); 3535 break; 3536 case RHYTHMDB_PROP_LANG: 3537 g_assert (podcast); 3538 if (podcast->lang != NULL) { 3539 rb_refstring_unref (podcast->lang); 3540 } 3541 podcast->lang = rb_refstring_new (g_value_get_string (value)); 3542 break; 3543 case RHYTHMDB_PROP_COPYRIGHT: 3544 g_assert (podcast); 3545 if (podcast->copyright != NULL) { 3546 rb_refstring_unref (podcast->copyright); 3547 } 3548 podcast->copyright = rb_refstring_new (g_value_get_string (value)); 3549 break; 3550 case RHYTHMDB_PROP_IMAGE: 3551 g_assert (podcast); 3552 if (podcast->image != NULL) { 3553 rb_refstring_unref (podcast->image); 3554 } 3555 podcast->image = rb_refstring_new (g_value_get_string (value)); 3556 break; 3557 case RHYTHMDB_PROP_POST_TIME: 3558 g_assert (podcast); 3559 podcast->post_time = g_value_get_ulong (value); 3560 break; 3561 case RHYTHMDB_NUM_PROPERTIES: 3562 g_assert_not_reached (); 3563 break; 3564 } 3565 } 3566 3567 if (value == &conv_value) { 3568 g_value_unset (&conv_value); 3569 } 3570 3571 /* set the dirty state */ 3572 db->priv->dirty = TRUE; 3573 } 3574 3575 /** 3576 * rhythmdb_entry_sync_mirrored: 3577 * @db: a #RhythmDB. 3578 * @type: a #RhythmDBEntry. 3579 * @propid: the property to sync the mirrored version of. 3580 * 3581 * Synchronise "mirrored" properties, such as the string version of the last-played 3582 * time. This should be called when a property is directly modified, passing the 3583 * original property. 3584 * 3585 * This should only be used by RhythmDB itself, or a backend (such as rhythmdb-tree). 3586 */ 3587 static void 3588 rhythmdb_entry_sync_mirrored (RhythmDBEntry *entry, 3589 guint propid) 3590 { 3591 static const char *never; 3592 char *val; 3593 3594 if (never == NULL) 3595 never = _("Never"); 3596 3597 switch (propid) { 3598 case RHYTHMDB_PROP_LAST_PLAYED_STR: 3599 { 3600 RBRefString *old, *new; 3601 3602 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_PLAYED_DIRTY)) 3603 break; 3604 3605 old = g_atomic_pointer_get (&entry->last_played_str); 3606 if (entry->last_played == 0) { 3607 new = rb_refstring_new (never); 3608 } else { 3609 val = rb_utf_friendly_time (entry->last_played); 3610 new = rb_refstring_new (val); 3611 g_free (val); 3612 } 3613 3614 if (g_atomic_pointer_compare_and_exchange (&entry->last_played_str, old, new)) { 3615 if (old != NULL) { 3616 rb_refstring_unref (old); 3617 } 3618 } else { 3619 rb_refstring_unref (new); 3620 } 3621 3622 break; 3623 } 3624 case RHYTHMDB_PROP_FIRST_SEEN_STR: 3625 { 3626 RBRefString *old, *new; 3627 3628 if (!(entry->flags & RHYTHMDB_ENTRY_FIRST_SEEN_DIRTY)) 3629 break; 3630 3631 old = g_atomic_pointer_get (&entry->first_seen_str); 3632 if (entry->first_seen == 0) { 3633 new = rb_refstring_new (never); 3634 } else { 3635 val = rb_utf_friendly_time (entry->first_seen); 3636 new = rb_refstring_new (val); 3637 g_free (val); 3638 } 3639 3640 if (g_atomic_pointer_compare_and_exchange (&entry->first_seen_str, old, new)) { 3641 if (old != NULL) { 3642 rb_refstring_unref (old); 3643 } 3644 } else { 3645 rb_refstring_unref (new); 3646 } 3647 3648 break; 3649 } 3650 case RHYTHMDB_PROP_LAST_SEEN_STR: 3651 { 3652 RBRefString *old, *new; 3653 3654 if (!(entry->flags & RHYTHMDB_ENTRY_LAST_SEEN_DIRTY)) 3655 break; 3656 3657 old = g_atomic_pointer_get (&entry->last_seen_str); 3658 /* only store last seen time as a string for hidden entries */ 3659 if (entry->flags & RHYTHMDB_ENTRY_HIDDEN) { 3660 val = rb_utf_friendly_time (entry->last_seen); 3661 new = rb_refstring_new (val); 3662 g_free (val); 3663 } else { 3664 new = NULL; 3665 } 3666 3667 if (g_atomic_pointer_compare_and_exchange (&entry->last_seen_str, old, new)) { 3668 if (old != NULL) { 3669 rb_refstring_unref (old); 3670 } 3671 } else { 3672 rb_refstring_unref (new); 3673 } 3674 3675 break; 3676 } 3677 default: 3678 break; 3679 } 3680 } 3681 3682 /** 3683 * rhythmdb_entry_delete: 3684 * @db: a #RhythmDB. 3685 * @entry: a #RhythmDBEntry. 3686 * 3687 * Delete entry @entry from the database, sending notification of its deletion. 3688 * This is usually used by sources where entries can disappear randomly, such 3689 * as a network source. 3690 */ 3691 void 3692 rhythmdb_entry_delete (RhythmDB *db, 3693 RhythmDBEntry *entry) 3694 { 3695 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3696 3697 g_return_if_fail (RHYTHMDB_IS (db)); 3698 g_return_if_fail (entry != NULL); 3699 3700 rb_debug ("deleting entry %p", entry); 3701 3702 /* ref the entry before adding to hash, it is unreffed when removed */ 3703 rhythmdb_entry_ref (entry); 3704 3705 klass->impl_entry_delete (db, entry);
Access to field 'impl_entry_delete' results in a dereference of a null pointer (loaded from variable 'klass')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

Access to field 'impl_entry_delete' results in a dereference of a null pointer (loaded from variable 'klass')
(emitted by clang-analyzer)

TODO: a detailed trace is available in the data model (not yet rendered in this report)

3706 3707 g_mutex_lock (&db->priv->change_mutex); 3708 g_hash_table_insert (db->priv->deleted_entries, entry, g_thread_self ()); 3709 g_mutex_unlock (&db->priv->change_mutex); 3710 3711 /* deleting an entry makes the db dirty */ 3712 db->priv->dirty = TRUE; 3713 } 3714 3715 /** 3716 * rhythmdb_entry_move_to_trash: 3717 * @db: the #RhythmDB 3718 * @entry: #RhythmDBEntry to trash 3719 * 3720 * Trashes the file represented by #entry. If possible, the file is 3721 * moved to the user's trash directory and the entry is set to hidden, 3722 * otherwise the error will be stored as the playback error for the entry. 3723 */ 3724 void 3725 rhythmdb_entry_move_to_trash (RhythmDB *db, 3726 RhythmDBEntry *entry) 3727 { 3728 const char *uri; 3729 GFile *file; 3730 GError *error = NULL; 3731 3732 uri = rb_refstring_get (entry->location); 3733 file = g_file_new_for_uri (uri); 3734 3735 g_file_trash (file, NULL, &error); 3736 if (error != NULL) { 3737 GValue value = { 0, }; 3738 3739 g_value_init (&value, G_TYPE_STRING); 3740 g_value_set_string (&value, error->message); 3741 rhythmdb_entry_set (db, entry, RHYTHMDB_PROP_PLAYBACK_ERROR, &value); 3742 g_value_unset (&value); 3743 3744 rb_debug ("trashing %s failed: %s", 3745 uri, 3746 error->message); 3747 g_error_free (error); 3748 3749 } else { 3750 rhythmdb_entry_set_visibility (db, entry, FALSE); 3751 } 3752 g_object_unref (file); 3753 } 3754 3755 /** 3756 * rhythmdb_entry_delete_by_type: 3757 * @db: a #RhythmDB. 3758 * @type: type of entried to delete. 3759 * 3760 * Delete all entries from the database of the given type. 3761 * This is usually used by non-permanent sources when they disappear, such as 3762 * removable media being removed, or a network share becoming unavailable. 3763 */ 3764 void 3765 rhythmdb_entry_delete_by_type (RhythmDB *db, 3766 RhythmDBEntryType *type) 3767 { 3768 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3769 3770 if (klass->impl_entry_delete_by_type) { 3771 klass->impl_entry_delete_by_type (db, type); 3772 } else { 3773 g_warning ("delete_by_type not implemented"); 3774 } 3775 } 3776 3777 /** 3778 * rhythmdb_nice_elt_name_from_propid: 3779 * @db: the #RhythmDB 3780 * @propid: property ID 3781 * 3782 * Returns a short non-translated name for the property #propid. 3783 * This name is suitable for use as an XML tag name, for example. 3784 * 3785 * Return value: property ID name, must not be freed 3786 */ 3787 const xmlChar * 3788 rhythmdb_nice_elt_name_from_propid (RhythmDB *db, 3789 RhythmDBPropType propid) 3790 { 3791 return (xmlChar *)rhythmdb_properties[propid].elt_name; 3792 } 3793 3794 /** 3795 * rhythmdb_propid_from_nice_elt_name: 3796 * @db: the #RhythmDB 3797 * @name: a property ID name 3798 * 3799 * Converts a property name returned by @rhythmdb_propid_from_nice_elt_name 3800 * back to a #RhythmDBPropType. If the name does not match a property ID, 3801 * -1 will be returned instead. 3802 * 3803 * Return value: a #RhythmDBPropType, or -1 3804 */ 3805 int 3806 rhythmdb_propid_from_nice_elt_name (RhythmDB *db, 3807 const xmlChar *name) 3808 { 3809 gpointer ret, orig; 3810 if (g_hash_table_lookup_extended (db->priv->propname_map, name, 3811 &orig, &ret)) { 3812 return GPOINTER_TO_INT (ret); 3813 } 3814 return -1; 3815 } 3816 3817 /** 3818 * rhythmdb_entry_lookup_by_location: 3819 * @db: a #RhythmDB. 3820 * @uri: the URI of the entry to lookup. 3821 * 3822 * Looks up the entry with location @uri. 3823 * 3824 * Returns: the entry with location @uri, or NULL if no such entry exists. 3825 */ 3826 RhythmDBEntry * 3827 rhythmdb_entry_lookup_by_location (RhythmDB *db, 3828 const char *uri) 3829 { 3830 RBRefString *rs; 3831 3832 rs = rb_refstring_find (uri); 3833 if (rs != NULL) { 3834 return rhythmdb_entry_lookup_by_location_refstring (db, rs); 3835 } else { 3836 return NULL; 3837 } 3838 } 3839 3840 /** 3841 * rhythmdb_entry_lookup_by_location_refstring: 3842 * @db: the #RhythmDB 3843 * @uri: #RBRefString for the entry location 3844 * 3845 * Looks up the entry with location @uri. 3846 * 3847 * Returns: the entry with location @uri, or NULL if no such entry exists. 3848 */ 3849 RhythmDBEntry * 3850 rhythmdb_entry_lookup_by_location_refstring (RhythmDB *db, 3851 RBRefString *uri) 3852 { 3853 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3854 3855 return klass->impl_lookup_by_location (db, uri); 3856 } 3857 3858 /** 3859 * rhythmdb_entry_lookup_by_id: 3860 * @db: a #RhythmDB. 3861 * @id: entry ID 3862 * 3863 * Looks up the entry with id @id. 3864 * 3865 * Returns: the entry with id @id, or NULL if no such entry exists. 3866 */ 3867 RhythmDBEntry * 3868 rhythmdb_entry_lookup_by_id (RhythmDB *db, 3869 gint id) 3870 { 3871 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3872 3873 return klass->impl_lookup_by_id (db, id); 3874 } 3875 3876 /** 3877 * rhythmdb_entry_lookup_from_string: 3878 * @db: a #RhythmDB. 3879 * @str: string 3880 * @is_id: whether the string is an entry ID or a location. 3881 * 3882 * Locates an entry using a string containing either an entry ID 3883 * or a location. 3884 * 3885 * Returns: the entry matching the string, or NULL if no such entry exists. 3886 */ 3887 RhythmDBEntry * 3888 rhythmdb_entry_lookup_from_string (RhythmDB *db, 3889 const char *str, 3890 gboolean is_id) 3891 { 3892 if (is_id) { 3893 gint id; 3894 3895 id = strtoul (str, NULL, 10); 3896 if (id == 0) 3897 return NULL; 3898 3899 return rhythmdb_entry_lookup_by_id (db, id); 3900 } else { 3901 return rhythmdb_entry_lookup_by_location (db, str); 3902 } 3903 } 3904 3905 /** 3906 * rhythmdb_entry_foreach: 3907 * @db: a #RhythmDB. 3908 * @func: (scope call): the function to call with each entry. 3909 * @data: user data to pass to the function. 3910 * 3911 * Calls the given function for each of the entries in the database. 3912 */ 3913 void 3914 rhythmdb_entry_foreach (RhythmDB *db, 3915 GFunc func, 3916 gpointer data) 3917 { 3918 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3919 3920 klass->impl_entry_foreach (db, func, data); 3921 } 3922 3923 /** 3924 * rhythmdb_entry_count: 3925 * @db: a #RhythmDB. 3926 * 3927 * Returns the number of entries in the database. 3928 * 3929 * Return value: number of entries 3930 */ 3931 gint64 3932 rhythmdb_entry_count (RhythmDB *db) 3933 { 3934 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3935 3936 return klass->impl_entry_count (db); 3937 } 3938 3939 /** 3940 * rhythmdb_entry_foreach_by_type: 3941 * @db: a #RhythmDB. 3942 * @entry_type: the type of entry to retrieve 3943 * @func: (scope call): the function to call with each entry 3944 * @data: user data to pass to the function. 3945 * 3946 * Calls the given function for each of the entries in the database 3947 * of a given type. 3948 */ 3949 void 3950 rhythmdb_entry_foreach_by_type (RhythmDB *db, 3951 RhythmDBEntryType *entry_type, 3952 GFunc func, 3953 gpointer data) 3954 { 3955 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3956 3957 klass->impl_entry_foreach_by_type (db, entry_type, func, data); 3958 } 3959 3960 /** 3961 * rhythmdb_entry_count_by_type: 3962 * @db: a #RhythmDB. 3963 * @entry_type: a #RhythmDBEntryType. 3964 * 3965 * Returns the number of entries in the database of a particular type. 3966 * 3967 * Return value: entry count 3968 */ 3969 gint64 3970 rhythmdb_entry_count_by_type (RhythmDB *db, 3971 RhythmDBEntryType *entry_type) 3972 { 3973 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3974 3975 return klass->impl_entry_count_by_type (db, entry_type); 3976 } 3977 3978 3979 /** 3980 * rhythmdb_evaluate_query: 3981 * @db: a #RhythmDB. 3982 * @query: a query. 3983 * @entry: a @RhythmDBEntry. 3984 * 3985 * Evaluates the given entry against the given query. 3986 * 3987 * Returns: whether the given entry matches the criteria of the given query. 3988 */ 3989 gboolean 3990 rhythmdb_evaluate_query (RhythmDB *db, 3991 GPtrArray *query, 3992 RhythmDBEntry *entry) 3993 { 3994 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 3995 3996 return klass->impl_evaluate_query (db, query, entry); 3997 } 3998 3999 static void 4000 rhythmdb_query_internal (RhythmDBQueryThreadData *data) 4001 { 4002 RhythmDBEvent *result; 4003 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (data->db); 4004 4005 rhythmdb_query_preprocess (data->db, data->query); 4006 4007 rb_debug ("doing query"); 4008 4009 klass->impl_do_full_query (data->db, data->query, 4010 data->results, 4011 &data->cancel); 4012 4013 rb_debug ("completed"); 4014 rhythmdb_query_results_query_complete (data->results); 4015 4016 result = g_slice_new0 (RhythmDBEvent); 4017 result->db = data->db; 4018 result->type = RHYTHMDB_EVENT_QUERY_COMPLETE; 4019 result->results = data->results; 4020 rhythmdb_push_event (data->db, result); 4021 4022 rhythmdb_query_free (data->query); 4023 } 4024 4025 static gpointer 4026 query_thread_main (RhythmDBQueryThreadData *data) 4027 { 4028 RhythmDBEvent *result; 4029 4030 rb_debug ("entering query thread"); 4031 4032 rhythmdb_query_internal (data); 4033 4034 result = g_slice_new0 (RhythmDBEvent); 4035 result->db = data->db; 4036 result->type = RHYTHMDB_EVENT_THREAD_EXITED; 4037 rhythmdb_push_event (data->db, result); 4038 g_free (data); 4039 return NULL; 4040 } 4041 4042 /** 4043 * rhythmdb_do_full_query_async_parsed: 4044 * @db: the #RhythmDB 4045 * @results: a #RhythmDBQueryResults instance to feed results to 4046 * @query: the query to run 4047 * 4048 * Asynchronously runs a parsed query across the database, feeding matching 4049 * entries to @results in chunks. This can only be called from the 4050 * main thread. 4051 * 4052 * Since @results is always a @RhythmDBQueryModel, 4053 * use the RhythmDBQueryModel::complete signal to identify when the 4054 * query is complete. 4055 */ 4056 void 4057 rhythmdb_do_full_query_async_parsed (RhythmDB *db, 4058 RhythmDBQueryResults *results, 4059 GPtrArray *query) 4060 { 4061 RhythmDBQueryThreadData *data; 4062 4063 data = g_new0 (RhythmDBQueryThreadData, 1); 4064 data->db = db; 4065 data->query = rhythmdb_query_copy (query); 4066 data->results = results; 4067 data->cancel = FALSE; 4068 4069 rhythmdb_read_enter (db); 4070 4071 rhythmdb_query_results_set_query (results, query); 4072 4073 g_object_ref (results); 4074 g_object_ref (db); 4075 g_atomic_int_inc (&db->priv->outstanding_threads); 4076 g_async_queue_ref (db->priv->action_queue); 4077 g_async_queue_ref (db->priv->event_queue); 4078 g_thread_pool_push (db->priv->query_thread_pool, data, NULL); 4079 } 4080 4081 /** 4082 * rhythmdb_do_full_query_async: 4083 * @db: the #RhythmDB 4084 * @results: a #RhythmDBQueryResults to feed results to 4085 * @Varargs: query parameters 4086 * 4087 * Asynchronously runs a query specified in the function arguments 4088 * across the database, feeding matching entries to @results in chunks. 4089 * This can only be called from the main thread. 4090 * 4091 * Since @results is always a @RhythmDBQueryModel, 4092 * use the RhythmDBQueryModel::complete signal to identify when the 4093 * query is complete. 4094 * 4095 * FIXME: example 4096 */ 4097 void 4098 rhythmdb_do_full_query_async (RhythmDB *db, 4099 RhythmDBQueryResults *results, 4100 ...) 4101 { 4102 GPtrArray *query; 4103 va_list args; 4104 4105 va_start (args, results); 4106 4107 query = rhythmdb_query_parse_valist (db, args); 4108 4109 rhythmdb_do_full_query_async_parsed (db, results, query); 4110 4111 rhythmdb_query_free (query); 4112 4113 va_end (args); 4114 } 4115 4116 static void 4117 rhythmdb_do_full_query_internal (RhythmDB *db, 4118 RhythmDBQueryResults *results, 4119 GPtrArray *query) 4120 { 4121 RhythmDBQueryThreadData *data; 4122 4123 data = g_new0 (RhythmDBQueryThreadData, 1); 4124 data->db = db; 4125 data->query = rhythmdb_query_copy (query); 4126 data->results = results; 4127 data->cancel = FALSE; 4128 4129 rhythmdb_read_enter (db); 4130 4131 rhythmdb_query_results_set_query (results, query); 4132 g_object_ref (results); 4133 4134 rhythmdb_query_internal (data); 4135 g_free (data); 4136 } 4137 4138 /** 4139 * rhythmdb_do_full_query_parsed: 4140 * @db: the #RhythmDB 4141 * @results: a #RhythmDBQueryResults instance to feed results to 4142 * @query: a parsed query 4143 * 4144 * Synchronously evaluates the parsed query @query, feeding results 4145 * to @results in chunks. Does not return until the query is complete. 4146 */ 4147 void 4148 rhythmdb_do_full_query_parsed (RhythmDB *db, 4149 RhythmDBQueryResults *results, 4150 GPtrArray *query) 4151 { 4152 rhythmdb_do_full_query_internal (db, results, query); 4153 } 4154 4155 /** 4156 * rhythmdb_do_full_query: 4157 * @db: the #RhythmDB 4158 * @results: a #RhythmDBQueryResults instance to feed results to 4159 * @Varargs: query parameters 4160 * 4161 * Synchronously evaluates @query, feeding results to @results in 4162 * chunks. Does not return until the query is complete. 4163 * This can only be called from the main thread. 4164 * 4165 * FIXME: example 4166 */ 4167 void 4168 rhythmdb_do_full_query (RhythmDB *db, 4169 RhythmDBQueryResults *results, 4170 ...) 4171 { 4172 GPtrArray *query; 4173 va_list args; 4174 4175 va_start (args, results); 4176 4177 query = rhythmdb_query_parse_valist (db, args); 4178 4179 rhythmdb_do_full_query_internal (db, results, query); 4180 4181 rhythmdb_query_free (query); 4182 4183 va_end (args); 4184 } 4185 4186 /* This should really be standard. */ 4187 #define ENUM_ENTRY(NAME, DESC) { NAME, "" #NAME "", DESC } 4188 4189 GType 4190 rhythmdb_query_type_get_type (void) 4191 { 4192 static GType etype = 0; 4193 4194 if (etype == 0) 4195 { 4196 static const GEnumValue values[] = 4197 { 4198 4199 ENUM_ENTRY (RHYTHMDB_QUERY_END, "query-end"), 4200 ENUM_ENTRY (RHYTHMDB_QUERY_DISJUNCTION, "disjunctive-marker"), 4201 ENUM_ENTRY (RHYTHMDB_QUERY_SUBQUERY, "subquery"), 4202 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_EQUALS, "equals"), 4203 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_EQUAL, "not-equal"), 4204 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LIKE, "fuzzy-match"), 4205 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_NOT_LIKE, "inverted-fuzzy-match"), 4206 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_PREFIX, "starts-with"), 4207 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_SUFFIX, "ends-with"), 4208 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_GREATER, "greater-than"), 4209 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_LESS, "less-than"), 4210 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_WITHIN, "within-current-time"), 4211 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_CURRENT_TIME_NOT_WITHIN, "not-within-current-time"), 4212 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_EQUALS, "year-equals"), 4213 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_NOT_EQUAL, "year-not-equals"), 4214 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_GREATER, "year-greater-than"), 4215 ENUM_ENTRY (RHYTHMDB_QUERY_PROP_YEAR_LESS, "year-less-than"), 4216 { 0, 0, 0 } 4217 }; 4218 4219 etype = g_enum_register_static ("RhythmDBQueryType", values); 4220 } 4221 4222 return etype; 4223 } 4224 4225 GType 4226 rhythmdb_prop_type_get_type (void) 4227 { 4228 static GType etype = 0; 4229 4230 if (etype == 0) 4231 { 4232 int i; 4233 static GEnumValue values[G_N_ELEMENTS(rhythmdb_properties)]; 4234 g_assert(G_N_ELEMENTS(rhythmdb_properties)-1 == RHYTHMDB_NUM_PROPERTIES); 4235 for (i = 0; i < G_N_ELEMENTS(rhythmdb_properties)-1; i++) { 4236 g_assert (i == rhythmdb_properties[i].prop_id); 4237 values[i].value = rhythmdb_properties[i].prop_id; 4238 values[i].value_name = rhythmdb_properties[i].prop_name; 4239 values[i].value_nick = rhythmdb_properties[i].elt_name; 4240 } 4241 etype = g_enum_register_static ("RhythmDBPropType", values); 4242 } 4243 4244 return etype; 4245 } 4246 4247 void 4248 rhythmdb_emit_entry_deleted (RhythmDB *db, 4249 RhythmDBEntry *entry) 4250 { 4251 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_DELETED], 0, entry); 4252 } 4253 4254 static gboolean 4255 rhythmdb_entry_extra_metadata_accumulator (GSignalInvocationHint *ihint, 4256 GValue *return_accu, 4257 const GValue *handler_return, 4258 gpointer data) 4259 { 4260 if (handler_return == NULL) 4261 return TRUE; 4262 4263 g_value_copy (handler_return, return_accu); 4264 return (g_value_get_boxed (return_accu) == NULL); 4265 } 4266 4267 /** 4268 * rhythmdb_entry_request_extra_metadata: 4269 * @db: a #RhythmDB 4270 * @entry: a #RhythmDBEntry 4271 * @property_name: the metadata predicate 4272 * 4273 * Emits a request for extra metadata for the @entry. 4274 * The @property_name argument is emitted as the ::detail part of the 4275 * "entry_extra_metadata_request" signal. It should be a namespaced RDF 4276 * predicate e.g. from Dublin Core, MusicBrainz, or internal to Rhythmbox 4277 * (namespace "rb:"). Suitable predicates would be those that are expensive to 4278 * acquire or only apply to a limited range of entries. 4279 * Handlers capable of providing a particular predicate may ensure they only 4280 * see appropriate requests by supplying an appropriate ::detail part when 4281 * connecting to the signal. Upon a handler returning a non-%NULL value, 4282 * emission will be stopped and the value returned to the caller; if no 4283 * handlers return a non-%NULL value, the caller will receive %NULL. Priority 4284 * is determined by signal connection order, with %G_CONNECT_AFTER providing a 4285 * second, lower rank of priority. 4286 * A handler returning a value should do so in a #GValue allocated on the heap; 4287 * the accumulator will take ownership. The caller should unset and free the 4288 * #GValue if non-%NULL when finished with it. 4289 * 4290 * Returns: an allocated, initialised, set #GValue, or NULL 4291 */ 4292 GValue * 4293 rhythmdb_entry_request_extra_metadata (RhythmDB *db, 4294 RhythmDBEntry *entry, 4295 const gchar *property_name) 4296 { 4297 GValue *value = NULL; 4298 4299 g_signal_emit (G_OBJECT (db), 4300 rhythmdb_signals[ENTRY_EXTRA_METADATA_REQUEST], 4301 g_quark_from_string (property_name), 4302 entry, 4303 &value); 4304 4305 return value; 4306 } 4307 4308 /** 4309 * rhythmdb_emit_entry_extra_metadata_notify: 4310 * @db: a #RhythmDB 4311 * @entry: a #RhythmDBEntry 4312 * @property_name: the metadata predicate 4313 * @metadata: a #GValue 4314 * 4315 * Emits a signal describing extra metadata for the @entry. The @property_name 4316 * argument is emitted as the ::detail part of the 4317 * "entry_extra_metadata_notify" signal and as the 'field' parameter. Handlers 4318 * can ensure they only get metadata they are interested in by supplying an 4319 * appropriate ::detail part when connecting to the signal. If handlers are 4320 * interested in the metadata they should ref or copy the contents of @metadata 4321 * and unref or free it when they are finished with it. 4322 */ 4323 void 4324 rhythmdb_emit_entry_extra_metadata_notify (RhythmDB *db, 4325 RhythmDBEntry *entry, 4326 const gchar *property_name, 4327 const GValue *metadata) 4328 { 4329 g_signal_emit (G_OBJECT (db), 4330 rhythmdb_signals[ENTRY_EXTRA_METADATA_NOTIFY], 4331 g_quark_from_string (property_name), 4332 entry, 4333 property_name, 4334 metadata); 4335 } 4336 4337 /** 4338 * rhythmdb_entry_gather_metadata: 4339 * @db: a #RhythmDB 4340 * @entry: a #RhythmDBEntry 4341 * 4342 * Gathers all metadata for the @entry. The returned GHashTable maps property 4343 * names and extra metadata names (described under 4344 * @rhythmdb_entry_request_extra_metadata) to GValues. Anything wanting to 4345 * provide extra metadata should connect to the "entry_extra_metadata_gather" 4346 * signal. 4347 * 4348 * Returns: (transfer full): a RBStringValueMap containing metadata for the entry. 4349 * This must be freed using g_object_unref. 4350 */ 4351 RBStringValueMap * 4352 rhythmdb_entry_gather_metadata (RhythmDB *db, 4353 RhythmDBEntry *entry) 4354 { 4355 RBStringValueMap *metadata; 4356 GEnumClass *klass; 4357 guint i; 4358 4359 metadata = rb_string_value_map_new (); 4360 4361 /* add core properties */ 4362 klass = g_type_class_ref (RHYTHMDB_TYPE_PROP_TYPE); 4363 for (i = 0; i < klass->n_values; i++) { 4364 GValue value = {0,}; 4365 gint prop; 4366 GType value_type; 4367 const char *name; 4368 4369 prop = klass->values[i].value; 4370 4371 /* only include easily marshallable types in the hash table */ 4372 value_type = rhythmdb_get_property_type (db, prop); 4373 switch (value_type) { 4374 case G_TYPE_STRING: 4375 case G_TYPE_BOOLEAN: 4376 case G_TYPE_ULONG: 4377 case G_TYPE_UINT64: 4378 case G_TYPE_DOUBLE: 4379 break; 4380 default: 4381 continue; 4382 } 4383 4384 /* skip deprecated properties */ 4385 switch (prop) { 4386 case RHYTHMDB_PROP_TRACK_GAIN: 4387 case RHYTHMDB_PROP_TRACK_PEAK: 4388 case RHYTHMDB_PROP_ALBUM_GAIN: 4389 case RHYTHMDB_PROP_ALBUM_PEAK: 4390 continue; 4391 default: 4392 break; 4393 } 4394 4395 g_value_init (&value, value_type); 4396 rhythmdb_entry_get (db, entry, prop, &value); 4397 name = (char *)rhythmdb_nice_elt_name_from_propid (db, prop); 4398 rb_string_value_map_set (metadata, name, &value); 4399 g_value_unset (&value); 4400 } 4401 g_type_class_unref (klass); 4402 4403 /* gather extra metadata */ 4404 g_signal_emit (G_OBJECT (db), 4405 rhythmdb_signals[ENTRY_EXTRA_METADATA_GATHER], 0, 4406 entry, 4407 metadata); 4408 4409 return metadata; 4410 } 4411 4412 static gboolean 4413 queue_is_empty (GAsyncQueue *queue) 4414 { 4415 return g_async_queue_length (queue) <= 0; 4416 } 4417 4418 /** 4419 * rhythmdb_is_busy: 4420 * @db: a #RhythmDB. 4421 * 4422 * Checks if the database has events to process. This probably isn't 4423 * very useful. 4424 * 4425 * Returns: whether the #RhythmDB has events to process. 4426 */ 4427 gboolean 4428 rhythmdb_is_busy (RhythmDB *db) 4429 { 4430 return (!db->priv->action_thread_running || 4431 db->priv->stat_thread_running || 4432 !queue_is_empty (db->priv->event_queue) || 4433 !queue_is_empty (db->priv->action_queue) || 4434 (db->priv->outstanding_stats != NULL)); 4435 } 4436 4437 /** 4438 * rhythmdb_get_progress_info: 4439 * @db: a #RhythmDB. 4440 * @text: used to return progress text 4441 * @progress: used to return progress fraction 4442 * 4443 * Provides progress information for rhythmdb operations, if any are running. 4444 */ 4445 void 4446 rhythmdb_get_progress_info (RhythmDB *db, char **text, float *progress) 4447 { 4448 if (db->priv->stat_thread_running && db->priv->stat_thread_count > 0) { 4449 g_free (*text); 4450 *text = g_strdup_printf (_("Checking (%d/%d)"), 4451 db->priv->stat_thread_done, 4452 db->priv->stat_thread_count); 4453 *progress = ((float)db->priv->stat_thread_done / 4454 (float)db->priv->stat_thread_count); 4455 } 4456 } 4457 4458 /** 4459 * rhythmdb_compute_status_normal: 4460 * @n_songs: the number of tracks. 4461 * @duration: the total duration of the tracks. 4462 * @size: the total size of the tracks. 4463 * @singular: singular form of the format string to use for entries (eg "%d song") 4464 * @plural: plural form of the format string to use for entries (eg "%d songs") 4465 * 4466 * Creates a string containing the "status" information about a list of tracks. 4467 * The singular and plural strings must be used in a direct ngettext call 4468 * elsewhere in order for them to be marked for translation correctly. 4469 * 4470 * Returns: the string, which should be freed with g_free. 4471 */ 4472 char * 4473 rhythmdb_compute_status_normal (gint n_songs, 4474 glong duration, 4475 guint64 size, 4476 const char *singular, 4477 const char *plural) 4478 { 4479 long days, hours, minutes; 4480 char *songcount = NULL; 4481 char *time = NULL; 4482 char *size_str = NULL; 4483 char *ret; 4484 const char *minutefmt; 4485 const char *hourfmt; 4486 const char *dayfmt; 4487 4488 songcount = g_strdup_printf (ngettext (singular, plural, n_songs), n_songs); 4489 4490 days = duration / (60 * 60 * 24); 4491 hours = (duration / (60 * 60)) - (days * 24); 4492 minutes = (duration / 60) - ((days * 24 * 60) + (hours * 60)); 4493 4494 minutefmt = ngettext ("%ld minute", "%ld minutes", minutes); 4495 hourfmt = ngettext ("%ld hour", "%ld hours", hours); 4496 dayfmt = ngettext ("%ld day", "%ld days", days); 4497 if (days > 0) { 4498 if (hours > 0) 4499 if (minutes > 0) { 4500 char *fmt; 4501 /* Translators: the format is "X days, X hours and X minutes" */ 4502 fmt = g_strdup_printf (_("%s, %s and %s"), dayfmt, hourfmt, minutefmt); 4503 time = g_strdup_printf (fmt, days, hours, minutes); 4504 g_free (fmt); 4505 } else { 4506 char *fmt; 4507 /* Translators: the format is "X days and X hours" */ 4508 fmt = g_strdup_printf (_("%s and %s"), dayfmt, hourfmt); 4509 time = g_strdup_printf (fmt, days, hours); 4510 g_free (fmt); 4511 } 4512 else 4513 if (minutes > 0) { 4514 char *fmt; 4515 /* Translators: the format is "X days and X minutes" */ 4516 fmt = g_strdup_printf (_("%s and %s"), dayfmt, minutefmt); 4517 time = g_strdup_printf (fmt, days, minutes); 4518 g_free (fmt); 4519 } else { 4520 time = g_strdup_printf (dayfmt, days); 4521 } 4522 } else { 4523 if (hours > 0) { 4524 if (minutes > 0) { 4525 char *fmt; 4526 /* Translators: the format is "X hours and X minutes" */ 4527 fmt = g_strdup_printf (_("%s and %s"), hourfmt, minutefmt); 4528 time = g_strdup_printf (fmt, hours, minutes); 4529 g_free (fmt); 4530 } else { 4531 time = g_strdup_printf (hourfmt, hours); 4532 } 4533 4534 } else { 4535 time = g_strdup_printf (minutefmt, minutes); 4536 } 4537 } 4538 4539 size_str = g_format_size (size); 4540 4541 if (size > 0 && duration > 0) { 4542 ret = g_strdup_printf ("%s, %s, %s", songcount, time, size_str); 4543 } else if (duration > 0) { 4544 ret = g_strdup_printf ("%s, %s", songcount, time); 4545 } else if (size > 0) { 4546 ret = g_strdup_printf ("%s, %s", songcount, size_str); 4547 } else { 4548 ret = g_strdup (songcount); 4549 } 4550 4551 g_free (songcount); 4552 g_free (time); 4553 g_free (size_str); 4554 4555 return ret; 4556 } 4557 4558 /** 4559 * rhythmdb_register_entry_type: 4560 * @db: the #RhythmDB 4561 * @entry_type: the new entry type to register 4562 * 4563 * Registers a new entry type. An entry type must be registered before 4564 * any entries can be created for it. 4565 */ 4566 void 4567 rhythmdb_register_entry_type (RhythmDB *db, RhythmDBEntryType *entry_type) 4568 { 4569 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 4570 char *name = NULL; 4571 4572 g_object_get (entry_type, "name", &name, NULL); 4573 g_assert (name != NULL); 4574 g_mutex_lock (&db->priv->entry_type_map_mutex); 4575 g_hash_table_insert (db->priv->entry_type_map, name, g_object_ref (entry_type)); 4576 g_mutex_unlock (&db->priv->entry_type_map_mutex); 4577 4578 if (klass->impl_entry_type_registered) 4579 klass->impl_entry_type_registered (db, entry_type); 4580 } 4581 4582 /** 4583 * rhythmdb_entry_type_foreach: 4584 * @db: a #RhythmDB 4585 * @func: callback function to call for each registered entry type 4586 * @data: data to pass to the callback 4587 * 4588 * Calls a function for each registered entry type. 4589 */ 4590 void 4591 rhythmdb_entry_type_foreach (RhythmDB *db, 4592 GHFunc func, 4593 gpointer data) 4594 { 4595 g_mutex_lock (&db->priv->entry_type_mutex); 4596 g_hash_table_foreach (db->priv->entry_type_map, func, data); 4597 g_mutex_unlock (&db->priv->entry_type_mutex); 4598 } 4599 4600 /** 4601 * rhythmdb_entry_type_get_by_name: 4602 * @db: a #RhythmDB 4603 * @name: name of the type to look for 4604 * 4605 * Locates a #RhythmDBEntryType by name. Returns NULL if no entry 4606 * type is registered with the specified name. 4607 * 4608 * Returns: (transfer none): the #RhythmDBEntryType 4609 */ 4610 RhythmDBEntryType * 4611 rhythmdb_entry_type_get_by_name (RhythmDB *db, 4612 const char *name) 4613 { 4614 gpointer t = NULL; 4615 4616 g_mutex_lock (&db->priv->entry_type_map_mutex); 4617 if (db->priv->entry_type_map) { 4618 t = g_hash_table_lookup (db->priv->entry_type_map, name); 4619 } 4620 g_mutex_unlock (&db->priv->entry_type_map_mutex); 4621 4622 return (RhythmDBEntryType *) t; 4623 } 4624 4625 static void 4626 rhythmdb_entry_set_mount_point (RhythmDB *db, 4627 RhythmDBEntry *entry, 4628 const gchar *realuri) 4629 { 4630 gchar *mount_point; 4631 GValue value = {0, }; 4632 4633 mount_point = rb_uri_get_mount_point (realuri); 4634 if (mount_point != NULL) { 4635 g_value_init (&value, G_TYPE_STRING); 4636 g_value_take_string (&value, mount_point); 4637 rhythmdb_entry_set_internal (db, entry, FALSE, 4638 RHYTHMDB_PROP_MOUNTPOINT, 4639 &value); 4640 g_value_unset (&value); 4641 } 4642 } 4643 4644 void 4645 rhythmdb_entry_set_visibility (RhythmDB *db, 4646 RhythmDBEntry *entry, 4647 gboolean visible) 4648 { 4649 GValue old_val = {0, }; 4650 gboolean old_visible; 4651 4652 g_return_if_fail (RHYTHMDB_IS (db)); 4653 g_return_if_fail (entry != NULL); 4654 4655 g_value_init (&old_val, G_TYPE_BOOLEAN); 4656 4657 rhythmdb_entry_get (db, entry, RHYTHMDB_PROP_HIDDEN, &old_val); 4658 old_visible = !g_value_get_boolean (&old_val); 4659 4660 if ((old_visible && !visible) || (!old_visible && visible)) { 4661 GValue new_val = {0, }; 4662 4663 g_value_init (&new_val, G_TYPE_BOOLEAN); 4664 g_value_set_boolean (&new_val, !visible); 4665 rhythmdb_entry_set_internal (db, entry, TRUE, 4666 RHYTHMDB_PROP_HIDDEN, &new_val); 4667 g_value_unset (&new_val); 4668 } 4669 g_value_unset (&old_val); 4670 } 4671 4672 static gboolean 4673 rhythmdb_idle_save (RhythmDB *db) 4674 { 4675 if (db->priv->dirty) { 4676 rb_debug ("database is dirty, doing regular save"); 4677 rhythmdb_save_async (db); 4678 } 4679 4680 return TRUE; 4681 } 4682 4683 static void 4684 rhythmdb_sync_library_location (RhythmDB *db) 4685 { 4686 if (db->priv->library_locations != NULL && 4687 g_strv_length (db->priv->library_locations) > 0) { 4688 rb_debug ("ending monitor of old library directories"); 4689 4690 rhythmdb_stop_monitoring (db); 4691 4692 g_strfreev (db->priv->library_locations); 4693 db->priv->library_locations = NULL; 4694 } 4695 4696 if (g_settings_get_boolean (db->priv->settings, "monitor-library")) { 4697 rb_debug ("starting library monitoring"); 4698 db->priv->library_locations = g_settings_get_strv (db->priv->settings, "locations"); 4699 4700 rhythmdb_start_monitoring (db); 4701 } 4702 } 4703 4704 static void 4705 db_settings_changed_cb (GSettings *settings, const char *key, RhythmDB *db) 4706 { 4707 if (g_strcmp0 (key, "locations") == 0 || g_strcmp0 (key, "monitor-library") == 0) { 4708 rhythmdb_sync_library_location (db); 4709 } 4710 } 4711 4712 char * 4713 rhythmdb_entry_dup_string (RhythmDBEntry *entry, 4714 RhythmDBPropType propid) 4715 { 4716 const char *s; 4717 4718 g_return_val_if_fail (entry != NULL, NULL); 4719 4720 s = rhythmdb_entry_get_string (entry, propid); 4721 if (s != NULL) { 4722 return g_strdup (s); 4723 } else { 4724 return NULL; 4725 } 4726 } 4727 4728 /** 4729 * rhythmdb_entry_get_string: 4730 * @entry: a #RhythmDBEntry 4731 * @propid: the #RhythmDBPropType to return 4732 * 4733 * Returns the value of a string property of #entry. 4734 * 4735 * Return value: property value, must not be freed 4736 */ 4737 const char * 4738 rhythmdb_entry_get_string (RhythmDBEntry *entry, 4739 RhythmDBPropType propid) 4740 { 4741 RhythmDBPodcastFields *podcast = NULL; 4742 4743 g_return_val_if_fail (entry != NULL, NULL); 4744 g_return_val_if_fail (entry->refcount > 0, NULL); 4745 4746 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED || 4747 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST || 4748 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH) 4749 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields); 4750 4751 rhythmdb_entry_sync_mirrored (entry, propid); 4752 4753 switch (propid) { 4754 case RHYTHMDB_PROP_TITLE: 4755 return rb_refstring_get (entry->title); 4756 case RHYTHMDB_PROP_ALBUM: 4757 return rb_refstring_get (entry->album); 4758 case RHYTHMDB_PROP_ARTIST: 4759 return rb_refstring_get (entry->artist); 4760 case RHYTHMDB_PROP_GENRE: 4761 return rb_refstring_get (entry->genre); 4762 case RHYTHMDB_PROP_COMMENT: 4763 return rb_refstring_get (entry->comment); 4764 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID: 4765 return rb_refstring_get (entry->musicbrainz_trackid); 4766 case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID: 4767 return rb_refstring_get (entry->musicbrainz_artistid); 4768 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID: 4769 return rb_refstring_get (entry->musicbrainz_albumid); 4770 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID: 4771 return rb_refstring_get (entry->musicbrainz_albumartistid); 4772 case RHYTHMDB_PROP_ARTIST_SORTNAME: 4773 return rb_refstring_get (entry->artist_sortname); 4774 case RHYTHMDB_PROP_ALBUM_SORTNAME: 4775 return rb_refstring_get (entry->album_sortname); 4776 case RHYTHMDB_PROP_ALBUM_ARTIST: 4777 return rb_refstring_get (entry->album_artist); 4778 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME: 4779 return rb_refstring_get (entry->album_artist_sortname); 4780 case RHYTHMDB_PROP_MEDIA_TYPE: 4781 return rb_refstring_get (entry->media_type); 4782 case RHYTHMDB_PROP_TITLE_SORT_KEY: 4783 return rb_refstring_get_sort_key (entry->title); 4784 case RHYTHMDB_PROP_ALBUM_SORT_KEY: 4785 return rb_refstring_get_sort_key (entry->album); 4786 case RHYTHMDB_PROP_ARTIST_SORT_KEY: 4787 return rb_refstring_get_sort_key (entry->artist); 4788 case RHYTHMDB_PROP_GENRE_SORT_KEY: 4789 return rb_refstring_get_sort_key (entry->genre); 4790 case RHYTHMDB_PROP_ARTIST_SORTNAME_SORT_KEY: 4791 return rb_refstring_get_sort_key (entry->artist_sortname); 4792 case RHYTHMDB_PROP_ALBUM_SORTNAME_SORT_KEY: 4793 return rb_refstring_get_sort_key (entry->album_sortname); 4794 case RHYTHMDB_PROP_ALBUM_ARTIST_SORT_KEY: 4795 return rb_refstring_get_sort_key (entry->album_artist); 4796 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME_SORT_KEY: 4797 return rb_refstring_get_sort_key (entry->album_artist_sortname); 4798 case RHYTHMDB_PROP_TITLE_FOLDED: 4799 return rb_refstring_get_folded (entry->title); 4800 case RHYTHMDB_PROP_ALBUM_FOLDED: 4801 return rb_refstring_get_folded (entry->album); 4802 case RHYTHMDB_PROP_ARTIST_FOLDED: 4803 return rb_refstring_get_folded (entry->artist); 4804 case RHYTHMDB_PROP_GENRE_FOLDED: 4805 return rb_refstring_get_folded (entry->genre); 4806 case RHYTHMDB_PROP_ARTIST_SORTNAME_FOLDED: 4807 return rb_refstring_get_folded (entry->artist_sortname); 4808 case RHYTHMDB_PROP_ALBUM_SORTNAME_FOLDED: 4809 return rb_refstring_get_folded (entry->album_sortname); 4810 case RHYTHMDB_PROP_ALBUM_ARTIST_FOLDED: 4811 return rb_refstring_get_folded (entry->album_artist); 4812 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME_FOLDED: 4813 return rb_refstring_get_folded (entry->album_artist_sortname); 4814 case RHYTHMDB_PROP_LOCATION: 4815 return rb_refstring_get (entry->location); 4816 case RHYTHMDB_PROP_MOUNTPOINT: 4817 return rb_refstring_get (entry->mountpoint); 4818 case RHYTHMDB_PROP_LAST_PLAYED_STR: 4819 return rb_refstring_get (entry->last_played_str); 4820 case RHYTHMDB_PROP_PLAYBACK_ERROR: 4821 return rb_refstring_get (entry->playback_error); 4822 case RHYTHMDB_PROP_FIRST_SEEN_STR: 4823 return rb_refstring_get (entry->first_seen_str); 4824 case RHYTHMDB_PROP_LAST_SEEN_STR: 4825 return rb_refstring_get (entry->last_seen_str); 4826 4827 /* synthetic properties */ 4828 case RHYTHMDB_PROP_SEARCH_MATCH: 4829 return NULL; 4830 case RHYTHMDB_PROP_KEYWORD: 4831 return NULL; 4832 4833 /* Podcast properties */ 4834 case RHYTHMDB_PROP_DESCRIPTION: 4835 if (podcast) 4836 return rb_refstring_get (podcast->description); 4837 else 4838 return NULL; 4839 case RHYTHMDB_PROP_SUBTITLE: 4840 if (podcast) 4841 return rb_refstring_get (podcast->subtitle); 4842 else 4843 return NULL; 4844 case RHYTHMDB_PROP_SUMMARY: 4845 if (podcast) 4846 return rb_refstring_get (podcast->summary); 4847 else 4848 return NULL; 4849 case RHYTHMDB_PROP_LANG: 4850 if (podcast) 4851 return rb_refstring_get (podcast->lang); 4852 else 4853 return NULL; 4854 case RHYTHMDB_PROP_COPYRIGHT: 4855 if (podcast) 4856 return rb_refstring_get (podcast->copyright); 4857 else 4858 return NULL; 4859 case RHYTHMDB_PROP_IMAGE: 4860 if (podcast) 4861 return rb_refstring_get (podcast->image); 4862 else 4863 return NULL; 4864 4865 default: 4866 g_assert_not_reached (); 4867 return NULL; 4868 } 4869 } 4870 4871 /** 4872 * rhythmdb_entry_get_refstring: 4873 * @entry: a #RhythmDBEntry 4874 * @propid: the property to return 4875 * 4876 * Returns an #RBRefString containing a string property of @entry. 4877 * 4878 * Return value: a #RBRefString, must be unreffed by caller. 4879 */ 4880 RBRefString * 4881 rhythmdb_entry_get_refstring (RhythmDBEntry *entry, 4882 RhythmDBPropType propid) 4883 { 4884 g_return_val_if_fail (entry != NULL, NULL); 4885 g_return_val_if_fail (entry->refcount > 0, NULL); 4886 4887 rhythmdb_entry_sync_mirrored (entry, propid); 4888 4889 switch (propid) { 4890 case RHYTHMDB_PROP_TITLE: 4891 return rb_refstring_ref (entry->title); 4892 case RHYTHMDB_PROP_ALBUM: 4893 return rb_refstring_ref (entry->album); 4894 case RHYTHMDB_PROP_ARTIST: 4895 return rb_refstring_ref (entry->artist); 4896 case RHYTHMDB_PROP_ALBUM_ARTIST: 4897 return rb_refstring_ref (entry->album_artist); 4898 case RHYTHMDB_PROP_GENRE: 4899 return rb_refstring_ref (entry->genre); 4900 case RHYTHMDB_PROP_COMMENT: 4901 return rb_refstring_ref (entry->comment); 4902 case RHYTHMDB_PROP_MUSICBRAINZ_TRACKID: 4903 return rb_refstring_ref (entry->musicbrainz_trackid); 4904 case RHYTHMDB_PROP_MUSICBRAINZ_ARTISTID: 4905 return rb_refstring_ref (entry->musicbrainz_artistid); 4906 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID: 4907 return rb_refstring_ref (entry->musicbrainz_albumid); 4908 case RHYTHMDB_PROP_MUSICBRAINZ_ALBUMARTISTID: 4909 return rb_refstring_ref (entry->musicbrainz_albumartistid); 4910 case RHYTHMDB_PROP_ARTIST_SORTNAME: 4911 return rb_refstring_ref (entry->artist_sortname); 4912 case RHYTHMDB_PROP_ALBUM_SORTNAME: 4913 return rb_refstring_ref (entry->album_sortname); 4914 case RHYTHMDB_PROP_ALBUM_ARTIST_SORTNAME: 4915 return rb_refstring_ref (entry->album_artist_sortname); 4916 case RHYTHMDB_PROP_MEDIA_TYPE: 4917 return rb_refstring_ref (entry->media_type); 4918 case RHYTHMDB_PROP_MOUNTPOINT: 4919 return rb_refstring_ref (entry->mountpoint); 4920 case RHYTHMDB_PROP_LAST_PLAYED_STR: 4921 return rb_refstring_ref (entry->last_played_str); 4922 case RHYTHMDB_PROP_FIRST_SEEN_STR: 4923 return rb_refstring_ref (entry->first_seen_str); 4924 case RHYTHMDB_PROP_LAST_SEEN_STR: 4925 return rb_refstring_ref (entry->last_seen_str); 4926 case RHYTHMDB_PROP_LOCATION: 4927 return rb_refstring_ref (entry->location); 4928 case RHYTHMDB_PROP_PLAYBACK_ERROR: 4929 return rb_refstring_ref (entry->playback_error); 4930 default: 4931 g_assert_not_reached (); 4932 return NULL; 4933 } 4934 } 4935 4936 /** 4937 * rhythmdb_entry_get_boolean: 4938 * @entry: a #RhythmDBEntry 4939 * @propid: property to return 4940 * 4941 * Returns the value of a boolean property of @entry. 4942 * 4943 * Return value: property value 4944 */ 4945 gboolean 4946 rhythmdb_entry_get_boolean (RhythmDBEntry *entry, 4947 RhythmDBPropType propid) 4948 { 4949 g_return_val_if_fail (entry != NULL, FALSE); 4950 4951 switch (propid) { 4952 case RHYTHMDB_PROP_HIDDEN: 4953 return ((entry->flags & RHYTHMDB_ENTRY_HIDDEN) != 0); 4954 default: 4955 g_assert_not_reached (); 4956 return FALSE; 4957 } 4958 } 4959 4960 /** 4961 * rhythmdb_entry_get_uint64: 4962 * @entry: a #RhythmDBEntry 4963 * @propid: property to return 4964 * 4965 * Returns the value of a 64bit unsigned integer property. 4966 * 4967 * Return value: property value 4968 */ 4969 guint64 4970 rhythmdb_entry_get_uint64 (RhythmDBEntry *entry, 4971 RhythmDBPropType propid) 4972 { 4973 g_return_val_if_fail (entry != NULL, 0); 4974 4975 switch (propid) { 4976 case RHYTHMDB_PROP_FILE_SIZE: 4977 return entry->file_size; 4978 default: 4979 g_assert_not_reached (); 4980 return 0; 4981 } 4982 } 4983 4984 /** 4985 * rhythmdb_entry_get_entry_type: 4986 * @entry: a #RhythmDBEntry 4987 * 4988 * Returns the #RhythmDBEntryType for @entry. This is used to access 4989 * entry type properties, to check that entries are of the same type, 4990 * and to call entry type methods. 4991 * 4992 * Return value: (transfer none): the #RhythmDBEntryType for @entry 4993 */ 4994 RhythmDBEntryType * 4995 rhythmdb_entry_get_entry_type (RhythmDBEntry *entry) 4996 { 4997 g_return_val_if_fail (entry != NULL, NULL); 4998 4999 return entry->type; 5000 } 5001 5002 /** 5003 * rhythmdb_entry_get_object: 5004 * @entry: a #RhythmDBEntry 5005 * @propid: the property to return 5006 * 5007 * Returns the value of an object property of @entry. 5008 * 5009 * Return value: (transfer none): property value 5010 */ 5011 GObject * 5012 rhythmdb_entry_get_object (RhythmDBEntry *entry, 5013 RhythmDBPropType propid) 5014 { 5015 g_return_val_if_fail (entry != NULL, NULL); 5016 5017 switch (propid) { 5018 case RHYTHMDB_PROP_TYPE: 5019 return G_OBJECT (entry->type); 5020 default: 5021 g_assert_not_reached (); 5022 return NULL; 5023 } 5024 } 5025 5026 /** 5027 * rhythmdb_entry_get_ulong: 5028 * @entry: a #RhythmDBEntry 5029 * @propid: property to return 5030 * 5031 * Returns the value of an unsigned long integer property of @entry. 5032 * 5033 * Return value: property value 5034 */ 5035 gulong 5036 rhythmdb_entry_get_ulong (RhythmDBEntry *entry, 5037 RhythmDBPropType propid) 5038 { 5039 RhythmDBPodcastFields *podcast = NULL; 5040 5041 g_return_val_if_fail (entry != NULL, 0); 5042 5043 if (entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_FEED || 5044 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_POST || 5045 entry->type == RHYTHMDB_ENTRY_TYPE_PODCAST_SEARCH) 5046 podcast = RHYTHMDB_ENTRY_GET_TYPE_DATA (entry, RhythmDBPodcastFields); 5047 5048 switch (propid) { 5049 case RHYTHMDB_PROP_ENTRY_ID: 5050 return entry->id; 5051 case RHYTHMDB_PROP_TRACK_NUMBER: 5052 return entry->tracknum; 5053 case RHYTHMDB_PROP_DISC_NUMBER: 5054 return entry->discnum; 5055 case RHYTHMDB_PROP_DURATION: 5056 return entry->duration; 5057 case RHYTHMDB_PROP_MTIME: 5058 return entry->mtime; 5059 case RHYTHMDB_PROP_FIRST_SEEN: 5060 return entry->first_seen; 5061 case RHYTHMDB_PROP_LAST_SEEN: 5062 return entry->last_seen; 5063 case RHYTHMDB_PROP_LAST_PLAYED: 5064 return entry->last_played; 5065 case RHYTHMDB_PROP_PLAY_COUNT: 5066 return entry->play_count; 5067 case RHYTHMDB_PROP_BITRATE: 5068 return entry->bitrate; 5069 case RHYTHMDB_PROP_DATE: 5070 if (g_date_valid (&entry->date)) 5071 return g_date_get_julian (&entry->date); 5072 else 5073 return 0; 5074 case RHYTHMDB_PROP_YEAR: 5075 if (g_date_valid (&entry->date)) 5076 return g_date_get_year (&entry->date); 5077 else 5078 return 0; 5079 case RHYTHMDB_PROP_POST_TIME: 5080 if (podcast) 5081 return podcast->post_time; 5082 else 5083 return 0; 5084 case RHYTHMDB_PROP_STATUS: 5085 if (podcast) 5086 return podcast->status; 5087 else 5088 return 0; 5089 default: 5090 g_assert_not_reached (); 5091 return 0; 5092 } 5093 } 5094 5095 /** 5096 * rhythmdb_entry_get_double: 5097 * @entry: a #RhythmDBEntry 5098 * @propid: the property to return 5099 * 5100 * Returns the value of a double-precision floating point property of @value. 5101 * 5102 * Return value: property value 5103 */ 5104 double 5105 rhythmdb_entry_get_double (RhythmDBEntry *entry, 5106 RhythmDBPropType propid) 5107 { 5108 g_return_val_if_fail (entry != NULL, 0); 5109 5110 switch (propid) { 5111 case RHYTHMDB_PROP_TRACK_GAIN: 5112 g_warning ("RHYTHMDB_PROP_TRACK_GAIN no longer supported"); 5113 return 0.0; 5114 case RHYTHMDB_PROP_TRACK_PEAK: 5115 g_warning ("RHYTHMDB_PROP_TRACK_PEAK no longer supported"); 5116 return 1.0; 5117 case RHYTHMDB_PROP_ALBUM_GAIN: 5118 g_warning ("RHYTHMDB_PROP_ALBUM_GAIN no longer supported"); 5119 return 0.0; 5120 case RHYTHMDB_PROP_ALBUM_PEAK: 5121 g_warning ("RHYTHMDB_PROP_ALBUM_PEAK no longer supported"); 5122 return 1.0; 5123 case RHYTHMDB_PROP_RATING: 5124 return entry->rating; 5125 case RHYTHMDB_PROP_BPM: 5126 return entry->bpm; 5127 default: 5128 g_assert_not_reached (); 5129 return 0.0; 5130 } 5131 } 5132 5133 5134 /** 5135 * rhythmdb_entry_keyword_add: 5136 * @db: the #RhythmDB 5137 * @entry: a #RhythmDBEntry. 5138 * @keyword: the keyword to add. 5139 * 5140 * Adds a keyword to an entry. 5141 * 5142 * Returns: whether the keyword was already on the entry 5143 */ 5144 gboolean 5145 rhythmdb_entry_keyword_add (RhythmDB *db, 5146 RhythmDBEntry *entry, 5147 RBRefString *keyword) 5148 { 5149 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 5150 gboolean ret; 5151 5152 ret = klass->impl_entry_keyword_add (db, entry, keyword); 5153 if (!ret) { 5154 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_KEYWORD_ADDED], 0, entry, keyword); 5155 } 5156 return ret; 5157 } 5158 5159 /** 5160 * rhythmdb_entry_keyword_remove: 5161 * @db: the #RhythmDB 5162 * @entry: a #RhythmDBEntry. 5163 * @keyword: the keyword to remove. 5164 * 5165 * Removed a keyword from an entry. 5166 * 5167 * Returns: whether the keyword had previously been added to the entry. 5168 */ 5169 gboolean 5170 rhythmdb_entry_keyword_remove (RhythmDB *db, 5171 RhythmDBEntry *entry, 5172 RBRefString *keyword) 5173 { 5174 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 5175 gboolean ret; 5176 5177 ret = klass->impl_entry_keyword_remove (db, entry, keyword); 5178 if (ret) { 5179 g_signal_emit (G_OBJECT (db), rhythmdb_signals[ENTRY_KEYWORD_REMOVED], 0, entry, keyword); 5180 } 5181 return ret; 5182 } 5183 5184 /** 5185 * rhythmdb_entry_keyword_has: 5186 * @db: the #RhythmDB 5187 * @entry: a #RhythmDBEntry. 5188 * @keyword: the keyword to check for. 5189 * 5190 * Checks whether a keyword is has been added to an entry. 5191 * 5192 * Returns: whether the keyword had been added to the entry. 5193 */ 5194 gboolean 5195 rhythmdb_entry_keyword_has (RhythmDB *db, 5196 RhythmDBEntry *entry, 5197 RBRefString *keyword) 5198 { 5199 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 5200 5201 return klass->impl_entry_keyword_has (db, entry, keyword); 5202 } 5203 5204 /** 5205 * rhythmdb_entry_keywords_get: 5206 * @db: the #RhythmDB 5207 * @entry: a #RhythmDBEntry. 5208 * 5209 * Gets the list ofkeywords that have been added to an entry. 5210 * 5211 * Returns: (element-type RBRefString) (transfer full): the list of keywords 5212 * that have been added to the entry. 5213 */ 5214 GList* 5215 rhythmdb_entry_keywords_get (RhythmDB *db, 5216 RhythmDBEntry *entry) 5217 { 5218 RhythmDBClass *klass = RHYTHMDB_GET_CLASS (db); 5219 5220 return klass->impl_entry_keywords_get (db, entry); 5221 } 5222 5223 /** 5224 * rhythmdb_entry_write_metadata_changes: 5225 * @db: the #RhythmDB 5226 * @entry: the #RhythmDBEntry to update 5227 * @changes: a list of changes to write 5228 * @error: returns error information 5229 * 5230 * This can be called from a #RhythmDBEntryType sync_metadata function 5231 * when the appropriate action is to write the metadata changes 5232 * to the file at the entry's location. 5233 */ 5234 void 5235 rhythmdb_entry_write_metadata_changes (RhythmDB *db, 5236 RhythmDBEntry *entry, 5237 GSList *changes, 5238 GError **error) 5239 { 5240 const char *uri; 5241 GError *local_error = NULL; 5242 GSList *t; 5243 5244 uri = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION); 5245 rb_metadata_reset (db->priv->metadata); 5246 5247 for (t = changes; t; t = t->next) { 5248 RBMetaDataField field; 5249 GValue val = {0,}; 5250 RhythmDBEntryChange *change = (RhythmDBEntryChange *)t->data; 5251 5252 if (metadata_field_from_prop (change->prop, &field) == FALSE) { 5253 continue; 5254 } 5255 5256 g_value_init (&val, rhythmdb_get_property_type (db, change->prop)); 5257 rhythmdb_entry_get (db, entry, change->prop, &val); 5258 rb_metadata_set (db->priv->metadata, field, &val); 5259 g_value_unset (&val); 5260 } 5261 5262 rb_metadata_save (db->priv->metadata, uri, &local_error); 5263 if (local_error != NULL) { 5264 RhythmDBAction *load_action; 5265 5266 /* reload the metadata, to revert the db changes */ 5267 rb_debug ("error saving metadata for %s: %s; reloading metadata to revert", 5268 rb_refstring_get (entry->location), 5269 local_error->message); 5270 load_action = g_slice_new0 (RhythmDBAction); 5271 load_action->type = RHYTHMDB_ACTION_LOAD; 5272 load_action->uri = rb_refstring_ref (entry->location); 5273 /* XXX entry types? */ 5274 g_async_queue_push (db->priv->action_queue, load_action); 5275 5276 g_propagate_error (error, local_error); 5277 } 5278 } 5279 5280 /** 5281 * rhythmdb_get_property_type: 5282 * @db: the #RhythmDB 5283 * @property_id: a property ID (#RhythmDBPropType) 5284 * 5285 * Returns the #GType for the value of the property. 5286 * 5287 * Return value: property value type 5288 */ 5289 GType 5290 rhythmdb_get_property_type (RhythmDB *db, 5291 guint property_id) 5292 { 5293 g_assert (property_id >= 0 && property_id < RHYTHMDB_NUM_PROPERTIES); 5294 return rhythmdb_properties[property_id].prop_type; 5295 } 5296 5297 /** 5298 * rhythmdb_entry_get_type: 5299 * 5300 * Returns the #GType for #RhythmDBEntry. The #GType for #RhythmDBEntry is a 5301 * boxed type, where copying the value references the entry and freeing it 5302 * unrefs it. 5303 * 5304 * Return value: value type 5305 */ 5306 GType 5307 rhythmdb_entry_get_type (void) 5308 { 5309 static GType type = 0; 5310 5311 if (G_UNLIKELY (type == 0)) { 5312 type = g_boxed_type_register_static ("RhythmDBEntry", 5313 (GBoxedCopyFunc)rhythmdb_entry_ref, 5314 (GBoxedFreeFunc)rhythmdb_entry_unref); 5315 } 5316 5317 return type; 5318 } 5319 5320 /** 5321 * rhythmdb_entry_change_get_type: 5322 * 5323 * Returns the #GType for #RhythmDBEntryChange. #RhythmDBEntryChange is stored as a 5324 * boxed value. Copying the value copies the full change, including old and new values. 5325 * 5326 * Return value: entry change value type 5327 */ 5328 GType 5329 rhythmdb_entry_change_get_type (void) 5330 { 5331 static GType type = 0; 5332 5333 if (G_UNLIKELY (type == 0)) { 5334 type = g_boxed_type_register_static ("RhythmDBEntryChange", 5335 (GBoxedCopyFunc)rhythmdb_entry_change_copy, 5336 (GBoxedFreeFunc)rhythmdb_entry_change_free); 5337 } 5338 return type; 5339 } 5340 5341 /** 5342 * rhythmdb_entry_is_lossless: 5343 * @entry: a #RhythmDBEntry 5344 * 5345 * Checks if @entry represents a file that is losslessly encoded. 5346 * An entry is considered lossless if it has no bitrate value and 5347 * its media type is "audio/x-flac". Other lossless encoding types 5348 * may be added in the future. 5349 * 5350 * Return value: %TRUE if @entry is lossless 5351 */ 5352 gboolean 5353 rhythmdb_entry_is_lossless (RhythmDBEntry *entry) 5354 { 5355 const char *media_type; 5356 5357 if (rhythmdb_entry_get_ulong (entry, RHYTHMDB_PROP_BITRATE) != 0) 5358 return FALSE; 5359 5360 media_type = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MEDIA_TYPE); 5361 return rb_gst_media_type_is_lossless (media_type); 5362 } 5363 5364 /** 5365 * rhythmdb_entry_create_ext_db_key: 5366 * @entry: a #RhythmDBEntry 5367 * @prop: the primary #RhythmDBPropType for metadata lookups 5368 * 5369 * Creates a #RBExtDBKey for finding external metadata 5370 * for a given property. This is mostly useful for finding album or 5371 * track related data. 5372 * 5373 * Return value: the new #RBExtDBKey 5374 */ 5375 RBExtDBKey * 5376 rhythmdb_entry_create_ext_db_key (RhythmDBEntry *entry, RhythmDBPropType prop) 5377 { 5378 RBExtDBKey *key; 5379 const char *str; 5380 5381 switch (prop) { 5382 case RHYTHMDB_PROP_ALBUM: 5383 key = rb_ext_db_key_create_lookup ("album", rhythmdb_entry_get_string (entry, prop)); 5384 rb_ext_db_key_add_field (key, 5385 "artist", 5386 rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST)); 5387 str = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM_ARTIST); 5388 if (g_strcmp0 (str, "") != 0 && g_strcmp0 (str, _("Unknown")) != 0) { 5389 rb_ext_db_key_add_field (key, "artist", str); 5390 } 5391 5392 str = rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_MUSICBRAINZ_ALBUMID); 5393 if (g_strcmp0 (str, "") != 0 && g_strcmp0 (str, _("Unknown")) != 0) { 5394 rb_ext_db_key_add_info (key, "musicbrainz-albumid", str); 5395 } 5396 5397 break; 5398 5399 case RHYTHMDB_PROP_TITLE: 5400 key = rb_ext_db_key_create_lookup ("title", rhythmdb_entry_get_string (entry, prop)); 5401 /* maybe these should be info? */ 5402 rb_ext_db_key_add_field (key, "artist", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ARTIST)); 5403 rb_ext_db_key_add_field (key, "album", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_ALBUM)); 5404 break; 5405 5406 case RHYTHMDB_PROP_ARTIST: 5407 /* not really sure what this might be useful for */ 5408 key = rb_ext_db_key_create_lookup ("artist", rhythmdb_entry_get_string (entry, prop)); 5409 break; 5410 5411 default: 5412 g_assert_not_reached (); 5413 } 5414 5415 rb_ext_db_key_add_info (key, "location", rhythmdb_entry_get_string (entry, RHYTHMDB_PROP_LOCATION)); 5416 return key; 5417 } 5418 5419 /** 5420 * rhythmdb_entry_matches_ext_db_key: 5421 * @db: #RhythmDB instance 5422 * @entry: a #RhythmDBEntry 5423 * @key: a #RBExtDBKey 5424 * 5425 * Checks whether @key matches @entry. 5426 * 5427 * Return value: %TRUE if the key matches the entry 5428 */ 5429 gboolean 5430 rhythmdb_entry_matches_ext_db_key (RhythmDB *db, RhythmDBEntry *entry, RBExtDBKey *key) 5431 { 5432 char **fields; 5433 int i; 5434 5435 fields = rb_ext_db_key_get_field_names (key); 5436 for (i = 0; fields[i] != NULL; i++) { 5437 RhythmDBPropType prop; 5438 RhythmDBPropType extra_prop; 5439 const char *v; 5440 5441 prop = rhythmdb_propid_from_nice_elt_name (db, (const xmlChar *)fields[i]); 5442 if (prop == -1) { 5443 if (rb_ext_db_key_field_matches (key, fields[i], NULL) == FALSE) { 5444 g_strfreev (fields); 5445 return FALSE; 5446 } 5447 continue; 5448 } 5449 5450 /* check additional values for some fields */ 5451 switch (prop) { 5452 case RHYTHMDB_PROP_ARTIST: 5453 extra_prop = RHYTHMDB_PROP_ALBUM_ARTIST; 5454 break; 5455 default: 5456 extra_prop = -1; 5457 break; 5458 } 5459 5460 if (extra_prop != -1) { 5461 v = rhythmdb_entry_get_string (entry, extra_prop); 5462 if (rb_ext_db_key_field_matches (key, fields[i], v)) 5463 continue; 5464 } 5465 5466 v = rhythmdb_entry_get_string (entry, prop); 5467 if (rb_ext_db_key_field_matches (key, fields[i], v) == FALSE) { 5468 g_strfreev (fields); 5469 return FALSE; 5470 } 5471 } 5472 5473 g_strfreev (fields); 5474 return TRUE; 5475 }