Logo Search packages:      
Sourcecode: nautilus-cd-burner version File versions  Download package

make-iso.c

/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*-
 * 
 * make-iso.c: code to generate iso files
 *
 * Copyright (C) 2002-2004 Red Hat, Inc.
 * Copyright (C) 2005 William Jon McCann <mccann@jhu.edu>
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public
 * License along with this program; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * Authors: Alexander Larsson <alexl@redhat.com>
 *          William Jon McCann <mccann@jhu.edu>
 */

#include "config.h"

#if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__)
#include <sys/param.h>
#include <sys/mount.h>
#else
#include <sys/vfs.h>
#endif /* __FreeBSD__ || __NetBSD__ || __OpenBSD__ */
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <math.h>

#include <glib.h>
#include <glib/gstdio.h>
#include <gtk/gtkmessagedialog.h>
#include <libgnomevfs/gnome-vfs.h>

#include "nautilus-cd-burner.h"
#include "make-iso.h"

#ifndef HAVE_MKDTEMP
#include "mkdtemp.h"
#endif

/* Profiling stuff adapted from gtkfilechooserdefault */
/* To use run:
 *  strace -ttt -f -o logfile.txt nautilus-cd-burner
 */

#undef PROFILE_MAKE_ISO
#ifdef PROFILE_MAKE_ISO

#define PROFILE_INDENT 4
static int profile_indent;

static void
profile_add_indent (int indent)
{
        profile_indent += indent;
        if (profile_indent < 0)
                g_error ("You screwed up your indentation");
}

static void
_ncb_make_iso_profile_log (const char *func,
                           int         indent,
                           const char *msg1,
                           const char *msg2)
{
        char *str;

        if (indent < 0)
                profile_add_indent (indent);

        if (profile_indent == 0)
                str = g_strdup_printf ("MARK: %s: %s %s %s", G_STRLOC, func, msg1 ? msg1 : "", msg2 ? msg2 : "");
        else
                str = g_strdup_printf ("MARK: %s: %*c %s %s %s", G_STRLOC, profile_indent - 1, ' ', func, msg1 ? msg1 : "", msg2 ? msg2 : "");

        access (str, F_OK);

        g_free (str);

        if (indent > 0)
                profile_add_indent (indent);
}

#define profile_start(x, y) _ncb_make_iso_profile_log (G_STRFUNC, PROFILE_INDENT, x, y)
#define profile_end(x, y)   _ncb_make_iso_profile_log (G_STRFUNC, -PROFILE_INDENT, x, y)
#define profile_msg(x, y)   _ncb_make_iso_profile_log (NULL, 0, x, y)
#else
#define profile_start(x, y)
#define profile_end(x, y)
#define profile_msg(x, y)
#endif

GQuark
nautilus_burn_iso_error_quark (void)
{
      static GQuark quark = 0;
      if (!quark)
            quark = g_quark_from_static_string ("nautilus_burn_iso_error");

      return quark;
}

static gboolean
make_iso_get_free_space (const char       *filename,
                   GnomeVFSFileSize *size)
{
      GnomeVFSURI *uri;

      *size = 0;

      uri = gnome_vfs_uri_new (filename);
      if (uri == NULL)
            return FALSE;

      if (gnome_vfs_get_volume_free_space (uri, size) != GNOME_VFS_OK) {
            gnome_vfs_uri_unref (uri);
            return FALSE;
      }

      gnome_vfs_uri_unref (uri);
      return TRUE;
}

struct mkisofs_output {
      GMainLoop  *loop;
      GString    *line;
      GString    *line_stderr;
      time_t      start_time;
      int         result;
      int         pid;
      const char *filename;
      guint64     iso_size;
      GList      *rates;
      GError     *error;
      gboolean    debug;
};

struct mkisofs_output *mkisofs_output_ptr;

/**
 * nautilus_burn_make_iso_cancel:
 *
 * Cancel current creation process
 *
 **/
void
nautilus_burn_make_iso_cancel (void)
{
      if (mkisofs_output_ptr) {
            kill (mkisofs_output_ptr->pid, SIGINT);
            g_unlink (mkisofs_output_ptr->filename);
            mkisofs_output_ptr->result = NAUTILUS_BURN_RECORDER_RESULT_CANCEL;
            g_main_loop_quit (mkisofs_output_ptr->loop);
      }
}

static void
write_all (int   fd,
         char *buf,
         int   len)
{
      int bytes;
      int res;
      
      bytes = 0;
      while (bytes < len) {
            res = write (fd, buf + bytes, len - bytes);
            if (res <= 0) {
                  return;
            }
            bytes += res;
      }
      return;
}

static void
copy_file (const char *source,
         const char *dest)
{
      int         sfd, dfd;
      struct stat stat_buf;
      char        buffer [1024 * 8];
      ssize_t     len;

      if (link (source, dest) == 0) {
            return;
      }
      
      if (g_stat (source, &stat_buf) != 0) {
            g_warning ("Trying to copy nonexisting file\n");
            return;
      }

      sfd = g_open (source, O_RDONLY, 0);
      if (sfd == -1) {
            g_warning ("Can't copy file (open source failed)\n");
            return;
      }
      
      dfd = g_open (dest, O_WRONLY | O_CREAT, stat_buf.st_mode);
      if (dfd == -1) {
            close (sfd);
            g_warning ("Can't copy file (open dest '%s' failed)\n", dest);
            perror ("error:");
            return;
      }

      while ( (len = read (sfd, &buffer, sizeof (buffer))) > 0) {
            write_all (dfd, buffer, len);
      }
      close (dfd);
      close (sfd);
}

static char *
escape_path (const char *str)
{
      char       *escaped, *d;
      const char *s;
      int         len;
      
      s = str;
      len = 1;
      while (*s != 0) {
            if (*s == '\\' ||
                *s == '=') {
                  len++;
            }
            
            len++;
            s++;
      }
      
      escaped = g_malloc (len);
      
      s = str;
      d = escaped;
      while (*s != 0) {
            if (*s == '\\' ||
                *s == '=') {
                  *d++ = '\\';
            }
            
            *d++ = *s++;
      }
      *d = 0;
      
      return escaped;
}

