1 /* Time-stamp: <2008-07-06 10:38:05 jcs>
2 |
3 |  Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
4 |  Part of the gtkpod project.
5 |
6 |  URL: http://www.gtkpod.org/
7 |  URL: http://gtkpod.sourceforge.net/
8 |
9 |  This program is free software; you can redistribute it and/or modify
10 |  it under the terms of the GNU General Public License as published by
11 |  the Free Software Foundation; either version 2 of the License, or
12 |  (at your option) any later version.
13 |
14 |  This program is distributed in the hope that it will be useful,
15 |  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 |  GNU General Public License for more details.
18 |
19 |  You should have received a copy of the GNU General Public License
20 |  along with this program; if not, write to the Free Software
21 |  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 |
23 |  iTunes and iPod are trademarks of Apple
24 |
25 |  This product is not supported/written/published by Apple!
26 |
27 |  $Id$
28 */
29 
30 /* This file provides functions for syncing a directory or directories
31  * with a playlist */
32 
33 #include <libintl.h>
34 #include <string.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <unistd.h>
38 #include "display_itdb.h"
39 #include "file.h"
40 #include "info.h"
41 #include "misc.h"
42 #include "misc_track.h"
43 #include "prefs.h"
44 #include "syncdir.h"
45 
46 
47 struct add_files_data
48 {
49     Playlist *playlist;
50     GList    **tracks_updated;
51     GHashTable *filepath_hash;
52 };
53 
54 /* Used in the callback after adding a new track to
55  * to add to the filehash */
56 struct added_file_data
57 {
58     GHashTable *filepath_hash;
59     gchar *filepath;
60 };
61 
62 
63 /**
64  * confirm_sync_dirs:
65  *
66  * @dirs_hash: hash table containing the directory names
67  * @key_sync_confirm_dirs: preference key to specify whether or not
68  *            the list of directories should be confirmed. The
69  *            confirmation dialog may change the value of this prefs
70  *            entry. If NULL confirmation takes place.
71  *
72  * Have the user confirm which directories should be included into the
73  * confirmation process.
74  *
75  * Return value: FALSE: user aborted. TRUE: otherwise. @dirs_hash will
76  * be adjusted to reflect the selected directories.
77  */
confirm_sync_dirs(GHashTable * dirs_hash,const gchar * key_sync_confirm_dirs)78 static gboolean confirm_sync_dirs (GHashTable *dirs_hash,
79 				   const gchar *key_sync_confirm_dirs)
80 {
81     g_return_val_if_fail (dirs_hash, FALSE);
82 
83     if (key_sync_confirm_dirs && !prefs_get_int (key_sync_confirm_dirs))
84 	return TRUE;
85 
86     /* FIXME: implement confirmation (doesn't strike me as a major
87      * feature -- feel free to contribute)
88      *
89      * The idea would be to have the user check/uncheck each of the
90      * individual directories. @dirs_hash will be adjusted to reflect
91      * the selected directories.
92      */
93     return TRUE;
94 }
95 
96 
97 /**
98  * confirm_delete_tracks:
99  *
100  * @tracks: GList with tracks that are supposed to be removed from the
101  *          iPod or the local repository.
102  * @key_sync_confirm_delete: preference key to specify whether or not
103  *          the removal of tracks should be confirmed. The
104  *          confirmation dialog may change the value of this prefs
105  *          entry. If NULL confirmation takes place.
106  *
107  * Return value: TRUE: it's OK to remove the tracks. FALSE: it's not
108  *               OK to remove the tracks. TRUE is also given if no
109  *               tracks are present, @key_sync_confirm_delete is NULL
110  *               or it's setting is 'FALSE' (0).
111  */
112 
confirm_delete_tracks(GList * tracks,const gchar * key_sync_confirm_delete)113 static gboolean confirm_delete_tracks (GList *tracks,
114 				       const gchar *key_sync_confirm_delete)
115 {
116     GtkResponseType response;
117     struct DeleteData dd;
118     gchar *label, *title;
119     GString *string;
120     iTunesDB *itdb;
121     Track *tr;
122 
123     if (tracks == NULL)
124 	return TRUE;
125 
126     if (key_sync_confirm_delete &&
127 	!prefs_get_int (key_sync_confirm_delete))
128 	return TRUE;
129 
130     tr = g_list_nth_data (tracks, 0);
131     g_return_val_if_fail (tr, FALSE);
132     itdb = tr->itdb;
133     g_return_val_if_fail (itdb, FALSE);
134 
135     dd.itdb = itdb;
136     dd.pl = NULL;
137     dd.tracks = tracks;
138     if (itdb->usertype & GP_ITDB_TYPE_IPOD)
139 	dd.deleteaction = DELETE_ACTION_IPOD;
140     if (itdb->usertype & GP_ITDB_TYPE_LOCAL)
141 	dd.deleteaction = DELETE_ACTION_DATABASE;
142 
143     delete_populate_settings (&dd,
144 			      &label, &title,
145 			      NULL, NULL,
146 			      &string);
147 
148     response = gtkpod_confirmation (
149 	-1,                       /* gint id, */
150 	TRUE,                     /* gboolean modal, */
151 	title,                    /* title */
152 	label,                    /* label */
153 	string->str,              /* scrolled text */
154 	NULL, 0, NULL,            /* option 1 */
155 	NULL, 0, NULL,            /* option 2 */
156 	TRUE,                     /* gboolean confirm_again, */
157 	key_sync_confirm_delete,  /* ConfHandlerOpt confirm_again_key,*/
158 	CONF_NULL_HANDLER,        /* ConfHandler ok_handler,*/
159 	NULL,                     /* don't show "Apply" button */
160 	CONF_NULL_HANDLER,        /* cancel_handler,*/
161 	NULL,                     /* gpointer user_data1,*/
162 	NULL);                    /* gpointer user_data2,*/
163 
164 
165     g_free (label);
166     g_free (title);
167     g_string_free (string, TRUE);
168 
169     if (response == GTK_RESPONSE_OK)
170     {
171 	/* it's OK to remove the tracks */
172 	return TRUE;
173     }
174     else
175     {
176 	/* better not delete the tracks */
177 	return FALSE;
178     }
179 }
180 
181 
182 /* Used by sync_show_summary() */
sync_add_tracks(GString * str,GList * tracks,const gchar * title)183 static void sync_add_tracks (GString *str,
184 			     GList *tracks, const gchar *title)
185 {
186     GList *gl;
187 
188     g_return_if_fail (str);
189     g_return_if_fail (title);
190 
191     if (tracks)
192     {
193 	g_string_append (str, title);
194 
195 	for (gl=tracks; gl; gl=gl->next)
196 	{
197 	    gchar *buf;
198 	    Track *tr = gl->data;
199 	    g_return_if_fail (tr);
200 
201 	    buf = get_track_info (tr, FALSE);
202 	    g_string_append_printf (str, "%s\n", buf);
203 	    g_free (buf);
204 	}
205 	g_string_append_printf (str, "\n\n");
206     }
207 }
208 
209 
210 
211 /**
212  * sync_show_summary:
213  *
214  * @key_sync_show_summary: preference key to specify whether or not a
215  *          summary should be shown or not. If NULL, the summary is
216  *          shown. This key may be changed by the confirmation dialog.
217  * @playlist: playlist where are syncing with.
218  * @tracks_to_delete_from_ipod: GList with tracks to be deleted from
219  *          the iPod or local repository.
220  * @tracks_to_delete_from_playlist: GList with tracks to be deleted
221  *          from @playlist.
222  * @tracks_updated: GList with tracks that have been updated.
223  */
show_sync_summary(const gchar * key_sync_show_summary,Playlist * playlist,GList * tracks_to_delete_from_ipod,GList * tracks_to_delete_from_playlist,GList * tracks_updated)224 static void show_sync_summary (const gchar *key_sync_show_summary,
225 			       Playlist *playlist,
226 			       GList *tracks_to_delete_from_ipod,
227 			       GList *tracks_to_delete_from_playlist,
228 			       GList *tracks_updated)
229 {
230     GString *summary;
231     Playlist *mpl;
232     gint no_length;
233 
234     g_return_if_fail (playlist);
235     g_return_if_fail (playlist->itdb);
236 
237     if (key_sync_show_summary && !prefs_get_int (key_sync_show_summary))
238 	return;
239 
240     summary = g_string_sized_new (2000);
241 
242     /* mpl->name is the repository's name */
243     mpl = itdb_playlist_mpl (playlist->itdb);
244     g_return_if_fail (mpl);
245     g_string_append_printf (summary,
246 			    _("Sync summary for %s/%s\n"),
247 			    mpl->name, playlist->name);
248 
249     /* used to check whether data was added or not */
250     no_length = strlen (summary->str);
251 
252     sync_add_tracks (
253 	summary,
254 	tracks_updated,
255 	ngettext ("The following track has been added or updated:\n",
256 		  "The following tracks have been added or updated:\n",
257 		  g_list_length (tracks_updated)));
258 
259     if (playlist->itdb->usertype & GP_ITDB_TYPE_IPOD)
260     {
261 	sync_add_tracks (
262 	    summary,
263 	    tracks_to_delete_from_ipod,
264 	    ngettext ("The following track has been completely removed from the iPod:\n",
265 		      "The following tracks have been completely removed from the iPod:\n",
266 		      g_list_length (tracks_to_delete_from_ipod)));
267     }
268     else
269     {
270 	sync_add_tracks (
271 	    summary,
272 	    tracks_to_delete_from_ipod,
273 	    ngettext ("The following track has been removed from the repository:\n",
274 		      "The following tracks have been removed from the repository:\n",
275 		      g_list_length (tracks_to_delete_from_ipod)));
276     }
277 
278     sync_add_tracks (summary,
279 		     tracks_to_delete_from_playlist,
280 		     ngettext ("The following track has been removed from the playlist:\n",
281 			       "The following tracks have been removed from the playlist:\n",
282 			       g_list_length (tracks_to_delete_from_playlist)));
283 
284     if (strlen (summary->str) == no_length)
285     {
286 	g_string_append (summary, _("Nothing was changed.\n"));
287     }
288 
289     gtkpod_confirmation (CONF_ID_SYNC_SUMMARY,
290 			 FALSE,
291 			 _("Sync summary"),
292 			 NULL,
293 			 summary->str,
294 			 NULL, 0, NULL,
295 			 NULL, 0, NULL,
296 			 TRUE,
297 			 key_sync_show_summary,
298 			 CONF_NULL_HANDLER, NULL, NULL,
299 			 NULL, NULL);
300 
301     g_string_free (summary, TRUE);
302 }
303 
304 
305 
306 
307 /* Callback for adding tracks (makes sure track isn't added to playlist
308  * again if it already exists */
sync_addtrackfunc(Playlist * plitem,Track * track,gpointer data)309 static void sync_addtrackfunc (Playlist *plitem, Track *track, gpointer data)
310 {
311 	struct added_file_data *afd = data;
312 
313     g_return_if_fail (plitem);
314     g_return_if_fail (track);
315 
316     g_return_if_fail (afd->filepath_hash);
317     g_return_if_fail (afd->filepath);
318 
319     /* add the new entry to the filepath */
320     g_hash_table_insert (afd->filepath_hash, g_strdup (afd->filepath), track);
321 
322     /* only add if @track isn't already a member of the current playlist */
323     if (!itdb_playlist_contains_track (plitem, track))
324 	gp_playlist_add_track (plitem, track, TRUE);
325 }
326 
327 
328 /* Builds a hash of all the tracks in the playlists db,
329  * hashed by the file path */
get_itdb_filepath_hash(Playlist * pl)330 static GHashTable *get_itdb_filepath_hash (Playlist *pl)
331 {
332     GHashTable* filepath_hash;
333     iTunesDB *itdb = pl->itdb;
334 
335     filepath_hash = g_hash_table_new_full (
336 	g_str_hash, g_str_equal, g_free, NULL);
337 
338     GList *gl;
339     for (gl=itdb->tracks; gl; gl=gl->next)
340     {
341 	ExtraTrackData *etr;
342 	Track *track = gl->data;
343 	g_return_val_if_fail (track, NULL);
344 
345 	etr = track->userdata;
346 	g_return_val_if_fail (etr, NULL);
347 
348 	/* track has filename info */
349 	if (etr->pc_path_locale && *etr->pc_path_locale)
350 	{
351 	    g_hash_table_insert (filepath_hash, g_strdup (etr->pc_path_locale), track);
352 	}
353     }
354 
355     return filepath_hash;
356 }
357 
358 
359 /**
360  * add_files:
361  *
362  * add all music/video files to the playlist @userdata->playlist.
363  * updated/newly added tracks are appended to @userdata->tracks_updated.
364  */
add_files(gpointer key,gpointer value,gpointer user_data)365 static void add_files (gpointer key, gpointer value, gpointer user_data)
366 {
367     struct add_files_data *afd = user_data;
368     Playlist *pl;
369     gchar *dirname;
370 
371     g_return_if_fail (key);
372     g_return_if_fail (afd);
373     g_return_if_fail (afd->playlist);
374     g_return_if_fail (afd->tracks_updated);
375     g_return_if_fail (afd->filepath_hash);
376 
377     dirname = key;
378     pl = afd->playlist;
379 
380     if (g_file_test (dirname, G_FILE_TEST_IS_DIR))
381     {
382 	GDir *dir = g_dir_open (dirname, 0, NULL);
383 	if (dir != NULL)
384 	{
385 	    G_CONST_RETURN gchar *next;
386 	    while ((next = g_dir_read_name (dir)))
387 	    {
388 		gchar *filename = g_build_filename (dirname, next, NULL);
389 		FileType filetype = determine_file_type (filename);
390 		gboolean updated = FALSE;
391 		Track *tr=NULL;
392 
393 		switch (filetype)
394 		{
395 		case FILE_TYPE_UNKNOWN:
396 		case FILE_TYPE_DIRECTORY:
397 		case FILE_TYPE_IMAGE:
398 		case FILE_TYPE_M3U:
399 		case FILE_TYPE_PLS:
400 		    /* ignore non-music/video files */
401 		    break;
402 		case FILE_TYPE_MP3:
403 		case FILE_TYPE_M4A:
404 		case FILE_TYPE_M4P:
405 		case FILE_TYPE_M4B:
406 		case FILE_TYPE_WAV:
407 		case FILE_TYPE_M4V:
408 		case FILE_TYPE_MP4:
409 		case FILE_TYPE_MOV:
410 		case FILE_TYPE_MPG:
411                 case FILE_TYPE_OGG:
412                 case FILE_TYPE_FLAC:
413 		    tr = g_hash_table_lookup (afd->filepath_hash, filename);
414 		    if (tr)
415 		    {   /* track is already present in playlist.
416 			   Update if date stamp is different. */
417 			struct stat filestat;
418 			ExtraTrackData *etr = tr->userdata;
419 			g_return_if_fail (etr);
420 
421 			stat (filename, &filestat);
422 /*
423 printf ("%ld %ld (%s)\n, %ld %d\n",
424 	filestat.st_mtime, etr->mtime,
425 	filename,
426 	filestat.st_size, tr->size);
427 */
428 			if ((filestat.st_mtime != etr->mtime) ||
429 			    (filestat.st_size != tr->size))
430 			{
431 			    update_track_from_file (pl->itdb, tr);
432 			    updated = TRUE;
433 			}
434 		    }
435 		    else
436 		    {   /* track is not known -- at least not by it's
437 			 * filename -> add to playlist using the
438 			 * standard function. Duplicate adding is
439 			 * avoided by an addtrack function checking
440 			 * for duplication */
441 			struct added_file_data data;
442 			data.filepath = filename;
443 			data.filepath_hash = afd->filepath_hash;
444 
445 			add_track_by_filename (pl->itdb, filename,
446 					       pl, FALSE,
447 					       sync_addtrackfunc, &data);
448 
449 			tr = g_hash_table_lookup (afd->filepath_hash, filename);
450 			updated = TRUE;
451 		    }
452 		    break;
453 		}
454 		if (tr && updated)
455 		{
456 		    *afd->tracks_updated =
457 			g_list_append (*afd->tracks_updated, tr);
458 		}
459 		g_free (filename);
460 	    }
461 	}
462 	g_dir_close (dir);
463     }
464 }
465 
466 
467 /**
468  * cache_directory:
469  *
470  * Add the given directory to the given hash table then recurse into the directory
471  * and add all its sub-directories.
472  *
473  * @dir: top-level directory to recurse into and store subdirectories
474  * @dirs_hash: pointer to a hash table that stores all directory paths
475  *
476  * Return value: none, dirs_hash stores all required paths
477  *
478  **/
cache_directory(const gchar * dir,GHashTable * dirs_hash)479 static void cache_directory (const gchar *dir, GHashTable *dirs_hash)
480 {
481     GDir *dir_handle;
482     const gchar *filename;
483     gchar *path;
484 
485     if (! g_file_test (dir, G_FILE_TEST_IS_DIR))
486         return;
487 
488     /* dir represents a directory so store it in the hash table */
489     g_hash_table_insert(dirs_hash, g_strdup(dir), NULL);
490 
491     dir_handle = g_dir_open (dir, 0, NULL);
492     if (dir_handle == NULL)
493         return;
494 
495     /* Loop through the filenames in the directory */
496     while ((filename = g_dir_read_name(dir_handle)))
497     {
498         /* Construct absolute path from dir and filename */
499         path = g_build_filename(dir, filename, NULL);
500 
501         /* If path is not directory then move on to next */
502         if (! g_file_test (path, G_FILE_TEST_IS_DIR))
503             continue;
504 
505         /* recursively walk down into sub directory */
506         cache_directory (path, dirs_hash);
507         g_free (path);
508     }
509 
510     g_dir_close(dir_handle);
511 }
512 
513 
514 /**
515  * sync_playlist:
516  *
517  * @playlist: playlist to sync with contents on hard disk
518  * @syncdir:  directory to sync with. If @syncdir is NULL, a list of
519  *            directories is created from all the filenames of the
520  *            member tracks
521  * @key_sync_confirm_dirs: preference key to specify whether or not
522  *            the list of directories should be confirmed. The
523  *            confirmation dialog may change the value of this prefs
524  *            entry. If NULL, @sync_confirm_dirs decides whether
525  *            confirmation takes place or not.
526  *            FIXME: not implemented at present.
527  * @sync_confirm_dirs: see under @key_sync_confirm_dirs.
528  * @key_sync_delete_tracks: preference key to specify whether or not
529  *            tracks no longer present in the directory list should be
530  *            removed from the iPod/database or not. Normally tracks
531  *            are only removed from the current playlist. If this key's
532  *            value is set to TRUE (1), they will be removed from the
533  *            iPod /database completely, if they are not a member of
534  *            other playlists. Note: to remove tracks from the MPL,
535  *            this has to be TRUE.
536  *            If NULL, @sync_delete_tracks will determine whether
537  *            tracks are removed or not. Also, if @playlist is the
538  *            MPL, tracks will be removed irrespective of this key's
539  *            value.
540  * @sync_delete_tracks: see under @key_sync_delete_tracks.
541  * @key_sync_confirm_delete: preference key to specify whether or not
542  *            the removal of tracks should be confirmed. The
543  *            confirmation dialog may change the value of this prefs
544  *            entry. If NULL, @sync_confirm_delete will determine
545  *            whether or not confirmation takes place.
546  * @sync_confirm_delete: see under @key_sync_confirm_delete
547  * @key_sync_show_summary: preference key to specify whether or not a
548  *            summary of removed and newly added or updated tracks
549  *            should be displayed. If NULL, @sync_show_shummary will
550  *            determine whether or not a summary is displayed.
551  * @sync_show_shummary: see under @key_sync_show_shummary
552  *
553  * Return value: none, but will give status information via the
554  * statusbar and information windows.
555  **/
sync_playlist(Playlist * playlist,const gchar * syncdir,const gchar * key_sync_confirm_dirs,gboolean sync_confirm_dirs,const gchar * key_sync_delete_tracks,gboolean sync_delete_tracks,const gchar * key_sync_confirm_delete,gboolean sync_confirm_delete,const gchar * key_sync_show_summary,gboolean sync_show_summary)556 void sync_playlist (Playlist *playlist,
557 		    const gchar *syncdir,
558 		    const gchar *key_sync_confirm_dirs,
559 		    gboolean sync_confirm_dirs,
560 		    const gchar *key_sync_delete_tracks,
561 		    gboolean sync_delete_tracks,
562 		    const gchar *key_sync_confirm_delete,
563 		    gboolean sync_confirm_delete,
564 		    const gchar *key_sync_show_summary,
565 		    gboolean sync_show_summary)
566 {
567     GHashTable *dirs_hash, *filepath_hash;
568     gboolean delete_tracks, is_mpl;
569     time_t current_time;
570     GList *tracks_to_delete_from_ipod = NULL;
571     GList *tracks_to_delete_from_playlist = NULL;
572     GList *tracks_updated = NULL;
573     struct add_files_data afd;
574     GList *gl;
575 
576     g_return_if_fail (playlist);
577 
578     /* Create a hash to keep the directory names ("key", and "value"
579        to be freed with g_free). key is dirname in local encoding,
580        value is dirname in utf8, if available */
581     dirs_hash = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
582 
583     /* If @syncdir is not NULL, put @syndir into the hash
584        table. Otherwise put the dirs of all tracks in @playlist into
585        the table. */
586 
587     if (syncdir)
588     {
589 	/* make sure the directory name does not end in '/' -- the
590 	   code below does not seem to like this */
591 	gint len = strlen (syncdir);
592 	gchar *dir = g_strdup (syncdir);
593 	if (len > 1)
594 	{
595 	    if (G_IS_DIR_SEPARATOR (dir[len-1]))
596 	    {
597 		dir[len-1] = 0;
598 	    }
599 	}
600 	cache_directory (dir, dirs_hash);
601     }
602     else
603     {
604 	for (gl = playlist->members; gl; gl=gl->next)
605 	{
606 	    ExtraTrackData *etr;
607 	    Track *track = gl->data;
608 	    g_return_if_fail (track);
609 	    etr = track->userdata;
610 	    g_return_if_fail (etr);
611 
612 	    if (etr->pc_path_locale && *etr->pc_path_locale)
613 	    {
614 		gchar *dirname_local;
615 
616 		dirname_local = g_path_get_dirname (etr->pc_path_locale);
617 		if (etr->pc_path_utf8 && *etr->pc_path_utf8)
618 		{
619 		    g_hash_table_insert (
620 			dirs_hash,
621 			dirname_local,
622 			g_path_get_dirname (etr->pc_path_utf8));
623 		}
624 		else
625 		{   /* no utf8 -- make sure we don't replace a dir
626 		     * entry that had the utf8 data set */
627 		    if (!g_hash_table_lookup (dirs_hash, dirname_local))
628 		    {
629 			g_hash_table_insert (dirs_hash,
630 					     dirname_local, NULL);
631 		    }
632 		    else
633 		    {
634 			g_free (dirname_local);
635 		    }
636 		}
637 	    }
638 	}
639     }
640 
641     /* Confirm directories */
642     if (key_sync_confirm_dirs || sync_confirm_dirs)
643     {
644 	if (!confirm_sync_dirs (dirs_hash, key_sync_confirm_dirs))
645 	{   /* aborted */
646 	    g_hash_table_destroy (dirs_hash);
647 	    return;
648 	}
649     }
650 
651     /* current_time can be used to recognize newly added/updated
652        tracks */
653     current_time = time (NULL);
654 
655     /* craete a hash with all files in the current playlist for faster
656      * comparison with files in the directory */
657     filepath_hash = get_itdb_filepath_hash (playlist);
658 
659     afd.playlist = playlist;
660     afd.tracks_updated = &tracks_updated;
661     afd.filepath_hash = filepath_hash;
662     /* Add all files in all directories present in dirs_hash */
663     g_hash_table_foreach (dirs_hash, add_files, &afd);
664 
665     /* we won't need this hash any more */
666     g_hash_table_destroy (filepath_hash);
667     filepath_hash = NULL;
668 
669     /* Remove updated and duplicate list so it won't pop up at a later
670        time */
671     display_updated ((void *)-1, NULL);
672     display_non_updated ((void *)-1, NULL);
673     gp_duplicate_remove (NULL, (void *)-1);
674 
675     /* Should tracks be deleted that were not present in the
676      * directories? */
677     if (key_sync_delete_tracks == NULL)
678     {
679 	delete_tracks = TRUE;
680     }
681     else
682     {
683 	delete_tracks = prefs_get_int (key_sync_delete_tracks);
684     }
685     /* Is playlist the MPL? */
686     is_mpl = itdb_playlist_is_mpl (playlist);
687 
688     /* Identify all tracks in playlist not being located in one of the
689        specified dirs, or no longer existing. */
690     for (gl=playlist->members; gl; gl=gl->next)
691     {
692 	ExtraTrackData *etr;
693 	gboolean remove;
694 
695 	Track *tr = gl->data;
696 	g_return_if_fail (tr);
697 	etr = tr->userdata;
698 	g_return_if_fail (etr);
699 
700 	remove = FALSE;
701 	if (etr->pc_path_locale && *etr->pc_path_locale)
702 	{
703 	    gchar *dirname_local;
704 
705 	    dirname_local = g_path_get_dirname (etr->pc_path_locale);
706 	    if (!g_hash_table_lookup_extended (dirs_hash, dirname_local,
707 					       NULL, NULL))
708 	    {   /* file is not in one of the specified directories */
709 		remove = TRUE;
710 	    }
711 	    else
712 	    {   /* check if file exists */
713 		if (g_file_test (etr->pc_path_locale,
714 				 G_FILE_TEST_EXISTS) == FALSE)
715 		{   /* no -- remove */
716 		    remove = TRUE;
717 		}
718 	    }
719 	    g_free (dirname_local);
720 	}
721 
722 	if (remove)
723 	{   /* decide whether track needs to be removed from the iPod
724 	     * (only member of this playlist) or only from this
725 	     * playlist (if delete_tracks is not set, no tracks are
726 	     * removed from the MPL) */
727 	    if (delete_tracks &&
728 		(is_mpl || (itdb_playlist_contain_track_number (tr)==1)))
729 	    {
730 		tracks_to_delete_from_ipod =
731 		    g_list_append (tracks_to_delete_from_ipod, tr);
732 	    }
733 	    else
734 	    {
735 		if (!is_mpl)
736 		{
737 		    tracks_to_delete_from_playlist =
738 			g_list_append (tracks_to_delete_from_playlist, tr);
739 		}
740 	    }
741 	}
742     }
743 
744 
745     if (tracks_to_delete_from_ipod &&
746 	(key_sync_confirm_delete || sync_confirm_delete) &&
747 	(confirm_delete_tracks (tracks_to_delete_from_ipod,
748 				key_sync_confirm_delete) == FALSE))
749     {   /* User doesn't want us to remove those tracks from the
750 	 * iPod. We'll therefore just remove them from the playlist
751 	 * (if playlist is the MPL, don't remove at all) */
752 	if (!is_mpl)
753 	{
754 	    tracks_to_delete_from_playlist = g_list_concat (
755 		tracks_to_delete_from_playlist,
756 		tracks_to_delete_from_ipod);
757 	}
758 	else
759 	{
760 	    g_list_free (tracks_to_delete_from_ipod);
761 	}
762 	tracks_to_delete_from_ipod = NULL;
763     }
764 
765 
766     if (key_sync_show_summary || sync_show_summary)
767     {
768 	show_sync_summary (key_sync_show_summary,
769 			   playlist,
770 			   tracks_to_delete_from_ipod,
771 			   tracks_to_delete_from_playlist,
772 			   tracks_updated);
773     }
774 
775     /* Remove completely */
776     for (gl=tracks_to_delete_from_ipod; gl; gl=gl->next)
777     {
778 	Track *tr = gl->data;
779 	g_return_if_fail (tr);
780 
781 	if (tr->itdb->usertype & GP_ITDB_TYPE_IPOD)
782 	    gp_playlist_remove_track (NULL, tr, DELETE_ACTION_IPOD);
783 	else if (tr->itdb->usertype & GP_ITDB_TYPE_LOCAL)
784 	    gp_playlist_remove_track (NULL, tr, DELETE_ACTION_DATABASE);
785     }
786 
787     /* Remove from playlist */
788     for (gl=tracks_to_delete_from_playlist; gl; gl=gl->next)
789     {
790 	Track *tr = gl->data;
791 	g_return_if_fail (tr);
792 
793 	gp_playlist_remove_track (playlist, tr, DELETE_ACTION_PLAYLIST);
794     }
795 
796     /* Was any data changed? */
797     if (tracks_to_delete_from_ipod ||
798 	tracks_to_delete_from_playlist ||
799 	tracks_updated)
800     {
801 	data_changed (playlist->itdb);
802 	gtkpod_tracks_statusbar_update ();
803     }
804 
805     g_list_free (tracks_to_delete_from_ipod);
806     g_list_free (tracks_to_delete_from_playlist);
807     g_list_free (tracks_updated);
808 }
809 
810 
811 /**
812  * sync_all_playlists:
813  *
814  * @itdb: repository whose playlists are to be updated
815  *
816  * Will update all playlists in @itdb according to options set. The
817  * following pref subkeys are relevant:
818  *
819  * sync_confirm_dirs
820  * sync_delete_tracks
821  * sync_confirm_delete
822  * sync_show_summary
823  */
824 
sync_all_playlists(iTunesDB * itdb)825 void sync_all_playlists (iTunesDB *itdb)
826 {
827     gint index;
828     GList *gl;
829 
830     g_return_if_fail (itdb);
831 
832     index = get_itdb_index (itdb);
833 
834     for (gl=itdb->playlists; gl; gl=gl->next)
835     {
836 	gint syncmode;
837 	Playlist *pl = gl->data;
838 	g_return_if_fail (pl);
839 
840 	syncmode = get_playlist_prefs_int (pl, KEY_SYNCMODE);
841 	if (syncmode != SYNC_PLAYLIST_MODE_NONE)
842 	{
843 	    gchar *key_sync_confirm_dirs =
844 		get_playlist_prefs_key (index, pl, KEY_SYNC_CONFIRM_DIRS);
845 	    gchar *key_sync_delete_tracks =
846 		get_playlist_prefs_key (index, pl, KEY_SYNC_DELETE_TRACKS);
847 	    gchar *key_sync_confirm_delete =
848 		get_playlist_prefs_key (index, pl, KEY_SYNC_CONFIRM_DELETE);
849 	    gchar *key_sync_show_summary =
850 		get_playlist_prefs_key (index, pl, KEY_SYNC_SHOW_SUMMARY);
851 	    gchar *syncdir = NULL;
852 
853 	    if (syncmode == SYNC_PLAYLIST_MODE_MANUAL)
854 	    {
855 		syncdir = get_playlist_prefs_string (pl,
856 						     KEY_MANUAL_SYNCDIR);
857 	    }
858 
859 	    sync_playlist (pl, syncdir,
860 			   key_sync_confirm_dirs, 0,
861 			   key_sync_delete_tracks, 0,
862 			   key_sync_confirm_delete, 0,
863 			   key_sync_show_summary, 0);
864 
865 	    g_free (key_sync_confirm_dirs);
866 	    g_free (key_sync_delete_tracks);
867 	    g_free (key_sync_confirm_delete);
868 	    g_free (key_sync_show_summary);
869 	    g_free (syncdir);
870 	}
871     }
872 }
873