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 (>ime);
470 g_time_val_add (>ime, 20000);
471 g_cond_timed_wait (cond, mutex, >ime);
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