static gboolean
dir_is_empty (const char *virtual_path)
{
      GnomeVFSFileInfo        *info;
      GnomeVFSDirectoryHandle *handle;
      GnomeVFSResult           result;
      char                    *escaped_path, *uri;
      gboolean                 found_file;
      
      escaped_path = gnome_vfs_escape_path_string (virtual_path);
      uri = g_strconcat ("burn:///", escaped_path, NULL);
      g_free (escaped_path);
      
      result = gnome_vfs_directory_open (&handle, uri, GNOME_VFS_FILE_INFO_DEFAULT);
      g_free (uri);

      if (result != GNOME_VFS_OK) {
            return TRUE;
      }

      info = gnome_vfs_file_info_new ();

      found_file = FALSE;
      
      while (TRUE) {
            result = gnome_vfs_directory_read_next (handle, info);
            if (result != GNOME_VFS_OK)
                  break;

            /* Skip "." and "..".  */
            if (info->name [0] == '.'
                && (info->name [1] == 0
                  || (info->name [1] == '.' && info->name [2] == 0))) {
                  gnome_vfs_file_info_clear (info);
                  continue;
            }
            
            found_file = TRUE;
            break;
      }

      gnome_vfs_directory_close (handle);
      gnome_vfs_file_info_unref (info);

      return !found_file;
}

static char *
get_backing_file (const char *virtual_path)
{
      GnomeVFSHandle *handle;
      GnomeVFSResult  res;
      char           *escaped_path, *uri;
      char           *mapping;

      escaped_path = gnome_vfs_escape_path_string (virtual_path);
      uri = g_strconcat ("burn:///", escaped_path, NULL);
      g_free (escaped_path);
      /* warning, this can hang on fifos etc */
      res = gnome_vfs_open (&handle,
                        uri,
                        GNOME_VFS_OPEN_READ);
      g_free (uri);
      if (res == GNOME_VFS_OK) {
            res =  gnome_vfs_file_control (handle,
                                     "mapping:get_mapping",
                                     &mapping);
            gnome_vfs_close   (handle);
            if (res == GNOME_VFS_OK) {
                  return mapping;
            }
      }
      return NULL;
}

static gboolean
ask_disable_joliet (GtkWindow *parent)
{
      GtkWidget *dialog;
      int        res;

      dialog = gtk_message_dialog_new (parent,
                               GTK_DIALOG_DESTROY_WITH_PARENT,
                               GTK_MESSAGE_QUESTION,
                               GTK_BUTTONS_OK_CANCEL,
                               _("Disable Microsoft Windows compatibility?"));
      gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (dialog),
                                      _("Some files don't have a suitable name for a Windows-compatible CD.\nDo you want to continue with Windows compatibility disabled?"));
      gtk_window_set_title (GTK_WINDOW (dialog), _("Windows compatibility"));
      res = gtk_dialog_run (GTK_DIALOG (dialog));
      gtk_widget_destroy (dialog);
      return (res == GTK_RESPONSE_OK);
}

struct mkisofs_state {
      FILE    *graft_file;
      char    *emptydir;
      int      depth;
      char    *tmpdir;
      char    *copy_to_dir;
      int      copy_depth;
      GList   *remove_files;
      gboolean found_file;
};

static void
graft_file_end_dir_visitor (struct mkisofs_state *state)
{
      char *last_slash;
      
      if (state->copy_depth > 0) {
            state->copy_depth--;
            if (state->copy_depth == 0) {
                  g_free (state->copy_to_dir);
                  state->copy_to_dir = NULL;
            } else {
                  last_slash = strrchr (state->copy_to_dir, G_DIR_SEPARATOR);
                  if (last_slash != NULL) {
                        *last_slash = 0;
                  }
            }
      }
}

/* FIXME: This should probably be an inline */
static gboolean
file_info_is_allowed (GnomeVFSFileInfo *info)
{
      g_return_val_if_fail (info != NULL, FALSE);

      /* only allow regular,directory,symlink files */
      if (info->type != GNOME_VFS_FILE_TYPE_REGULAR
          && info->type != GNOME_VFS_FILE_TYPE_DIRECTORY
          && info->type != GNOME_VFS_FILE_TYPE_SYMBOLIC_LINK) {
            return FALSE;
      } else if (info->type == GNOME_VFS_FILE_TYPE_REGULAR
               && !(info->permissions & GNOME_VFS_PERM_ACCESS_READABLE)) {
            return FALSE;
      }

      return TRUE;
}

static gboolean
graft_file_visitor (const char           *rel_path,
                GnomeVFSFileInfo     *info,
                struct mkisofs_state *state,
                gboolean             *recurse)
{
      char *mapping, *path1, *path2;
      char *new_copy_dir;
      char *copy_path;

      *recurse = TRUE;

      if (! file_info_is_allowed (info)) {
            g_warning ("Skipping file: %s", rel_path);
            return TRUE;
      }

      if (state->copy_to_dir != NULL) {
            if (info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
                  new_copy_dir = g_build_filename (state->copy_to_dir,
                                           info->name,
                                           NULL);
                  g_free (state->copy_to_dir);
                  state->copy_to_dir = new_copy_dir;
                  g_mkdir (state->copy_to_dir, 0777);
                  state->remove_files = g_list_prepend (state->remove_files, g_strdup (state->copy_to_dir));
                  state->copy_depth++;
            } else {
                  copy_path = g_build_filename (state->copy_to_dir, info->name, NULL);
                  mapping = get_backing_file (rel_path);
                  if (mapping != NULL) {
                        copy_file (mapping, copy_path);
                        state->remove_files = g_list_prepend (state->remove_files, g_strdup (copy_path));
                  }
            }
            return TRUE;
      }
      
      if (info->type != GNOME_VFS_FILE_TYPE_DIRECTORY) {
            mapping = get_backing_file (rel_path);
            if (mapping != NULL) {
                  path1 = escape_path (rel_path);
                  path2 = escape_path (mapping);
                  state->found_file = TRUE;
                  fprintf (state->graft_file, "%s=%s\n", path1, path2);
                  g_free (path1);
                  g_free (path2);
                  g_free (mapping);
            }
      } else {
            if (dir_is_empty (rel_path)) {
                  path1 = escape_path (rel_path);
                  path2 = escape_path (state->emptydir);
                  state->found_file = TRUE;
                  fprintf (state->graft_file, "%s/=%s\n", path1, path2);
                  g_free (path1);
                  g_free (path2);
            } else if (state->depth >= 6) {
                  new_copy_dir = g_build_filename (state->tmpdir, "subdir.XXXXXX", NULL);
                  copy_path = mkdtemp (new_copy_dir);
                  if (copy_path != NULL) {
                        state->remove_files = g_list_prepend (state->remove_files, g_strdup (copy_path));
                        state->copy_depth = 1;
                        state->copy_to_dir = copy_path;
                        path1 = escape_path (rel_path);
                        path2 = escape_path (copy_path);
                        state->found_file = TRUE;
                        fprintf (state->graft_file, "%s/=%s\n", path1, path2);
                        g_free (path1);
                        g_free (path2);
                  } else {
                        g_free (new_copy_dir);
                        g_warning ("Couldn't create temp subdir\n");
                        *recurse = FALSE;
                  }
            } 
      }

      return TRUE;
}
      
