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 }