1 /*
2 |  Copyright (C) 2002-2007 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 "info.h"
34 #include "infodlg.h"
35 #include "misc.h"
36 #include "misc_track.h"
37 #include "mp3file.h"
38 #include "prefs.h"
39 #include "tools.h"
40 #include <errno.h>
41 #include <fcntl.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <sys/stat.h>
45 #include <sys/types.h>
46 #include <sys/wait.h>
47 #include <unistd.h>
48 
49 
50 /*pipe's definition*/
51 enum {
52     READ = 0,
53     WRITE = 1
54 };
55 
56 enum {
57     BUFLEN = 1000,
58 };
59 
60 /* ------------------------------------------------------------
61 
62 		     Normalize Volume
63 
64    ------------------------------------------------------------ */
65 
66 
67 #ifdef G_THREADS_ENABLED
68 static  GMutex *mutex = NULL;
69 static GCond  *cond = NULL;
70 static gboolean mutex_data = FALSE;
71 #endif
72 
73 
74 /* Run @command on @track_path.
75  *
76  * Command may include options, like "mp3gain -q -k %s"
77  *
78  * %s is replaced by @track_path if present, otherwise @track_path is
79  * added at the end.
80  *
81  * Return value: TRUE if the command ran successfully, FALSE if any
82  * error occurred.
83  */
run_exec_on_track(const gchar * commandline,const gchar * track_path)84 static gboolean run_exec_on_track (const gchar *commandline,
85 				   const gchar *track_path)
86 {
87     gchar *command_full_path = NULL;
88     gchar *command = NULL;
89     gchar *command_base = NULL;
90     const gchar *nextarg;
91     gboolean success = FALSE;
92     gboolean percs = FALSE;
93     GPtrArray *args;
94 
95     int status, fdnull, ret;
96     pid_t tpid;
97 
98     g_return_val_if_fail (commandline, FALSE);
99     g_return_val_if_fail (track_path, FALSE);
100 
101 
102     /* skip whitespace */
103     while (g_ascii_isspace (*commandline))  ++commandline;
104 
105     /* find the command itself -- separated by ' ' */
106     nextarg = strchr (commandline, ' ');
107     if (!nextarg)
108     {
109 	nextarg = commandline + strlen (commandline);
110     }
111 
112     command = g_strndup (commandline, nextarg-commandline);
113 
114     command_full_path = g_find_program_in_path (command);
115 
116     if (!command_full_path)
117     {
118 	gtkpod_warning (_("Could not find '%s'.\nPlease specifiy the exact path in the Tools section of the preference dialog or install the program if it is not installed on your system.\n\n"), command);
119 	goto cleanup;
120     }
121 
122     command_base = g_path_get_basename (command_full_path);
123 
124     /* Create the command line to be used with execv(). */
125     args =  g_ptr_array_sized_new (strlen (commandline));
126     /* add the full path */
127     g_ptr_array_add (args, command_full_path);
128     /* add the basename */
129     g_ptr_array_add (args, command_base);
130     /* add the command line arguments */
131 
132     commandline = nextarg;
133 
134     /* skip whitespace */
135     while (g_ascii_isspace (*commandline))  ++commandline;
136 
137     while (*commandline != 0)
138     {
139 	const gchar *next;
140 
141 	next = strchr (commandline, ' ');
142 	/* next argument is everything to the end */
143 	if (!next)
144 	    next = commandline + strlen (commandline);
145 
146 	if (strncmp (commandline, "%s", 2) == 0)
147 	{   /* substitute %s with @track_path */
148 	    g_ptr_array_add (args, g_strdup (track_path));
149 	    percs = TRUE;
150 	}
151 	else
152 	{
153 	    g_ptr_array_add (args,
154 			     g_strndup (commandline, next-commandline));
155 	}
156 
157 	/* skip to next argument */
158 	commandline = next;
159 
160 	/* skip whitespace */
161 	while (g_ascii_isspace (*commandline))  ++commandline;
162     }
163 
164     /* Add @track_path if "%s" was not present */
165     if (!percs)
166 	g_ptr_array_add (args, g_strdup (track_path));
167 
168     /* need NULL pointer */
169     g_ptr_array_add (args, NULL);
170 
171     tpid = fork ();
172 
173     switch (tpid)
174     {
175     case 0: /* we are the child */
176     {
177 	gchar **argv = (gchar **)args->pdata;
178 #if 0
179 	gchar **bufp = argv;
180 	while (*bufp)	{ puts (*bufp); ++bufp;	}
181 #endif
182 	/* redirect output to /dev/null */
183 	if ((fdnull = open("/dev/null", O_WRONLY | O_NDELAY)) != -1)
184 	{
185 	    dup2(fdnull, fileno(stdout));
186 	}
187 	execv(argv[0], &argv[1]);
188 	exit(0);
189 	break;
190     }
191     case -1: /* we are the parent, fork() failed  */
192 	g_ptr_array_free (args, TRUE);
193 	break;
194     default: /* we are the parent, everything's fine */
195 	tpid = waitpid (tpid, &status, 0);
196 	g_ptr_array_free (args, TRUE);
197 	if (WIFEXITED(status))
198 	    ret = WEXITSTATUS(status);
199 	else
200 	    ret = 2;
201 	if (ret > 1)
202 	{
203 	    gtkpod_warning (_("Execution of '%s' failed.\n\n"),
204 			    command_full_path);
205 	}
206 	else
207 	{
208 	    success = TRUE;
209 	}
210 	break;
211     }
212 
213 
214   cleanup:
215     g_free (command_full_path);
216     g_free (command);
217     g_free (command_base);
218 
219     return success;
220 }
221 
222 
223 
224 
225 /* reread the soundcheck value from the file */
nm_get_soundcheck(Track * track)226 static gboolean nm_get_soundcheck (Track *track)
227 {
228     gboolean success = FALSE;
229     gchar *path;
230     gchar *commandline = NULL;
231 
232     g_return_val_if_fail (track, FALSE);
233 
234     if (read_soundcheck (track))
235 	return TRUE;
236 
237     path = get_file_name_from_source (track, SOURCE_PREFER_LOCAL);
238 
239     if (path)
240     {
241 	switch (determine_file_type (path))
242 	{
243 	case FILE_TYPE_MP3:
244 	    commandline = prefs_get_string ("path_mp3gain");
245 	    if (!commandline)
246 	    {
247 		gtkpod_warning (
248 		    _("Did not normalize '%s'. Set mp3gain path in the Tools section of the preferences.\n"), path);
249 	    }
250 	    break;
251 	case FILE_TYPE_M4A:
252 	case FILE_TYPE_M4P:
253 	case FILE_TYPE_M4B:
254 	    commandline = prefs_get_string ("path_aacgain");
255 	    if (!commandline)
256 	    {
257 		gtkpod_warning (
258 		    _("Did not normalize '%s'. Set aacgain path in the Tools section of the preferences.\n"), path);
259 	    }
260 	    break;
261 	case FILE_TYPE_WAV: /* FIXME */
262 	case FILE_TYPE_OGG: /* FIXME */
263 	case FILE_TYPE_FLAC: /* FIXME */
264 	case FILE_TYPE_M4V:
265 	case FILE_TYPE_MP4:
266 	case FILE_TYPE_MOV:
267 	case FILE_TYPE_MPG:
268 	case FILE_TYPE_UNKNOWN:
269 	    gtkpod_warning (
270 		_("Normalization failed: file type not supported (%s).\n\n"),
271 		path);
272 	    break;
273 	case FILE_TYPE_M3U:
274 	case FILE_TYPE_PLS:
275 	case FILE_TYPE_IMAGE:
276 	case FILE_TYPE_DIRECTORY:
277 	    break;
278 	}
279 	if (commandline)
280 	    success = run_exec_on_track (commandline, path);
281 	g_free (path);
282     }
283     else
284     {
285 	gchar *buf = get_track_info (track, FALSE);
286 	gtkpod_warning (
287 	    _("Normalization failed: file not available (%s).\n\n"),
288 	    buf);
289 	g_free (buf);
290     }
291 
292     if (success)
293 	return read_soundcheck (track);
294     else
295 	return FALSE;
296 }
297 
298 
299 
300 #ifdef G_THREADS_ENABLED
301 /* Threaded getTrackGain*/
th_nm_get_soundcheck(gpointer track)302 static gpointer th_nm_get_soundcheck (gpointer track)
303 {
304    gboolean success = nm_get_soundcheck ((Track *)track);
305    g_mutex_lock (mutex);
306    mutex_data = TRUE; /* signal that thread will end */
307    g_cond_signal (cond);
308    g_mutex_unlock (mutex);
309    return GUINT_TO_POINTER(success);
310 }
311 #endif
312 
313 /* normalize the newly inserted tracks (i.e. non-transferred tracks) */
nm_new_tracks(iTunesDB * itdb)314 void nm_new_tracks (iTunesDB *itdb)
315 {
316     GList *tracks=NULL;
317     GList *gl;
318 
319     g_return_if_fail (itdb);
320 
321     for (gl=itdb->tracks; gl; gl=gl->next)
322     {
323 	Track *track = gl->data;
324 	g_return_if_fail (track);
325 	if(!track->transferred)
326 	{
327 	    tracks = g_list_append (tracks, track);
328 	}
329     }
330     nm_tracks_list (tracks);
331     g_list_free (tracks);
332 }
333 
normalization_abort(gboolean * abort)334 static void normalization_abort(gboolean *abort)
335 {
336    *abort=TRUE;
337 }
338 
nm_tracks_list(GList * list)339 void nm_tracks_list (GList *list)
340 {
341   gint count, succ_count, n, nrs;
342   guint32 old_soundcheck;
343   gboolean success;
344   static gboolean abort;
345   GtkWidget *dialog, *progress_bar, *label, *track_label;
346   GtkWidget *image, *hbox;
347   time_t diff, start, fullsecs, hrs, mins, secs;
348   gchar *progtext = NULL;
349 
350 #ifdef G_THREADS_ENABLED
351   GThread *thread = NULL;
352   GTimeVal gtime;
353   if (!mutex) mutex = g_mutex_new ();
354   if (!cond) cond = g_cond_new ();
355 #endif
356 
357   block_widgets ();
358 
359   /* create the dialog window */
360   dialog = gtk_dialog_new_with_buttons (_("Information"),
361 					 GTK_WINDOW (gtkpod_window),
362 					 GTK_DIALOG_DESTROY_WITH_PARENT,
363 					 GTK_STOCK_CANCEL,
364 					 GTK_RESPONSE_NONE,
365 					 NULL);
366 
367 
368   /* emulate gtk_message_dialog_new */
369   image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_INFO,
370 				    GTK_ICON_SIZE_DIALOG);
371   label = gtk_label_new (
372       _("Press button to abort."));
373 
374   gtk_misc_set_alignment (GTK_MISC (image), 0.5, 0.0);
375   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
376   gtk_label_set_selectable (GTK_LABEL (label), TRUE);
377 
378   /* hbox to put the image+label in */
379   hbox = gtk_hbox_new (FALSE, 6);
380   gtk_box_pack_start (GTK_BOX (hbox), image, FALSE, FALSE, 0);
381   gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0);
382 
383   /* Create the progress bar */
384   progress_bar = gtk_progress_bar_new ();
385   progtext = g_strdup (_("Normalizing..."));
386   gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar), progtext);
387   g_free (progtext);
388 
389   /* Create label for track name */
390   track_label = gtk_label_new (NULL);
391   gtk_label_set_line_wrap (GTK_LABEL (label), TRUE);
392   gtk_label_set_selectable (GTK_LABEL (label), TRUE);
393 
394   /* Indicate that user wants to abort */
395   g_signal_connect_swapped (GTK_OBJECT (dialog), "response",
396 			    G_CALLBACK (normalization_abort),
397 			    &abort);
398 
399   /* Add the image/label + progress bar to dialog */
400   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
401 		      hbox, FALSE, FALSE, 0);
402   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
403 		      track_label, FALSE, FALSE, 0);
404   gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dialog)->vbox),
405 		      progress_bar, FALSE, FALSE, 0);
406   gtk_widget_show_all (dialog);
407 
408   while (widgets_blocked && gtk_events_pending ())  gtk_main_iteration ();
409 
410   /* count number of tracks to be normalized */
411   n = g_list_length(list);
412   count = 0; /* tracks processed */
413   succ_count = 0;  /* tracks normalized */
414   nrs = 0;
415   abort = FALSE;
416   start = time(NULL);
417 
418   if(n==0)
419   {
420      /* FIXME we should tell something*/
421 
422   }
423   else
424   {
425       /* we need ***much*** longer timeout */
426       gtkpod_statusbar_timeout (30*STATUSBAR_TIMEOUT);
427   }
428   while (!abort &&  (list!=NULL))
429   {
430      Track  *track = list->data;
431      gchar *label_buf = g_strdup_printf ("%d/%d", count, n);
432 
433      gtk_label_set_text (GTK_LABEL (track_label), label_buf);
434 
435      gtkpod_statusbar_message (_("%s - %s"),
436 			       track->artist, track->title);
437      C_FREE (label_buf);
438 
439      while (widgets_blocked && gtk_events_pending ())
440 	 gtk_main_iteration ();
441 
442      /* need to know so we can update the display when necessary */
443      old_soundcheck = track->soundcheck;
444 
445 #ifdef G_THREADS_ENABLED
446      mutex_data = FALSE;
447      thread = g_thread_create (th_nm_get_soundcheck, track, TRUE, NULL);
448      if (thread)
449      {
450 	 gboolean first_abort = TRUE;
451 	 g_mutex_lock (mutex);
452 	 do
453 	 {
454 	     while (widgets_blocked && gtk_events_pending ())
455 		 gtk_main_iteration ();
456 	     /* wait a maximum of 10 ms */
457 
458 	     if (abort && first_abort)
459 	     {
460 		 first_abort = FALSE;
461 		 progtext = g_strdup (_("Aborting..."));
462 		 gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar),
463 					   progtext);
464 		 g_free (progtext);
465 		 gtkpod_statusbar_message(_("Will abort after current mp3gain process ends."));
466 		 while (widgets_blocked && gtk_events_pending ())
467 		     gtk_main_iteration ();
468 	     }
469 	     g_get_current_time (&gtime);
470 	     g_time_val_add (&gtime, 20000);
471 	     g_cond_timed_wait (cond, mutex, &gtime);
472 	 }
473 	 while(!mutex_data);
474 	 success = GPOINTER_TO_UINT(g_thread_join (thread));
475 	 g_mutex_unlock (mutex);
476      }
477      else
478      {
479 	 g_warning ("Thread creation failed, falling back to default.\n");
480 	 success = nm_get_soundcheck (track);
481      }
482 #else
483      success = nm_get_soundcheck (track);
484 #endif
485 
486      /*normalization part*/
487      if(!success)
488      {
489 	 gchar *path = get_file_name_from_source (track, SOURCE_PREFER_LOCAL);
490 	 gtkpod_warning (
491 	     _("'%s-%s' (%s) could not be normalized.\n\n"),
492 	     track->artist, track->title, path? path:"");
493 	 g_free (path);
494      }
495      else
496      {
497 	 ++succ_count;
498 	 if(old_soundcheck != track->soundcheck)
499 	 {
500 	     pm_track_changed (track);
501 	     data_changed (track->itdb);
502 	 }
503      }
504      /*end normalization*/
505 
506      ++count;
507      gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR (progress_bar),
508 				   (gdouble) count/n);
509 
510      diff = time(NULL) - start;
511      fullsecs = (diff*n/count)-diff;
512      hrs  = fullsecs / 3600;
513      mins = (fullsecs % 3600) / 60;
514      secs = ((fullsecs % 60) / 5) * 5;
515      /* don't bounce up too quickly (>10% change only) */
516 /*	      left = ((mins < left) || (100*mins >= 110*left)) ? mins : left;*/
517      progtext = g_strdup_printf (
518 	 _("%d%% (%d:%02d:%02d left)"),
519 	 count*100/n, (int)hrs, (int)mins, (int)secs);
520      gtk_progress_bar_set_text(GTK_PROGRESS_BAR (progress_bar),
521 			       progtext);
522      g_free (progtext);
523 
524      while (widgets_blocked && gtk_events_pending ())  gtk_main_iteration ();
525      list=g_list_next(list);
526   } /*end while*/
527 
528   gtkpod_statusbar_timeout (0);
529 
530   gtkpod_statusbar_message (ngettext ("Normalized %d of %d tracks.",
531 				      "Normalized %d of %d tracks.", n),
532 			    count, n);
533 
534   gtk_widget_destroy (dialog);
535   release_widgets();
536 }
537 
538 
539 
540 /* ------------------------------------------------------------
541 
542 	    Synchronize Contacts / Calendar
543 
544    ------------------------------------------------------------ */
545 
546 typedef enum
547 {
548     SYNC_CONTACTS,
549     SYNC_CALENDAR,
550     SYNC_NOTES
551 } SyncType;
552 
553 
554 /* FIXME: tools need to be defined for each itdb separately */
555 /* replace %i in all strings of argv with prefs_get_ipod_mount() */
tools_sync_replace_percent_i(iTunesDB * itdb,gchar ** argv)556 static void tools_sync_replace_percent_i (iTunesDB *itdb, gchar **argv)
557 {
558     gchar *ipod_mount;
559     gint ipod_mount_len;
560     gint offset = 0;
561 
562     if (!itdb) return;
563 
564     ipod_mount = get_itdb_prefs_string (itdb, KEY_MOUNTPOINT);
565 
566     if (!ipod_mount) ipod_mount = g_strdup ("");
567 
568     ipod_mount_len = strlen (ipod_mount);
569 
570     while (argv && *argv)
571     {
572 	gchar *str = *argv;
573 	gchar *pi = strstr (str+offset, "%i");
574 	if (pi)
575 	{
576 	    /* len: -2: replace "%i"; +1: trailing 0 */
577 	    gint len = strlen (str) - 2 + ipod_mount_len + 1;
578 	    gchar *new_str = g_malloc0 (sizeof (gchar) * len);
579 	    strncpy (new_str, str, pi-str);
580 	    strcpy (new_str + (pi-str), ipod_mount);
581 	    strcpy (new_str + (pi-str) + ipod_mount_len, pi+2);
582 	    g_free (str);
583 	    str = new_str;
584 	    *argv = new_str;
585 	    /* set offset to point behind the inserted ipod_path in
586 	       case ipod_path contains "%i" */
587 	    offset = (pi-str) + ipod_mount_len;
588 	}
589 	else
590 	{
591 	    offset = 0;
592 	    ++argv;
593 	}
594     }
595 
596     g_free (ipod_mount);
597 }
598 
599 
600 /* execute the specified script, giving out error/status messages on
601    the way */
tools_sync_script(iTunesDB * itdb,SyncType type)602 static gboolean tools_sync_script (iTunesDB *itdb, SyncType type)
603 {
604     gchar *script=NULL;
605     gchar *script_path, *buf;
606     gchar **argv = NULL;
607     GString *script_output;
608     gint fdpipe[2];  /*a pipe*/
609     gint len;
610     pid_t pid,tpid;
611 
612     switch (type)
613     {
614     case SYNC_CONTACTS:
615 	script = get_itdb_prefs_string (itdb, "path_sync_contacts");
616 	break;
617     case SYNC_CALENDAR:
618 	script = get_itdb_prefs_string (itdb, "path_sync_calendar");
619 	break;
620     case SYNC_NOTES:
621 	script = get_itdb_prefs_string (itdb, "path_sync_notes");
622 	break;
623     default:
624 	fprintf (stderr, "Programming error: tools_sync_script () called with %d\n", type);
625 	return FALSE;
626     }
627 
628     /* remove leading and trailing whitespace */
629     if (script) g_strstrip (script);
630 
631     if (!script || (strlen (script) == 0))
632     {
633 	gtkpod_warning (_("Please specify the command to be called on the 'Tools' section of the preferences dialog.\n"));
634 	g_free (script);
635 	return FALSE;
636     }
637 
638     argv = g_strsplit (script, " ", -1);
639 
640     tools_sync_replace_percent_i (itdb, argv);
641 
642     script_path = g_find_program_in_path (argv[0]);
643     if (!script_path)
644     {
645 	gtkpod_warning (_("Could not find the command '%s'.\n\nPlease verify the setting in the 'Tools' section of the preferences dialog.\n\n"), argv[0]);
646 	g_free (script);
647 	g_strfreev (argv);
648 	return FALSE;
649     }
650 
651     /* set up arg list (first parameter should be basename of script */
652     g_free (argv[0]);
653     argv[0] = g_path_get_basename (script_path);
654 
655     buf = g_malloc (BUFLEN);
656     script_output = g_string_sized_new (BUFLEN);
657 
658     /*create the pipe*/
659     pipe(fdpipe);
660     /*then fork*/
661     pid=fork();
662 
663     /*and cast mp3gain*/
664     switch (pid)
665     {
666     case -1: /* parent and error, now what?*/
667 	break;
668     case 0: /*child*/
669 	close(fdpipe[READ]);
670 	dup2(fdpipe[WRITE],fileno(stdout));
671 	dup2(fdpipe[WRITE],fileno(stderr));
672 	close(fdpipe[WRITE]);
673 	execv(script_path, argv);
674 	break;
675     default: /*parent*/
676 	close(fdpipe[WRITE]);
677 	tpid = waitpid (pid, NULL, 0); /*wait for script termination */
678 	do
679 	{
680 	    len = read (fdpipe[READ], buf, BUFLEN);
681 	    if (len > 0) g_string_append_len (script_output, buf, len);
682 	} while (len > 0);
683 	close(fdpipe[READ]);
684 	/* display output in window, if any */
685 	if (strlen (script_output->str))
686 	{
687 	    gtkpod_warning (_("'%s' returned the following output:\n%s\n"),
688 			      script_path, script_output->str);
689 	}
690 	break;
691     } /*end switch*/
692 
693     /*free everything left*/
694     g_free (script_path);
695     g_free (buf);
696     g_strfreev (argv);
697     g_string_free (script_output, TRUE);
698     return TRUE;
699 }
700 
tools_sync_all(iTunesDB * itdb)701 gboolean tools_sync_all (iTunesDB *itdb)
702 {
703     gboolean success;
704 
705     success = tools_sync_script (itdb, SYNC_CALENDAR);
706     if (success)
707 	success = tools_sync_script (itdb, SYNC_CONTACTS);
708     if (success)
709 	tools_sync_script (itdb, SYNC_NOTES);
710     return success;
711 }
712 
tools_sync_contacts(iTunesDB * itdb)713 gboolean tools_sync_contacts (iTunesDB *itdb)
714 {
715     return tools_sync_script (itdb, SYNC_CONTACTS);
716 }
717 
tools_sync_calendar(iTunesDB * itdb)718 gboolean tools_sync_calendar (iTunesDB *itdb)
719 {
720     return tools_sync_script (itdb, SYNC_CALENDAR);
721 }
722 
tools_sync_notes(iTunesDB * itdb)723 gboolean tools_sync_notes (iTunesDB *itdb)
724 {
725     return tools_sync_script (itdb, SYNC_NOTES);
726 }
727 
728 /* ------------------------------------------------------------
729 
730 	    Play Now / Enqueue
731 
732    ------------------------------------------------------------ */
733 
734 
735 /*
736  * do_command_on_entries - execute @play on tracks in @selected_tracks
737  * @play: the command to execute (e.g. "xmms -e %s")
738  * @what: e.g. "Enqueue" or "Play Now" (used for error messages)
739  * @selected tracks: list of tracks to to be placed in the command line
740  * at the position of "%s"
741  *
742  */
743 void
do_command_on_entries(const gchar * command,const gchar * what,GList * selected_tracks)744 do_command_on_entries (const gchar *command, const gchar *what,
745 		       GList *selected_tracks)
746 {
747     GList *l;
748     gchar *str, *commandc, *next;
749     gboolean percs = FALSE; /* did "%s" already appear? */
750     GPtrArray *args;
751 
752     if ((!command) || (strlen (command) == 0))
753     {
754 	gtkpod_statusbar_message  (_("No command set for '%s'"), what);
755 	return;
756     }
757 
758     while (g_ascii_isspace (*command))  ++command;
759 
760     /* find the command itself -- separated by ' ' */
761     next = strchr (command, ' ');
762     if (!next)
763     {
764 	str = g_strdup (command);
765     }
766     else
767     {
768 	str = g_strndup (command, next-command);
769     }
770     /* get the full path */
771     commandc = g_find_program_in_path (str);
772     if (!commandc)
773     {
774 	gtkpod_statusbar_message  (_("Could not find command '%s' specified for '%s'"),
775 				   str, what);
776 	g_free (str);
777 	return;
778     }
779     C_FREE (str);
780 
781     /* Create the command line */
782     args = g_ptr_array_sized_new (g_list_length (selected_tracks) + 10);
783     /* first the full path */
784     g_ptr_array_add (args, commandc);
785     do
786     {
787 	const gchar *next;
788 	gboolean end;
789 
790 	next = strchr (command, ' ');
791 	if (next == NULL) next = command + strlen (command);
792 
793 	if (next == command)  end = TRUE;
794 	else                  end = FALSE;
795 
796 	if (!end && (strncmp (command, "%s", 2) != 0))
797 	{   /* current token is not "%s" */
798 	    gchar *buf;
799 	    buf = g_strndup (command, next-command);
800 	    g_ptr_array_add (args, buf);
801 	}
802 	else if (!percs)
803 	{
804 	    for(l = selected_tracks; l; l = l->next)
805 	    {
806 		Track *tr = l->data;
807 		g_return_if_fail (tr);
808 		str = get_file_name_from_source(tr, SOURCE_PREFER_LOCAL);
809 		if(str)
810 		{
811 		    g_ptr_array_add (args, str);
812 		}
813 	    }
814 	    percs = TRUE; /* encountered a '%s' */
815 	}
816 	command = next;
817 	/* skip whitespace */
818 	while (g_ascii_isspace (*command))  ++command;
819     } while (*command);
820     /* need NULL pointer */
821     g_ptr_array_add (args, NULL);
822 
823     switch(fork())
824     {
825     case 0: /* we are the child */
826     {
827 	gchar **argv = (gchar **)args->pdata;
828 #if DEBUG_MISC
829 	gchar **bufp = argv;
830 	while (*bufp)	{ puts (*bufp); ++bufp;	}
831 #endif
832 	execv(argv[0], &argv[1]);
833 	g_ptr_array_free (args, TRUE);
834 	exit(0);
835 	break;
836     }
837     case -1: /* we are the parent, fork() failed  */
838 	g_ptr_array_free (args, TRUE);
839 	break;
840     default: /* we are the parent, everything's fine */
841 	break;
842     }
843 }
844 
845 
846 /*
847  * play_entries_now - play the entries currently selected in xmms
848  * @selected_tracks: list of tracks to be played
849  */
tools_play_tracks(GList * selected_tracks)850 void tools_play_tracks (GList *selected_tracks)
851 {
852     gchar *path = prefs_get_string ("path_play_now");
853     do_command_on_entries (path,
854 			   _("Play Now"),
855 			   selected_tracks);
856     g_free (path);
857 }
858 
859 /*
860  * play_entries_now - play the entries currently selected in xmms
861  * @selected_tracks: list of tracks to be played
862  */
tools_enqueue_tracks(GList * selected_tracks)863 void tools_enqueue_tracks (GList *selected_tracks)
864 {
865     gchar *path = prefs_get_string ("path_play_enqueue");
866     do_command_on_entries (path,
867 			   _("Enqueue"),
868 			   selected_tracks);
869     g_free (path);
870 }
871 
872 
873 
874 /* ------------------------------------------------------------
875 
876       Callbacks
877 
878    ------------------------------------------------------------ */
879 
880 G_MODULE_EXPORT gboolean
on_gtkpod_info_delete_event(GtkWidget * widget,GdkEvent * event,gpointer user_data)881 on_gtkpod_info_delete_event            (GtkWidget       *widget,
882 					GdkEvent        *event,
883 					gpointer         user_data)
884 {
885     close_info_dialog ();
886     return TRUE; /* don't close again -- info_close_window() already does it */
887 }
888 
889 
890 G_MODULE_EXPORT void
on_info_close_clicked(GtkButton * button,gpointer user_data)891 on_info_close_clicked                  (GtkButton       *button,
892 					gpointer         user_data)
893 {
894     close_info_dialog ();
895 }
896