static void
create_graft_file (GnomeVFSURI          *uri,
               const char           *prefix,
               struct mkisofs_state *state)
{
      GnomeVFSFileInfo        *info;
      GnomeVFSDirectoryHandle *handle;
      GnomeVFSResult           result;
      gboolean                 stop;

      profile_start ("start", NULL);

      result = gnome_vfs_directory_open_from_uri (&handle,
                                        uri,
                                        GNOME_VFS_FILE_INFO_DEFAULT
                                        | GNOME_VFS_FILE_INFO_GET_ACCESS_RIGHTS);
      if (result != GNOME_VFS_OK)
            return;

      info = gnome_vfs_file_info_new ();

      stop = FALSE;
      while (! stop) {
            char    *rel_path;
            gboolean recurse;

            result = gnome_vfs_directory_read_next (handle, info);
            if (result != GNOME_VFS_OK)
                  break;

            /* Skip "." and "..".  */
            if (info->name [0] == '.'
                && (info->name [1] == 0
                  || (info->name [1] == '.' && info->name [2] == 0))) {
                  gnome_vfs_file_info_clear (info);
                  continue;
            }

            if (prefix == NULL)
                  rel_path = g_strdup (info->name);
            else
                  rel_path = g_strconcat (prefix, info->name, NULL);

            recurse = FALSE;
            stop = ! graft_file_visitor (rel_path, info, state, &recurse);
            
            if (! stop
                && recurse
                && info->type == GNOME_VFS_FILE_TYPE_DIRECTORY) {
                  GnomeVFSURI *new_uri;
                  char        *new_prefix;

                  if (prefix == NULL)
                        new_prefix = g_strconcat (info->name, "/",
                                            NULL);
                  else
                        new_prefix = g_strconcat (prefix, info->name,
                                            "/", NULL);

                  new_uri = gnome_vfs_uri_append_file_name (uri, info->name);

                  state->depth++;
                  create_graft_file (new_uri,
                                 new_prefix,
                                 state);
                  state->depth--;
                  graft_file_end_dir_visitor (state);

                  gnome_vfs_uri_unref (new_uri);
                  g_free (new_prefix);
            }

            g_free (rel_path);

            gnome_vfs_file_info_clear (info);

            if (stop)
                  break;
      }

      gnome_vfs_directory_close (handle);
      gnome_vfs_file_info_unref (info);

      profile_end ("end", NULL);
}

static gboolean  
mkisofs_stdout_read (GIOChannel   *source,
                 GIOCondition  condition,
                 gpointer      data)
{
      struct mkisofs_output *mkisofs_output = data;
      char *line;
      GIOStatus status;

      if (condition & G_IO_IN) {
            status = g_io_channel_read_line (source,
                                     &line, NULL, NULL, NULL);

            if (line && mkisofs_output->debug) {
                  g_print ("make_iso stdout: %s", line);
            }

            if (status == G_IO_STATUS_NORMAL) {
                  g_free (line);
            } else if (status == G_IO_STATUS_EOF) {
                  if (mkisofs_output->debug)
                        g_print ("make_iso stdout: EOF\n");
                  return FALSE;
            }
      } else if (condition & G_IO_HUP) {
            if (mkisofs_output->debug)
                  g_print ("make_iso stdout: HUP\n");
            return FALSE;
      }

      return TRUE;
}

static void
mkisofs_output_error (struct mkisofs_output *mkisofs_output,
                  const char            *message)
{
      nautilus_burn_progress_set_fraction (1.0);
      nautilus_burn_progress_set_time (-1);

      if (!mkisofs_output->error)
            mkisofs_output->error = g_error_new_literal (NAUTILUS_BURN_ISO_ERROR,
                                               NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                               message);
      mkisofs_output->result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
}

static gboolean  
mkisofs_stderr_read (GIOChannel   *source,
                 GIOCondition  condition,
                 gpointer      data)
{
      struct mkisofs_output *mkisofs_output = data;
      char                  *line;
      char                   fraction_str [7];
      double                 fraction;
      GIOStatus              status;

      if (condition & G_IO_IN) {
            status = g_io_channel_read_line (source, &line, NULL, NULL, NULL);

            if (line && mkisofs_output->debug) {
                  g_print ("make_iso stderr: %s", line);
            }

            if (status == G_IO_STATUS_NORMAL) {
                  char *pos;

                  if (strncmp (line, "Total translation table size", 28) == 0) {
                        nautilus_burn_progress_set_fraction (1.0);
                        mkisofs_output->result = NAUTILUS_BURN_RECORDER_RESULT_FINISHED;
                  }

                  if ((pos = strstr (line, "estimate finish"))) {
                        if (sscanf (line, "%6c%% done, estimate finish",
                                  fraction_str) == 1) {
                              fraction_str [6] = 0;
                              fraction = g_strtod (fraction_str, NULL);
                              nautilus_burn_progress_set_fraction (fraction / 100.0);
                              
                              if (pos) {
                                    struct tm tm;
                                    time_t    t_finish;
                                    long      secs;

                                    pos += strlen ("estimate finish ");
                                    /* mkisofs uses ctime for format time string */
                                    strptime (pos, "%a %b %d %H:%M:%S %Y", &tm);
                                    t_finish = mktime (&tm);
                                    secs = difftime (t_finish, time (NULL));
                                    nautilus_burn_progress_set_time (secs);
                              }
                        }
                  }

                  if (strstr (line, "Incorrectly encoded string")) {
                        mkisofs_output_error (mkisofs_output, _("Some files have invalid filenames."));
                  }

                  if (strstr (line, "Unknown charset")) {
                        mkisofs_output_error (mkisofs_output, _("Unknown character encoding."));
                  }

                  if (strstr (line, "No space left on device")) {
                        mkisofs_output_error (mkisofs_output, _("There is no space left on the device."));
                  }

                  if (strstr (line, "Value too large for defined data type")) {
                        /* TODO: get filename from error message */
                        mkisofs_output_error (mkisofs_output, _("File too large for filesystem."));
                  }

                  g_free (line);
            } else if (status == G_IO_STATUS_EOF) {
                  if (mkisofs_output->debug)
                        g_print ("make_iso stderr: EOF\n");
                  nautilus_burn_progress_set_fraction (1.0);
                  nautilus_burn_progress_set_time (-1);
                  g_main_loop_quit (mkisofs_output->loop);
                  return FALSE;
            }
      } else if (condition & G_IO_HUP) {
            /* only handle the HUP when we have read all available lines of output */
            if (mkisofs_output->debug)
                  g_print ("make_iso stderr: HUP\n");
            nautilus_burn_progress_set_fraction (1.0);
            nautilus_burn_progress_set_time (-1);
            g_main_loop_quit (mkisofs_output->loop);
            return FALSE;
      }

      return TRUE;
}

