1 /*
2 |  Copyright (C) 2002-2005 Jorg Schuler <jcsjcs at users sourceforge net>
3 |  Part of the gtkpod project.
4 |
5 |  URL: http://www.gtkpod.org/
6 |  URL: http://gtkpod.sourceforge.net/
7 |
8 |  This program is free software; you can redistribute it and/or modify
9 |  it under the terms of the GNU General Public License as published by
10 |  the Free Software Foundation; either version 2 of the License, or
11 |  (at your option) any later version.
12 |
13 |  This program is distributed in the hope that it will be useful,
14 |  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16 |  GNU General Public License for more details.
17 |
18 |  You should have received a copy of the GNU General Public License
19 |  along with this program; if not, write to the Free Software
20 |  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 |
22 |  iTunes and iPod are trademarks of Apple
23 |
24 |  This product is not supported/written/published by Apple!
25 |
26 |  $Id$
27 */
28 
29 #ifdef HAVE_CONFIG_H
30 #  include <config.h>
31 #endif
32 
33 #include <string.h>
34 #include "charset.h"
35 #include "itdb.h"
36 #include "info.h"
37 #include "sha1.h"
38 #include "misc.h"
39 #include "misc_track.h"
40 #include "prefs.h"
41 
42 
43 #define DEBUG_MISC 0
44 
45 
46 /*------------------------------------------------------------------*\
47  *                                                                  *
48  *             Add new playlist asking user for name                *
49  *                                                                  *
50 \*------------------------------------------------------------------*/
51 
52 /* Add a new playlist at position @position. The name for the new
53  * playlist is queried from the user. A default (@dflt) name can be
54  * provided.
55  * Return value: the new playlist or NULL if the dialog was
56  * cancelled. */
add_new_pl_user_name(iTunesDB * itdb,gchar * dflt,gint32 position)57 Playlist *add_new_pl_user_name (iTunesDB *itdb,
58 				gchar *dflt, gint32 position)
59 {
60     ExtraiTunesDBData *eitdb;
61     Playlist *result = NULL;
62     gchar *name;
63 
64     g_return_val_if_fail (itdb, NULL);
65 
66     eitdb = itdb->userdata;
67     g_return_val_if_fail (eitdb, NULL);
68 
69     if (!eitdb->itdb_imported)
70     {
71 	gtkpod_warning_simple(_("Please load the iPod before adding playlists."));
72 	return NULL;
73     }
74 
75     name = get_user_string (
76 	_("New Playlist"),
77 	_("Please enter a name for the new playlist"),
78 	dflt? dflt:_("New Playlist"),
79 	NULL, NULL, GTK_STOCK_ADD);
80     if (name)
81     {
82 	result = gp_playlist_add_new (itdb, name, FALSE, position);
83 	gtkpod_tracks_statusbar_update ();
84     }
85     return result;
86 }
87 
88 
89 /* Add a new playlist or smart playlist at position @position. The
90  * name for the new playlist is queried from the user. A default
91  * (@dflt) name can be provided.
92  * Return value: none. In the case of smart playlists, the playlist
93  * will not be created immediately. */
add_new_pl_or_spl_user_name(iTunesDB * itdb,gchar * dflt,gint32 position)94 void add_new_pl_or_spl_user_name (iTunesDB *itdb,
95 				  gchar *dflt, gint32 position)
96 {
97     ExtraiTunesDBData *eitdb;
98     gboolean is_spl = FALSE;
99     gchar *name;
100 
101     g_return_if_fail (itdb);
102 
103     eitdb = itdb->userdata;
104     g_return_if_fail (eitdb);
105 
106     if (!eitdb->itdb_imported)
107     {
108 	gtkpod_warning_simple(_("Please load the iPod before adding playlists."));
109 	return;
110     }
111 
112     name = get_user_string (
113 	_("New Playlist"),
114 	_("Please enter a name for the new playlist"),
115 	dflt? dflt:_("New Playlist"),
116 	_("Smart Playlist"), &is_spl, GTK_STOCK_ADD);
117 
118     if (name)
119     {
120 	if (!is_spl)
121 	{   /* add standard playlist */
122 	    gp_playlist_add_new (itdb, name, FALSE, position);
123 	    gtkpod_tracks_statusbar_update ();
124 	}
125 	else
126 	{   /* add smart playlist */
127 	    spl_edit_new (itdb, name, position);
128 	}
129     }
130 }
131 
132 /* callback */
133 G_MODULE_EXPORT void
on_smart_playlist_activate(GtkMenuItem * menuitem,gpointer user_data)134 on_smart_playlist_activate             (GtkMenuItem     *menuitem,
135                                         gpointer         user_data)
136 {
137     iTunesDB *itdb = gp_get_selected_itdb();
138 
139     if (itdb)
140     {
141 	spl_edit_new (itdb, NULL, -1);
142     }
143     else
144     {
145 	message_sb_no_itdb_selected ();
146     }
147 }
148 
149 
150 /*------------------------------------------------------------------*\
151  *                                                                  *
152  *                     Special Playlist Stuff                       *
153  *                                                                  *
154 \*------------------------------------------------------------------*/
155 
156 /* used for special playlist creation */
157 typedef gboolean (*PL_InsertFunc)(Track *track, gpointer userdata);
158 
159 /* generate_category_playlists: Create a playlist for each category
160    @cat (T_ARTIST, T_ALBUM, T_GENRE, T_COMPOSER) */
generate_category_playlists(iTunesDB * itdb,T_item cat)161 void generate_category_playlists (iTunesDB *itdb, T_item cat)
162 {
163     Playlist *master_pl;
164     gchar *qualifier;
165     GList *gl;
166 
167     g_return_if_fail (itdb);
168 
169     /* Initialize the "qualifier". It is used to indicate the category of
170        automatically generated playlists */
171     switch (cat)
172     {
173     case T_ARTIST:
174 	qualifier = _("AR:");
175 	break;
176     case T_ALBUM:
177 	qualifier = _("AL:");
178 	break;
179     case T_GENRE:
180 	qualifier = _("GE:");
181 	break;
182     case T_COMPOSER:
183 	qualifier = _("CO:");
184 	break;
185     case T_YEAR:
186 	qualifier = _("YE:");
187 	break;
188     default:
189 	g_return_if_reached ();
190 	break;
191     }
192 
193     /* FIXME: delete all playlists named '[<qualifier> .*]' and
194      * remember which playlist was selected if it gets deleted */
195 
196     master_pl = itdb_playlist_mpl (itdb);
197     g_return_if_fail (master_pl);
198 
199     for (gl=master_pl->members; gl; gl=gl->next)
200     {
201 	Track *track = gl->data;
202 	Playlist *cat_pl = NULL;
203 	gchar *category = NULL;
204 	const gchar *track_cat;
205 
206 	track_cat = track_get_item (track, cat);
207 
208 	if (track_cat)
209 	{
210 	    /* some tracks have empty strings in the genre field */
211 	    if(track_cat[0] == '\0')
212 	    {
213 		category = g_strdup_printf ("[%s %s]",
214 					    qualifier, _("Unknown"));
215 	    }
216 	    else
217 	    {
218 		category = g_strdup_printf ("[%s %s]",
219 					    qualifier, track_cat);
220 	    }
221 
222 	    /* look for category playlist */
223 	    cat_pl = itdb_playlist_by_name (itdb, category);
224 	    /* or, create category playlist */
225 	    if(!cat_pl)
226 	    {
227 		cat_pl = gp_playlist_add_new (itdb, category,
228 					      FALSE, -1);
229 	    }
230 	    gp_playlist_add_track (cat_pl, track, TRUE);
231 	    g_free (category);
232 	}
233     }
234     gtkpod_tracks_statusbar_update();
235 }
236 
237 /* Generate a new playlist containing all the tracks currently
238    displayed */
generate_displayed_playlist(void)239 Playlist *generate_displayed_playlist (void)
240 {
241     GList *tracks = tm_get_all_tracks ();
242     Playlist *result = NULL;
243 
244     if (tracks)
245     {
246 	result= generate_new_playlist (gp_get_selected_itdb (), tracks);
247 	g_list_free (tracks);
248     }
249     return result;
250 }
251 
252 
253 /* Generate a new playlist containing all the tracks currently
254    selected */
generate_selected_playlist(void)255 Playlist *generate_selected_playlist (void)
256 {
257     GList *tracks = tm_get_selected_tracks ();
258     Playlist *result=NULL;
259 
260     if (tracks)
261     {
262 	result= generate_new_playlist (gp_get_selected_itdb (), tracks);
263 	g_list_free (tracks);
264     }
265     return result;
266 }
267 
268 
269 /* Generates a playlist containing a random selection of
270    prefs_get_int("misc_track_nr") tracks in random order from the currently
271    displayed tracks */
generate_random_playlist(iTunesDB * itdb)272 Playlist *generate_random_playlist (iTunesDB *itdb)
273 {
274     GRand *grand = g_rand_new ();
275     Playlist *new_pl = NULL;
276     gchar *pl_name, *pl_name1;
277     GList *rtracks = NULL;
278     GList *tracks = tm_get_all_tracks ();
279     gint tracks_max = prefs_get_int("misc_track_nr");
280     gint tracks_nr = 0;
281 
282 
283     while (tracks && (tracks_nr < tracks_max))
284     {
285 	/* get random number between 0 and g_list_length()-1 */
286 	gint rn = g_rand_int_range (grand, 0, g_list_length (tracks));
287 	GList *random = g_list_nth (tracks, rn);
288 	rtracks = g_list_append (rtracks, random->data);
289 	tracks = g_list_delete_link (tracks, random);
290 	++tracks_nr;
291     }
292     pl_name1 = g_strdup_printf (_("Random (%d)"), tracks_max);
293     pl_name = g_strdup_printf ("[%s]", pl_name1);
294     new_pl = generate_playlist_with_name (itdb, rtracks, pl_name, TRUE);
295     g_free (pl_name1);
296     g_free (pl_name);
297     g_list_free (tracks);
298     g_list_free (rtracks);
299     g_rand_free (grand);
300     return new_pl;
301 }
302 
303 
randomize_current_playlist(void)304 void randomize_current_playlist (void)
305 {
306     Playlist *pl= pm_get_selected_playlist ();
307 
308     if (!pl)
309     {
310 	message_sb_no_playlist_selected ();
311 	return;
312     }
313 
314     if (prefs_get_int("tm_autostore"))
315     {
316 	prefs_set_int("tm_autostore", FALSE);
317 	gtkpod_warning (_("Auto Store of track view disabled.\n\n"));
318 /* 	sort_window_update (); */
319     }
320 
321     itdb_playlist_randomize (pl);
322 
323     st_adopt_order_in_playlist ();
324     tm_adopt_order_in_sorttab ();
325 }
326 
327 
not_listed_make_track_list(gpointer key,gpointer track,gpointer tracks)328 static void not_listed_make_track_list (gpointer key, gpointer track,
329 					gpointer tracks)
330 {
331     *(GList **)tracks = g_list_append (*(GList **)tracks, (Track *)track);
332 }
333 
334 /* Generate a playlist containing all tracks that are not part of any
335    playlist.
336    For this, playlists starting with a "[" (generated playlists) are
337    being ignored. */
generate_not_listed_playlist(iTunesDB * itdb)338 Playlist *generate_not_listed_playlist (iTunesDB *itdb)
339 {
340     GHashTable *hash;
341     GList *gl, *tracks=NULL;
342     guint32 i;
343     gchar *pl_name;
344     Playlist *new_pl, *pl;
345 
346     g_return_val_if_fail (itdb, NULL);
347 
348     /* Create hash with all track/track pairs */
349     pl = itdb_playlist_mpl (itdb);
350     g_return_val_if_fail (pl, NULL);
351     hash = g_hash_table_new (NULL, NULL);
352     for (gl=pl->members; gl != NULL; gl=gl->next)
353     {
354 	g_hash_table_insert (hash, gl->data, gl->data);
355     }
356     /* remove all tracks that are members of other playlists */
357     i=1;
358     do
359     {
360 	pl = itdb_playlist_by_nr (itdb, i);
361 	++i;
362 	/* skip playlists starting with a '[' */
363 	if (pl && pl->name && (pl->name[0] != '['))
364 	{
365 	    for (gl=pl->members; gl != NULL; gl=gl->next)
366 	    {
367 		g_hash_table_remove (hash, gl->data);
368 	    }
369 	}
370     } while (pl);
371 
372     g_hash_table_foreach (hash, not_listed_make_track_list, &tracks);
373     g_hash_table_destroy (hash);
374     hash = NULL;
375 
376     pl_name = g_strdup_printf ("[%s]", _("Not Listed"));
377 
378     new_pl = generate_playlist_with_name (itdb, tracks, pl_name, TRUE);
379     g_free (pl_name);
380     g_list_free (tracks);
381     return new_pl;
382 }
383 
384 
385 /* Generate a playlist consisting of the tracks in @tracks
386  * with @name name. If @del_old ist TRUE, delete any old playlist with
387  * the same name. */
generate_playlist_with_name(iTunesDB * itdb,GList * tracks,gchar * pl_name,gboolean del_old)388 Playlist *generate_playlist_with_name (iTunesDB *itdb,GList *tracks,
389 				       gchar *pl_name, gboolean del_old)
390 {
391     Playlist *new_pl=NULL;
392     gint n = g_list_length (tracks);
393 
394     g_return_val_if_fail (itdb, new_pl);
395 
396     if(n>0)
397     {
398 	gboolean select = FALSE;
399 	GList *l;
400 	if (del_old)
401 	{
402 	    /* currently selected playlist */
403 	    Playlist *sel_pl= pm_get_selected_playlist ();
404 	    if (sel_pl->itdb != itdb)
405 	    {   /* different itdb */
406 		sel_pl = NULL;
407 	    }
408 	    /* remove all playlists with named @plname */
409 	    gp_playlist_remove_by_name (itdb, pl_name);
410 	    /* check if we deleted the selected playlist */
411 	    if (sel_pl)
412 	    {
413 		if (g_list_find (itdb->playlists, sel_pl) == NULL)
414 		    select = TRUE;
415 	    }
416 	}
417 	new_pl = gp_playlist_add_new (itdb, pl_name, FALSE, -1);
418 	g_return_val_if_fail (new_pl, new_pl);
419 	for (l=tracks; l; l=l->next)
420 	{
421 	    Track *track = l->data;
422 	    g_return_val_if_fail (track, new_pl);
423 	    gp_playlist_add_track (new_pl, track, TRUE);
424 	}
425 	gtkpod_statusbar_message (
426 	    ngettext ("Created playlist '%s' with %d track.",
427 		      "Created playlist '%s' with %d tracks.",
428 		      n), pl_name, n);
429 	if (new_pl && select)
430 	{   /* need to select newly created playlist because the old
431 	     * selection was deleted */
432 	    pm_select_playlist (new_pl);
433 	}
434     }
435     else
436     {   /* n==0 */
437 	gtkpod_statusbar_message (_("No tracks available, playlist not created"));
438     }
439     gtkpod_tracks_statusbar_update();
440     return new_pl;
441 }
442 
443 /* Generate a playlist named "New Playlist" consisting of the tracks
444  * in @tracks. */
generate_new_playlist(iTunesDB * itdb,GList * tracks)445 Playlist *generate_new_playlist (iTunesDB *itdb, GList *tracks)
446 {
447     gchar *name = get_user_string (
448 	_("New Playlist"),
449 	_("Please enter a name for the new playlist"),
450 	_("New Playlist"),
451 	NULL, NULL, GTK_STOCK_ADD);
452     if (name)
453 	return generate_playlist_with_name (itdb, tracks, name, FALSE);
454     return NULL;
455 }
456 
457 /* look at the add_ranked_playlist help:
458  * BEWARE this function shouldn't be used by other functions */
create_ranked_glist(iTunesDB * itdb,gint tracks_nr,PL_InsertFunc insertfunc,GCompareFunc comparefunc,gpointer userdata)459 static GList *create_ranked_glist(iTunesDB *itdb, gint tracks_nr,
460 				  PL_InsertFunc insertfunc,
461 				  GCompareFunc comparefunc,
462 				  gpointer userdata)
463 {
464    GList *tracks=NULL;
465    gint f=0;
466    GList *gl;
467 
468    g_return_val_if_fail (itdb, tracks);
469 
470    for (gl=itdb->tracks; gl; gl=gl->next)
471    {
472        Track *track = gl->data;
473        g_return_val_if_fail (track, tracks);
474        if (track && (!insertfunc || insertfunc (track, userdata)))
475        {
476 	   tracks = g_list_insert_sorted (tracks, track, comparefunc);
477 	   ++f;
478 	   if (tracks_nr && (f>tracks_nr))
479 	   {   /*cut the tail*/
480 	       tracks = g_list_remove(tracks,
481 				      g_list_nth_data(tracks, tracks_nr));
482 	       --f;
483 	   }
484        }
485    }
486    return tracks;
487 }
488 /* Generate or update a playlist named @pl_name, containing
489  * @tracks_nr tracks.
490  *
491  * @str is the playlist's name (no [ or ])
492  * @insertfunc: determines which tracks to enter into the new playlist.
493  *              If @insertfunc is NULL, all tracks are added.
494  * @comparefunc: determines order of tracks
495  * @tracks_nr: max. number of tracks in playlist or 0 for no limit.
496  *
497  * Return value: the newly created playlist
498  */
update_ranked_playlist(iTunesDB * itdb,gchar * str,gint tracks_nr,PL_InsertFunc insertfunc,GCompareFunc comparefunc,gpointer userdata)499 static Playlist *update_ranked_playlist(iTunesDB *itdb,
500 					gchar *str, gint tracks_nr,
501 					PL_InsertFunc insertfunc,
502 					GCompareFunc comparefunc,
503 					gpointer userdata)
504 {
505     Playlist *result = NULL;
506     gchar *pl_name = g_strdup_printf ("[%s]", str);
507     GList *tracks;
508 
509     g_return_val_if_fail (itdb, result);
510 
511     tracks = create_ranked_glist(itdb, tracks_nr,
512 				 insertfunc, comparefunc, userdata);
513 
514     if (tracks)
515     /* else generate_playlist_with_name prints something*/
516     {
517 	result = generate_playlist_with_name (itdb, tracks, pl_name, TRUE);
518     }
519     g_list_free (tracks);
520     g_free (pl_name);
521     return result;
522 }
523 
524 
525 /* ------------------------------------------------------------ */
526 /* Generate a new playlist containing the most listened (playcount
527  * reverse order) tracks. to enter this playlist a track must have been
528  * played */
529 
530 /* Sort Function: determines the order of the generated playlist */
531 
532 /* NOTE: THE USE OF 'COMP' ARE NECESSARY FOR THE TIME_PLAYED COMPARES
533    WHERE A SIGN OVERFLOW MAY OCCUR BECAUSE OF THE 32 BIT UNSIGNED MAC
534    TIMESTAMPS. */
Most_Listened_CF(gconstpointer aa,gconstpointer bb)535 static gint Most_Listened_CF (gconstpointer aa, gconstpointer bb)
536 {
537     gint result = 0;
538     const Track *a = aa;
539     const Track *b = bb;
540 
541     if (a && b)
542     {
543 	result = COMP (b->playcount, a->playcount);
544 	if (result == 0) result = COMP (b->rating, a->rating);
545 	if (result == 0) result = COMP (b->time_played, a->time_played);
546     }
547     return result;
548 }
549 
550 /* Insert function: determines whether a track is entered into the playlist */
Most_Listened_IF(Track * track,gpointer userdata)551 static gboolean Most_Listened_IF (Track *track, gpointer userdata)
552 {
553     if (track)   return (track->playcount != 0);
554     return      FALSE;
555 }
556 
most_listened_pl(iTunesDB * itdb)557 void most_listened_pl (iTunesDB *itdb)
558 {
559     gint tracks_nr = prefs_get_int("misc_track_nr");
560     gchar *str;
561 
562     g_return_if_fail (itdb);
563     str = g_strdup_printf (_("Most Listened (%d)"), tracks_nr);
564     update_ranked_playlist (itdb, str, tracks_nr,
565 			    Most_Listened_IF, Most_Listened_CF, (gpointer)0 );
566     g_free (str);
567 }
568 
569 
570 /* ------------------------------------------------------------ */
571 /* Generate a new playlist containing all songs never listened to. */
572 
573 /* Sort Function: determines the order of the generated playlist */
574 
575 /* NOTE: THE USE OF 'COMP' ARE NECESSARY FOR THE TIME_PLAYED COMPARES
576    WHERE A SIGN OVERFLOW MAY OCCUR BECAUSE OF THE 32 BIT UNSIGNED MAC
577    TIMESTAMPS. */
Never_Listened_CF(gconstpointer aa,gconstpointer bb)578 static gint Never_Listened_CF (gconstpointer aa, gconstpointer bb)
579 {
580     gint result = 0;
581     const Track *a = aa;
582     const Track *b = bb;
583 
584     if (a && b)
585     {
586 	result = COMP (b->rating, a->rating);
587     }
588     return result;
589 }
590 
591 /* Insert function: determines whether a track is entered into the playlist */
Never_Listened_IF(Track * track,gpointer userdata)592 static gboolean Never_Listened_IF (Track *track, gpointer userdata)
593 {
594     if (track)   return (track->playcount == 0);
595     return      FALSE;
596 }
597 
never_listened_pl(iTunesDB * itdb)598 void never_listened_pl (iTunesDB *itdb)
599 {
600     gint tracks_nr = 0;  /* no limit */
601     gchar *str;
602 
603     g_return_if_fail (itdb);
604     str = g_strdup_printf (_("Never Listened"));
605     update_ranked_playlist (itdb, str, tracks_nr,
606 			    Never_Listened_IF, Never_Listened_CF, (gpointer)0 );
607     g_free (str);
608 }
609 
610 
611 /* ------------------------------------------------------------ */
612 /* Generate a new playlist containing the most rated (rate
613  * reverse order) tracks. */
614 
615 /* Sort Function: determines the order of the generated playlist */
Most_Rated_CF(gconstpointer aa,gconstpointer bb)616 static gint Most_Rated_CF (gconstpointer aa, gconstpointer bb)
617 {
618     gint result = 0;
619     const Track *a = aa;
620     const Track *b = bb;
621 
622     if (a && b)
623     {
624 	result = COMP (b->rating, a->rating);
625 	if (result == 0) result = COMP (b->playcount, a->playcount);
626 	if (result == 0) result = COMP (b->time_played, a->time_played);
627     }
628     return result;
629 }
630 
631 /* Insert function: determines whether a track is entered into the playlist */
Most_Rated_IF(Track * track,gpointer userdata)632 static gboolean Most_Rated_IF (Track *track, gpointer userdata)
633 {
634     if (track) return ((track->playcount != 0) || prefs_get_int("not_played_track"));
635     return FALSE;
636 }
637 
most_rated_pl(iTunesDB * itdb)638 void most_rated_pl (iTunesDB *itdb)
639 {
640     gint tracks_nr = prefs_get_int("misc_track_nr");
641     gchar *str;
642 
643     g_return_if_fail (itdb);
644     str =  g_strdup_printf (_("Best Rated (%d)"), tracks_nr);
645     update_ranked_playlist (itdb, str, tracks_nr,
646 			    Most_Rated_IF, Most_Rated_CF, (gpointer)0 );
647     g_free (str);
648 }
649 
650 
651 /* ------------------------------------------------------------ */
652 /* Generate 6 playlists,one for each rating 1..5 and unrated    */
653 
654 
655 /* Sort Function: determines the order of the generated playlist */
All_Ratings_CF(gconstpointer aa,gconstpointer bb)656 static gint All_Ratings_CF (gconstpointer aa, gconstpointer bb)
657 {
658     gint result = 0;
659     const Track *a = aa;
660     const Track *b = bb;
661 
662     if (a && b)
663     {
664 	result = COMP (b->playcount, a->playcount);
665 	if (result == 0) result = COMP (b->time_played, a->time_played);
666     }
667     return result;
668 }
669 
670 
671 /* Insert function: determines whether a track is entered into the playlist */
All_Ratings_IF(Track * track,gpointer user_data)672 static gboolean All_Ratings_IF (Track *track, gpointer user_data)
673 {
674     guint playlist_nr = GPOINTER_TO_UINT(user_data);
675     if (track) return (track->rating == playlist_nr*20);
676     return FALSE;
677 }
678 
679 
each_rating_pl(iTunesDB * itdb)680 void each_rating_pl(iTunesDB *itdb)
681 {
682     gchar *str;
683     guint playlist_nr;
684 
685     g_return_if_fail (itdb);
686     str = _("Unrated tracks");
687     for (playlist_nr = 0; playlist_nr < 6; playlist_nr ++ )
688     {
689 	if (playlist_nr > 0)
690 	{
691 	    str = g_strdup_printf (_("Rated %d"), playlist_nr);
692 	}
693     	update_ranked_playlist (itdb, str, 0,
694 				All_Ratings_IF, All_Ratings_CF,
695 				GUINT_TO_POINTER(playlist_nr));
696     }
697     g_free (str);
698 }
699 
700 
701 /* ------------------------------------------------------------ */
702 /* Generate a new playlist containing the last listened (last time play
703  * reverse order) tracks. */
704 
705 /* Sort Function: determines the order of the generated playlist */
Last_Listened_CF(gconstpointer aa,gconstpointer bb)706 static gint Last_Listened_CF (gconstpointer aa, gconstpointer bb)
707 {
708     gint result = 0;
709     const Track *a = aa;
710     const Track *b = bb;
711 
712     if (a && b)
713     {
714 	result = COMP (b->time_played, a->time_played);
715 	if (result == 0) result = COMP (b->rating, a->rating);
716 	if (result == 0) result = COMP (b->playcount, a->playcount);
717     }
718     return result;
719 }
720 
721 /* Insert function: determines whether a track is entered into the playlist */
Last_Listened_IF(Track * track,gpointer userdata)722 static gboolean Last_Listened_IF (Track *track, gpointer userdata)
723 {
724     if (track)   return (track->playcount != 0);
725     return      FALSE;
726 }
727 
last_listened_pl(iTunesDB * itdb)728 void last_listened_pl (iTunesDB *itdb)
729 {
730     gint tracks_nr = prefs_get_int("misc_track_nr");
731     gchar *str;
732 
733     g_return_if_fail (itdb);
734     str = g_strdup_printf (_("Recent (%d)"), tracks_nr);
735     update_ranked_playlist (itdb, str, tracks_nr,
736 			    Last_Listened_IF, Last_Listened_CF, (gpointer)0);
737     g_free (str);
738 }
739 
740 
741 /* ------------------------------------------------------------ */
742 /* Generate a new playlist containing the tracks listened to since the
743  * last time the iPod was connected to a computer (and the playcount
744  * file got wiped) */
745 
746 /* Sort Function: determines the order of the generated playlist */
since_last_CF(gconstpointer aa,gconstpointer bb)747 static gint since_last_CF (gconstpointer aa, gconstpointer bb)
748 {
749     gint result = 0;
750     const Track *a = aa;
751     const Track *b = bb;
752 
753     if (a && b)
754     {
755 	result = COMP (b->recent_playcount, a->recent_playcount);
756 	if (result == 0) result = COMP (b->time_played, a->time_played);
757 	if (result == 0) result = COMP (b->playcount, a->playcount);
758 	if (result == 0) result = COMP (b->rating, a->rating);
759     }
760     return result;
761 }
762 
763 /* Insert function: determines whether a track is entered into the playlist */
since_last_IF(Track * track,gpointer userdata)764 static gboolean since_last_IF (Track *track, gpointer userdata)
765 {
766     if (track && (track->recent_playcount != 0))  return TRUE;
767     else                                        return FALSE;
768 }
769 
since_last_pl(iTunesDB * itdb)770 void since_last_pl (iTunesDB *itdb)
771 {
772     g_return_if_fail (itdb);
773     update_ranked_playlist (itdb, _("Last Time"), 0,
774 			    since_last_IF, since_last_CF, (gpointer)0);
775 }
776 
777 
778 /*------------------------------------------------------------------*\
779  *                                                                  *
780  *                Find Orphans                                      *
781  *                                                                  *
782 \*------------------------------------------------------------------*/
783 
784 /******************************************************************************
785  * Attempt to do everything at once:
786  *  - find dangling links in iTunesDB
787  *  - find orphaned files in mounted directory
788  * Will be done by creating first a hash of all known in iTunesDB filenames,
789  * and then checking every file on HDD in the hash table. If it is present -
790  * remove from hashtable, if not present - it is orphaned. If at the end
791  * hashtable still has some elements - they're dangling...
792  *
793  * TODO: instead of using case-sensitive comparison function it might be better
794  *  just to convert all filenames to lowercase before doing any comparisons
795  * FIX:
796  *  offline... when you import db offline and then switch to online mode you still
797  *  have offline db loaded and if it is different from IPOD's - then a lot of crap
798  *  can happen... didn't check yet
799  ******************************************************************************/
800 
801 
802 /* compare @str1 and @str2 case-sensitively only */
str_cmp(gconstpointer str1,gconstpointer str2,gpointer data)803 gint str_cmp (gconstpointer str1, gconstpointer str2, gpointer data)
804 {
805 	return compare_string_case_insensitive((gchar *)str1, (gchar *)str2);
806 	/*return strcmp((gchar *)str1, (gchar *)str2);*/
807 }
808 
treeKeyDestroy(gpointer key)809 static void treeKeyDestroy(gpointer key) { g_free(key); }
treeValueDestroy(gpointer value)810 static void treeValueDestroy(gpointer value) { }
811 
812 
813 /* call back function for traversing what is left from the tree -
814  * dangling files - files present in DB but not present physically on iPOD.
815  * It adds found tracks to the dandling list so user can see what is missing
816  * and then decide on what to do with them */
remove_dangling(gpointer key,gpointer value,gpointer pl_dangling)817 gboolean remove_dangling (gpointer key, gpointer value, gpointer pl_dangling)
818 {
819 /*     printf("Found dangling item pointing file %s\n", ((Track*)value)->ipod_path); */
820     Track *track = (Track*)value;
821     GList **l_dangling = ((GList **)pl_dangling);
822     gchar *filehash = NULL;
823     gint lind;
824     ExtraTrackData *etr;
825 
826     g_return_val_if_fail (l_dangling, FALSE);
827     g_return_val_if_fail (track, FALSE);
828     etr = track->userdata;
829     g_return_val_if_fail (etr, FALSE);
830 
831     /* 1 - Original file is present on PC */
832     /* 0 - Doesn't exist */
833     lind = 0;
834     if (etr->pc_path_locale && *etr->pc_path_locale &&
835 	g_file_test (etr->pc_path_locale, G_FILE_TEST_EXISTS))
836     {
837 	lind = 1;
838     }
839     l_dangling[lind]=g_list_append(l_dangling[lind], track);
840 
841     g_free(filehash);
842     return FALSE;               /* do not stop traversal */
843 }
844 
ntokens(gchar ** tokens)845 guint ntokens(gchar** tokens)
846 {
847     guint n=0;
848     while (tokens[n]) n++;
849     return n;
850 }
851 
852 
process_gtk_events_blocked()853 void process_gtk_events_blocked()
854 {    while (widgets_blocked && gtk_events_pending ()) gtk_main_iteration ();  }
855 
856 
857 
858 /* Frees memory busy by the lists containing tracks stored in
859    @user_data1 */
860 static void
check_db_danglingcancel0(gpointer user_data1,gpointer user_data2)861 check_db_danglingcancel0  (gpointer user_data1, gpointer user_data2)
862 {
863     g_list_free((GList *)user_data1);
864     gtkpod_statusbar_message (_("Removal of dangling tracks with no files on PC was canceled."));
865 }
866 
867 
868 /* Frees memory busy by the lists containing tracks stored in
869    @user_data1 */
870 static void
check_db_danglingcancel1(gpointer user_data1,gpointer user_data2)871 check_db_danglingcancel1  (gpointer user_data1, gpointer user_data2)
872 {
873     g_list_free((GList *)user_data1);
874     gtkpod_statusbar_message (_("Handling of dangling tracks with files on PC was canceled."));
875 }
876 
877 /* "dangling": tracks that are in database but not on disk */
878 /* To be called for ok to remove dangling Tracks with with no files
879  * linked.  Frees @user_data1 and @user_data2*/
880 static void
check_db_danglingok0(gpointer user_data1,gpointer user_data2)881 check_db_danglingok0 (gpointer user_data1, gpointer user_data2)
882 {
883     GList *tlist = ((GList *)user_data1);
884     GList *l_dangling = tlist;
885     iTunesDB *itdb = user_data2;
886 
887     g_return_if_fail (itdb);
888     /* traverse the list and append to the str */
889     for (tlist = g_list_first(tlist);
890 	 tlist != NULL;
891 	 tlist = g_list_next(tlist))
892     {
893 	Track *track = tlist->data;
894 	g_return_if_fail (track);
895 
896         /* printf("Removing track %d\n", track->ipod_id); */
897 	/* remove track from database */
898 	gp_playlist_remove_track (NULL, track, DELETE_ACTION_DATABASE);
899     }
900     g_list_free(l_dangling);
901     data_changed (itdb);
902     gtkpod_statusbar_message (_("Dangling tracks with no files on PC were removed."));
903 }
904 
905 
906 
907 /* To be called for ok to remove dangling Tracks with with no files linked.
908  * Frees @user_data1 and @user_data2*/
909 static void
check_db_danglingok1(gpointer user_data1,gpointer user_data2)910 check_db_danglingok1 (gpointer user_data1, gpointer user_data2)
911 {
912     GList *tlist = ((GList *)user_data1);
913     GList *l_dangling = tlist;
914     iTunesDB *itdb = user_data2;
915 
916     g_return_if_fail (itdb);
917 
918     block_widgets ();
919 
920     /* traverse the list and append to the str */
921     for (tlist = g_list_first(tlist);
922 	 tlist != NULL;
923 	 tlist = g_list_next(tlist))
924     {
925 	Track *oldtrack;
926 	Track *track = tlist->data;
927 	ExtraTrackData *etr;
928 	gchar *buf;
929 
930 	g_return_if_fail (track);
931 	etr = track->userdata;
932 	g_return_if_fail (etr);
933         /* printf("Handling track %d\n", track->ipod_id); */
934 
935 	buf = get_track_info (track, TRUE);
936 	gtkpod_statusbar_message (_("Processing '%s'..."), buf);
937 	g_free (buf);
938 
939 	while (widgets_blocked && gtk_events_pending ())
940 	    gtk_main_iteration ();
941 
942 	/* Indicate that file needs to be transfered */
943 	track->transferred=FALSE;
944 	/* Update SHA1 information */
945 	/* remove track from sha1 hash and reinsert it
946 	   (hash value may have changed!) */
947 	sha1_track_remove (track);
948 	/* need to remove the old value manually! */
949 	g_free (etr->sha1_hash);
950 	etr->sha1_hash = NULL;
951 	oldtrack = sha1_track_exists_insert (itdb, track);
952 	if (oldtrack) { /* track exists, remove old track
953 			  and register the new version */
954 	    sha1_track_remove (oldtrack);
955 	    gp_duplicate_remove (track, oldtrack);
956 	    sha1_track_exists_insert (itdb, track);
957 	}
958 	/* mark for conversion / transfer */
959 	file_convert_add_track (track);
960     }
961     g_list_free(l_dangling);
962     data_changed (itdb);
963     gtkpod_statusbar_message (_("Dangling tracks with files on PC were handled."));
964     /* I don't think it's too interesting to pop up the list of
965        duplicates -- but we should reset the list. */
966     gp_duplicate_remove (NULL, (void *)-1);
967 
968     release_widgets ();
969 }
970 
971 
972 
glist_list_tracks(GList * tlist,GString * str)973 static void glist_list_tracks (GList * tlist, GString * str)
974 	{
975 	    if (str==NULL)
976 	    {
977 		fprintf(stderr, "Report the bug please: shouldn't be NULL at %s:%d\n",__FILE__,__LINE__);
978 		return;
979 	    }
980 	    /* traverse the list and append to the str */
981 	    for (tlist = g_list_first(tlist);
982 		 tlist != NULL;
983 		 tlist = g_list_next(tlist))
984 	    {
985 		ExtraTrackData *etr;
986 		Track *track = tlist->data;
987 		g_return_if_fail (track);
988 		etr = track->userdata;
989 		g_return_if_fail (etr);
990 		g_string_append_printf
991 		    (str,"%s(%d) %s-%s -> %s\n",_("Track"),
992 		     track->id, track->artist,  track->title,  etr->pc_path_utf8);
993 	    }
994 	} /* end of glist_list_tracks */
995 
996 /* checks iTunesDB for presence of dangling links and checks IPODs
997  * Music directory on subject of orphaned files */
check_db(iTunesDB * itdb)998 void check_db (iTunesDB *itdb)
999 {
1000 
1001 
1002     GTree *files_known = NULL;
1003     GDir  *dir_des = NULL;
1004 
1005     gchar *pathtrack=NULL;
1006     gchar *ipod_filename = NULL;
1007 #   define localdebug  0      /* may be later becomes more general verbose param */
1008     Playlist* pl_orphaned = NULL;
1009     GList * l_dangling[2] = {NULL, NULL}; /* 2 kinds of dangling tracks: with approp
1010 					   * files and without */
1011     /* 1 - Original file is present on PC and has the same sha1*/
1012     /* 0 - Doesn't exist */
1013 
1014     gpointer foundtrack ;
1015     gint h,i;
1016     gint norphaned = 0;
1017     gint ndangling = 0;
1018     gchar ** tokens;
1019     const gchar *mountpoint = itdb_get_mountpoint (itdb);
1020     ExtraiTunesDBData *eitdb;
1021     GList *gl;
1022     gchar *music_dir = NULL;
1023 
1024     g_return_if_fail (itdb);
1025     eitdb = itdb->userdata;
1026     g_return_if_fail (eitdb);
1027 
1028     /* If an iTunesDB exists on the iPod, the user probably is making
1029        a mistake and we should tell him about it */
1030     if (!eitdb->itdb_imported)
1031     {
1032 		gchar *itunesdb_filename = itdb_get_itunesdb_path (mountpoint);
1033 
1034 		if (itunesdb_filename)
1035 		{
1036 			const gchar *str = _("You did not import the existing iTunesDB. This is most likely incorrect and will result in the loss of the existing database.\n\nIf you abort the operation, you can import the existing database before calling this function again.\n");
1037 
1038 			gint result = gtkpod_confirmation_hig (GTK_WINDOW (gtkpod_window),
1039 												   GTK_MESSAGE_WARNING,
1040 												   _("Existing iTunes database not imported"),
1041 												   str,
1042 												   _("Proceed anyway"),
1043 												   _("Abort operation"),
1044 												   NULL,
1045 												   NULL);
1046 
1047 			if (result == GTK_RESPONSE_CANCEL)
1048 			{
1049 				return;
1050 			}
1051 		}
1052     }
1053 
1054     gtkpod_statusbar_timeout (30*STATUSBAR_TIMEOUT);
1055     block_widgets();
1056 
1057     gtkpod_statusbar_message(_("Creating a tree of known files"));
1058     gtkpod_tracks_statusbar_update();
1059 
1060     /* put all files in the hash table */
1061     files_known = g_tree_new_full (str_cmp, NULL,
1062 				   treeKeyDestroy, treeValueDestroy);
1063     for (gl=itdb->tracks; gl; gl=gl->next)
1064     {
1065 	Track *track = gl->data;
1066         gint ntok=0;
1067 	g_return_if_fail (track);
1068         /* we don't want to report non-transferred files as dangling */
1069 	if (!track->transferred) continue;
1070 	tokens = g_strsplit(track->ipod_path,":",(track->ipod_path[0]==':'?4:3));
1071         ntok=ntokens(tokens);
1072 	if (ntok>=3)
1073 	{
1074 	    pathtrack=g_strdup (tokens[ntok-1]);
1075 	}
1076 	else
1077 	{
1078 	    /* illegal ipod_path */
1079 	    /* the track has NO ipod_path, so we want the item to
1080 	       ultimately be deleted from DB, * however, we need to
1081 	       add it to tree it such a way that:
1082                a) it will be unique
1083                b) it won't match to any existing file on the ipod
1084 
1085                so use something invented using the pointer to the
1086                track structure as a way to generate uniqueness
1087             */
1088            pathtrack=g_strdup_printf ("NOFILE-%p", track);
1089 	}
1090 
1091         if (localdebug)
1092 	{
1093             fprintf(stdout,"File %s\n", pathtrack);
1094 	    fflush(stdout);
1095 	}
1096 
1097 	g_tree_insert (files_known, pathtrack, track);
1098 	g_strfreev(tokens);
1099     }
1100 
1101     gtkpod_statusbar_message(_("Checking iPOD files against known files in DB"));
1102     gtkpod_tracks_statusbar_update();
1103     process_gtk_events_blocked();
1104 
1105     music_dir = itdb_get_music_dir (mountpoint);
1106 
1107     for(h=0; h<itdb_musicdirs_number (itdb); h++)
1108     {
1109 	/* directory name */
1110 	gchar *ipod_dir=g_strdup_printf("F%02d",h); /* just directory name */
1111 	gchar *ipod_fulldir;
1112 	/* full path */
1113 	ipod_fulldir = itdb_get_path (music_dir, ipod_dir);
1114 	if(ipod_fulldir && (dir_des=g_dir_open(ipod_fulldir,0,NULL))) {
1115 	    while ((ipod_filename=g_strdup(g_dir_read_name(dir_des))))
1116 		/* we have a file in the directory*/
1117 	    {
1118 		pathtrack=g_strdup_printf("%s%c%s", ipod_dir, ':', ipod_filename);
1119 
1120                 if (localdebug) {
1121                     fprintf(stdout,"Considering %s ", pathtrack);
1122                     fflush(stdout);
1123                 }
1124 
1125 		if ( g_tree_lookup_extended (files_known, pathtrack,
1126 					     &foundtrack, &foundtrack) )
1127 		{ /* file is not orphaned */
1128 		    g_tree_remove(files_known, pathtrack); /* we don't need this any more */
1129                     if (localdebug) fprintf(stdout," good ");
1130 		}
1131 		else
1132 		{  /* Now deal with orphaned... */
1133 		    gchar *fn_orphaned;
1134 		    gchar *num_str = g_strdup_printf ("F%02d", h);
1135 		    Track *dupl_track;
1136 
1137 		    const gchar *p_dcomps[] =
1138 			{ num_str, ipod_filename, NULL };
1139 
1140 		    fn_orphaned = itdb_resolve_path (music_dir, p_dcomps);
1141 
1142 		    if (!pl_orphaned)
1143 		    {
1144 			gchar *str = g_strdup_printf ("[%s]", _("Orphaned"));
1145 			pl_orphaned = gp_playlist_by_name_or_add (
1146 			    itdb, str, FALSE);
1147 			g_free (str);
1148 		    }
1149 
1150 		    norphaned++;
1151 
1152                     if (localdebug) fprintf(stdout,"to orphaned ");
1153 		    if ((dupl_track = sha1_file_exists (itdb, fn_orphaned,
1154 						       TRUE)))
1155 		    {  /* This orphan has already been added again.
1156 			  It will be removed with the next sync */
1157 			Track *track = gp_track_new ();
1158 			gchar *fn_utf8 = charset_to_utf8 (fn_orphaned);
1159 			const gchar *dir_rel = music_dir + strlen (mountpoint);
1160 			if (*dir_rel == G_DIR_SEPARATOR) ++dir_rel;
1161 			track->ipod_path = g_strdup_printf (
1162 			    "%c%s%c%s%c%s",
1163 			    G_DIR_SEPARATOR, dir_rel,
1164 			    G_DIR_SEPARATOR, num_str,
1165 			    G_DIR_SEPARATOR, ipod_filename);
1166 			itdb_filename_fs2ipod (track->ipod_path);
1167 
1168 			gp_track_validate_entries (track);
1169 			mark_track_for_deletion (itdb, track);
1170 			gtkpod_warning (_(
1171 			 "The following orphaned file had already "
1172 			 "been added to the iPod again. It will be "
1173 			 "removed with the next sync:\n%s\n\n"),
1174 			 fn_utf8);
1175 			g_free (fn_utf8);
1176 		    }
1177 		    else
1178 		    {
1179 			add_track_by_filename(itdb,
1180 					      fn_orphaned, pl_orphaned,
1181 					      FALSE, NULL, NULL);
1182 		    }
1183 		    g_free (fn_orphaned);
1184 		    g_free (num_str);
1185 		}
1186                 if (localdebug) fprintf(stdout," done\n");
1187 
1188 		g_free(ipod_filename);
1189 		g_free(pathtrack);
1190 	    }
1191             g_dir_close(dir_des);
1192 	}
1193         g_free(ipod_dir);
1194  	g_free(ipod_fulldir);
1195 	process_gtk_events_blocked();
1196     }
1197 
1198     ndangling=g_tree_nnodes(files_known);
1199     gtkpod_statusbar_message (_("Found %d orphaned and %d dangling files. Processing..."),
1200 			      norphaned, ndangling);
1201 
1202     gtkpod_tracks_statusbar_update();
1203 
1204     g_free(music_dir);
1205     music_dir = NULL;
1206 
1207     /* Now lets deal with dangling tracks */
1208     /* Traverse the tree - leftovers are dangling - put them in two lists */
1209     g_tree_foreach(files_known, remove_dangling, l_dangling);
1210 
1211     for (i=0;i<2;i++)
1212     {
1213 	GString *str_dangs = g_string_sized_new(2000);
1214 	gint ndang=0;
1215 	gchar *buf;
1216 
1217 	glist_list_tracks(l_dangling[i], str_dangs); /* compose String list of the tracks */
1218 	ndang = g_list_length(l_dangling[i]);
1219 	if (ndang)
1220 	{
1221 	    if (i==1)
1222 		buf = g_strdup_printf (
1223 		    ngettext ("The following dangling track has a file on PC.\nPress OK to have them transfered from the file on next Sync, CANCEL to leave it as is.",
1224 			      "The following %d dangling tracks have files on PC.\nPress OK to have them transfered from the files on next Sync, CANCEL to leave them as is.",
1225 			      ndang), ndang);
1226 	    else
1227 		buf = g_strdup_printf (
1228 		    ngettext ("The following dangling track doesn't have file on PC. \nPress OK to remove it, CANCEL to leave it as is.",
1229 			      "The following %d dangling tracks do not have files on PC. \nPress OK to remove them, CANCEL to leave them. as is",
1230 			      ndang), ndang);
1231 
1232 	    if (gtkpod_confirmation
1233 		((i==1?CONF_ID_DANGLING1:CONF_ID_DANGLING0), /* we want unique window for each */
1234 		 FALSE,         /* gboolean modal, */
1235 		 _("Dangling Tracks"), /* title */
1236 		 buf,           /* label */
1237 		 str_dangs->str, /* scrolled text */
1238 		 NULL, 0, NULL, /* option 1 */
1239 		 NULL, 0, NULL, /* option 2 */
1240 		 TRUE,          /* gboolean confirm_again, */
1241 		 NULL,          /* ConfHandlerOpt confirm_again_handler,*/
1242 		 i==1?check_db_danglingok1:check_db_danglingok0, /* ConfHandler ok_handler,*/
1243 		 NULL,          /* don't show "Apply" button */
1244 		 i==1?check_db_danglingcancel1:check_db_danglingcancel0, /* cancel_handler,*/
1245 		 l_dangling[i], /* gpointer user_data1,*/
1246 		 itdb)             /* gpointer user_data2,*/
1247 		== GTK_RESPONSE_REJECT)
1248 	    {   /* free memory */
1249 		g_list_free(l_dangling[i]);
1250 	    }
1251 	    g_free (buf);
1252 	    g_string_free (str_dangs, TRUE);
1253 	}
1254     }
1255 
1256     if (pl_orphaned) data_changed (itdb);
1257     g_tree_destroy (files_known);
1258     gtkpod_statusbar_message (_("Found %d orphaned and %d dangling files. Done."),
1259 			      norphaned, ndangling);
1260     gtkpod_statusbar_timeout (0);
1261     release_widgets ();
1262 }
1263