udapted vi.po
[rhythmbox.git] / sources / rb-playlist-source-recorder.c
blobfc84b18a67e06067d5b338c7cb5f060f683d7ffb
1 /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
3 * arch-tag: Implementation of playlist source recorder object
5 * Copyright (C) 2002 Jorn Baayen <jorn@nl.linux.org>
6 * Copyright (C) 2003 Colin Walters <walters@gnome.org>
7 * Copyright (C) 2004-2006 William Jon McCann <mccann@jhu.edu>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
25 #include "config.h"
27 #include <stdlib.h>
28 #include <unistd.h>
29 #include <string.h>
30 #include <errno.h>
31 #include <math.h>
33 #include <glib/gprintf.h>
34 #include <glib/gi18n.h>
35 #include <gtk/gtk.h>
36 #include <libgnomevfs/gnome-vfs-uri.h>
37 #include <libgnomevfs/gnome-vfs-utils.h>
38 #include <glade/glade.h>
39 #include <nautilus-burn-drive.h>
40 #include <nautilus-burn-drive-selection.h>
42 #ifndef NAUTILUS_BURN_CHECK_VERSION
43 #define NAUTILUS_BURN_CHECK_VERSION(a,b,c) FALSE
44 #endif
46 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
47 #include <nautilus-burn.h>
48 #endif
50 #include "rb-file-helpers.h"
51 #include "rb-glade-helpers.h"
52 #include "rb-preferences.h"
53 #include "rb-dialog.h"
54 #include "rb-util.h"
55 #include "rb-shell.h"
56 #include "rb-playlist-source.h"
57 #include "rb-playlist-source-recorder.h"
58 #include "rb-debug.h"
59 #include "eel-gconf-extensions.h"
61 #ifndef HAVE_MKDTEMP
62 #include "mkdtemp.h"
63 #else
64 extern char *mkdtemp (char *template);
65 #endif
67 /* NAUTILUS_BURN_DRIVE_SIZE_TO_TIME was added in 2.12 */
68 #ifndef NAUTILUS_BURN_DRIVE_SIZE_TO_TIME
69 #define nautilus_burn_drive_eject _nautilus_burn_drive_eject
70 #define nautilus_burn_drive_new_from_path _nautilus_burn_drive_new_from_path
71 #define nautilus_burn_drive_media_type_get_string _nautilus_burn_drive_media_type_get_string
72 #endif
73 /* NAUTILUS_BURN_DRIVE_SIZE_TO_TIME was added in 2.14 */
74 #ifndef HAVE_BURN_DRIVE_UNREF
75 #define nautilus_burn_drive_unref nautilus_burn_drive_free
76 #define nautilus_burn_drive_ref nautilus_burn_drive_copy
77 #endif
79 #include "rb-recorder.h"
81 #define CONF_STATE_BURN_SPEED CONF_PREFIX "/state/burn_speed"
83 #define AUDIO_BYTERATE (2 * 44100 * 2)
84 #define MAX_PLAYLIST_DURATION 6000
86 static void rb_playlist_source_recorder_class_init (RBPlaylistSourceRecorderClass *klass);
87 static void rb_playlist_source_recorder_init (RBPlaylistSourceRecorder *source);
88 static void rb_playlist_source_recorder_finalize (GObject *object);
90 void rb_playlist_source_recorder_device_changed_cb (NautilusBurnDriveSelection *selection,
91 const char *device_path,
92 RBPlaylistSourceRecorder *source);
94 GtkWidget * rb_playlist_source_recorder_device_menu_create (void);
96 typedef struct
98 char *artist;
99 char *title;
100 char *uri;
101 gulong duration;
102 } RBRecorderSong;
104 struct RBPlaylistSourceRecorderPrivate
106 GtkWidget *parent;
108 RBShell *shell;
110 char *name;
112 RBRecorder *recorder;
113 GSList *songs;
114 GSList *current;
115 GTimer *timer;
116 guint64 start_pos;
118 GtkWidget *cd_icon;
119 GtkWidget *vbox;
120 GtkWidget *multiple_copies_checkbutton;
121 GtkWidget *cancel_button;
122 GtkWidget *burn_button;
123 GtkWidget *message_label;
124 GtkWidget *progress_label;
125 GtkWidget *progress;
126 GtkWidget *device_menu;
127 GtkWidget *speed_combobox;
128 GtkWidget *options_box;
129 GtkWidget *progress_frame;
131 gboolean burning;
132 gboolean already_converted;
133 gboolean handling_error;
134 gboolean confirmed_exit;
136 char *tmp_dir;
139 typedef enum {
140 NAME_CHANGED,
141 FILE_ADDED,
142 ERROR,
143 LAST_SIGNAL
144 } RBPlaylistSourceRecorderSignalType;
146 static guint rb_playlist_source_recorder_signals [LAST_SIGNAL] = { 0 };
148 #define RB_PLAYLIST_SOURCE_RECORDER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), RB_TYPE_PLAYLIST_SOURCE_RECORDER, RBPlaylistSourceRecorderPrivate))
150 G_DEFINE_TYPE(RBPlaylistSourceRecorder, rb_playlist_source_recorder, GTK_TYPE_DIALOG)
152 static void
153 rb_playlist_source_recorder_style_set (GtkWidget *widget,
154 GtkStyle *previous_style)
156 GtkDialog *dialog;
158 if (GTK_WIDGET_CLASS (rb_playlist_source_recorder_parent_class)->style_set)
159 GTK_WIDGET_CLASS (rb_playlist_source_recorder_parent_class)->style_set (widget, previous_style);
161 dialog = GTK_DIALOG (widget);
163 gtk_container_set_border_width (GTK_CONTAINER (dialog->vbox), 12);
164 gtk_box_set_spacing (GTK_BOX (dialog->vbox), 24);
166 gtk_container_set_border_width (GTK_CONTAINER (dialog->action_area), 0);
167 gtk_box_set_spacing (GTK_BOX (dialog->action_area), 6);
170 static void
171 rb_playlist_source_recorder_class_init (RBPlaylistSourceRecorderClass *klass)
173 GObjectClass *object_class = G_OBJECT_CLASS (klass);
174 GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
176 widget_class->style_set = rb_playlist_source_recorder_style_set;
178 object_class->finalize = rb_playlist_source_recorder_finalize;
180 rb_playlist_source_recorder_signals [NAME_CHANGED] =
181 g_signal_new ("name_changed",
182 G_OBJECT_CLASS_TYPE (object_class),
183 G_SIGNAL_RUN_LAST,
185 NULL, NULL,
186 g_cclosure_marshal_VOID__STRING,
187 G_TYPE_NONE,
189 G_TYPE_STRING);
191 rb_playlist_source_recorder_signals [FILE_ADDED] =
192 g_signal_new ("file_added",
193 G_OBJECT_CLASS_TYPE (object_class),
194 G_SIGNAL_RUN_LAST,
196 NULL, NULL,
197 g_cclosure_marshal_VOID__STRING,
198 G_TYPE_NONE,
200 G_TYPE_STRING);
202 g_type_class_add_private (klass, sizeof (RBPlaylistSourceRecorderPrivate));
205 GtkWidget *
206 rb_playlist_source_recorder_device_menu_create (void)
208 GtkWidget *widget;
209 char *value;
211 widget = nautilus_burn_drive_selection_new ();
212 g_object_set (widget, "file-image", FALSE, NULL);
213 g_object_set (widget, "show-recorders-only", TRUE, NULL);
215 value = eel_gconf_get_string (CONF_STATE_BURN_DEVICE);
216 if (value) {
217 nautilus_burn_drive_selection_set_device (NAUTILUS_BURN_DRIVE_SELECTION (widget),
218 value);
219 g_free (value);
222 gtk_widget_show (widget);
224 return widget;
227 static const NautilusBurnDrive *
228 lookup_current_recorder (RBPlaylistSourceRecorder *source)
230 const NautilusBurnDrive *drive;
232 drive = nautilus_burn_drive_selection_get_drive (NAUTILUS_BURN_DRIVE_SELECTION (source->priv->device_menu));
234 return drive;
237 static int
238 get_speed_selection (GtkWidget *combobox)
240 int speed;
241 GtkTreeModel *model;
242 GtkTreeIter iter;
244 speed = 0;
246 if (! gtk_combo_box_get_active_iter (GTK_COMBO_BOX (combobox), &iter))
247 return speed;
249 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combobox));
250 gtk_tree_model_get (model, &iter, 1, &speed, -1);
252 return speed;
255 #ifndef HAVE_BURN_DRIVE_GET_WRITE_SPEEDS
257 #define nautilus_burn_drive_get_write_speeds get_write_speeds
259 static const int *
260 get_write_speeds (NautilusBurnDrive *drive)
262 int max_speed;
263 int i;
264 static int *write_speeds = NULL;
266 if (write_speeds == NULL) {
267 max_speed = drive->max_speed_write;
268 write_speeds = g_new0 (int, max_speed + 1);
270 for (i = 0; i < max_speed; i++) {
271 write_speeds [i] = max_speed - i;
275 return (const int*)write_speeds;
277 #endif
279 static void
280 update_speed_combobox (RBPlaylistSourceRecorder *source)
282 GtkWidget *combobox;
283 char *name;
284 int i;
285 int default_speed;
286 int default_speed_index;
287 const NautilusBurnDrive *drive;
288 GtkTreeModel *model;
289 GtkTreeIter iter;
291 /* Find active recorder: */
292 drive = lookup_current_recorder (source);
294 /* add speed items: */
295 combobox = source->priv->speed_combobox;
296 model = gtk_combo_box_get_model (GTK_COMBO_BOX (combobox));
297 gtk_list_store_clear (GTK_LIST_STORE (model));
299 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
300 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
301 0, _("Maximum possible"),
302 1, 0,
303 -1);
305 default_speed = eel_gconf_get_integer (CONF_STATE_BURN_SPEED);
306 default_speed_index = -1;
308 i = 0;
309 if (drive) {
310 const int *write_speeds;
312 write_speeds = nautilus_burn_drive_get_write_speeds ((NautilusBurnDrive *)drive);
314 for (i = 0; write_speeds [i] > 0; i++) {
316 #ifdef NAUTILUS_BURN_DRIVE_CD_SPEED
317 name = g_strdup_printf ("%d \303\227", (int)NAUTILUS_BURN_DRIVE_CD_SPEED (write_speeds [i]));
318 #else
319 name = g_strdup_printf ("%d \303\227", write_speeds [i]);
320 #endif
322 if (write_speeds [i] == default_speed) {
323 default_speed_index = i + 1;
326 gtk_list_store_append (GTK_LIST_STORE (model), &iter);
327 gtk_list_store_set (GTK_LIST_STORE (model), &iter,
328 0, name,
329 1, write_speeds [i],
330 -1);
331 g_free (name);
336 /* Disable speed if no items in list */
337 gtk_widget_set_sensitive (combobox, i > 0);
339 /* if the default speed was not set then make it the minimum for safety */
340 if (default_speed_index == -1) {
341 default_speed_index = i;
344 /* for now assume equivalence between index in comboxbox and speed */
345 gtk_combo_box_set_active (GTK_COMBO_BOX (combobox), default_speed_index);
348 void
349 rb_playlist_source_recorder_device_changed_cb (NautilusBurnDriveSelection *selection,
350 const char *device_path,
351 RBPlaylistSourceRecorder *source)
353 if (!device_path)
354 return;
356 eel_gconf_set_string (CONF_STATE_BURN_DEVICE, device_path);
358 update_speed_combobox (source);
361 static void
362 set_media_device (RBPlaylistSourceRecorder *source)
364 const char *device;
365 GError *error;
367 device = nautilus_burn_drive_selection_get_device (NAUTILUS_BURN_DRIVE_SELECTION (source->priv->device_menu));
369 if (device && strcmp (device, "")) {
370 rb_recorder_set_device (source->priv->recorder, device, &error);
371 if (error) {
372 g_warning (_("Invalid writer device: %s"), device);
373 /* ignore and let rb_recorder try to find a default */
378 static void
379 set_message_text (RBPlaylistSourceRecorder *source,
380 const char *message,
381 ...)
383 char *markup;
384 char *text = "";
385 va_list args;
387 va_start (args, message);
388 g_vasprintf (&text, message, args);
389 va_end (args);
391 markup = g_strdup_printf ("%s", text);
393 gtk_label_set_text (GTK_LABEL (source->priv->message_label), markup);
394 g_free (markup);
395 g_free (text);
398 static gboolean
399 response_idle_cb (RBPlaylistSourceRecorder *source)
401 GDK_THREADS_ENTER ();
402 gtk_dialog_response (GTK_DIALOG (source),
403 GTK_RESPONSE_CANCEL);
404 GDK_THREADS_LEAVE ();
406 return FALSE;
409 static gboolean
410 error_dialog_response_cb (GtkWidget *dialog,
411 gint response_id,
412 gpointer data)
414 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (data);
416 gtk_widget_destroy (GTK_WIDGET (dialog));
418 g_idle_add ((GSourceFunc)response_idle_cb, source);
420 return TRUE;
423 static void
424 error_dialog (RBPlaylistSourceRecorder *source,
425 const char *primary,
426 const char *secondary,
427 ...)
429 char *text = "";
430 va_list args;
431 GtkWidget *dialog;
433 va_start (args, secondary);
434 g_vasprintf (&text, secondary, args);
435 va_end (args);
437 dialog = gtk_message_dialog_new (GTK_WINDOW (source),
438 GTK_DIALOG_DESTROY_WITH_PARENT,
439 GTK_MESSAGE_ERROR,
440 GTK_BUTTONS_CLOSE,
441 primary);
443 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
444 text);
446 gtk_window_set_title (GTK_WINDOW (dialog), "");
448 gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
450 g_signal_connect (dialog,
451 "response",
452 G_CALLBACK (error_dialog_response_cb),
453 source);
455 gtk_widget_show (dialog);
457 g_free (text);
460 /* Adapted from totem_time_to_string_text */
461 static char *
462 time_to_string_text (long time)
464 char *secs, *mins, *hours, *string;
465 int sec, min, hour;
467 sec = time % 60;
468 time = time - sec;
469 min = (time % (60 * 60)) / 60;
470 time = time - (min * 60);
471 hour = time / (60 * 60);
473 hours = g_strdup_printf (ngettext ("%d hour", "%d hours", hour), hour);
475 mins = g_strdup_printf (ngettext ("%d minute",
476 "%d minutes", min), min);
478 secs = g_strdup_printf (ngettext ("%d second",
479 "%d seconds", sec), sec);
481 if (hour > 0) {
482 /* hour:minutes:seconds */
483 string = g_strdup_printf (_("%s %s %s"), hours, mins, secs);
484 } else if (min > 0) {
485 /* minutes:seconds */
486 string = g_strdup_printf (_("%s %s"), mins, secs);
487 } else if (sec > 0) {
488 /* seconds */
489 string = g_strdup_printf (_("%s"), secs);
490 } else {
491 /* 0 seconds */
492 string = g_strdup (_("0 seconds"));
495 g_free (hours);
496 g_free (mins);
497 g_free (secs);
499 return string;
502 static void
503 progress_set_time (GtkWidget *progress,
504 long seconds)
506 char *text;
508 if (seconds >= 0) {
509 char *remaining;
510 remaining = time_to_string_text (seconds);
511 text = g_strdup_printf (_("About %s left"), remaining);
512 g_free (remaining);
513 } else {
514 text = g_strdup (" ");
517 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progress), text);
518 g_free (text);
521 static void
522 progress_set_fraction (GtkWidget *progress,
523 gdouble fraction)
525 gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progress), fraction);
528 #ifndef NAUTILUS_BURN_DRIVE_SIZE_TO_TIME
529 /* copied from nautilus-burn-drive 2.12 */
530 static gboolean
531 _nautilus_burn_drive_eject (NautilusBurnDrive *drive)
533 char *cmd;
534 gboolean res;
536 g_return_val_if_fail (drive != NULL, FALSE);
538 if (drive->device == NULL)
539 return FALSE;
541 cmd = g_strdup_printf ("eject %s", drive->device);
542 res = g_spawn_command_line_sync (cmd, NULL, NULL, NULL, NULL);
543 g_free (cmd);
545 /* delay a bit to make sure eject finishes */
546 sleep (2);
548 return res;
550 /* copied from nautilus-burn-drive 2.12 */
551 static NautilusBurnDrive *
552 _nautilus_burn_drive_new_from_path (const char *device)
554 GList *drives, *l;
555 NautilusBurnDrive *drive;
557 drives = nautilus_burn_drive_get_list (FALSE, FALSE);
559 drive = NULL;
561 for (l = drives; l != NULL; l = l->next) {
562 NautilusBurnDrive *d = l->data;
563 if (g_str_equal (device, d->device)) {
564 drive = nautilus_burn_drive_ref (d);
568 g_list_foreach (drives, (GFunc)nautilus_burn_drive_unref, NULL);
569 g_list_free (drives);
571 return drive;
574 /* copied from nautilus-burn-drive 2.12 */
575 static const char *
576 _nautilus_burn_drive_media_type_get_string (NautilusBurnMediaType type)
578 switch (type) {
579 case NAUTILUS_BURN_MEDIA_TYPE_BUSY:
580 return _("Could not determine media type because CD drive is busy");
581 case NAUTILUS_BURN_MEDIA_TYPE_ERROR:
582 return _("Couldn't open media");
583 case NAUTILUS_BURN_MEDIA_TYPE_UNKNOWN:
584 return _("Unknown Media");
585 case NAUTILUS_BURN_MEDIA_TYPE_CD:
586 return _("Commercial CD or Audio CD");
587 case NAUTILUS_BURN_MEDIA_TYPE_CDR:
588 return _("CD-R");
589 case NAUTILUS_BURN_MEDIA_TYPE_CDRW:
590 return _("CD-RW");
591 case NAUTILUS_BURN_MEDIA_TYPE_DVD:
592 return _("DVD");
593 case NAUTILUS_BURN_MEDIA_TYPE_DVDR:
594 return _("DVD-R, or DVD-RAM");
595 case NAUTILUS_BURN_MEDIA_TYPE_DVDRW:
596 return _("DVD-RW");
597 case NAUTILUS_BURN_MEDIA_TYPE_DVD_RAM:
598 return _("DVD-RAM");
599 case NAUTILUS_BURN_MEDIA_TYPE_DVD_PLUS_R:
600 return _("DVD+R");
601 case NAUTILUS_BURN_MEDIA_TYPE_DVD_PLUS_RW:
602 return _("DVD+RW");
603 default:
604 break;
607 return _("Broken media type");
609 #endif /* NAUTILUS_BURN_DRIVE_SIZE_TO_TIME */
611 static int
612 burn_cd (RBPlaylistSourceRecorder *source,
613 GError **error)
615 int res;
616 int speed;
618 speed = 1;
620 set_media_device (source);
622 set_message_text (source, _("Writing audio to CD"));
624 speed = get_speed_selection (source->priv->speed_combobox);
626 progress_set_fraction (source->priv->progress, 0);
627 progress_set_time (source->priv->progress, -1);
629 source->priv->burning = TRUE;
630 res = rb_recorder_burn (source->priv->recorder, speed, error);
631 source->priv->burning = FALSE;
633 if (res == RB_RECORDER_RESULT_FINISHED) {
634 NautilusBurnDrive *drive;
635 gboolean do_another;
636 const char *finished_msg;
638 finished_msg = _("Finished creating audio CD.");
640 rb_shell_hidden_notify (source->priv->shell, 0, finished_msg, source->priv->cd_icon, "", FALSE);
642 /* save the write speed that was used */
643 eel_gconf_set_integer (CONF_STATE_BURN_SPEED, speed);
645 /* Always eject the disk after writing. Too many drives mess up otherwise */
646 drive = (NautilusBurnDrive *)lookup_current_recorder (source);
647 nautilus_burn_drive_eject (drive);
649 do_another = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (source->priv->multiple_copies_checkbutton));
650 if (!do_another) {
651 set_message_text (source, finished_msg);
652 gtk_widget_set_sensitive (GTK_WIDGET (source), FALSE);
653 g_idle_add ((GSourceFunc)response_idle_cb, source);
654 return res;
656 set_message_text (source, _("Finished creating audio CD.\nCreate another copy?"));
657 } else if (res == RB_RECORDER_RESULT_ERROR) {
658 set_message_text (source, _("Writing failed. Try again?"));
659 } else {
660 set_message_text (source, _("Writing cancelled. Try again?"));
663 progress_set_fraction (source->priv->progress, 0);
664 progress_set_time (source->priv->progress, -1);
666 gtk_widget_set_sensitive (GTK_WIDGET (source->priv->burn_button), TRUE);
667 gtk_widget_set_sensitive (GTK_WIDGET (source->priv->options_box), TRUE);
669 return res;
672 static char *
673 get_song_description (RBRecorderSong *song)
675 char *desc = NULL;
677 if (song->artist && song->title)
678 desc = g_strdup_printf ("%s - %s", song->title, song->artist);
679 else if (song->title)
680 desc = g_strdup (song->title);
681 else if (song->artist)
682 desc = g_strdup (song->artist);
684 return desc;
687 static void
688 write_file (RBPlaylistSourceRecorder *source,
689 GError **error)
691 RBRecorderSong *song = source->priv->current->data;
692 char *cdtext = NULL;
693 char *markup;
695 gtk_widget_set_sensitive (source->priv->progress_frame, TRUE);
697 cdtext = get_song_description (song);
699 markup = g_markup_printf_escaped ("<i>Converting '%s'</i>", cdtext);
700 gtk_label_set_markup (GTK_LABEL (source->priv->progress_label), markup);
701 g_free (markup);
703 rb_recorder_open (source->priv->recorder, song->uri, cdtext, error);
705 g_free (cdtext);
707 if (error && *error) {
708 return;
711 rb_recorder_write (source->priv->recorder, error);
712 if (error && *error) {
713 return;
717 static gboolean
718 burn_cd_idle (RBPlaylistSourceRecorder *source)
720 GError *error = NULL;
721 int res;
723 GDK_THREADS_ENTER ();
725 res = burn_cd (source, &error);
726 if (error) {
727 error_dialog (source,
728 _("Audio recording error"),
729 error->message);
730 g_error_free (error);
733 GDK_THREADS_LEAVE ();
734 return FALSE;
737 static void
738 eos_cb (RBRecorder *recorder,
739 gpointer data)
741 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (data);
742 GError *error = NULL;
744 rb_debug ("Caught eos!");
746 rb_recorder_close (source->priv->recorder, NULL);
748 gtk_label_set_text (GTK_LABEL (source->priv->progress_label), "");
750 if (source->priv->current->next) {
752 source->priv->current = source->priv->current->next;
754 write_file (source, &error);
755 if (error) {
756 error_dialog (source,
757 _("Audio Conversion Error"),
758 error->message);
759 g_error_free (error);
760 return;
762 } else {
763 if (source->priv->timer) {
764 g_timer_destroy (source->priv->timer);
765 source->priv->timer = NULL;
768 source->priv->already_converted = TRUE;
770 g_idle_add ((GSourceFunc)burn_cd_idle, source);
774 static void
775 rb_playlist_source_recorder_error (RBPlaylistSourceRecorder *source,
776 GError *error)
779 if (source->priv->handling_error) {
780 rb_debug ("Ignoring error: %s", error->message);
781 return;
784 rb_debug ("Error: %s", error->message);
786 error_dialog (source,
787 _("Recording error"),
788 error->message);
790 source->priv->handling_error = FALSE;
791 rb_debug ("Exiting error hander");
794 static void
795 error_cb (GObject *object,
796 GError *error,
797 gpointer data)
799 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (data);
801 if (source->priv->handling_error)
802 return;
804 source->priv->handling_error = TRUE;
806 rb_playlist_source_recorder_error (source, error);
809 static void
810 track_progress_changed_cb (GObject *object,
811 double fraction,
812 long secs,
813 gpointer data)
815 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (data);
816 double album_fraction;
817 double rate;
818 double elapsed;
819 double album_secs;
820 GSList *l;
821 guint64 total;
822 guint64 prev_total;
823 guint64 song_length;
824 guint64 position;
826 total = 0;
827 prev_total = 0;
828 song_length = 0;
829 for (l = source->priv->songs; l; l = l->next) {
830 RBRecorderSong *song = l->data;
832 if (song == source->priv->current->data) {
833 prev_total = total;
834 song_length = song->duration;
836 total += song->duration;
839 position = prev_total + song_length * fraction;
840 if (! source->priv->timer) {
841 source->priv->timer = g_timer_new ();
842 source->priv->start_pos = position;
845 album_fraction = (float)position / (float)total;
847 elapsed = g_timer_elapsed (source->priv->timer, NULL);
849 rate = (double)(position - source->priv->start_pos) / elapsed;
851 if (rate >= 1)
852 album_secs = ceil ((total - position) / rate);
853 else
854 album_secs = -1;
856 progress_set_time (source->priv->progress, album_secs);
857 progress_set_fraction (source->priv->progress, album_fraction);
859 static void
860 interrupt_burn_dialog_response_cb (GtkDialog *dialog,
861 gint response_id,
862 gpointer data)
864 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (data);
866 if (response_id == GTK_RESPONSE_ACCEPT) {
867 if (source->priv->burning) {
868 rb_recorder_burn_cancel (source->priv->recorder);
869 } else {
870 source->priv->confirmed_exit = TRUE;
871 gtk_dialog_response (GTK_DIALOG (source),
872 GTK_RESPONSE_CANCEL);
874 } else {
875 source->priv->confirmed_exit = FALSE;
878 gtk_widget_destroy (GTK_WIDGET (dialog));
881 static void
882 response_cb (GtkDialog *dialog,
883 gint response_id)
885 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (dialog);
886 GError *error = NULL;
888 /* Act only on response IDs we recognize */
889 if (!(response_id == GTK_RESPONSE_ACCEPT
890 || response_id == GTK_RESPONSE_CANCEL
891 || response_id == GTK_RESPONSE_DELETE_EVENT)) {
892 g_signal_stop_emission_by_name (dialog, "response");
893 return;
896 if (response_id == GTK_RESPONSE_CANCEL || response_id == GTK_RESPONSE_DELETE_EVENT) {
897 if (source->priv->burning
898 && !source->priv->confirmed_exit) {
899 GtkWidget *interrupt_dialog;
901 source->priv->confirmed_exit = FALSE;
903 interrupt_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
904 GTK_DIALOG_DESTROY_WITH_PARENT,
905 GTK_MESSAGE_QUESTION,
906 GTK_BUTTONS_NONE,
907 _("Do you wish to interrupt writing this disc?"));
909 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (interrupt_dialog),
910 _("This may result in an unusable disc."));
912 gtk_window_set_title (GTK_WINDOW (interrupt_dialog), "");
914 gtk_container_set_border_width (GTK_CONTAINER (interrupt_dialog), 6);
916 gtk_dialog_add_buttons (GTK_DIALOG (interrupt_dialog),
917 _("_Cancel"), GTK_RESPONSE_CANCEL,
918 _("_Interrupt"), GTK_RESPONSE_ACCEPT,
919 NULL);
921 gtk_dialog_set_default_response (GTK_DIALOG (interrupt_dialog),
922 GTK_RESPONSE_CANCEL);
924 g_signal_connect (interrupt_dialog,
925 "response",
926 G_CALLBACK (interrupt_burn_dialog_response_cb),
927 source);
929 gtk_widget_show (interrupt_dialog);
931 g_signal_stop_emission_by_name (dialog, "response");
933 return;
936 if (response_id == GTK_RESPONSE_ACCEPT) {
937 rb_playlist_source_recorder_start (source, &error);
938 if (error) {
939 error_dialog (source,
940 _("Could not create audio CD"),
941 error->message);
942 g_error_free (error);
944 g_signal_stop_emission_by_name (dialog, "response");
948 static gboolean
949 insert_media_request_cb (RBRecorder *recorder,
950 gboolean is_reload,
951 gboolean can_rewrite,
952 gboolean busy_cd,
953 gpointer data)
955 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (data);
956 GtkWidget *dialog;
957 const char *msg;
958 const char *title;
959 int res;
961 if (busy_cd) {
962 msg = N_("Please make sure another application is not using the drive.");
963 title = N_("Drive is busy");
964 } else if (is_reload && can_rewrite) {
965 msg = N_("Please put a rewritable or blank CD in the drive.");
966 title = N_("Insert a rewritable or blank CD");
967 } else if (is_reload && !can_rewrite) {
968 msg = N_("Please put a blank CD in the drive.");
969 title = N_("Insert a blank CD");
970 } else if (can_rewrite) {
971 msg = N_("Please replace the disc in the drive with a rewritable or blank CD.");
972 title = N_("Reload a rewritable or blank CD");
973 } else {
974 msg = N_("Please replace the disc in the drive with a blank CD.");
975 title = N_("Reload a blank CD");
978 GDK_THREADS_ENTER ();
979 dialog = gtk_message_dialog_new (GTK_WINDOW (source),
980 GTK_DIALOG_DESTROY_WITH_PARENT,
981 GTK_MESSAGE_ERROR,
982 GTK_BUTTONS_OK_CANCEL,
983 "%s", title);
985 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), msg);
987 gtk_window_set_title (GTK_WINDOW (dialog), "");
989 gtk_container_set_border_width (GTK_CONTAINER (dialog), 6);
991 res = gtk_dialog_run (GTK_DIALOG (dialog));
993 gtk_widget_destroy (dialog);
994 GDK_THREADS_LEAVE ();
996 if (res == GTK_RESPONSE_CANCEL)
997 return FALSE;
999 return TRUE;
1002 static void
1003 burn_progress_changed_cb (RBRecorder *recorder,
1004 gdouble fraction,
1005 long secs,
1006 gpointer data)
1008 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (data);
1010 progress_set_fraction (source->priv->progress, fraction);
1011 progress_set_time (source->priv->progress, secs);
1014 static void
1015 burn_action_changed_cb (RBRecorder *recorder,
1016 RBRecorderAction action,
1017 gpointer data)
1019 RBPlaylistSourceRecorder *source = RB_PLAYLIST_SOURCE_RECORDER (data);
1020 const char *text;
1022 text = NULL;
1024 switch (action) {
1025 case RB_RECORDER_ACTION_FILE_CONVERTING:
1026 text = N_("Converting audio tracks");
1027 break;
1028 case RB_RECORDER_ACTION_DISC_PREPARING_WRITE:
1029 text = N_("Preparing to write CD");
1030 break;
1031 case RB_RECORDER_ACTION_DISC_WRITING:
1032 text = N_("Writing CD");
1033 break;
1034 case RB_RECORDER_ACTION_DISC_FIXATING:
1035 text = N_("Finishing write");
1036 break;
1037 case RB_RECORDER_ACTION_DISC_BLANKING:
1038 text = N_("Erasing CD");
1039 break;
1040 default:
1041 g_warning (_("Unhandled action in burn_action_changed_cb"));
1044 if (text)
1045 set_message_text (source, text);
1048 static int
1049 ask_rewrite_disc (RBPlaylistSourceRecorder *source,
1050 const char *device)
1052 GtkWidget *dialog;
1053 GtkWidget *button;
1054 GtkWidget *image;
1055 int res;
1056 NautilusBurnMediaType type;
1057 char *msg;
1058 NautilusBurnDrive *drive;
1060 #if NAUTILUS_BURN_CHECK_VERSION(2,15,3)
1061 drive = nautilus_burn_drive_monitor_get_drive_for_device (nautilus_burn_get_drive_monitor (),
1062 device);
1063 #else
1064 drive = nautilus_burn_drive_new_from_path (device);
1065 #endif
1067 type = nautilus_burn_drive_get_media_type (drive);
1069 msg = g_strdup_printf (_("This %s appears to have information already recorded on it."),
1070 nautilus_burn_drive_media_type_get_string (type));
1072 dialog = gtk_message_dialog_new (GTK_WINDOW (source),
1073 GTK_DIALOG_DESTROY_WITH_PARENT,
1074 GTK_MESSAGE_WARNING,
1075 GTK_BUTTONS_NONE,
1076 "%s", _("Erase information on this disc?"));
1078 gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog), msg);
1079 g_free (msg);
1081 gtk_window_set_title (GTK_WINDOW (dialog), "");
1083 image = gtk_image_new_from_stock (GTK_STOCK_REFRESH, GTK_ICON_SIZE_BUTTON);
1084 gtk_widget_show (image);
1085 button = gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Try Another"), RB_RECORDER_RESPONSE_RETRY);
1086 g_object_set (button, "image", image, NULL);
1088 gtk_dialog_add_button (GTK_DIALOG (dialog), GTK_STOCK_CANCEL, RB_RECORDER_RESPONSE_CANCEL);
1090 image = gtk_image_new_from_stock (GTK_STOCK_CLEAR, GTK_ICON_SIZE_BUTTON);
1091 gtk_widget_show (image);
1092 button = gtk_dialog_add_button (GTK_DIALOG (dialog), _("_Erase Disc"), RB_RECORDER_RESPONSE_ERASE);
1093 g_object_set (button, "image", image, NULL);
1095 gtk_dialog_set_default_response (GTK_DIALOG (dialog),
1096 RB_RECORDER_RESPONSE_CANCEL);
1098 res = gtk_dialog_run (GTK_DIALOG (dialog));
1100 gtk_widget_destroy (dialog);
1102 if (res == RB_RECORDER_RESPONSE_RETRY) {
1103 nautilus_burn_drive_eject (drive);
1106 nautilus_burn_drive_unref (drive);
1108 return res;
1111 static int
1112 warn_data_loss_cb (RBRecorder *recorder,
1113 RBPlaylistSourceRecorder *source)
1115 char *device;
1116 int res;
1118 device = rb_recorder_get_device (recorder, NULL);
1119 GDK_THREADS_ENTER();
1120 res = ask_rewrite_disc (source, device);
1121 GDK_THREADS_LEAVE();
1123 g_free (device);
1125 return res;
1128 static void
1129 setup_speed_combobox (GtkWidget *combobox)
1131 GtkCellRenderer *cell;
1132 GtkListStore *store;
1134 store = gtk_list_store_new (2, G_TYPE_STRING, G_TYPE_INT);
1136 gtk_combo_box_set_model (GTK_COMBO_BOX (combobox),
1137 GTK_TREE_MODEL (store));
1138 g_object_unref (store);
1140 cell = gtk_cell_renderer_text_new ();
1141 gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (combobox), cell, TRUE);
1142 gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (combobox), cell,
1143 "text", 0,
1144 NULL);
1147 static int
1148 delete_event_handler (GtkWidget *widget,
1149 GdkEventAny *event,
1150 gpointer user_data)
1152 /* emit response signal */
1153 gtk_dialog_response (GTK_DIALOG (widget), GTK_RESPONSE_DELETE_EVENT);
1155 /* Do the destroy by default */
1156 return TRUE;
1159 static void
1160 rb_playlist_source_recorder_init (RBPlaylistSourceRecorder *source)
1162 GladeXML *xml;
1163 GError *error = NULL;
1164 GtkWidget *widget;
1165 GtkWidget *hbox;
1166 int font_size;
1167 PangoAttrList *pattrlist;
1168 PangoAttribute *attr;
1170 g_signal_connect (GTK_DIALOG (source),
1171 "delete_event",
1172 G_CALLBACK (delete_event_handler),
1173 NULL);
1175 source->priv = RB_PLAYLIST_SOURCE_RECORDER_GET_PRIVATE (source);
1177 gtk_window_set_resizable (GTK_WINDOW (source), FALSE);
1178 gtk_dialog_set_has_separator (GTK_DIALOG (source), FALSE);
1179 source->priv->cancel_button = gtk_dialog_add_button (GTK_DIALOG (source),
1180 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
1182 source->priv->burn_button = gtk_button_new ();
1183 GTK_WIDGET_SET_FLAGS (source->priv->burn_button, GTK_CAN_DEFAULT);
1185 widget = gtk_alignment_new (0.5, 0.5, 0, 0);
1186 gtk_container_add (GTK_CONTAINER (source->priv->burn_button), widget);
1187 gtk_widget_show (widget);
1188 hbox = gtk_hbox_new (FALSE, 6);
1189 gtk_container_add (GTK_CONTAINER (widget), hbox);
1190 gtk_widget_show (hbox);
1191 widget = gtk_image_new_from_stock (GTK_STOCK_CDROM, GTK_ICON_SIZE_BUTTON);
1192 source->priv->cd_icon = widget;
1193 g_object_ref (source->priv->cd_icon);
1194 gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
1195 gtk_widget_show (widget);
1196 widget = gtk_label_new_with_mnemonic (_("C_reate"));
1197 gtk_box_pack_start (GTK_BOX (hbox), widget, TRUE, TRUE, 0);
1198 gtk_widget_show (widget);
1199 gtk_dialog_add_action_widget (GTK_DIALOG (source),
1200 source->priv->burn_button,
1201 GTK_RESPONSE_ACCEPT);
1202 gtk_widget_show (source->priv->burn_button);
1204 gtk_dialog_set_default_response (GTK_DIALOG (source), GTK_RESPONSE_ACCEPT);
1206 xml = rb_glade_xml_new ("recorder.glade",
1207 "recorder_vbox",
1208 source);
1210 source->priv->vbox = glade_xml_get_widget (xml, "recorder_vbox");
1212 source->priv->message_label = glade_xml_get_widget (xml, "message_label");
1213 source->priv->progress_label = glade_xml_get_widget (xml, "progress_label");
1215 source->priv->progress = glade_xml_get_widget (xml, "progress");
1216 gtk_progress_bar_set_ellipsize (GTK_PROGRESS_BAR (source->priv->progress), PANGO_ELLIPSIZE_END);
1217 gtk_progress_bar_set_text (GTK_PROGRESS_BAR (source->priv->progress), " ");
1218 gtk_widget_set_size_request (source->priv->progress, 400, -1);
1220 source->priv->progress_frame = glade_xml_get_widget (xml, "progress_frame");
1222 source->priv->options_box = glade_xml_get_widget (xml, "options_box");
1223 source->priv->device_menu = glade_xml_get_widget (xml, "device_menu");
1224 source->priv->multiple_copies_checkbutton = glade_xml_get_widget (xml, "multiple_copies_checkbutton");
1226 source->priv->speed_combobox = glade_xml_get_widget (xml, "speed_combobox");
1227 setup_speed_combobox (source->priv->speed_combobox);
1229 widget = glade_xml_get_widget (xml, "device_label");
1230 gtk_label_set_mnemonic_widget (GTK_LABEL (widget), source->priv->device_menu);
1232 rb_glade_boldify_label (xml, "progress_frame_label");
1233 rb_glade_boldify_label (xml, "options_expander_label");
1235 pattrlist = pango_attr_list_new ();
1236 attr = pango_attr_weight_new (PANGO_WEIGHT_BOLD);
1237 attr->start_index = 0;
1238 attr->end_index = G_MAXINT;
1239 pango_attr_list_insert (pattrlist, attr);
1241 font_size = pango_font_description_get_size (GTK_WIDGET (source->priv->message_label)->style->font_desc);
1242 attr = pango_attr_size_new (font_size * 1.2);
1243 attr->start_index = 0;
1244 attr->end_index = G_MAXINT;
1245 pango_attr_list_insert (pattrlist, attr);
1247 gtk_label_set_attributes (GTK_LABEL (source->priv->message_label), pattrlist);
1249 pango_attr_list_unref (pattrlist);
1251 gtk_box_pack_start (GTK_BOX (GTK_DIALOG (source)->vbox),
1252 source->priv->vbox,
1253 TRUE, TRUE, 0);
1254 gtk_widget_show_all (source->priv->vbox);
1256 source->priv->recorder = rb_recorder_new (&error);
1257 if (error) {
1258 GtkWidget *dialog;
1259 char *msg = g_strdup_printf (_("Failed to create the recorder: %s"),
1260 error->message);
1261 g_error_free (error);
1262 dialog = gtk_message_dialog_new (NULL, GTK_DIALOG_MODAL,
1263 GTK_MESSAGE_ERROR,
1264 GTK_BUTTONS_CLOSE,
1265 msg);
1266 gtk_dialog_run (GTK_DIALOG (dialog));
1267 g_free (msg);
1268 return;
1271 update_speed_combobox (source);
1272 g_signal_connect (source->priv->device_menu, "device-changed",
1273 G_CALLBACK (rb_playlist_source_recorder_device_changed_cb),
1274 xml);
1276 g_signal_connect_object (G_OBJECT (source->priv->recorder), "eos",
1277 G_CALLBACK (eos_cb), source, 0);
1279 g_signal_connect_object (G_OBJECT (source->priv->recorder), "error",
1280 G_CALLBACK (error_cb), source, 0);
1282 g_signal_connect_object (G_OBJECT (source->priv->recorder), "action-changed",
1283 G_CALLBACK (burn_action_changed_cb), source, 0);
1285 g_signal_connect_object (G_OBJECT (source->priv->recorder), "track-progress-changed",
1286 G_CALLBACK (track_progress_changed_cb), source, 0);
1288 g_signal_connect_object (G_OBJECT (source->priv->recorder), "insert-media-request",
1289 G_CALLBACK (insert_media_request_cb), source, 0);
1291 g_signal_connect_object (G_OBJECT (source->priv->recorder), "warn-data-loss",
1292 G_CALLBACK (warn_data_loss_cb), source, 0);
1294 g_signal_connect_object (G_OBJECT (source->priv->recorder), "burn-progress-changed",
1295 G_CALLBACK (burn_progress_changed_cb), source, 0);
1297 g_signal_connect (GTK_DIALOG (source), "response",
1298 G_CALLBACK (response_cb), NULL);
1301 static RBRecorderSong *
1302 recorder_song_new ()
1304 RBRecorderSong *song = g_new0 (RBRecorderSong, 1);
1305 return song;
1308 static void
1309 recorder_song_free (RBRecorderSong *song)
1311 g_return_if_fail (song != NULL);
1313 g_free (song->title);
1314 g_free (song->uri);
1315 g_free (song);
1318 static void
1319 free_song_list (GSList *songs)
1321 GSList *l;
1323 for (l = songs; l; l = l->next) {
1324 recorder_song_free ((RBRecorderSong *)l->data);
1327 g_slist_free (songs);
1328 songs = NULL;
1331 static void
1332 rb_playlist_source_recorder_finalize (GObject *object)
1334 RBPlaylistSourceRecorder *source;
1336 g_return_if_fail (object != NULL);
1337 g_return_if_fail (RB_IS_PLAYLIST_SOURCE_RECORDER (object));
1339 source = RB_PLAYLIST_SOURCE_RECORDER (object);
1341 g_return_if_fail (source->priv != NULL);
1343 rb_debug ("Finalize source recorder");
1345 g_object_unref (source->priv->shell);
1347 g_object_unref (source->priv->cd_icon);
1349 g_free (source->priv->name);
1350 source->priv->name = NULL;
1352 free_song_list (source->priv->songs);
1354 g_object_unref (source->priv->recorder);
1355 source->priv->recorder = NULL;
1357 if (source->priv->tmp_dir) {
1358 if (rmdir (source->priv->tmp_dir) < 0)
1359 g_warning (_("Could not remove temporary directory '%s': %s"),
1360 source->priv->tmp_dir,
1361 g_strerror (errno));
1362 g_free (source->priv->tmp_dir);
1363 source->priv->tmp_dir = NULL;
1366 G_OBJECT_CLASS (rb_playlist_source_recorder_parent_class)->finalize (object);
1369 GtkWidget *
1370 rb_playlist_source_recorder_new (GtkWidget *parent,
1371 RBShell *shell,
1372 const char *name)
1374 GtkWidget *result;
1375 RBPlaylistSourceRecorder *source;
1377 result = g_object_new (RB_TYPE_PLAYLIST_SOURCE_RECORDER,
1378 "title", _("Create Audio CD"),
1379 NULL);
1381 source = RB_PLAYLIST_SOURCE_RECORDER (result);
1382 if (parent) {
1383 source->priv->parent = gtk_widget_get_toplevel (parent);
1385 gtk_window_set_transient_for (GTK_WINDOW (source),
1386 GTK_WINDOW (source->priv->parent));
1387 gtk_window_set_destroy_with_parent (GTK_WINDOW (source), TRUE);
1390 source->priv->shell = g_object_ref (shell);
1392 if (name) {
1393 source->priv->name = g_strdup (name);
1395 set_message_text (source, _("Create audio CD from '%s'?"), name);
1398 return result;
1401 void
1402 rb_playlist_source_recorder_set_name (RBPlaylistSourceRecorder *source,
1403 const char *name,
1404 GError **error)
1406 g_return_if_fail (source != NULL);
1407 g_return_if_fail (RB_IS_PLAYLIST_SOURCE_RECORDER (source));
1409 g_return_if_fail (name != NULL);
1411 g_free (source->priv->name);
1412 source->priv->name = g_strdup (name);
1414 g_signal_emit (G_OBJECT (source),
1415 rb_playlist_source_recorder_signals [NAME_CHANGED],
1417 name);
1420 gboolean
1421 rb_playlist_source_recorder_add_from_model (RBPlaylistSourceRecorder *source,
1422 GtkTreeModel *model,
1423 RBPlaylistSourceIterFunc func,
1424 GError **error)
1426 GtkTreeIter iter;
1427 gboolean failed;
1428 GSList *songs = NULL;
1429 GSList *l;
1430 guint64 length = 0;
1432 g_return_val_if_fail (source != NULL, FALSE);
1433 g_return_val_if_fail (RB_IS_PLAYLIST_SOURCE_RECORDER (source), FALSE);
1435 g_return_val_if_fail (model != NULL, FALSE);
1437 if (! gtk_tree_model_get_iter_first (model, &iter)) {
1438 g_set_error (error,
1439 RB_RECORDER_ERROR,
1440 RB_RECORDER_ERROR_GENERAL,
1441 _("Unable to build an audio track list."));
1443 return FALSE;
1446 /* Make sure we can use all of the songs before we
1447 modify the song list */
1448 failed = FALSE;
1449 do {
1450 RBRecorderSong *song = recorder_song_new ();
1451 gboolean res;
1453 res = func (model, &iter, &song->uri, &song->artist, &song->title, &song->duration);
1454 if (! res) {
1455 failed = TRUE;
1456 g_set_error (error,
1457 RB_RECORDER_ERROR,
1458 RB_RECORDER_ERROR_GENERAL,
1459 _("Unable to build an audio track list."));
1460 break;
1463 length += song->duration;
1464 if (length > MAX_PLAYLIST_DURATION) {
1465 failed = TRUE;
1466 g_set_error (error,
1467 RB_RECORDER_ERROR,
1468 RB_RECORDER_ERROR_GENERAL,
1469 _("This playlist is too long to write to an audio CD."));
1470 break;
1473 songs = g_slist_append (songs, song);
1474 } while (gtk_tree_model_iter_next (model, &iter));
1476 if (failed) {
1477 free_song_list (songs);
1479 return FALSE;
1482 /* now that we've checked all the songs, add them to the song list */
1483 for (l = songs; l; l = l->next) {
1484 RBRecorderSong *song = l->data;
1486 source->priv->songs = g_slist_append (source->priv->songs, song);
1488 g_signal_emit (G_OBJECT (source),
1489 rb_playlist_source_recorder_signals [FILE_ADDED],
1491 song->uri);
1494 return TRUE;
1497 static guint64
1498 rb_playlist_source_recorder_get_total_duration (RBPlaylistSourceRecorder *source)
1500 GSList *l;
1501 guint64 length = 0;
1503 for (l = source->priv->songs; l; l = l->next) {
1504 RBRecorderSong *song = l->data;
1505 length += song->duration;
1508 return length;
1511 static guint64
1512 rb_playlist_source_recorder_estimate_total_size (RBPlaylistSourceRecorder *source)
1514 guint64 length;
1516 length = rb_playlist_source_recorder_get_total_duration (source);
1518 return length * AUDIO_BYTERATE;
1521 static gboolean
1522 check_dir_has_space (const char *path,
1523 guint64 bytes_needed)
1525 GnomeVFSResult result = GNOME_VFS_OK;
1526 GnomeVFSURI *dir_uri = NULL;
1527 GnomeVFSFileSize free_bytes = 0;
1529 if (!g_file_test (path, G_FILE_TEST_IS_DIR))
1530 return FALSE;
1532 dir_uri = gnome_vfs_uri_new (path);
1533 if (dir_uri == NULL) {
1534 g_warning (_("Cannot get free space at %s"), path);
1535 return FALSE;
1538 result = gnome_vfs_get_volume_free_space (dir_uri, &free_bytes);
1540 gnome_vfs_uri_unref (dir_uri);
1542 if (result != GNOME_VFS_OK) {
1543 g_warning (_("Cannot get free space at %s"), path);
1544 return FALSE;
1547 if (bytes_needed >= free_bytes)
1548 return FALSE;
1550 return TRUE;
1553 static char *
1554 find_tmp_dir (RBPlaylistSourceRecorder *source,
1555 guint64 bytes_needed,
1556 GError **error)
1558 char *path;
1560 g_return_val_if_fail (source != NULL, NULL);
1561 g_return_val_if_fail (RB_IS_PLAYLIST_SOURCE_RECORDER (source), NULL);
1563 /* Use a configurable temporary directory? */
1564 path = NULL;
1565 if (path && strcmp (path, "")
1566 && check_dir_has_space (path, bytes_needed))
1567 return path;
1568 else if (g_get_tmp_dir () &&
1569 check_dir_has_space (g_get_tmp_dir (), bytes_needed))
1570 return g_strdup (g_get_tmp_dir ());
1571 else if (g_get_home_dir () &&
1572 check_dir_has_space (g_get_home_dir (), bytes_needed))
1573 return g_strdup (g_get_home_dir ());
1574 else
1575 return NULL;
1578 static gboolean
1579 check_tmp_dir (RBPlaylistSourceRecorder *source,
1580 GError **error)
1582 char *path;
1583 char *template;
1584 char *subdir;
1585 guint64 bytes_needed;
1587 g_return_val_if_fail (source != NULL, FALSE);
1588 g_return_val_if_fail (RB_IS_PLAYLIST_SOURCE_RECORDER (source), FALSE);
1590 bytes_needed = rb_playlist_source_recorder_estimate_total_size (source);
1592 path = find_tmp_dir (source, bytes_needed, error);
1593 if (!path)
1594 return FALSE;
1596 template = g_build_filename (path, "rb-burn-tmp-XXXXXX", NULL);
1597 subdir = mkdtemp (template);
1599 if (!subdir)
1600 return FALSE;
1602 g_free (source->priv->tmp_dir);
1603 source->priv->tmp_dir = subdir;
1604 rb_recorder_set_tmp_dir (source->priv->recorder,
1605 source->priv->tmp_dir,
1606 error);
1608 if (error && *error)
1609 return FALSE;
1611 return TRUE;
1614 static gboolean
1615 check_media_length (RBPlaylistSourceRecorder *source,
1616 GError **error)
1618 gint64 duration = rb_playlist_source_recorder_get_total_duration (source);
1619 char *message = NULL;
1620 gint64 media_duration;
1621 char *duration_string;
1623 media_duration = rb_recorder_get_media_length (source->priv->recorder, NULL);
1624 duration_string = g_strdup_printf ("%" G_GINT64_FORMAT, duration / 60);
1626 /* Only check if the playlist is greater than 74 minutes */
1627 if ((media_duration < 0) && (duration > 4440)) {
1628 message = g_strdup_printf (_("This playlist is %s minutes long. "
1629 "This exceeds the length of a standard audio CD. "
1630 "If the destination media is larger than a standard audio CD "
1631 "please insert it in the drive and try again."),
1632 duration_string);
1635 g_free (duration_string);
1637 if (message) {
1638 error_dialog (source,
1639 _("Playlist too long"),
1640 message);
1641 g_free (message);
1643 return FALSE;
1646 return TRUE;
1649 void
1650 rb_playlist_source_recorder_start (RBPlaylistSourceRecorder *source,
1651 GError **error)
1653 g_return_if_fail (source != NULL);
1654 g_return_if_fail (RB_IS_PLAYLIST_SOURCE_RECORDER (source));
1656 source->priv->current = source->priv->songs;
1658 gtk_widget_set_sensitive (source->priv->burn_button, FALSE);
1659 gtk_widget_set_sensitive (source->priv->options_box, FALSE);
1661 if (source->priv->already_converted) {
1662 g_idle_add ((GSourceFunc)burn_cd_idle, source);
1663 } else {
1664 gboolean is_ok;
1666 set_media_device (source);
1668 is_ok = check_media_length (source, error);
1669 if (! is_ok) {
1670 return;
1673 is_ok = check_tmp_dir (source, error);
1674 if (! is_ok) {
1675 guint64 mib_needed = rb_playlist_source_recorder_estimate_total_size (source) / 1048576;
1676 char *mib_needed_string = g_strdup_printf ("%" G_GUINT64_FORMAT, mib_needed);
1678 error_dialog (source,
1679 _("Could not find temporary space!"),
1680 _("Could not find enough temporary space to convert audio tracks. %s MiB required."),
1681 mib_needed_string);
1682 g_free (mib_needed_string);
1684 return;
1687 write_file (source, error);