static gboolean
readcd_stderr_read (GIOChannel   *source,
                GIOCondition  condition,
                gpointer      data)
{
      struct mkisofs_output *mkisofs_output = data;

      if (condition & G_IO_IN) {
            char     *line;
            char      buf [1];
            GIOStatus status;

            status = g_io_channel_read_line (source,
                                     &line, NULL, NULL, NULL);

            if (line && mkisofs_output->debug) {
                  g_print ("readcd stderr: %s", line);
            }

            if (status == G_IO_STATUS_NORMAL) {
                  char *pos;

                  if (mkisofs_output->line_stderr) {
                        g_string_append (mkisofs_output->line_stderr, line);
                        g_free (line);
                        line = g_string_free (mkisofs_output->line_stderr, FALSE);
                        mkisofs_output->line_stderr = NULL;
                  }

                  pos = strstr (line, "addr:");
                  if (pos) {
                        guint64 byte;
                        double  fraction;

                        pos += strlen ("addr:");
                        byte = (guint64) atol (pos) * 2048; /* reports blocks of 2048 bytes */
                        if (mkisofs_output->iso_size > 0) {
                              fraction = (double) byte / mkisofs_output->iso_size;
                              nautilus_burn_progress_set_fraction (fraction);
                              nautilus_burn_progress_set_time (-1);
                        }
                  }

                  if (strstr (line, "Time total:")) {
                        mkisofs_output->result = NAUTILUS_BURN_RECORDER_RESULT_FINISHED;
                  }

                  g_free (line);
            } else if (status == G_IO_STATUS_AGAIN) {
                  /* A non-terminated line was read, read the data into the buffer. */
                  status = g_io_channel_read_chars (source, buf, 1, NULL, NULL);
                  if (status == G_IO_STATUS_NORMAL) {
                        if (mkisofs_output->line_stderr == NULL) {
                              mkisofs_output->line_stderr = g_string_new (NULL);
                        }
                        g_string_append_c (mkisofs_output->line_stderr, buf [0]);
                  }
            } else if (status == G_IO_STATUS_EOF) {
                  g_main_loop_quit (mkisofs_output->loop);

                  return FALSE;
            }
      } else if (condition & G_IO_HUP) {
            /* only handle the HUP when we have read all available lines of output */
            if (mkisofs_output->debug)
                  g_print ("readcd stderr: HUP\n");
            g_main_loop_quit (mkisofs_output->loop);

            return FALSE;
      }

      return TRUE;
}

static gdouble
get_average_rate (GList  **rates,
              gdouble  rate)
{
      const unsigned int max_num = 16;
      const unsigned int scale  = 1000;
      gdouble            average;
      gint32             int_rate;
      GList             *l;

      if (g_list_length (*rates) > max_num) {
            *rates = g_list_delete_link (*rates, *rates);
      }
      int_rate = (gint32)ceil (scale * rate);

      *rates = g_list_append (*rates, GINT_TO_POINTER (int_rate));

      average = 0;
      for (l = *rates; l != NULL; l = l->next) {
            gdouble r = (gdouble)GPOINTER_TO_INT (l->data) / scale;
            average += r;
      }

      average /= g_list_length (*rates);

      return average;
}

static gboolean
cdrdao_stderr_line (struct mkisofs_output *mkisofs_output,
                const char            *line)
{
      int t1, t2, s1, s2, s3;
      int min, sec, sub;

      if (line && mkisofs_output->debug) {
            g_print ("cdrdao stderr: %s", line);
      }

      if (sscanf (line, "Copying audio tracks %d-%d: start %d:%d:%d, length %d:%d:%d",
                &t1, &t2, &s1, &s2, &s3, &min, &sec, &sub) == 8) {
            mkisofs_output->iso_size = (guint64) min * 60 + sec;
      }

      if (sscanf (line, "%d:%d:%d", &min, &sec, &sub) == 3) {
            if (mkisofs_output->iso_size > 0) {
                  gdouble fraction;
                  guint64 secs = (guint64) min * 60 + sec;

                  if (mkisofs_output->start_time > 0) {
                        guint64 elapsed;
                        guint64 remaining;
                        gdouble rate;

                        elapsed = time (NULL) - mkisofs_output->start_time;
                        rate = (gdouble)secs / (gdouble)elapsed;

                        if (rate > 0) {
                              gdouble ave_rate;

                              ave_rate = get_average_rate (&mkisofs_output->rates, rate);
                              remaining = (mkisofs_output->iso_size - secs) / ave_rate;
                              nautilus_burn_progress_set_time (remaining);
                        }

                  }

                  fraction = (gdouble) secs / mkisofs_output->iso_size;
                  nautilus_burn_progress_set_fraction (fraction);
            }
      }

      if (strstr (line, "toc and track data finished successfully")) {
            mkisofs_output->result = NAUTILUS_BURN_RECORDER_RESULT_FINISHED;
      }

      return TRUE;
}

static gboolean
cdrdao_stderr_read (GIOChannel   *source,
                GIOCondition  condition,
                gpointer      data)
{
      struct mkisofs_output *mkisofs_output = data;
      gboolean               res            = TRUE;

      if (condition & G_IO_IN) {
            char     *line;
            char      buf [1];
            GIOStatus status;

            status = g_io_channel_read_line (source,
                                     &line, NULL, NULL, NULL);

            if (status == G_IO_STATUS_NORMAL) {
                  if (mkisofs_output->line_stderr) {
                        g_string_append (mkisofs_output->line_stderr, line);
                        g_free (line);
                        line = g_string_free (mkisofs_output->line_stderr, FALSE);
                        mkisofs_output->line_stderr = NULL;
                  }

                  res = cdrdao_stderr_line (mkisofs_output, line);
                  
                  g_free (line);
            } else if (status == G_IO_STATUS_AGAIN) {
                  /* A non-terminated line was read, read the data into the buffer. */
                  status = g_io_channel_read_chars (source, buf, 1, NULL, NULL);

                  if (status == G_IO_STATUS_NORMAL) {
                        char *line2;

                        if (mkisofs_output->line_stderr == NULL) {
                              mkisofs_output->line_stderr = g_string_new (NULL);
                        }
                        g_string_append_c (mkisofs_output->line_stderr, buf [0]);

                        switch (buf [0]) {
                        case '\n':
                        case '\r':
                        case '\xe2':
                        case '\0':
                              line2 = g_string_free (mkisofs_output->line_stderr, FALSE);
                              mkisofs_output->line_stderr = NULL;
                              res = cdrdao_stderr_line (mkisofs_output, line2);
                              g_free (line2);
                              break;
                        default:
                              break;
                        }
                  }
            } else if (status == G_IO_STATUS_EOF) {
                  g_main_loop_quit (mkisofs_output->loop);

                  res = FALSE;
            }
      } else if (condition & G_IO_HUP) {
            /* only handle the HUP when we have read all available lines of output */
            if (mkisofs_output->debug)
                  g_print ("cdrdao stderr: HUP\n");
            g_main_loop_quit (mkisofs_output->loop);

            res = FALSE;
      }

      return res;
}

