hythmbox-2.98/plugins/ipod/rb-ipod-db.c

No issues found

  1 /*
  2  *  Copyright (C) 2007 Christophe Fergeau  <teuf@gnome.org>
  3  *
  4  *  This program is free software; you can redistribute it and/or modify
  5  *  it under the terms of the GNU General Public License as published by
  6  *  the Free Software Foundation; either version 2 of the License, or
  7  *  (at your option) any later version.
  8  *
  9  *  The Rhythmbox authors hereby grant permission for non-GPL compatible
 10  *  GStreamer plugins to be used and distributed together with GStreamer
 11  *  and Rhythmbox. This permission is above and beyond the permissions granted
 12  *  by the GPL license by which Rhythmbox is covered. If you modify this code
 13  *  you may extend this exception to your version of the code, but you are not
 14  *  obligated to do so. If you do not wish to do so, delete this exception
 15  *  statement from your version.
 16  *
 17  *  This program is distributed in the hope that it will be useful,
 18  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 19  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 20  *  GNU General Public License for more details.
 21  *
 22  *  You should have received a copy of the GNU General Public License
 23  *  along with this program; if not, write to the Free Software
 24  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301  USA.
 25  *
 26  */
 27 #include "config.h"
 28 
 29 #include <string.h>
 30 
 31 #include "rb-ipod-db.h"
 32 #include "rb-debug.h"
 33 
 34 /*
 35  * This class is used to handle asynchronous saving of the iPod database 
 36  * content. We must be really careful when doing that because libgpod API
 37  * - and Itdb_iTunesDB in particular - isn't thread-safe at all.
 38  * Thus this class was introduced in an attempt to wrap that complexity, and
 39  * to hide it as much as possible from RbIpodSource.
 40  * 
 41  * When a save request for the iPod metadata is requested through 
 42  * rb_ipod_db_save_async, we start by delaying the saving by a few seconds
 43  * (using g_timeout_add) in case we'd get a bunch of very close save requests.
 44  * When the timeout callback triggers, we start by marking the IpodDB object
 45  * as read-only, ie while the async save is going on *NO MODIFICATIONS AT ALL
 46  * MUST BE MADE TO THE ITDB_ITUNESDB OBJECT*. Once the IpodDB is marked as 
 47  * read-only, we create a thread whose only purpose is to save the 
 48  * Itdb_iTunesDB content, and once it has done its job, we go back to the main
 49  * thread through a g_idle_add and mark the IpodDB object as read/write.
 50  *
 51  * Since the UI is not blocked during the async database saving (that's the 
 52  * whole point of this exercise after all ;), we log all IpodDB modifications
 53  * attempts in IpodDB::delayed_actions, and we'll replay them once the 
 54  * async saving is done. When these IpodDB modifications should trigger changes
 55  * in what RB displays (eg, it may be a song removal/addition from/to 
 56  * the iPod), RbIpodSource should update the GUI immediatly even if the IpodDB
 57  * hasn't been updated yet (actually, RbIpodSource shouldn't even know if
 58  * the IpodDB changes it requested were delayed or not).
 59  *
 60  * The approach describes above could seem to nicely hide the Itdb_iTunesDB
 61  * from RbIpodSource and prevent all unwanted modifications of the 
 62  * Itdb_iTunesDB while it's being saved, but there's unfortunately a big
 63  * gotcha: the Itdb_Track and Itdb_Playlist classes contains a pointer to the
 64  * Itdb_iTunesDB they are attached to, and seemingly innocent libgpod functions
 65  * can be called on those objects and modify the Itdb_iTunesDB through that 
 66  * pointer without RbIpodDB knowing. As an example, itdb_track_remove is one 
 67  * such functions. Consequently, one needs to be *really* careful when calling
 68  * libgpod functions from RbIpodSource and needs to consider if this function
 69  * should be wrapped in RbIpodDb (with the appropriate delayed handling) 
 70  * instead of directly calling it from RbIpodSource
 71  */
 72 
 73 typedef struct _RbIpodDelayedAction RbIpodDelayedAction;
 74 static void rb_ipod_free_delayed_action (RbIpodDelayedAction *action);
 75 static void rb_ipod_db_queue_remove_track (RbIpodDb *db,
 76 					   Itdb_Track *track);
 77 static void rb_ipod_db_queue_set_ipod_name (RbIpodDb *db, 
 78 					    const char *new_name);
 79 static void rb_ipod_db_queue_add_track (RbIpodDb *db, Itdb_Track *track);
 80 static void rb_ipod_db_queue_add_playlist (RbIpodDb *db, 
 81 					   Itdb_Playlist *playlist);
 82 static void rb_ipod_db_queue_remove_playlist (RbIpodDb *db, 
 83 					   Itdb_Playlist *playlist);
 84 static void rb_ipod_db_queue_rename_playlist (RbIpodDb *db, 
 85 					   Itdb_Playlist *playlist,
 86 					   const char *name);
 87 static void rb_ipod_db_queue_add_to_playlist (RbIpodDb *ipod_db, 
 88 					      Itdb_Playlist *playlist,
 89 					      Itdb_Track *track);
 90 static void rb_ipod_db_queue_remove_from_playlist (RbIpodDb *ipod_db, 
 91 						  Itdb_Playlist *playlist,
 92 						   Itdb_Track *track);
 93 static void rb_ipod_db_queue_set_thumbnail (RbIpodDb *db,
 94 					    Itdb_Track *track,
 95 					    GdkPixbuf *pixbuf);
 96 static void rb_ipod_db_process_delayed_actions (RbIpodDb *ipod_db);
 97 
 98 typedef struct {
 99 	Itdb_iTunesDB *itdb;
100 	gboolean needs_shuffle_db;
101 
102 	/* Read-only only applies to the iPod database, it's not an issue if
103 	 * the track transfer code copies a new song to the iPod while it's set
104 	 */
105 	/* FIXME: if a track is copied to the iPod while this is set, and if 
106 	 * the iPod goes away before we process the delayed actions and sync
107 	 * the database, then the copied file will be leaked (ie hidden in the 
108 	 * iPod directory tree but not accessible from the iPod UI)
109 	 */
110 	gboolean read_only;
111 	GQueue *delayed_actions;
112 	GThread *saving_thread;
113 
114 	guint save_timeout_id;
115 	guint save_idle_id;
116 
117 } RbIpodDbPrivate;
118 
119 G_DEFINE_DYNAMIC_TYPE (RbIpodDb, rb_ipod_db, G_TYPE_OBJECT)
120 
121 #define IPOD_DB_GET_PRIVATE(o)   (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_IPOD_DB, RbIpodDbPrivate))
122 
123 enum {
124 	BEFORE_SAVE,
125 	LAST_SIGNAL
126 };
127 
128 static guint signals[LAST_SIGNAL];
129 
130 static void
131 rb_itdb_save (RbIpodDb *ipod_db, GError **error)
132 {
133 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
134 	GError *err = NULL;
135 
136 	rb_debug ("Writing iPod database to disk");
137 	if (itdb_write (priv->itdb, &err) == FALSE) {
138 		g_warning ("Could not write database to iPod: %s", err->message);
139 		g_propagate_error (error, err);
140 		return;
141 	}
142 	if (priv->needs_shuffle_db) {
143 		itdb_shuffle_write (priv->itdb, error);
144 	}
145 #ifdef HAVE_ITDB_START_STOP_SYNC
146 	itdb_stop_sync (priv->itdb);
147 #endif
148 }
149 
150 static void
151 rb_ipod_db_init (RbIpodDb *db)
152 
153 {	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (db);
154 
155 	priv->delayed_actions = g_queue_new ();
156 }
157 
158 static void 
159 rb_ipod_db_dispose (GObject *object)
160 {
161 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (object);
162 	gboolean db_dirty = FALSE;
163 
164 	if (priv->saving_thread != NULL) {
165 		g_thread_join (priv->saving_thread);
166 		priv->saving_thread = NULL;
167 	}
168 
169 	if (priv->save_idle_id != 0) {
170 		g_source_remove (priv->save_idle_id);
171 		priv->save_idle_id = 0;
172 	}
173 
174 	/* Be careful, the order of the following cleanups is important, first
175 	 * we process the queued ipod database modifications, which may trigger
176 	 * modifications of save_timeout_id, then we make sure we cancel 
177 	 * potential in progress saves, and finally we sync the database
178 	 */
179 	if (priv->delayed_actions) {
180 		
181 		if (g_queue_get_length (priv->delayed_actions) != 0) {
182 			rb_ipod_db_process_delayed_actions (RB_IPOD_DB(object));
183 			db_dirty = TRUE;
184 		}
185 		/* The queue should be empty, but better be safe than 
186 		 * leaking 
187 		 */
188 		g_queue_foreach (priv->delayed_actions, 
189 				 (GFunc)rb_ipod_free_delayed_action,
190 				 NULL);
191 		g_queue_free (priv->delayed_actions);
192 		priv->delayed_actions = NULL;
193 	}
194 
195 	if (priv->save_timeout_id != 0) {
196 		g_source_remove (priv->save_timeout_id);
197 		priv->save_timeout_id = 0;
198 		db_dirty = TRUE;
199 	}
200 
201  	if (priv->itdb != NULL) {
202 		if (db_dirty) {
203 			rb_itdb_save (RB_IPOD_DB (object), NULL);
204 		}
205  		itdb_free (priv->itdb);
206 
207  		priv->itdb = NULL;
208 	}
209 
210 
211 	G_OBJECT_CLASS (rb_ipod_db_parent_class)->dispose (object);
212 }
213 
214 static void
215 rb_ipod_db_class_init (RbIpodDbClass *klass)
216 {
217 	GObjectClass *object_class = G_OBJECT_CLASS (klass);
218 
219 	object_class->dispose = rb_ipod_db_dispose;
220 
221 	/**
222 	 * RbIpodDb::before-save
223 	 * @db: the #RbIpodDb
224 	 *
225 	 * Emitted before iPod database write to disk.
226 	 */
227 	signals[BEFORE_SAVE] =
228 		g_signal_new ("before-save",
229 			      RB_TYPE_IPOD_DB,
230 			      G_SIGNAL_RUN_FIRST,
231 			      0,
232 			      NULL, NULL,
233 			      g_cclosure_marshal_VOID__VOID,
234 			      G_TYPE_NONE,
235 			      0);
236 
237 	g_type_class_add_private (klass, sizeof (RbIpodDbPrivate));
238 }
239 
240 static void
241 rb_ipod_db_class_finalize (RbIpodDbClass *klass)
242 {
243 }
244 
245 enum _RbIpodDelayedActionType {
246 	RB_IPOD_ACTION_SET_NAME,
247 	RB_IPOD_ACTION_ADD_TRACK,
248 	RB_IPOD_ACTION_REMOVE_TRACK,
249 	RB_IPOD_ACTION_ADD_PLAYLIST,
250 	RB_IPOD_ACTION_REMOVE_PLAYLIST,
251 	RB_IPOD_ACTION_RENAME_PLAYLIST,
252 	RB_IPOD_ACTION_SET_THUMBNAIL,
253 	RB_IPOD_ACTION_ADD_TO_PLAYLIST,
254 	RB_IPOD_ACTION_REMOVE_FROM_PLAYLIST
255 };
256 typedef enum _RbIpodDelayedActionType RbIpodDelayedActionType;
257 
258 struct _RbIpodDelayedSetThumbnail {
259 	Itdb_Track *track;
260 	GdkPixbuf *pixbuf;
261 };
262 typedef struct _RbIpodDelayedSetThumbnail RbIpodDelayedSetThumbnail;
263 
264 struct _RbIpodDelayedPlaylistOp {
265 	Itdb_Playlist *playlist;
266 	void *data;
267 };
268 typedef struct _RbIpodDelayedPlaylistOp RbIpodDelayedPlaylistOp;
269 
270 struct _RbIpodDelayedAction {
271 	RbIpodDelayedActionType type;
272 	union {
273 		gchar *name;
274 		Itdb_Track *track;
275 		RbIpodDelayedSetThumbnail thumbnail_data;
276 		RbIpodDelayedPlaylistOp playlist_op;
277 	};
278 };
279 
280 static void
281 rb_ipod_free_delayed_action (RbIpodDelayedAction *action) 
282 {
283 	switch (action->type) {
284 	case RB_IPOD_ACTION_SET_NAME:
285 		g_free (action->name);
286 		break;
287 	case RB_IPOD_ACTION_SET_THUMBNAIL:
288 		g_object_unref (action->thumbnail_data.pixbuf);
289 		break;
290 	case RB_IPOD_ACTION_ADD_TRACK:
291 		if (action->track != NULL) {
292 			g_warning ("non NULL Itdb_Track, shouldn't happen");
293 			itdb_track_free (action->track);
294 		}
295 		break;
296 	case RB_IPOD_ACTION_ADD_PLAYLIST:
297 		/* Do nothing */
298 		break;
299 	case RB_IPOD_ACTION_REMOVE_PLAYLIST:
300 		/* Do nothing */
301 		break;
302 	case RB_IPOD_ACTION_RENAME_PLAYLIST:
303 		g_free (action->playlist_op.data);
304 		break;
305 	case RB_IPOD_ACTION_REMOVE_TRACK:
306 		/* Do nothing */
307 		break;
308 	case RB_IPOD_ACTION_ADD_TO_PLAYLIST:
309 		/* Do nothing */
310 		break;
311 	case RB_IPOD_ACTION_REMOVE_FROM_PLAYLIST:
312 		/* Do nothing */
313 		break;
314 	}
315 	g_free (action);
316 }
317 
318 const char *
319 rb_ipod_db_get_ipod_name (RbIpodDb *db)
320 {
321 	Itdb_Playlist *mpl;
322 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (db);
323 	mpl = itdb_playlist_mpl (priv->itdb);
324 	if (mpl == NULL) {
325 		rb_debug ("Couldn't find iPod master playlist");
326 		return NULL;
327 	}
328 
329 	return mpl->name;	
330 }
331 
332 static void
333 rb_ipod_db_set_ipod_name_internal (RbIpodDb *ipod_db, const char *name)
334 {
335 	Itdb_Playlist *mpl;
336 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
337 
338 	mpl = itdb_playlist_mpl (priv->itdb);
339 	if (mpl != NULL) {
340 		if (mpl->name != NULL) {
341 			rb_debug ("Renaming iPod from %s to %s", mpl->name, name);
342 			if (strcmp (mpl->name, name) == 0) {
343 				rb_debug ("iPod is already named %s", name);
344 				return;
345 			}
346 		}
347 		g_free (mpl->name);
348 		mpl->name = g_strdup (name);
349 	} else {
350 		g_warning ("iPod's master playlist is missing");
351 	}
352 
353 	rb_ipod_db_save_async (ipod_db);
354 }
355 
356 static void
357 rb_ipod_db_remove_track_internal (RbIpodDb *ipod_db, Itdb_Track *track)
358 {
359 	GList *it;
360 
361 	for (it = track->itdb->playlists; it != NULL; it = it->next) {
362 		itdb_playlist_remove_track ((Itdb_Playlist *)it->data, track);
363 	}
364 	itdb_track_remove (track);
365 
366 	rb_ipod_db_save_async (ipod_db);
367 }
368 
369 static void
370 rb_ipod_db_set_thumbnail_internal (RbIpodDb *ipod_db,
371 				   Itdb_Track *track, 
372 				   GdkPixbuf *pixbuf)
373 {
374 	g_return_if_fail (track != NULL);
375 	g_return_if_fail (pixbuf != NULL);
376 
377 	itdb_track_set_thumbnails_from_pixbuf (track, pixbuf);
378 
379 	rb_ipod_db_save_async (ipod_db);
380 }
381 
382 
383 static void
384 rb_ipod_db_add_playlist_internal (RbIpodDb *ipod_db, 
385 				  Itdb_Playlist *playlist)
386 {
387 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
388 
389 	itdb_playlist_add (priv->itdb, playlist, -1);
390 
391 	rb_ipod_db_save_async (ipod_db);
392 }
393 
394 static void
395 rb_ipod_db_remove_playlist_internal (RbIpodDb *ipod_db, 
396 				     Itdb_Playlist *playlist)
397 {
398 	itdb_playlist_remove (playlist);
399 	rb_ipod_db_save_async (ipod_db);
400 }
401 
402 static void
403 rb_ipod_db_rename_playlist_internal (RbIpodDb *ipod_db, 
404 				     Itdb_Playlist *playlist,
405 				     const char *name)
406 {
407 	g_free (playlist->name);
408 	playlist->name = g_strdup (name);
409 	rb_ipod_db_save_async (ipod_db);
410 }
411 
412 static void
413 rb_ipod_db_add_track_internal (RbIpodDb *ipod_db, Itdb_Track *track)
414 {
415 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
416 
417 	itdb_track_add (priv->itdb, track, -1);
418 	itdb_playlist_add_track (itdb_playlist_mpl (priv->itdb),
419 				 track, -1);
420 
421 	rb_ipod_db_save_async (ipod_db);
422 }
423 
424 static void
425 rb_ipod_db_add_to_playlist_internal (RbIpodDb* ipod_db,
426 				     Itdb_Playlist *playlist,
427 				     Itdb_Track *track)
428 {
429 	itdb_playlist_add_track (playlist, track, -1);
430 	rb_ipod_db_save_async (ipod_db);
431 }
432 
433 static void 
434 rb_ipod_db_remove_from_playlist_internal (RbIpodDb* ipod_db, 
435 					  Itdb_Playlist *playlist,
436 					  Itdb_Track *track)
437 {
438 	itdb_playlist_remove_track (playlist, track);
439 	rb_ipod_db_save_async (ipod_db);
440 }
441 
442 
443 void
444 rb_ipod_db_set_thumbnail (RbIpodDb* ipod_db,
445 			  Itdb_Track *track, 
446 			  GdkPixbuf *pixbuf)
447 {
448 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
449 
450 	if (priv->read_only) {
451 		rb_ipod_db_queue_set_thumbnail (ipod_db, track, pixbuf);
452 	} else {
453 		rb_ipod_db_set_thumbnail_internal (ipod_db, track, pixbuf);
454 	}
455 }
456 
457 void
458 rb_ipod_db_add_track (RbIpodDb* ipod_db, Itdb_Track *track)
459 {
460 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
461 
462 	if (priv->read_only) {
463 		rb_ipod_db_queue_add_track (ipod_db, track);
464 	} else {
465 		rb_ipod_db_add_track_internal (ipod_db, track);
466 	}
467 }
468 
469 void
470 rb_ipod_db_add_playlist (RbIpodDb* ipod_db, Itdb_Playlist *playlist)
471 {
472 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
473 
474 	if (priv->read_only) {
475 		rb_ipod_db_queue_add_playlist (ipod_db, playlist);
476 	} else {
477 		rb_ipod_db_add_playlist_internal (ipod_db, playlist);
478 	}
479 }
480 
481 void
482 rb_ipod_db_remove_playlist (RbIpodDb* ipod_db, Itdb_Playlist *playlist)
483 {
484 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
485 
486 	if (priv->read_only) {
487 		rb_ipod_db_queue_remove_playlist (ipod_db, playlist);
488 	} else {
489 		rb_ipod_db_remove_playlist_internal (ipod_db, playlist);
490 	}
491 }
492 
493 void
494 rb_ipod_db_rename_playlist (RbIpodDb* ipod_db, Itdb_Playlist *playlist, const char *name)
495 {
496 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
497 
498 	if (priv->read_only) {
499 		rb_ipod_db_queue_rename_playlist (ipod_db, playlist, name);
500 	} else {
501 		rb_ipod_db_rename_playlist_internal (ipod_db, playlist, name);
502 	}
503 }
504 
505 
506 void
507 rb_ipod_db_remove_track (RbIpodDb* ipod_db, Itdb_Track *track)
508 {
509 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
510 
511 	if (priv->read_only) {
512 		rb_ipod_db_queue_remove_track (ipod_db, track);
513 	} else {
514 		rb_ipod_db_remove_track_internal (ipod_db, track);
515 	}
516 }
517 
518 void
519 rb_ipod_db_set_ipod_name (RbIpodDb *ipod_db, const char *name)
520 {
521 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
522 
523 	if (priv->read_only) {
524 		rb_ipod_db_queue_set_ipod_name (ipod_db, name);
525 	} else {
526 		rb_ipod_db_set_ipod_name_internal (ipod_db, name);
527 	}
528 }
529 
530 void
531 rb_ipod_db_add_to_playlist (RbIpodDb* ipod_db, Itdb_Playlist *playlist,
532 				 Itdb_Track *track)
533 {
534 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
535 
536 	if (priv->read_only) {
537 		rb_ipod_db_queue_add_to_playlist (ipod_db, playlist, track);
538 	} else {
539 		rb_ipod_db_add_to_playlist_internal (ipod_db, playlist, track);
540 	}	
541 }
542 
543 void
544 rb_ipod_db_remove_from_playlist (RbIpodDb* ipod_db, 
545 				 Itdb_Playlist *playlist,
546 				 Itdb_Track *track)
547 {
548 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
549 
550 	if (priv->read_only) {
551 		rb_ipod_db_queue_remove_from_playlist (ipod_db, playlist,
552 						       track);
553 	} else {
554 		rb_ipod_db_remove_from_playlist_internal (ipod_db, 
555 							  playlist, track);
556 	}	
557 }
558 
559 
560 static void 
561 rb_ipod_db_process_delayed_actions (RbIpodDb *ipod_db)
562 {
563 	RbIpodDelayedAction *action;
564 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
565 
566 	rb_debug ("Handling delayed iPod actions");
567 
568 	action = g_queue_pop_head (priv->delayed_actions);
569 	if (action != NULL) {
570 		/* Schedule a database save if there was at least one delayed
571 		 * action
572 		 */
573 		rb_ipod_db_save_async (ipod_db);
574 	}
575 	while (action != NULL) {
576 		switch (action->type) {
577 		case RB_IPOD_ACTION_SET_NAME:
578 			rb_debug ("IPOD_ACTION_SET_NAME (%s)",
579 				  action->name);
580 			rb_ipod_db_set_ipod_name_internal (ipod_db,
581 							   action->name);
582 			break;
583 		case RB_IPOD_ACTION_SET_THUMBNAIL:
584 			rb_debug ("IPOD_ACTION_SET_THUMBNAIL");
585 			rb_ipod_db_set_thumbnail_internal (ipod_db,
586 							   action->thumbnail_data.track,
587 							   action->thumbnail_data.pixbuf);
588 			break;
589 		case RB_IPOD_ACTION_REMOVE_TRACK:
590 			rb_debug ("IPOD_ACTION_REMOVE_TRACK");
591 			rb_ipod_db_remove_track_internal (ipod_db,
592 							  action->track);
593 			break;
594 		case RB_IPOD_ACTION_ADD_TRACK:
595 			rb_debug ("IPOD_ACTION_ADD_TRACK");
596 			rb_ipod_db_add_track_internal (ipod_db, action->track);
597 			/* The track was added to the iPod database, 'action'
598 			 * is no longer responsible for its memory handling
599 			 */
600 			action->track = NULL;
601 			break;
602 		case RB_IPOD_ACTION_ADD_PLAYLIST:
603 			rb_debug ("IPOD_ACTION_ADD_PLAYLIST");
604 			rb_ipod_db_add_playlist_internal (ipod_db, 
605 							  action->playlist_op.playlist);
606 			break;
607 		case RB_IPOD_ACTION_REMOVE_PLAYLIST:
608 			rb_debug ("IPOD_ACTION_REMOVE_PLAYLIST");
609 			rb_ipod_db_remove_playlist_internal (ipod_db, 
610 							     action->playlist_op.playlist);
611 			break;
612 		case RB_IPOD_ACTION_RENAME_PLAYLIST:
613 			rb_debug ("IPOD_ACTION_RENAME_PLAYLIST");
614 			rb_ipod_db_rename_playlist_internal (ipod_db,
615 							     action->playlist_op.playlist,
616 							     (const char *)action->playlist_op.data);
617 			break;
618 		case RB_IPOD_ACTION_ADD_TO_PLAYLIST:
619 			rb_debug ("IPOD_ACTION_ADD_TO_PLAYLIST");
620 			rb_ipod_db_add_to_playlist_internal (ipod_db, 
621 							     action->playlist_op.playlist,
622 							     (Itdb_Track *)action->playlist_op.data);
623 			break;
624 		case RB_IPOD_ACTION_REMOVE_FROM_PLAYLIST:
625 			rb_debug ("IPOD_ACTION_REMOVE_FROM_PLAYLIST");
626 			rb_ipod_db_remove_from_playlist_internal (ipod_db, 
627 								  action->playlist_op.playlist,
628 								  (Itdb_Track *)action->playlist_op.data);
629 			break;
630 		}
631 		rb_ipod_free_delayed_action (action);
632 		action = g_queue_pop_head (priv->delayed_actions);
633 	}
634 }
635 
636 static void
637 rb_ipod_db_queue_remove_track (RbIpodDb *ipod_db,
638 			       Itdb_Track *track)
639 {
640 	RbIpodDelayedAction *action;
641 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
642 	
643 	g_assert (priv->read_only);
644 	rb_debug ("Queueing track remove action since the iPod database is currently read-only");
645 	action = g_new0 (RbIpodDelayedAction, 1);
646 	action->type = RB_IPOD_ACTION_REMOVE_TRACK;
647 	action->track = track;
648 	g_queue_push_tail (priv->delayed_actions, action);
649 }
650 
651 static void
652 rb_ipod_db_queue_set_ipod_name (RbIpodDb *ipod_db, 
653 				const char *new_name)
654 {
655 	RbIpodDelayedAction *action;
656 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
657 		
658 	g_assert (priv->read_only);
659 	rb_debug ("Queueing set name action since the iPod database is currently read-only");
660 	action = g_new0 (RbIpodDelayedAction, 1);
661 	action->type = RB_IPOD_ACTION_SET_NAME;
662 	action->name = g_strdup (new_name);
663 	g_queue_push_tail (priv->delayed_actions, action);
664 }
665 
666 static void
667 rb_ipod_db_queue_add_playlist (RbIpodDb *ipod_db,
668 			       Itdb_Playlist *playlist)
669 {
670 	RbIpodDelayedAction *action;
671 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
672 	
673 	g_assert (priv->read_only);
674 	rb_debug ("Queueing add playlist action since the iPod database is currently read-only");
675 	action = g_new0 (RbIpodDelayedAction, 1);
676 	action->type = RB_IPOD_ACTION_ADD_PLAYLIST;
677 	action->playlist_op.playlist = playlist;
678 	g_queue_push_tail (priv->delayed_actions, action);
679 }
680 
681 static void
682 rb_ipod_db_queue_remove_playlist (RbIpodDb *ipod_db,
683 				  Itdb_Playlist *playlist)
684 {
685 	RbIpodDelayedAction *action;
686 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
687 	
688 	g_assert (priv->read_only);
689 	rb_debug ("Queueing remove playlist action since the iPod database is currently read-only");
690 	action = g_new0 (RbIpodDelayedAction, 1);
691 	action->type = RB_IPOD_ACTION_REMOVE_PLAYLIST;
692 	action->playlist_op.playlist = playlist;
693 	g_queue_push_tail (priv->delayed_actions, action);
694 }
695 
696 static void
697 rb_ipod_db_queue_rename_playlist (RbIpodDb *ipod_db,
698 				  Itdb_Playlist *playlist,
699 				  const char *name)
700 {
701 	RbIpodDelayedAction *action;
702 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
703 	
704 	g_assert (priv->read_only);
705 	rb_debug ("Queueing rename playlist action since the iPod database is currently read-only");
706         g_print ("playlist queueing: %p %p %s\n", playlist, playlist->name, playlist->name);
707 	action = g_new0 (RbIpodDelayedAction, 1);
708 	action->type = RB_IPOD_ACTION_RENAME_PLAYLIST;
709 	action->playlist_op.playlist = playlist;
710 	action->playlist_op.data = g_strdup (name);
711 	g_queue_push_tail (priv->delayed_actions, action);
712 }
713 
714 static void
715 rb_ipod_db_queue_add_track (RbIpodDb *ipod_db, Itdb_Track *track)
716 {
717 	RbIpodDelayedAction *action;
718 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
719 	
720 	g_assert (priv->read_only);
721 	rb_debug ("Queueing add track action since the iPod database is currently read-only");
722 	action = g_new0 (RbIpodDelayedAction, 1);
723 	action->type = RB_IPOD_ACTION_ADD_TRACK;
724 	action->track = track;
725 	g_queue_push_tail (priv->delayed_actions, action);
726 }
727 
728 static void
729 rb_ipod_db_queue_add_to_playlist (RbIpodDb *ipod_db, 
730 				  Itdb_Playlist *playlist,
731 				  Itdb_Track *track)
732 {
733 	RbIpodDelayedAction *action;
734 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
735 	
736 	g_assert (priv->read_only);
737 	rb_debug ("Queueing add to playlist action since the iPod database is currently read-only");
738 	action = g_new0 (RbIpodDelayedAction, 1);
739 	action->type = RB_IPOD_ACTION_ADD_TO_PLAYLIST;
740 	action->playlist_op.playlist = playlist;
741 	action->playlist_op.data = track;
742 	g_queue_push_tail (priv->delayed_actions, action);
743 }
744 
745 static void
746 rb_ipod_db_queue_remove_from_playlist (RbIpodDb *ipod_db, 
747 				       Itdb_Playlist *playlist,
748 				       Itdb_Track *track)
749 {
750 	RbIpodDelayedAction *action;
751 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
752 	
753 	g_assert (priv->read_only);
754 	rb_debug ("Queueing remove from playlist action since the iPod database is currently read-only");
755 	action = g_new0 (RbIpodDelayedAction, 1);
756 	action->type = RB_IPOD_ACTION_REMOVE_FROM_PLAYLIST;
757 	action->playlist_op.playlist = playlist;
758 	action->playlist_op.data = track;
759 	g_queue_push_tail (priv->delayed_actions, action);
760 }
761 
762 static void
763 rb_ipod_db_queue_set_thumbnail (RbIpodDb *ipod_db,
764 				Itdb_Track *track,
765 				GdkPixbuf *pixbuf)
766 {
767 	RbIpodDelayedAction *action;
768 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
769 	
770 	g_assert (priv->read_only);
771 	rb_debug ("Queueing set thumbnail action since the iPod database is currently read-only");
772 	action = g_new0 (RbIpodDelayedAction, 1);
773 	action->type = RB_IPOD_ACTION_SET_THUMBNAIL;
774 	action->thumbnail_data.track = track;
775 	action->thumbnail_data.pixbuf = g_object_ref (pixbuf);
776 	g_queue_push_tail (priv->delayed_actions, action);
777 }
778 
779 static gboolean
780 rb_ipod_db_load (RbIpodDb *ipod_db, GMount *mount)
781 {
782 	GFile *mount_root;
783 	char *mount_path;
784 	const Itdb_IpodInfo *info;
785 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
786 
787 	mount_root = g_mount_get_root (mount);
788 	if (mount_root == NULL) {
789 		return FALSE;
790 	}
791 	mount_path = g_file_get_path (mount_root);
792 	g_object_unref (mount_root);
793 
794  	priv->itdb = itdb_parse (mount_path, NULL);
795 	g_free (mount_path);
796 
797         if (priv->itdb == NULL) {
798 		return FALSE;
799         }
800 
801 	info = itdb_device_get_ipod_info (priv->itdb->device);
802 	if (info->ipod_generation == ITDB_IPOD_GENERATION_UNKNOWN ||
803 	    info->ipod_generation == ITDB_IPOD_GENERATION_SHUFFLE_1 ||
804 	    info->ipod_generation == ITDB_IPOD_GENERATION_SHUFFLE_2 ||
805 	    info->ipod_generation == ITDB_IPOD_GENERATION_SHUFFLE_3) {
806 		priv->needs_shuffle_db = TRUE;
807 	} else {
808 		priv->needs_shuffle_db = FALSE;
809 	}
810 
811         return TRUE;
812 }
813 
814 RbIpodDb *rb_ipod_db_new (GMount *mount)
815 {
816 	RbIpodDb *db;
817         gboolean success;
818 
819 	g_return_val_if_fail (mount != NULL, NULL);
820 
821 	db = g_object_new (RB_TYPE_IPOD_DB, NULL);
822 	if (db == NULL) {
823 		return NULL;
824 	}
825 
826 	success = rb_ipod_db_load (db, mount);
827 
828 	if (success == FALSE) {
829 		return NULL;
830 	} else {
831 		return db;
832 	}
833 }
834 
835 
836 /* Threaded iTunesDB save */
837 static gboolean
838 ipod_db_saved_idle_cb (RbIpodDb *ipod_db)
839 {
840 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
841 
842 	g_assert (priv->read_only);
843 	g_thread_join (priv->saving_thread);
844 	priv->saving_thread = NULL;
845 	priv->read_only = FALSE;
846 	rb_debug ("Switching iPod database to read-write");
847 
848 	rb_ipod_db_process_delayed_actions (ipod_db);
849 
850 	priv->save_idle_id = 0;
851 
852 	rb_debug ("End of iPod database save");
853 	return FALSE;
854 }
855 
856 static gpointer
857 saving_thread (RbIpodDb *ipod_db)
858 {
859 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
860 
861 	g_assert (priv->read_only);
862 
863 	rb_itdb_save (ipod_db, NULL);
864 	priv->save_idle_id = g_idle_add ((GSourceFunc)ipod_db_saved_idle_cb, 
865 					 ipod_db);
866 	
867 	return NULL;
868 }
869 
870 static gboolean
871 save_timeout_cb (RbIpodDb *ipod_db)
872 {
873 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
874 
875 	if (priv->read_only) {
876 		g_warning ("Database is read-only, not saving");
877 		return TRUE;
878 	}
879 
880 	/* Tell everyone about the save */
881 	g_signal_emit (G_OBJECT (ipod_db),
882 		       signals[BEFORE_SAVE],
883 		       0);
884 
885 	rb_debug ("Starting iPod database save");
886 	rb_debug ("Switching iPod database to read-only");
887 	priv->read_only = TRUE;
888 	
889 	priv->saving_thread = g_thread_new ("ipod-db-save",
890 					    (GThreadFunc)saving_thread,
891 					    ipod_db);
892 	priv->save_timeout_id = 0;
893 	return FALSE;
894 }
895 
896 void
897 rb_ipod_db_save_async (RbIpodDb *ipod_db)
898 {
899 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
900 
901 	if (priv->save_timeout_id == 0) {
902 #ifdef HAVE_ITDB_START_STOP_SYNC
903 		itdb_start_sync (priv->itdb);
904 #endif
905 		rb_debug ("Scheduling iPod database save in 2 seconds");
906 	} else {
907 		g_source_remove (priv->save_timeout_id);
908 		rb_debug ("Database save already scheduled, pushing back save in 2 seconds from now");
909 	}
910 	priv->save_timeout_id = g_timeout_add_seconds (2,
911 			                               (GSourceFunc)save_timeout_cb,
912 						       ipod_db);
913 }
914 
915 GList *
916 rb_ipod_db_get_playlists (RbIpodDb *ipod_db)
917 {
918 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
919 
920 	return g_list_copy (priv->itdb->playlists);
921 }
922 
923 Itdb_Playlist *
924 rb_ipod_db_get_playlist_by_name (RbIpodDb *ipod_db,
925 				 gchar *name)
926 {
927 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
928 
929 	return itdb_playlist_by_name (priv->itdb, name);
930 }
931 
932 GList *
933 rb_ipod_db_get_tracks (RbIpodDb *ipod_db)
934 {
935 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
936 
937 	return priv->itdb->tracks;
938 }
939 
940 const char *
941 rb_ipod_db_get_mount_path (RbIpodDb *ipod_db)
942 {
943 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
944 
945 	return itdb_get_mountpoint (priv->itdb);
946 }
947 
948 Itdb_Device *
949 rb_ipod_db_get_device (RbIpodDb *ipod_db)
950 {
951 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
952 	if (priv->itdb == NULL) {
953 		return NULL;
954 	}
955 
956 	return priv->itdb->device;
957 }
958 
959 guint32
960 rb_ipod_db_get_database_version (RbIpodDb *ipod_db)
961 {
962 	RbIpodDbPrivate *priv = IPOD_DB_GET_PRIVATE (ipod_db);
963 
964 	return priv->itdb->version;
965 }
966 
967 void
968 _rb_ipod_db_register_type (GTypeModule *module)
969 {
970 	rb_ipod_db_register_type (module);
971 }