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