/* this is an ugly hack until utf8/iconv support is added into upstream mkisofs */
static gboolean
ncb_mkisofs_supports_utf8 (void)
{
      char    *standard_error;
      gboolean supported;
      gboolean res;

      res = g_spawn_command_line_sync ("mkisofs -input-charset utf8", NULL, &standard_error, NULL, NULL);
      if (res && !g_strrstr (standard_error, "Unknown charset"))
            supported = TRUE;
      else
            supported = FALSE;

      g_free (standard_error);

      return supported;
}

static gboolean
start_async_with_watch (char        **args,
                  GPid         *ppid,
                  GIOFunc       out_watch_func,
                  GIOFunc       err_watch_func,
                  gpointer      user_data,
                  guint        *out_watch_id,
                  guint        *err_watch_id,
                  int          *input_pipe,
                  GError      **error)
{
      gboolean    ret;
      int       stdin_pipe;
      int       stdout_pipe;
      int       stderr_pipe;
      GPid      pid = 0;

      g_return_val_if_fail (args != NULL, FALSE);

      ret = g_spawn_async_with_pipes (NULL,
                              args,
                              NULL,
                              G_SPAWN_SEARCH_PATH,
                              NULL,
                              NULL,
                              &pid,
                              &stdin_pipe,
                              &stdout_pipe,
                              &stderr_pipe,
                              error);

      if (!ret)
            return FALSE;

      if (ppid)
            *ppid = pid;

      if (input_pipe)
            *input_pipe = stdin_pipe;

      fcntl (stdout_pipe, F_SETFL, O_NONBLOCK);
      fcntl (stderr_pipe, F_SETFL, O_NONBLOCK);

      if (out_watch_func) {
            GIOChannel *channel;
            guint     id;

            channel = g_io_channel_unix_new (stdout_pipe);
            g_io_channel_set_flags (channel,
                              g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK,
                              NULL);
            g_io_channel_set_encoding (channel, NULL, NULL);

            id = g_io_add_watch (channel,
                             G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
                             out_watch_func,
                             user_data);

            g_io_channel_unref (channel);

            if (out_watch_id)
                  *out_watch_id = id;
      } else {
            if (out_watch_id)
                  *out_watch_id = -1;
      }

      if (err_watch_func) {
            GIOChannel *channel;
            guint     id;

            channel = g_io_channel_unix_new (stderr_pipe);
            g_io_channel_set_flags (channel,
                              g_io_channel_get_flags (channel) | G_IO_FLAG_NONBLOCK,
                              NULL);
            g_io_channel_set_encoding (channel, NULL, NULL);

            id = g_io_add_watch (channel,
                             G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
                             err_watch_func,
                             user_data);

            g_io_channel_unref (channel);

            if (err_watch_id)
                  *err_watch_id = id;
      } else {
            if (err_watch_id)
                  *err_watch_id = -1;
      }

      return ret;
}

static void
run_process (struct mkisofs_output    *mkisofs_output,
           GIOFunc                   out_watch_func,
           GIOFunc                   err_watch_func,
           GPtrArray                *argv)
{
      int         res;
      guint       stdout_tag, stderr_tag;
      GError     *error;

      mkisofs_output->line_stderr = NULL;

 retry:
      mkisofs_output->result     = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
      mkisofs_output->line       = NULL;
      mkisofs_output->start_time = time (NULL);

      if (mkisofs_output->line_stderr != NULL) {
            g_string_truncate (mkisofs_output->line_stderr, 0);
      } else {
            mkisofs_output->line_stderr = g_string_new (NULL);
      }

      nautilus_burn_progress_set_text (_("Creating disc image"));
      nautilus_burn_progress_set_fraction (0.0);
      nautilus_burn_progress_set_image_spinning (TRUE);

      if (mkisofs_output->debug) {
            guint i;
            g_print ("launching command: ");
            for (i = 0; i < argv->len - 1; i++) {
                  g_print ("%s ", (char *) g_ptr_array_index (argv, i));
            }
            g_print ("\n");
      }

      error = NULL;

      res = start_async_with_watch ((char **)argv->pdata,
                              &mkisofs_output->pid,
                              out_watch_func,
                              err_watch_func,
                              mkisofs_output,
                              &stdout_tag,
                              &stderr_tag,
                              NULL,
                              &error);

      if (! res) {
            g_warning ("command failed: %s\n", error->message);
            g_error_free (error);
            /* TODO: Better error handling */
      } else {
            mkisofs_output->loop = g_main_loop_new (NULL, FALSE);

            g_main_loop_run (mkisofs_output->loop);
            g_main_loop_unref (mkisofs_output->loop);
            
            if (stdout_tag > 0)
                  g_source_remove (stdout_tag);
            if (stderr_tag > 0)
                  g_source_remove (stderr_tag);

            if (mkisofs_output->result == NAUTILUS_BURN_RECORDER_RESULT_RETRY) {
                  goto retry;
            }
      }

      g_list_free (mkisofs_output->rates);

      nautilus_burn_progress_set_image_spinning (FALSE);
}

static char *
get_image_space_error_message (guint64 iso_size,
                         guint64 space_available)
{
      char   *space_needed_string;
      char   *message;
      guint64 space_needed;

      space_needed = iso_size - space_available;
      space_needed_string = g_strdup_printf ("%" G_GINT64_FORMAT, space_needed / 1048576);
      message = g_strdup_printf (_("The selected location does not have enough space to store the disc image (%s MiB needed)."),
                           space_needed_string);
      g_free (space_needed_string);

      return message;
}

/**
 * nautilus_burn_make_iso:
 * @filename: name of a file to use for the image
 * @label: a string to use as the label for the image
 * @flags: #NautilusBurnImageCreateFlags
 * @type: #NautilusBurnImageType
 * @error: a return location for errors
 *
 * Create an ISO image in filename from the data files in burn:///
 *
 * Return value: #NautilusBurnRecorderResult
 **/
int
nautilus_burn_make_iso (const char                  *filename,
                  const char                  *label,
                  NautilusBurnImageCreateFlags flags,
                  NautilusBurnImageType       *type,
                  GError                     **error)
{
      GnomeVFSURI          *uri;
      char                 *filelist = NULL;
      struct mkisofs_state  state = {NULL};
      char                 *tempdir;
      GList                *l;
      int                   stdout_pipe, stderr_pipe;
      int                   i;
      GError               *sub_error;
      const char           *argv [20]; /* Shouldn't need more than 20 arguments */
      struct mkisofs_output mkisofs_output;
      GIOChannel           *channel;
      guint                 stdout_tag, stderr_tag;
      char                 *stdout_data, *stderr_data;
      char                 *dirname;
      int                   exit_status, res;
      guint64               iso_size;
      GnomeVFSFileSize      size;
      gboolean              has_utf8_support;
      gboolean              use_joliet;

      use_joliet = (flags & NAUTILUS_BURN_IMAGE_CREATE_JOLIET);

      if (type)
            *type = NAUTILUS_BURN_IMAGE_TYPE_ISO9660;

      memset (&mkisofs_output, 0, sizeof (mkisofs_output));

      if (label && (strlen (label) > 32)) {
            g_set_error (error,
                       G_FILE_ERROR,
                       0,
                       _("The label for the image is too long."));
            return NAUTILUS_BURN_RECORDER_RESULT_ERROR;
      }

      mkisofs_output.error = NULL;
      mkisofs_output.debug = flags & NAUTILUS_BURN_IMAGE_CREATE_DEBUG;

      dirname = g_strdup_printf ("iso-%s.XXXXXX", g_get_user_name ());
      tempdir = g_build_filename (g_get_tmp_dir (), dirname, NULL);
      g_free (dirname);
      state.tmpdir = mkdtemp (tempdir);

      mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;

      if (state.tmpdir == 0) {
            mkisofs_output.error = g_error_new (NAUTILUS_BURN_ISO_ERROR,
                                        NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                        _("Unable to create temporary directory: %s."),
                                        g_strerror (errno));
            goto cleanup;
      }
      
      state.emptydir = g_build_filename (state.tmpdir, "emptydir", NULL);
      g_mkdir (state.emptydir, 0777);
      state.remove_files = g_list_prepend (state.remove_files, g_strdup (state.emptydir));

      filelist = g_build_filename (state.tmpdir, "filelist", NULL);

      state.graft_file = fopen (filelist, "w");
      if (state.graft_file == NULL) {
            mkisofs_output.error = g_error_new (NAUTILUS_BURN_ISO_ERROR, 
                                        NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                        _("Unable to create temporary file: %s."),
                                        g_strerror (errno));
            goto cleanup;
      }
      
      uri = gnome_vfs_uri_new ("burn:///");
      state.found_file = FALSE;

      profile_start ("start", "creating file list");
      create_graft_file (uri, NULL, &state);
      profile_end ("end", "creating file list");

      gnome_vfs_uri_unref (uri);

      fclose (state.graft_file);
      state.remove_files = g_list_prepend (state.remove_files, g_strdup (filelist));

      if (!state.found_file) {
            mkisofs_output.error = g_error_new_literal (NAUTILUS_BURN_ISO_ERROR, 
                                              NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                              _("There are no files to write to disc."));
            mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
            goto cleanup;
      }

      has_utf8_support = ncb_mkisofs_supports_utf8 ();

 retry:
      i = 0;
      argv [i++] = "mkisofs";
      argv [i++] = "-r";
      if (use_joliet) {
            argv [i++] = "-J";
      }
      if (has_utf8_support) {
            argv [i++] = "-input-charset";
            argv [i++] = "utf8";
      }
      argv [i++] = "-q";
      argv [i++] = "-graft-points";
      argv [i++] = "-path-list";
      argv [i++] = filelist;
      argv [i++] = "-print-size";
      argv [i++] = NULL;

      sub_error = NULL;

      if (flags & NAUTILUS_BURN_IMAGE_CREATE_DEBUG) {
            g_print ("launching command: ");
            for (i = 0; argv [i] != NULL; i++) {
                  g_print ("%s ", argv [i]);
            }
            g_print ("\n");
      }

      if (!g_spawn_sync (NULL,
                     (char **)argv,
                     NULL,
                     G_SPAWN_SEARCH_PATH,
                     NULL, NULL,
                     &stdout_data,
                     &stderr_data,
                     &exit_status,
                     &sub_error)) {
            mkisofs_output.error = g_error_new (NAUTILUS_BURN_ISO_ERROR, 
                                        NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                        _("Could not run sub process: %s."),
                                        sub_error->message);
            g_error_free (sub_error);
            mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
            goto cleanup;
      }

      if (exit_status != 0 && use_joliet) {
            if (strstr (stderr_data, "Joliet tree sort failed.") != NULL) {
                  g_free (stdout_data);
                  g_free (stderr_data);
                  if (ask_disable_joliet (nautilus_burn_progress_get_window ())) {
                        use_joliet = FALSE;
                        goto retry;
                  } else {
                        mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
                        mkisofs_output.error = g_error_new_literal (NAUTILUS_BURN_ISO_ERROR, 
                                                          NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                                          _("The operation was cancelled by the user."));
                        goto cleanup;
                  }
            }
      }

      g_free (stderr_data);
      iso_size = (guint64)atol (stdout_data) * 2048 ; /* mkisofs reports blocks of 2048 bytes */
      g_free (stdout_data);

      dirname = g_path_get_dirname (filename);
      res = make_iso_get_free_space (dirname, &size);

      if (res == -1) {
            g_warning ("Cannot get free space at %s\n", dirname);
      } else if (iso_size > size) {
            if (flags & NAUTILUS_BURN_IMAGE_CREATE_WARN_SPACE) {
                  char *message;

                  message = get_image_space_error_message (iso_size,
                                                 size);

                  mkisofs_output.error = g_error_new (NAUTILUS_BURN_ISO_ERROR, 
                                              NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                              "%s", message);
                  g_free (message);

                  mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
            } else {
                  mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_RETRY;
            }

            g_free (dirname);

            goto cleanup;
      }

      g_free (dirname);

      i = 0;
      argv [i++] = "mkisofs";
      argv [i++] = "-r";
      if (use_joliet) {
            argv [i++] = "-J";
      }
      if (has_utf8_support) {
            argv [i++] = "-input-charset";
            argv [i++] = "utf8";
      }
      argv [i++] = "-graft-points";
      argv [i++] = "-path-list";
      argv [i++] = filelist;
      if (label) {
            argv [i++] = "-V";
            argv [i++] = label;
      }
      argv [i++] = "-o";
      argv [i++] = filename;
      argv [i++] = NULL;

      if (flags & NAUTILUS_BURN_IMAGE_CREATE_DEBUG) {
            g_print ("launching command: ");
            for (i = 0; argv [i] != NULL; i++) {
                  g_print ("%s ", argv [i]);
            }
            g_print ("\n");
      }

      nautilus_burn_progress_set_text (_("Creating disc image"));
      nautilus_burn_progress_set_fraction (0.0);
      nautilus_burn_progress_set_image_spinning (TRUE);
      sub_error = NULL;
      if (!g_spawn_async_with_pipes  (NULL,
                              (char **)argv,
                              NULL,
                              G_SPAWN_SEARCH_PATH,
                              NULL, NULL,
                              &mkisofs_output.pid,
                              /*stdin*/NULL,
                              &stdout_pipe,
                              &stderr_pipe,
                              &sub_error)) {
            mkisofs_output.error = g_error_new (NAUTILUS_BURN_ISO_ERROR, 
                                        NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                        _("Command failed: %s"),
                                        sub_error->message);
            g_error_free (sub_error);
            mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
            goto cleanup;
      } else {
            mkisofs_output.loop = g_main_loop_new (NULL, FALSE);
      
            channel = g_io_channel_unix_new (stdout_pipe);
            g_io_channel_set_encoding (channel, NULL, NULL);
            stdout_tag = g_io_add_watch (channel, 
                                   (G_IO_IN | G_IO_HUP | G_IO_ERR), 
                                   mkisofs_stdout_read,
                                   &mkisofs_output);
            g_io_channel_unref (channel);
            channel = g_io_channel_unix_new (stderr_pipe);
            g_io_channel_set_encoding (channel, NULL, NULL);
            stderr_tag = g_io_add_watch (channel, 
                                   (G_IO_IN | G_IO_HUP | G_IO_ERR), 
                                   mkisofs_stderr_read,
                                   &mkisofs_output);
            g_io_channel_unref (channel);

            mkisofs_output_ptr = &mkisofs_output;
            mkisofs_output.filename = filename;
            
            g_main_loop_run (mkisofs_output.loop);
            g_main_loop_unref (mkisofs_output.loop);
            
            g_source_remove (stdout_tag);
            g_source_remove (stderr_tag);
      }
      mkisofs_output_ptr = NULL;

 cleanup:
      for (l = state.remove_files; l != NULL; l = l->next) {
            g_remove ((char *)l->data);
            g_free (l->data);
      }
      g_list_free (state.remove_files);
      
      g_free (filelist);
      g_free (state.emptydir);
      g_rmdir (tempdir);
      g_free (tempdir);
      nautilus_burn_progress_set_image_spinning (FALSE);

      if (mkisofs_output.result == NAUTILUS_BURN_RECORDER_RESULT_ERROR) {
            if (!mkisofs_output.error)
                  mkisofs_output.error = g_error_new_literal (NAUTILUS_BURN_ISO_ERROR,
                                                    NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                                    _("Unknown error"));
            g_unlink (mkisofs_output.filename);

            g_propagate_error (error, mkisofs_output.error);
      }

      return mkisofs_output.result;
}

/**
 * nautilus_burn_make_iso_from_drive:
 * @filename: name of a file to use for the image
 * @drive: #NautilusBurnDrive from which to read the source media
 * @warn_low_space: set %TRUE issue a warning when disk space is low
 *
 * Create an ISO image in filename from the data in @drive
 *
 * Return value: #NautilusBurnRecorderResult
 **/
int
nautilus_burn_make_iso_from_drive (const char                  *filename,
                           NautilusBurnDrive           *drive,
                           NautilusBurnImageCreateFlags flags,
                           NautilusBurnImageType       *type,
                           char                       **toc_filename,
                           GError                     **error)
{
      GPtrArray            *argv = NULL;
      GError               *sub_error;
      struct mkisofs_output mkisofs_output;
      char                 *device_arg, *outfile_arg;
      char                 *tocfile_arg    = NULL;
      char                 *stdout_data, *stderr_data;
      char                 *pos;
      char                 *dirname;
      int                   exit_status, res;
      guint64               iso_size;
      GnomeVFSFileSize      size;
      NautilusBurnMediaType media_type;
      gboolean              is_rewritable, is_blank, has_data, has_audio;
      GIOFunc               out_watch_func = NULL;
      GIOFunc               err_watch_func = NULL;

      memset (&mkisofs_output, 0, sizeof (mkisofs_output));

      if (toc_filename)
            *toc_filename = NULL;
      if (type)
            *type = NAUTILUS_BURN_IMAGE_TYPE_UNKNOWN;

      mkisofs_output.error    = NULL;
      mkisofs_output.debug    = (flags & NAUTILUS_BURN_IMAGE_CREATE_DEBUG);
      mkisofs_output.result   = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
      mkisofs_output.filename = filename;

      media_type = nautilus_burn_drive_get_media_type_full (drive,
                                                &is_rewritable,
                                                &is_blank,
                                                &has_data,
                                                &has_audio);
      if (has_audio) {
            device_arg = g_strdup_printf ("%s", drive->cdrecord_id);
            outfile_arg = g_strdup_printf ("%s", filename);
            tocfile_arg = g_strdup_printf ("%s.toc", filename);
            if (toc_filename)
                  *toc_filename = g_strdup (tocfile_arg);
            if (type)
                  *type = NAUTILUS_BURN_IMAGE_TYPE_BINCUE;

            argv = g_ptr_array_new ();
            g_ptr_array_add (argv, "cdrdao");
            g_ptr_array_add (argv, "disk-info");
            g_ptr_array_add (argv, "--device");
            g_ptr_array_add (argv, device_arg);
            g_ptr_array_add (argv, NULL);
            
      } else {
            device_arg = g_strdup_printf ("-dev=%s", drive->cdrecord_id);
            outfile_arg = g_strdup_printf ("-f=%s", filename);
            if (toc_filename)
                  *toc_filename = g_strdup_printf ("%s.toc", filename);
            if (type)
                  *type = NAUTILUS_BURN_IMAGE_TYPE_ISO9660;

            argv = g_ptr_array_new ();
            g_ptr_array_add (argv, "readcd");
            g_ptr_array_add (argv, "-sectors=0-0");
            g_ptr_array_add (argv, device_arg);
            g_ptr_array_add (argv, outfile_arg);
            g_ptr_array_add (argv, NULL);
      }

      sub_error = NULL;

      if (!g_spawn_sync (NULL,
                     (char **)argv->pdata,
                     NULL,
                     G_SPAWN_SEARCH_PATH,
                     NULL, NULL,
                     &stdout_data,
                     &stderr_data,
                     &exit_status,
                     &sub_error)) {
            mkisofs_output.error = g_error_new (NAUTILUS_BURN_ISO_ERROR, 
                                        NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                        _("Could not run sub process: %s."),
                                        sub_error->message);
            g_error_free (sub_error);
            mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
            goto cleanup;
      }

      g_ptr_array_free (argv, TRUE);
      argv = NULL;
      g_free (stdout_data);

      if (has_audio) {
            /* assume audio CD is 800 MiB */
            iso_size = 838860800;
      } else {
            pos = strstr (stderr_data, "Capacity:");
            if (pos) {
                  pos += strlen ("Capacity:");
                  iso_size = (guint64) atol (pos) * 2048; /* reports blocks of 2048 bytes */
            } else {
                  iso_size = 0;
            }
            mkisofs_output.iso_size = iso_size;
      }

      g_free (stderr_data);

      dirname = g_path_get_dirname (filename);
      res = make_iso_get_free_space (dirname, &size);

      if (res == -1) {
            g_warning ("Cannot get free space at %s\n", dirname);
            g_free (dirname);
      } else if (iso_size > size) {
            g_free (dirname);
            if (flags & NAUTILUS_BURN_IMAGE_CREATE_WARN_SPACE) {
                  char *message;

                  message = get_image_space_error_message (iso_size,
                                                 size);

                  mkisofs_output.error = g_error_new (NAUTILUS_BURN_ISO_ERROR, 
                                              NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                              "%s", message);
                  g_free (message);

                  mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_ERROR;
            } else {
                  mkisofs_output.result = NAUTILUS_BURN_RECORDER_RESULT_RETRY;
            }

            goto cleanup;
      }

      if (has_audio) {
            argv = g_ptr_array_new ();
            g_ptr_array_add (argv, "cdrdao");
            g_ptr_array_add (argv, "read-cd");
            g_ptr_array_add (argv, "--read-raw");
            g_ptr_array_add (argv, "--datafile");
            g_ptr_array_add (argv, outfile_arg);
            g_ptr_array_add (argv, "--device");
            g_ptr_array_add (argv, device_arg);
            g_ptr_array_add (argv, "-v");
            g_ptr_array_add (argv, "2");
            g_ptr_array_add (argv, tocfile_arg);
            g_ptr_array_add (argv, NULL);

            out_watch_func = NULL;
            err_watch_func = cdrdao_stderr_read;
      } else {
            argv = g_ptr_array_new ();
            g_ptr_array_add (argv, "readcd");
            g_ptr_array_add (argv, device_arg);
            g_ptr_array_add (argv, outfile_arg);
            g_ptr_array_add (argv, NULL);

            out_watch_func = NULL;
            err_watch_func = readcd_stderr_read;
      }

      mkisofs_output_ptr = &mkisofs_output;
      run_process (&mkisofs_output,
                 out_watch_func,
                 err_watch_func,
                 argv);
      mkisofs_output_ptr = NULL;

 cleanup:
      g_free (outfile_arg);
      g_free (tocfile_arg);
      g_free (device_arg);
      if (argv) {
            g_ptr_array_free (argv, TRUE);
            argv = NULL;
      }

      if (mkisofs_output.result == NAUTILUS_BURN_RECORDER_RESULT_ERROR) {
            if (!mkisofs_output.error)
                  mkisofs_output.error = g_error_new_literal (NAUTILUS_BURN_ISO_ERROR,
                                                    NAUTILUS_BURN_ISO_ERROR_GENERAL,
                                                    _("Unknown error"));

            g_propagate_error (error, mkisofs_output.error);
      }

      return mkisofs_output.result;
}

/* Originally eel_make_valid_utf8 */
static char *
ncb_make_valid_utf8 (const char *name)
{
      GString    *string;
      const char *remainder, *invalid;
      int         remaining_bytes, valid_bytes;

      string = NULL;
      remainder = name;
      remaining_bytes = strlen (name);

      while (remaining_bytes != 0) {
            if (g_utf8_validate (remainder, remaining_bytes, &invalid)) {
                  break;
            }
            valid_bytes = invalid - remainder;

            if (string == NULL) {
                  string = g_string_sized_new (remaining_bytes);
            }
            g_string_append_len (string, remainder, valid_bytes);
            g_string_append_c (string, '?');

            remaining_bytes -= valid_bytes + 1;
            remainder = invalid + 1;
      }

      if (string == NULL) {
            return g_strdup (name);
      }

      g_string_append (string, remainder);
      g_string_append (string, _(" (invalid Unicode)"));
      g_assert (g_utf8_validate (string->str, -1, NULL));

      return g_string_free (string, FALSE);
}

/**
 * nautilus_burn_verify_iso:
 * @filename: name of a file to use for the image
 * @iso_label: return location for the image label
 * @error: return location for errors
 *
 * Verify that filename is a valid ISO image
 *
 * Return value: %TRUE if filename is a valid ISO image, otherwise %FALSE
 **/
gboolean
nautilus_burn_verify_iso (const char *filename,
                    char      **iso_label,
                    GError    **error)
{
      FILE  *file;
#define BUFFER_SIZE 128
      char  buf [BUFFER_SIZE+1];
      int   res;
      char *str, *str2;

      file = fopen (filename, "rb");
      if (file == NULL) {
            int err = errno;
            *error = g_error_new_literal (g_file_error_quark (),
                                    g_file_error_from_errno (err),
                                    strerror (err));
            return FALSE;
      }
      /* Verify we have an ISO image */
      /* This check is for the raw sector images */
      res = fseek (file, 37633L, SEEK_SET);
      if (res) {
            goto bail;
      }
      res = fread (buf, sizeof (char), 5, file);
      if (res != 5 || strncmp (buf, "CD001", 5) != 0) {
            /* Standard ISO images */
            res = fseek (file, 32769L, SEEK_SET);
            if (res) {
                  goto bail;
            }
            res = fread (buf, sizeof (char), 5, file);
            if (res != 5 || strncmp (buf, "CD001", 5) != 0) {
                  /* High Sierra images */
                  res = fseek (file, 32776L, SEEK_SET);
                  if (res) {
                        goto bail;
                  }
                  res = fread (buf, sizeof (char), 5, file);
                  if (res != 5 || strncmp (buf, "CDROM", 5) != 0) {
                        goto bail;
                  }
            }
      }
      /* Extract the volume label from the image */
      res = fseek (file, 32808L, SEEK_SET);
      if (res) {
            goto bail;
      }
      res = fread (buf, sizeof(char), BUFFER_SIZE, file);
      if (res != BUFFER_SIZE) {
            goto bail;
      }
      buf [BUFFER_SIZE] = '\0';
      str = g_strdup (g_strstrip (buf));
      if (!g_utf8_validate (str, -1, NULL)) {
            /* Hmm, not UTF-8. Try the current locale. */
            str2 = g_locale_to_utf8 (str, -1, NULL, NULL, NULL);
            if (str2 == NULL) {
                  str2 = ncb_make_valid_utf8 (str);
            }
            g_free (str);
            str = str2;
      }
      fclose (file);
      *iso_label = str;
      return TRUE;

 bail:
      *error = g_error_new_literal (NAUTILUS_BURN_ISO_ERROR,
                              NAUTILUS_BURN_ISO_ERROR_GENERAL,
                              _("Not a valid disc image."));

      return FALSE;
}


Generated by  Doxygen 1.6.0   Back to index