[gnome-disk-utility] Make "Create Disk Image" feature tolerate read errors



commit 5762bd304c14332644d2f5bdc96cfce29f4dbfcd
Author: David Zeuthen <zeuthen gmail com>
Date:   Tue Oct 16 12:02:37 2012 -0400

    Make "Create Disk Image" feature tolerate read errors
    
    The "Create Disk Image" feature is often used to copy data from a
    failing drive (or scratched medium) so it is far from uncommon to
    encounter read errors. In fact, you could argue that you mostly only
    use this feature when dealing with a failing disk (or medium).
    
    Therefore, if a read fails (or a partial read is returned), simply
    skip the bytes we didn't get and write zeroes in the disk image
    instead. Also convey that to the user.
    
    Right now the granularity is 1 MiB. In the future we could something
    like what e.g. GNU ddrescue is doing, see
    
     http://www.gnu.org/software/ddrescue/manual/ddrescue_manual.html#Algorithm
     http://ubuntu-rescue-remix.org/node/51
    
    That is, we could record a list of failed reads and then go backwards
    through that list to try to recover even more data. But for now, just
    having the ability to skip data is really good.
    
    The user experience has also changed - when copying is complete
    instead of dismissing the dialog, we:
    
     - show a "Close" button instead of a "Cancel" button
    
     - play the "complete" sound
    
     - slightly change the progress string to convey that the operation
       is complete
    
     - show a "Show in Folder" button that will open a Nautilus window and
       select the created disk image file
    
    Implementation-wise we also do the copying a dedicated thread - much
    easier to read than the async mess we had before.
    
    http://people.freedesktop.org/~david/gnome-disks-create-disk-image-ft-1.png
    http://people.freedesktop.org/~david/gnome-disks-create-disk-image-ft-2.png
    http://people.freedesktop.org/~david/gnome-disks-create-disk-image-ft-3.png
    
    Signed-off-by: David Zeuthen <zeuthen gmail com>

 configure.ac                         |    2 +
 data/ui/create-disk-image-dialog.ui  |  323 ++++++++-----
 src/disks/Makefile.am                |    2 +
 src/disks/gducreatediskimagedialog.c |  860 ++++++++++++++++++++++++----------
 4 files changed, 801 insertions(+), 386 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index d4e6178..662c384 100644
--- a/configure.ac
+++ b/configure.ac
@@ -86,12 +86,14 @@ UDISKS2_REQUIRED=2.0.90
 GTK3_REQUIRED=3.5.8
 LIBSECRET1_REQUIRED=0.7
 PWQUALITY_REQUIRED=1.0.0
+CANBERRA_REQUIRED=0.1
 
 PKG_CHECK_MODULES(GLIB2, [gmodule-2.0 gio-unix-2.0 >= $GLIB2_REQUIRED])
 PKG_CHECK_MODULES(UDISKS2, [udisks2 >= $UDISKS2_REQUIRED])
 PKG_CHECK_MODULES(GTK3, [gtk+-3.0 >= $GTK3_REQUIRED])
 PKG_CHECK_MODULES(LIBSECRET1, [libsecret-1 >= $LIBSECRET1_REQUIRED])
 PKG_CHECK_MODULES(PWQUALITY, [pwquality >= $PWQUALITY_REQUIRED])
+PKG_CHECK_MODULES(CANBERRA, [libcanberra-gtk3 >= CANBERRA_REQUIRED])
 
 GLIB_GSETTINGS
 
diff --git a/data/ui/create-disk-image-dialog.ui b/data/ui/create-disk-image-dialog.ui
index b24f116..5e41a0a 100644
--- a/data/ui/create-disk-image-dialog.ui
+++ b/data/ui/create-disk-image-dialog.ui
@@ -2,9 +2,10 @@
 <interface>
   <!-- interface-requires gtk+ 3.0 -->
   <object class="GtkDialog" id="create-disk-image-dialog">
-    <property name="width_request">500</property>
+    <property name="width_request">550</property>
     <property name="can_focus">False</property>
     <property name="border_width">12</property>
+    <property name="title" translatable="yes">Create Disk Image</property>
     <property name="resizable">False</property>
     <property name="type_hint">dialog</property>
     <child internal-child="vbox">
@@ -13,100 +14,155 @@
         <property name="orientation">vertical</property>
         <property name="spacing">12</property>
         <child>
-          <object class="GtkNotebook" id="notebook">
+          <object class="GtkGrid" id="grid1">
+            <property name="visible">True</property>
             <property name="can_focus">False</property>
-            <property name="margin_left">12</property>
-            <property name="show_tabs">False</property>
-            <property name="show_border">False</property>
+            <property name="hexpand">True</property>
+            <property name="row_spacing">12</property>
+            <property name="column_spacing">12</property>
             <child>
-              <object class="GtkGrid" id="grid1">
+              <object class="GtkLabel" id="name-label">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
-                <property name="row_spacing">12</property>
-                <property name="column_spacing">12</property>
-                <child>
-                  <object class="GtkLabel" id="label5">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="xalign">1</property>
-                    <property name="label" translatable="yes">_Name</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic_widget">destination_name_entry</property>
-                    <style><class name="dim-label"/></style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">0</property>
-                    <property name="width">1</property>
-                    <property name="height">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="label6">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="xalign">1</property>
-                    <property name="label" translatable="yes">Save in _folder</property>
-                    <property name="use_underline">True</property>
-                    <property name="mnemonic_widget">destination_folder_fcbutton</property>
-                    <style><class name="dim-label"/></style>
-                  </object>
-                  <packing>
-                    <property name="left_attach">0</property>
-                    <property name="top_attach">1</property>
-                    <property name="width">1</property>
-                    <property name="height">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkEntry" id="destination_name_entry">
-                    <property name="visible">True</property>
-                    <property name="can_focus">True</property>
-                    <property name="hexpand">True</property>
-                    <property name="invisible_char">â</property>
-                    <property name="activates_default">True</property>
-                    <property name="invisible_char_set">True</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">0</property>
-                    <property name="width">1</property>
-                    <property name="height">1</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkFileChooserButton" id="destination_folder_fcbutton">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="orientation">vertical</property>
-                    <property name="action">select-folder</property>
-                    <property name="local_only">False</property>
-                    <property name="title" translatable="yes">Select a Folder</property>
-                  </object>
-                  <packing>
-                    <property name="left_attach">1</property>
-                    <property name="top_attach">1</property>
-                    <property name="width">1</property>
-                    <property name="height">1</property>
-                  </packing>
-                </child>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">_Name</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">name-entry</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
               </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">1</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
             </child>
-            <child type="tab">
-              <placeholder/>
+            <child>
+              <object class="GtkLabel" id="folder-label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">Save in _Folder</property>
+                <property name="use_underline">True</property>
+                <property name="mnemonic_widget">folder-fcbutton</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">2</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkEntry" id="name-entry">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="hexpand">True</property>
+                <property name="invisible_char">â</property>
+                <property name="activates_default">True</property>
+                <property name="invisible_char_set">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">1</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
             </child>
             <child>
-              <object class="GtkBox" id="vbox1">
+              <object class="GtkFileChooserButton" id="folder-fcbutton">
                 <property name="visible">True</property>
                 <property name="can_focus">False</property>
                 <property name="orientation">vertical</property>
-                <property name="spacing">12</property>
+                <property name="action">select-folder</property>
+                <property name="local_only">False</property>
+                <property name="title" translatable="yes">Select a Folder</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">2</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="destination-key-label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">Destination</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">3</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="destination-label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="selectable">True</property>
+                <property name="ellipsize">middle</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">3</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="label3">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="label" translatable="yes">Source</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">0</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkLabel" id="source-label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">0</property>
+                <property name="selectable">True</property>
+              </object>
+              <packing>
+                <property name="left_attach">1</property>
+                <property name="top_attach">0</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkVBox" id="copying-vbox">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="spacing">6</property>
                 <child>
-                  <object class="GtkLabel" id="copying_label">
+                  <object class="GtkProgressBar" id="copying-progressbar">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="xalign">0</property>
-                    <property name="label" translatable="yes">Copying data to disk image...</property>
+                    <property name="hexpand">True</property>
                   </object>
                   <packing>
                     <property name="expand">False</property>
@@ -115,33 +171,10 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkVBox" id="vbox2">
+                  <object class="GtkLabel" id="copying-label">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
-                    <property name="spacing">6</property>
-                    <child>
-                      <object class="GtkProgressBar" id="copying_progressbar">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkLabel" id="copying_progress_label">
-                        <property name="visible">True</property>
-                        <property name="can_focus">False</property>
-                        <property name="xalign">0</property>
-                      </object>
-                      <packing>
-                        <property name="expand">True</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
+                    <property name="xalign">0</property>
                   </object>
                   <packing>
                     <property name="expand">False</property>
@@ -151,23 +184,29 @@
                 </child>
               </object>
               <packing>
-                <property name="position">1</property>
+                <property name="left_attach">1</property>
+                <property name="top_attach">4</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
               </packing>
             </child>
-            <child type="tab">
-              <placeholder/>
-            </child>
-            <child>
-              <placeholder/>
-            </child>
-            <child type="tab">
-              <placeholder/>
-            </child>
             <child>
-              <placeholder/>
-            </child>
-            <child type="tab">
-              <placeholder/>
+              <object class="GtkLabel" id="copying-key-label">
+                <property name="visible">True</property>
+                <property name="can_focus">False</property>
+                <property name="xalign">1</property>
+                <property name="yalign">0</property>
+                <property name="label" translatable="yes">Progress</property>
+                <style>
+                  <class name="dim-label"/>
+                </style>
+              </object>
+              <packing>
+                <property name="left_attach">0</property>
+                <property name="top_attach">4</property>
+                <property name="width">1</property>
+                <property name="height">1</property>
+              </packing>
             </child>
           </object>
           <packing>
@@ -181,13 +220,11 @@
             <property name="can_focus">False</property>
             <property name="layout_style">end</property>
             <child>
-              <object class="GtkButton" id="button1">
+              <object class="GtkButton" id="cancel-button">
                 <property name="label">gtk-cancel</property>
-                <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="receives_default">True</property>
-                <property name="use_action_appearance">False</property>
                 <property name="use_stock">True</property>
               </object>
               <packing>
@@ -197,14 +234,12 @@
               </packing>
             </child>
             <child>
-              <object class="GtkButton" id="start_copying_button">
+              <object class="GtkButton" id="start-copying-button">
                 <property name="label" translatable="yes">_Start Creating...</property>
-                <property name="use_action_appearance">False</property>
                 <property name="visible">True</property>
                 <property name="can_focus">True</property>
                 <property name="can_default">True</property>
                 <property name="receives_default">True</property>
-                <property name="use_action_appearance">False</property>
                 <property name="use_underline">True</property>
               </object>
               <packing>
@@ -213,6 +248,36 @@
                 <property name="position">1</property>
               </packing>
             </child>
+            <child>
+              <object class="GtkButton" id="close-button">
+                <property name="label">gtk-close</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_stock">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">2</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="show-in-folder-button">
+                <property name="label" translatable="yes">_Show in Folder</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">True</property>
+                <property name="use_underline">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">True</property>
+                <property name="position">3</property>
+                <property name="secondary">True</property>
+              </packing>
+            </child>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -224,8 +289,10 @@
       </object>
     </child>
     <action-widgets>
-      <action-widget response="-6">button1</action-widget>
-      <action-widget response="-5">start_copying_button</action-widget>
+      <action-widget response="-6">cancel-button</action-widget>
+      <action-widget response="-5">start-copying-button</action-widget>
+      <action-widget response="-7">close-button</action-widget>
+      <action-widget response="1">show-in-folder-button</action-widget>
     </action-widgets>
   </object>
 </interface>
diff --git a/src/disks/Makefile.am b/src/disks/Makefile.am
index 54854ba..94d7e4b 100644
--- a/src/disks/Makefile.am
+++ b/src/disks/Makefile.am
@@ -69,6 +69,7 @@ gnome_disks_CFLAGS = 					\
 	$(LIBSECRET1_CFLAGS)				\
 	$(LIBSYSTEMD_LOGIN_CFLAGS)			\
 	$(PWQUALITY_CFLAGS)				\
+	$(CANBERRA_CFLAGS)				\
 	$(WARN_CFLAGS)					\
 	-lm						\
 	$(NULL)
@@ -80,6 +81,7 @@ gnome_disks_LDADD = 					\
 	$(LIBSECRET1_LIBS)				\
 	$(LIBSYSTEMD_LOGIN_LIBS)			\
 	$(PWQUALITY_LIBS)				\
+	$(CANBERRA_LIBS)				\
         $(top_builddir)/src/libgdu/libgdu.la        	\
 	$(NULL)
 
diff --git a/src/disks/gducreatediskimagedialog.c b/src/disks/gducreatediskimagedialog.c
index c338271..b0badad 100644
--- a/src/disks/gducreatediskimagedialog.c
+++ b/src/disks/gducreatediskimagedialog.c
@@ -18,6 +18,8 @@
 #include <sys/ioctl.h>
 #include <linux/fs.h>
 
+#include <canberra-gtk.h>
+
 #include "gduapplication.h"
 #include "gduwindow.h"
 #include "gducreatediskimagedialog.h"
@@ -38,9 +40,6 @@
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-/* TODO: make dynamic? */
-#define BUFFER_SIZE (1*1024*1024)
-
 typedef struct
 {
   volatile gint ref_count;
@@ -53,45 +52,78 @@ typedef struct
   GtkBuilder *builder;
   GtkWidget *dialog;
 
-  GtkWidget *notebook;
-  GtkWidget *start_copying_button;
-  GtkWidget *destination_name_entry;
-  GtkWidget *destination_name_fcbutton;
+  GtkWidget *source_label;
+  GtkWidget *name_label;
+  GtkWidget *name_entry;
+  GtkWidget *folder_label;
+  GtkWidget *folder_fcbutton;
+  GtkWidget *destination_key_label;
+  GtkWidget *destination_label;
 
-  GtkWidget *copying_label;
+  GtkWidget *copying_key_label;
+  GtkWidget *copying_vbox;
   GtkWidget *copying_progressbar;
-  GtkWidget *copying_progress_label;
+  GtkWidget *copying_label;
+
+  GtkWidget *start_copying_button;
+  GtkWidget *cancel_button;
+  GtkWidget *close_button;
+  GtkWidget *show_in_folder_button;
 
   GCancellable *cancellable;
-  int fd;
-  GInputStream *block_stream;
   GFile *output_file;
   GFileOutputStream *output_file_stream;
-  guint64 block_size;
-  gboolean delete_on_free;
-
-  guchar *buffer;
-  guint64 total_bytes_read;
-  guint64 buffer_bytes_written;
-  guint64 buffer_bytes_to_write;
 
+  /* must hold copy_lock when reading/writing these */
+  GMutex copy_lock;
   GduEstimator *estimator;
 
+  guint64 num_error_bytes;
+  gint64 start_time_usec;
+  gint64 end_time_usec;
+
+  guint update_id;
+  GError *copy_error;
+
   gulong response_signal_handler_id;
   gboolean completed;
-} CreateDiskImageData;
+} DialogData;
+
+static const struct {
+  goffset offset;
+  const gchar *name;
+} widget_mapping[] = {
+  {G_STRUCT_OFFSET (DialogData, source_label), "source-label"},
+  {G_STRUCT_OFFSET (DialogData, name_label), "name-label"},
+  {G_STRUCT_OFFSET (DialogData, name_entry), "name-entry"},
+  {G_STRUCT_OFFSET (DialogData, folder_label), "folder-label"},
+  {G_STRUCT_OFFSET (DialogData, folder_fcbutton), "folder-fcbutton"},
+  {G_STRUCT_OFFSET (DialogData, destination_key_label), "destination-key-label"},
+  {G_STRUCT_OFFSET (DialogData, destination_label), "destination-label"},
+
+  {G_STRUCT_OFFSET (DialogData, copying_key_label), "copying-key-label"},
+  {G_STRUCT_OFFSET (DialogData, copying_vbox), "copying-vbox"},
+  {G_STRUCT_OFFSET (DialogData, copying_progressbar), "copying-progressbar"},
+  {G_STRUCT_OFFSET (DialogData, copying_label), "copying-label"},
+
+  {G_STRUCT_OFFSET (DialogData, start_copying_button), "start-copying-button"},
+  {G_STRUCT_OFFSET (DialogData, cancel_button), "cancel-button"},
+  {G_STRUCT_OFFSET (DialogData, close_button), "close-button"},
+  {G_STRUCT_OFFSET (DialogData, show_in_folder_button), "show-in-folder-button"},
+  {0, NULL}
+};
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static CreateDiskImageData *
-create_disk_image_data_ref (CreateDiskImageData *data)
+static DialogData *
+dialog_data_ref (DialogData *data)
 {
   g_atomic_int_inc (&data->ref_count);
   return data;
 }
 
 static void
-create_disk_image_data_unref (CreateDiskImageData *data)
+dialog_data_unref (DialogData *data)
 {
   if (g_atomic_int_dec_and_test (&data->ref_count))
     {
@@ -108,50 +140,54 @@ create_disk_image_data_unref (CreateDiskImageData *data)
         }
       g_clear_object (&data->cancellable);
       g_clear_object (&data->output_file_stream);
-      g_clear_object (&data->block_stream);
-      if (data->delete_on_free)
-        {
-          GError *error = NULL;
-          if (!g_file_delete (data->output_file, NULL, &error))
-            {
-              g_warning ("Error deleting file: %s (%s, %d)",
-                         error->message, g_quark_to_string (error->domain), error->code);
-              g_error_free (error);
-            }
-        }
       g_object_unref (data->window);
       g_object_unref (data->object);
       g_object_unref (data->block);
       g_clear_object (&data->drive);
       if (data->builder != NULL)
         g_object_unref (data->builder);
-      g_free (data->buffer);
       g_clear_object (&data->estimator);
+      g_mutex_clear (&data->copy_lock);
       g_free (data);
     }
 }
 
+static gboolean
+unref_in_idle (gpointer user_data)
+{
+  DialogData *data = user_data;
+  dialog_data_unref (data);
+  return FALSE; /* remove source */
+}
+
+static void
+dialog_data_unref_in_idle (DialogData *data)
+{
+  g_idle_add (unref_in_idle, data);
+}
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-create_disk_image_data_complete (CreateDiskImageData *data)
+dialog_data_complete_and_unref (DialogData *data)
 {
   if (!data->completed)
     {
       data->completed = TRUE;
       g_cancellable_cancel (data->cancellable);
-      create_disk_image_data_unref (data);
+      gtk_widget_hide (data->dialog);
     }
+  dialog_data_unref (data);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-create_disk_image_update (CreateDiskImageData *data)
+create_disk_image_update (DialogData *data)
 {
   gboolean can_proceed = FALSE;
 
-  if (strlen (gtk_entry_get_text (GTK_ENTRY (data->destination_name_entry))) > 0)
+  if (strlen (gtk_entry_get_text (GTK_ENTRY (data->name_entry))) > 0)
     can_proceed = TRUE;
 
   gtk_dialog_set_response_sensitive (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK, can_proceed);
@@ -162,7 +198,7 @@ on_notify (GObject     *object,
            GParamSpec  *pspec,
            gpointer     user_data)
 {
-  CreateDiskImageData *data = user_data;
+  DialogData *data = user_data;
   create_disk_image_update (data);
 }
 
@@ -170,8 +206,9 @@ on_notify (GObject     *object,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-create_disk_image_populate (CreateDiskImageData *data)
+create_disk_image_populate (DialogData *data)
 {
+  UDisksObjectInfo *info = NULL;
   gchar *device_name;
   gchar *now_string;
   gchar *proposed_filename = NULL;
@@ -214,67 +251,108 @@ create_disk_image_populate (CreateDiskImageData *data)
                                            now_string);
     }
 
-  gtk_entry_set_text (GTK_ENTRY (data->destination_name_entry), proposed_filename);
+  gtk_entry_set_text (GTK_ENTRY (data->name_entry), proposed_filename);
   g_free (proposed_filename);
   g_free (device_name);
   g_date_time_unref (now);
   g_time_zone_unref (tz);
   g_free (now_string);
 
-  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (data->destination_name_fcbutton), FALSE);
+  gdu_utils_configure_file_chooser_for_disk_images (GTK_FILE_CHOOSER (data->folder_fcbutton), FALSE);
+
+  /* Source label */
+  info = udisks_client_get_object_info (gdu_window_get_client (data->window), data->object);
+  gtk_label_set_text (GTK_LABEL (data->source_label), info->one_liner);
+  if (info != NULL)
+    udisks_object_info_unref (info);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static void copy_more (CreateDiskImageData *data);
+static gchar *
+get_pretty_uri (GFile *file)
+{
+  gchar *ret;
+  gchar *s;
 
-static void write_more (CreateDiskImageData *data);
+  if (g_file_is_native (file))
+    {
+      const gchar *homedir;
+
+      ret = g_file_get_path (file);
+
+      homedir = g_get_home_dir ();
+      if (g_str_has_prefix (ret, homedir))
+        {
+          s = ret;
+          ret = g_strdup_printf ("~/%s", ret + strlen (homedir) + 1);
+          g_free (s);
+        }
+    }
+  else
+    {
+      s = g_file_get_uri (file);
+      ret = g_uri_unescape_string (s, NULL);
+      g_free (s);
+    }
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
 
 static void
-write_cb (GOutputStream  *output_stream,
-          GAsyncResult  *res,
-          gpointer       user_data)
+update_gui (DialogData *data,
+            gboolean    done)
 {
-  CreateDiskImageData *data = user_data;
-  GError *error;
-  gssize bytes_written;
-  guint64 bytes_per_sec;
-  guint64 usec_remaining;
+  guint64 bytes_completed = 0;
+  guint64 bytes_target = 1;
+  guint64 bytes_per_sec = 0;
+  guint64 usec_remaining = 1;
+  guint64 num_error_bytes = 0;
   gchar *s, *s2, *s3, *s4, *s5;
+  gdouble progress;
 
-  error = NULL;
-  bytes_written = g_output_stream_write_finish (output_stream, res, &error);
-  if (error != NULL)
+  g_mutex_lock (&data->copy_lock);
+  if (data->estimator != NULL)
     {
-      if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
-        gdu_utils_show_error (GTK_WINDOW (data->dialog),
-                              _("Error writing to image"), error);
-      g_error_free (error);
-      create_disk_image_data_complete (data);
-      goto out;
+      bytes_per_sec = gdu_estimator_get_bytes_per_sec (data->estimator);
+      usec_remaining = gdu_estimator_get_usec_remaining (data->estimator);
+      bytes_completed = gdu_estimator_get_completed_bytes (data->estimator);
+      bytes_target = gdu_estimator_get_target_bytes (data->estimator);
+      num_error_bytes = data->num_error_bytes;
     }
+  data->update_id = 0;
+  g_mutex_unlock (&data->copy_lock);
 
-  data->buffer_bytes_written += bytes_written;
-  data->buffer_bytes_to_write -= bytes_written;
-
-  /* update progress bar and estimator */
-  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (data->copying_progressbar),
-                                 ((gdouble) data->total_bytes_read) / ((gdouble) data->block_size));
-
-  gdu_estimator_add_sample (data->estimator, data->total_bytes_read);
-  bytes_per_sec = gdu_estimator_get_bytes_per_sec (data->estimator);
-  usec_remaining = gdu_estimator_get_usec_remaining (data->estimator);
-  if (bytes_per_sec > 0 && usec_remaining > 0)
+  if (done)
+    {
+      gint64 duration_usec = data->end_time_usec - data->start_time_usec;
+      s2 = g_format_size (bytes_target);
+      s3 = gdu_utils_format_duration_usec (duration_usec, GDU_FORMAT_DURATION_FLAGS_SUBSECOND_PRECISION);
+      s4 = g_format_size (G_USEC_PER_SEC * bytes_target / duration_usec);
+      /* Translators: string used for conveying how long the copy took.
+       *              The first %s is the amount of bytes copied (ex. "650 MB").
+       *              The second %s is the time it took to copy (ex. "1 minute", or "Less than a minute").
+       *              The third %s is the average amount of bytes transfered per second (ex. "8.9 MB").
+       */
+      s = g_strdup_printf (_("%s copied in %s (%s/sec)"), s2, s3, s4);
+      g_free (s4);
+      g_free (s3);
+      g_free (s2);
+    }
+  else if (bytes_per_sec > 0 && usec_remaining > 0)
     {
-      s2 = g_format_size (data->total_bytes_read);
-      s3 = g_format_size (data->block_size);
+      s2 = g_format_size (bytes_completed);
+      s3 = g_format_size (bytes_target);
       s4 = gdu_utils_format_duration_usec (usec_remaining,
                                            GDU_FORMAT_DURATION_FLAGS_NO_SECONDS);
       s5 = g_format_size (bytes_per_sec);
-      /* Translators: string used for conveying progress of copy operation.
-       * The first two %s are strings with the amount of bytes (ex. "3.4 MB" and "300 MB").
-       * The third %s is the estimated amount of time remaining (ex. "1 minute", "5 minutes" or "Less than a minute").
-       * The fourth %s is the average amount of bytes transfered per second (ex. "8.9 MB").
+      /* Translators: string used for conveying progress of copy operation when there are no errors.
+       *              The first %s is the amount of bytes copied (ex. "650 MB").
+       *              The second %s is the size of the device (ex. "8.5 GB").
+       *              The third %s is the estimated amount of time remaining (ex. "1 minute" or "5 minutes").
+       *              The fourth %s is the average amount of bytes transfered per second (ex. "8.9 MB").
        */
       s = g_strdup_printf (_("%s of %s copied â %s remaining (%s/sec)"), s2, s3, s4, s5);
       g_free (s5);
@@ -284,8 +362,8 @@ write_cb (GOutputStream  *output_stream,
     }
   else
     {
-      s2 = g_format_size (data->total_bytes_read);
-      s3 = g_format_size (data->block_size);
+      s2 = g_format_size (bytes_completed);
+      s3 = g_format_size (bytes_target);
       /* Translators: string used for convey progress of a copy operation where we don't know time remaining / speed.
        * The first two %s are strings with the amount of bytes (ex. "3.4 MB" and "300 MB").
        */
@@ -293,171 +371,387 @@ write_cb (GOutputStream  *output_stream,
       g_free (s2);
       g_free (s3);
     }
+
+  if (num_error_bytes > 0)
+    {
+      s2 = g_format_size (num_error_bytes);
+      /* Translators: Shown when there are read errors and we skip some data.
+       *              The first %s is the amount of unreadable data (ex. "512 kB").
+       */
+      s3 = g_strdup_printf (_("%s replaced with zeroes because of read errors"), s2);
+      /* TODO: once https://bugzilla.gnome.org/show_bug.cgi?id=657194 is resolved, use that instead
+       * of hard-coding the color
+       */
+      s4 = g_strdup_printf ("%s\n<span foreground=\"#ff0000\">%s</span>", s, s3);
+      g_free (s3);
+      g_free (s2);
+      g_free (s);
+      s = s4;
+    }
+
   s2 = g_strconcat ("<small>", s, "</small>", NULL);
-  gtk_label_set_markup (GTK_LABEL (data->copying_progress_label), s2);
+  gtk_label_set_markup (GTK_LABEL (data->copying_label), s2);
   g_free (s);
 
-  write_more (data);
-
- out:
-  create_disk_image_data_unref (data);
+  if (done)
+    progress = 1.0;
+  else
+    progress = ((gdouble) bytes_completed) / ((gdouble) bytes_target);
+  gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (data->copying_progressbar), progress);
 }
 
-static void
-write_more (CreateDiskImageData *data)
+static gboolean
+on_update_ui (gpointer user_data)
 {
-  if (data->buffer_bytes_to_write == 0)
-    {
-      copy_more (data);
-    }
-  else
-    {
-      g_output_stream_write_async (G_OUTPUT_STREAM (data->output_file_stream),
-                                   data->buffer + data->buffer_bytes_written,
-                                   data->buffer_bytes_to_write,
-                                   G_PRIORITY_DEFAULT,
-                                   data->cancellable,
-                                   (GAsyncReadyCallback) write_cb,
-                                   create_disk_image_data_ref (data));
-    }
+  DialogData *data = user_data;
+  update_gui (data, FALSE);
+  dialog_data_unref (data);
+  return FALSE; /* remove source */
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static void
-read_cb (GInputStream  *input_stream,
-         GAsyncResult  *res,
-         gpointer       user_data)
+static gboolean
+on_show_error (gpointer user_data)
 {
-  CreateDiskImageData *data = user_data;
-  GError *error;
-  gssize bytes_read;
+  DialogData *data = user_data;
 
-  error = NULL;
-  bytes_read = g_input_stream_read_finish (input_stream, res, &error);
-  if (error != NULL)
-    {
-      gchar *s;
-      s = g_strdup_printf (_("Error reading from offset %" G_GUINT64_FORMAT " of device %s"),
-                           (guint64) data->total_bytes_read,
-                           udisks_block_get_preferred_device (data->block));
-      if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
-        gdu_utils_show_error (GTK_WINDOW (data->dialog), s, error);
-      g_free (s);
-      g_error_free (error);
-      create_disk_image_data_complete (data);
-      goto out;
-    }
+  g_assert (data->copy_error != NULL);
+  gdu_utils_show_error (GTK_WINDOW (data->dialog),
+                        _("Error creating disk image"),
+                        data->copy_error);
+  g_clear_error (&data->copy_error);
 
-  /* TODO: handle zero bytes being read */
+  dialog_data_complete_and_unref (data);
 
-  data->total_bytes_read += bytes_read;
+  dialog_data_unref (data);
+  return FALSE; /* remove source */
+}
 
-  data->buffer_bytes_written = 0;
-  data->buffer_bytes_to_write = bytes_read;
-  write_more (data);
+/* ---------------------------------------------------------------------------------------------------- */
 
- out:
-  create_disk_image_data_unref (data);
+static gboolean
+on_success (gpointer user_data)
+{
+  DialogData *data = user_data;
+  const gchar *sound_message;
+
+  update_gui (data, TRUE);
+
+  gtk_widget_hide (data->cancel_button);
+  gtk_widget_show (data->close_button);
+  gtk_widget_show (data->show_in_folder_button);
+
+  /* Translators: A descriptive string for the 'complete' sound, see CA_PROP_EVENT_DESCRIPTION */
+  sound_message = _("Disk image creation complete");
+  ca_gtk_play_for_widget (GTK_WIDGET (data->dialog), 0,
+                          CA_PROP_EVENT_ID, "complete",
+                          CA_PROP_EVENT_DESCRIPTION, sound_message,
+                          NULL);
+
+  dialog_data_unref (data);
+  return FALSE; /* remove source */
 }
 
-static void
-copy_more (CreateDiskImageData *data)
+/* ---------------------------------------------------------------------------------------------------- */
+
+/* Note that error on reading is *not* considered an error - instead 0
+ * is returned.
+ *
+ * Error conditions include failure to seek or write to output.
+ *
+ * Returns: Number of bytes actually read (e.g. not include padding) -1 if @error is set.
+ */
+static gssize
+copy_span (int              fd,
+           GOutputStream   *output_stream,
+           guint64          offset,
+           guint64          size,
+           guchar          *buffer,
+           gboolean         pad_with_zeroes,
+           GCancellable    *cancellable,
+           GError         **error)
 {
-  guint64 bytes_to_read;
+  gint64 ret = -1;
+  ssize_t num_bytes_read;
+  gsize num_bytes_to_write;
+
+  g_return_val_if_fail (-1, buffer != NULL);
+  g_return_val_if_fail (-1, G_IS_OUTPUT_STREAM (output_stream));
+  g_return_val_if_fail (-1, buffer != NULL);
+  g_return_val_if_fail (-1, cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+  g_return_val_if_fail (-1, error == NULL || *error == NULL);
+
+  if (lseek (fd, offset, SEEK_SET) == (off_t) -1)
+    {
+      g_set_error (error,
+                   G_IO_ERROR, g_io_error_from_errno (errno),
+                   "Error seeking to offset %" G_GUINT64_FORMAT ": %s",
+                   offset, strerror (errno));
+      goto out;
+    }
+
+ copy_read_again:
+  num_bytes_read = read (fd, buffer, size);
+  if (num_bytes_read < 0)
+    {
+      if (errno == EAGAIN || errno == EINTR)
+        goto copy_read_again;
+      /* do not consider this an error - treat as zero bytes read */
+      num_bytes_read = 0;
+    }
+  else
+    {
+      /* EOF */
+      if (num_bytes_read == 0)
+        {
+          g_set_error (error,
+                       G_IO_ERROR, G_IO_ERROR_FAILED,
+                       "Reading from offset %" G_GUINT64_FORMAT " returned zero bytes",
+                       offset);
+          goto out;
+        }
+    }
+
+  num_bytes_to_write = num_bytes_read;
+  if (pad_with_zeroes && (guint64) num_bytes_read < size)
+    {
+      memset (buffer + num_bytes_read, 0, size - num_bytes_read);
+      num_bytes_to_write = size;
+    }
+
+  if (!g_seekable_seek (G_SEEKABLE (output_stream),
+                        offset,
+                        G_SEEK_SET,
+                        cancellable,
+                        error))
+    {
+      g_prefix_error (error,
+                      "Error seeking to offset %" G_GUINT64_FORMAT ": ",
+                      offset);
+      goto out;
+    }
 
-  bytes_to_read = data->block_size - data->total_bytes_read;
-  if (bytes_to_read == 0)
+  if (!g_output_stream_write_all (G_OUTPUT_STREAM (output_stream),
+                                  buffer,
+                                  num_bytes_to_write,
+                                  G_PRIORITY_DEFAULT,
+                                  cancellable,
+                                  error))
     {
-      data->delete_on_free = FALSE;
-      create_disk_image_data_complete (data);
+      g_prefix_error (error,
+                      "Error writing %" G_GUINT64_FORMAT " bytes to offset %" G_GUINT64_FORMAT ": ",
+                      num_bytes_to_write,
+                      offset);
       goto out;
     }
-  if (bytes_to_read > BUFFER_SIZE)
-    bytes_to_read = BUFFER_SIZE;
-
-  g_input_stream_read_async (data->block_stream,
-                             data->buffer,
-                             bytes_to_read,
-                             G_PRIORITY_DEFAULT,
-                             data->cancellable,
-                             (GAsyncReadyCallback) read_cb,
-                             create_disk_image_data_ref (data));
+
+  ret = num_bytes_read;
+
  out:
-  ;
+
+  return ret;
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-static void
-start_copying (CreateDiskImageData *data)
+static gpointer
+copy_thread_func (gpointer user_data)
 {
-  g_assert (data->fd != 0);
+  DialogData *data = user_data;
+  guchar *buffer_unaligned = NULL;
+  guchar *buffer = NULL;
+  guint64 block_device_size = 0;
+  long page_size;
+  GError *error = NULL;
+  GError *error2 = NULL;
+  gint64 last_update_usec = -1;
+  gint fd = -1;
+  gint buffer_size;
+  guint64 num_bytes_completed = 0;
+
+  /* default to 1 MiB blocks */
+  buffer_size = (1 * 1024 * 1024);
+
+  /* Most OSes put ACLs for logged-in users on /dev/sr* nodes (this is
+   * so CD burning tools etc. work) so see if we can open the device
+   * file ourselves. If so, great, since this avoids a polkit dialog.
+   *
+   * As opposed to udisks' OpenForBackup() we also avoid O_EXCL since
+   * the disc is read-only by its very nature. As a side-effect this
+   * allows creating a disk image of a mounted disc.
+   */
+  if (g_str_has_prefix (udisks_block_get_device (data->block), "/dev/sr"))
+    {
+      fd = open (udisks_block_get_device (data->block), O_RDONLY);
+    }
+
+  /* Otherwise, request the fd from udisks */
+  if (fd == -1)
+    {
+      GUnixFDList *fd_list = NULL;
+      GVariant *fd_index = NULL;
+      if (!udisks_block_call_open_for_backup_sync (data->block,
+                                                   g_variant_new ("a{sv}", NULL), /* options */
+                                                   NULL, /* fd_list */
+                                                   &fd_index,
+                                                   &fd_list,
+                                                   NULL, /* cancellable */
+                                                   &error))
+        goto out;
+
+      fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_index), &error);
+      if (error != NULL)
+        {
+          g_prefix_error (&error,
+                          "Error extracing fd with handle %d from D-Bus message: ",
+                          g_variant_get_handle (fd_index));
+          goto out;
+        }
+      if (fd_index != NULL)
+        g_variant_unref (fd_index);
+      g_clear_object (&fd_list);
+    }
+
+  g_assert (fd != -1);
 
   /* We can't use udisks_block_get_size() because the media may have
    * changed and udisks may not have noticed. TODO: maybe have a
    * Block.GetSize() method instead...
    */
-  if (ioctl (data->fd, BLKGETSIZE64, &data->block_size) != 0)
+  if (ioctl (fd, BLKGETSIZE64, &block_device_size) != 0)
     {
-      GError *error = g_error_new (G_IO_ERROR, g_io_error_from_errno (errno), "%s", strerror (errno));
-      gdu_utils_show_error (GTK_WINDOW (data->dialog), _("Error determining size of device"), error);
-      g_error_free (error);
-      create_disk_image_data_complete (data);
+      error = g_error_new (G_IO_ERROR, g_io_error_from_errno (errno),
+                           "%s", strerror (errno));
+      g_prefix_error (&error, _("Error determining size of device: "));
       goto out;
     }
 
-  /* now that we know the user picked a folder, update file chooser settings */
-  gdu_utils_file_chooser_for_disk_images_update_settings (GTK_FILE_CHOOSER (data->destination_name_fcbutton));
+  if (block_device_size == 0)
+    {
+      error = g_error_new (G_IO_ERROR, G_IO_ERROR_FAILED,
+                           _("Device is size 0"));
+      goto out;
+    }
 
-  data->cancellable = g_cancellable_new ();
-  data->buffer = g_new0 (guchar, BUFFER_SIZE);
-  data->estimator = gdu_estimator_new (data->block_size);
-  data->block_stream = g_unix_input_stream_new (data->fd, TRUE);
+  page_size = sysconf (_SC_PAGESIZE);
+  buffer_unaligned = g_new0 (guchar, buffer_size + page_size);
+  buffer = (guchar*) (((gintptr) (buffer_unaligned + page_size)) & (~(page_size - 1)));
+
+  g_mutex_lock (&data->copy_lock);
+  data->estimator = gdu_estimator_new (block_device_size);
+  data->update_id = 0;
+  data->num_error_bytes = 0;
+  data->start_time_usec = g_get_real_time ();
+  g_mutex_unlock (&data->copy_lock);
+
+  /* Read huge (e.g. 1 MiB) blocks and write it to the output
+   * file even if it was only partially read.
+   */
+  num_bytes_completed = 0;
+  while (num_bytes_completed < block_device_size)
+    {
+      gssize num_bytes_to_read;
+      gssize num_bytes_read;
+      gint64 now_usec;
+
+      num_bytes_to_read = buffer_size;
+      if (num_bytes_to_read + num_bytes_completed > block_device_size)
+        num_bytes_to_read = block_device_size - num_bytes_completed;
+
+      /* Update GUI - but only every 200 ms and only if last update isn't pending */
+      g_mutex_lock (&data->copy_lock);
+      now_usec = g_get_monotonic_time ();
+      if (now_usec - last_update_usec > 200 * G_USEC_PER_SEC / 1000 || last_update_usec < 0)
+        {
+          if (num_bytes_completed > 0)
+            gdu_estimator_add_sample (data->estimator, num_bytes_completed);
+          if (data->update_id == 0)
+            data->update_id = g_idle_add (on_update_ui, dialog_data_ref (data));
+          last_update_usec = now_usec;
+        }
+      g_mutex_unlock (&data->copy_lock);
+
+      num_bytes_read = copy_span (fd,
+                                  G_OUTPUT_STREAM (data->output_file_stream),
+                                  num_bytes_completed,
+                                  num_bytes_to_read,
+                                  buffer,
+                                  TRUE, /* pad_with_zeroes */
+                                  data->cancellable,
+                                  &error);
+      if (num_bytes_read < 0)
+        goto out;
+
+      /*g_print ("read %" G_GUINT64_FORMAT " bytes (requested %" G_GUINT64_FORMAT ") from offset %" G_GUINT64_FORMAT "\n",
+               num_bytes_read,
+               num_bytes_to_read,
+               num_bytes_completed);*/
+
+      if (num_bytes_read < num_bytes_to_read)
+        {
+          guint64 num_bytes_skipped = num_bytes_to_read - num_bytes_read;
+          g_mutex_lock (&data->copy_lock);
+          data->num_error_bytes += num_bytes_skipped;
+          g_mutex_unlock (&data->copy_lock);
+        }
+      num_bytes_completed += num_bytes_to_read;
+    }
 
-  /* Alright, time to start copying! */
-  copy_more (data);
  out:
-  ;
-}
+  data->end_time_usec = g_get_real_time ();
 
-static void
-open_cb (UDisksBlock  *block,
-         GAsyncResult *res,
-         gpointer      user_data)
-{
-  CreateDiskImageData *data = user_data;
-  GError *error;
-  GUnixFDList *fd_list = NULL;
-  GVariant *fd_index = NULL;
+  /* in either case, close the stream */
+  if (!g_output_stream_close (G_OUTPUT_STREAM (data->output_file_stream),
+                              NULL, /* cancellable */
+                              &error2))
+    {
+      g_warning ("Error closing file output stream: %s (%s, %d)",
+                 error2->message, g_quark_to_string (error->domain), error->code);
+      g_clear_error (&error2);
+    }
+  g_clear_object (&data->output_file_stream);
 
-  error = NULL;
-  if (!udisks_block_call_open_for_backup_finish (block,
-                                                 &fd_index,
-                                                 &fd_list,
-                                                 res,
-                                                 &error))
+  if (error != NULL)
     {
+      /* show error in GUI */
       if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
-        gdu_utils_show_error (GTK_WINDOW (data->dialog), _("Error opening device"), error);
-      g_error_free (error);
-      create_disk_image_data_complete (data);
-      goto out;
-    }
+        {
+          data->copy_error = error; error = NULL;
+          g_idle_add (on_show_error, dialog_data_ref (data));
+        }
+      g_clear_error (&error);
 
-  data->fd = g_unix_fd_list_get (fd_list, g_variant_get_handle (fd_index), NULL);
+      /* Cleanup */
+      if (!g_file_delete (data->output_file, NULL, &error))
+        {
+          g_warning ("Error deleting file: %s (%s, %d)",
+                     error->message, g_quark_to_string (error->domain), error->code);
+          g_clear_error (&error);
+        }
+    }
+  else
+    {
+      /* success */
+      g_idle_add (on_success, dialog_data_ref (data));
+    }
+  if (fd != -1 )
+    {
+      if (close (fd) != 0)
+        g_warning ("Error closing fd: %m");
+    }
 
-  start_copying (data);
+  g_free (buffer_unaligned);
 
- out:
-  if (fd_index != NULL)
-    g_variant_unref (fd_index);
-  g_clear_object (&fd_list);
+  dialog_data_unref_in_idle (data); /* unref on main thread */
+  return NULL;
 }
 
+/* ---------------------------------------------------------------------------------------------------- */
+
 /* returns TRUE if OK to overwrite or file doesn't exist */
 static gboolean
-check_overwrite (CreateDiskImageData *data)
+check_overwrite (DialogData *data)
 {
   GFile *folder = NULL;
   const gchar *name;
@@ -467,8 +761,8 @@ check_overwrite (CreateDiskImageData *data)
   GtkWidget *dialog;
   gint response;
 
-  name = gtk_entry_get_text (GTK_ENTRY (data->destination_name_entry));
-  folder = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->destination_name_fcbutton));
+  name = gtk_entry_get_text (GTK_ENTRY (data->name_entry));
+  folder = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->folder_fcbutton));
   file = g_file_get_child (folder, name);
   if (!g_file_query_exists (file, NULL))
     goto out;
@@ -513,18 +807,18 @@ check_overwrite (CreateDiskImageData *data)
 }
 
 static gboolean
-open_device (CreateDiskImageData *data)
+open_device (DialogData *data)
 {
   gboolean ret = TRUE;
   const gchar *name;
   GFile *folder;
   GError *error;
+  gchar *uri = NULL;
 
-  name = gtk_entry_get_text (GTK_ENTRY (data->destination_name_entry));
-  folder = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->destination_name_fcbutton));
+  name = gtk_entry_get_text (GTK_ENTRY (data->name_entry));
+  folder = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->folder_fcbutton));
 
   error = NULL;
-  data->fd = -1;
   data->output_file = g_file_get_child (folder, name);
   data->output_file_stream = g_file_replace (data->output_file,
                                              NULL, /* etag */
@@ -534,70 +828,119 @@ open_device (CreateDiskImageData *data)
                                              &error);
   if (data->output_file_stream == NULL)
     {
-      if (!(error->domain == G_IO_ERROR && error->code == G_IO_ERROR_CANCELLED))
-        gdu_utils_show_error (GTK_WINDOW (data->dialog), _("Error opening file for writing"), error);
-      g_error_free (error);
+      gdu_utils_show_error (GTK_WINDOW (data->dialog), _("Error opening file for writing"), error);
+      g_clear_error (&error);
       g_object_unref (folder);
-      create_disk_image_data_complete (data);
+      dialog_data_complete_and_unref (data);
       ret = FALSE;
       goto out;
     }
-  data->delete_on_free = TRUE;
 
-  /* Most OSes put ACLs for logged-in users on /dev/sr* nodes (this is
-   * so CD burning tools etc. works) so see if we can open the device
-   * file ourselves. If so, great, since this avoids a polkit dialog.
-   *
-   * As opposed to udisks' OpenForBackup() we also avoid O_EXCL since
-   * the disc is read-only by its very nature. As a side-effect this
-   * allows creating a disk image of a mounted disc.
-   */
-  if (g_str_has_prefix (udisks_block_get_device (data->block), "/dev/sr"))
-    {
-      data->fd = open (udisks_block_get_device (data->block), O_RDONLY);
-    }
+  uri = get_pretty_uri (data->output_file);
+  gtk_label_set_text (GTK_LABEL (data->destination_label), uri);
 
-  if (data->fd != -1)
-    {
-      start_copying (data);
-    }
-  else
-    {
-      udisks_block_call_open_for_backup (data->block,
-                                         g_variant_new ("a{sv}", NULL), /* options */
-                                         NULL, /* fd_list */
-                                         NULL, /* cancellable */
-                                         (GAsyncReadyCallback) open_cb,
-                                         data);
-    }
+  /* now that we know the user picked a folder, update file chooser settings */
+  gdu_utils_file_chooser_for_disk_images_update_settings (GTK_FILE_CHOOSER (data->folder_fcbutton));
+
+  g_thread_new ("copy-disk-image-thread",
+                copy_thread_func,
+                dialog_data_ref (data));
 
  out:
   g_clear_object (&folder);
+  g_free (uri);
   return ret;
 }
 
 static void
+show_items_cb (GObject       *source_object,
+               GAsyncResult  *res,
+               gpointer       user_data)
+{
+  DialogData *data = user_data;
+  GError *error;
+  error = NULL;
+  if (!g_dbus_connection_call_finish (G_DBUS_CONNECTION (source_object),
+                                      res,
+                                      &error))
+    {
+      gdu_utils_show_error (GTK_WINDOW (data->dialog), _("Error showing item"), error);
+      g_error_free (error);
+    }
+  dialog_data_unref (data);
+}
+
+/* http://www.freedesktop.org/wiki/Specifications/file-manager-interface */
+static void
+show_in_folder (DialogData *data)
+{
+  gchar *uris[2] = {NULL, NULL};
+  GDBusConnection *session_bus;
+  const gchar *startup_id;
+
+  /* TODO: figure out how to set this */
+  startup_id = "";
+
+  uris[0] = g_file_get_uri (data->output_file);
+
+  session_bus = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, NULL);
+  g_dbus_connection_call (session_bus,
+                          "org.freedesktop.FileManager1",
+                          "/org/freedesktop/FileManager1",
+                          "org.freedesktop.FileManager1",
+                          "ShowItems",
+                          g_variant_new ("(^ass)", uris, startup_id),
+                          NULL, /* reply-type */
+                          G_DBUS_CALL_FLAGS_NONE,
+                          -1, /* timeout */
+                          NULL,
+                          show_items_cb,
+                          dialog_data_ref (data));
+  g_clear_object (&session_bus);
+  g_free (uris[0]);
+}
+
+
+
+static void
 on_dialog_response (GtkDialog     *dialog,
                     gint           response,
                     gpointer       user_data)
 {
-  CreateDiskImageData *data = user_data;
-  if (response == GTK_RESPONSE_OK)
+  DialogData *data = user_data;
+
+  switch (response)
     {
+    case GTK_RESPONSE_OK:
       if (check_overwrite (data))
         {
-          gtk_label_set_markup (GTK_LABEL (data->copying_label), _("Copying data from device..."));
+          /* Hide name, entry widgets, "Start Creating" button and show destination, progress widgets */
+          gtk_widget_hide (data->name_label);
+          gtk_widget_hide (data->name_entry);
+          gtk_widget_hide (data->folder_label);
+          gtk_widget_hide (data->folder_fcbutton);
+          gtk_widget_show (data->destination_key_label);
+          gtk_widget_show (data->destination_label);
+          gtk_widget_show (data->copying_key_label);
+          gtk_widget_show (data->copying_vbox);
 
-          /* Advance to the progress page */
-          gtk_notebook_set_current_page (GTK_NOTEBOOK (data->notebook), 1);
           gtk_widget_hide (data->start_copying_button);
 
           open_device (data);
         }
-    }
-  else
-    {
-      create_disk_image_data_complete (data);
+      break;
+
+    case GTK_RESPONSE_CANCEL:
+      dialog_data_complete_and_unref (data);
+      break;
+
+    case GTK_RESPONSE_CLOSE:
+      dialog_data_complete_and_unref (data);
+      break;
+
+    case 1: /* show_in_folder */
+      show_in_folder (data);
+      break;
     }
 }
 
@@ -605,49 +948,50 @@ void
 gdu_create_disk_image_dialog_show (GduWindow    *window,
                                    UDisksObject *object)
 {
-  CreateDiskImageData *data;
-  gchar *s;
+  DialogData *data;
+  guint n;
 
-  data = g_new0 (CreateDiskImageData, 1);
-  data->fd = -1;
+  data = g_new0 (DialogData, 1);
   data->ref_count = 1;
+  g_mutex_init (&data->copy_lock);
   data->window = g_object_ref (window);
   data->object = g_object_ref (object);
   data->block = udisks_object_get_block (object);
   g_assert (data->block != NULL);
   data->drive = udisks_client_get_drive_for_block (gdu_window_get_client (window), data->block);
+  data->cancellable = g_cancellable_new ();
 
   data->dialog = GTK_WIDGET (gdu_application_new_widget (gdu_window_get_application (window),
                                                          "create-disk-image-dialog.ui",
                                                          "create-disk-image-dialog",
                                                          &data->builder));
-  data->notebook = GTK_WIDGET (gtk_builder_get_object (data->builder, "notebook"));
-  data->start_copying_button = GTK_WIDGET (gtk_builder_get_object (data->builder, "start_copying_button"));
-  data->destination_name_entry = GTK_WIDGET (gtk_builder_get_object (data->builder, "destination_name_entry"));
-  g_signal_connect (data->destination_name_entry, "notify::text", G_CALLBACK (on_notify), data);
-  data->destination_name_fcbutton = GTK_WIDGET (gtk_builder_get_object (data->builder, "destination_folder_fcbutton"));
-  data->copying_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_label"));
-  data->copying_progressbar = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_progressbar"));
-  data->copying_progress_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_progress_label"));
+  for (n = 0; widget_mapping[n].name != NULL; n++)
+    {
+      gpointer *p = (gpointer *) ((char *) data + widget_mapping[n].offset);
+      *p = gtk_builder_get_object (data->builder, widget_mapping[n].name);
+    }
+  g_signal_connect (data->name_entry, "notify::text", G_CALLBACK (on_notify), data);
 
   create_disk_image_populate (data);
   create_disk_image_update (data);
 
-  /* Translators: This is the window title for the non-modal "Create Disk Image" dialog. The %s is the device. */
-  s = g_strdup_printf (_("Create Disk Image (%s)"),
-                       udisks_block_get_preferred_device (data->block));
-  gtk_window_set_title (GTK_WINDOW (data->dialog), s);
-  g_free (s);
   gtk_dialog_set_default_response (GTK_DIALOG (data->dialog), GTK_RESPONSE_OK);
   /* Only select the precomputed filename, not the .img / .iso extension */
-  gtk_editable_select_region (GTK_EDITABLE (data->destination_name_entry), 0,
-                              strlen (gtk_entry_get_text (GTK_ENTRY (data->destination_name_entry))) - 4);
+  gtk_editable_select_region (GTK_EDITABLE (data->name_entry), 0,
+                              strlen (gtk_entry_get_text (GTK_ENTRY (data->name_entry))) - 4);
 
   data->response_signal_handler_id = g_signal_connect (data->dialog,
                                                        "response",
                                                        G_CALLBACK (on_dialog_response),
                                                        data);
 
-  gtk_widget_show_all (data->dialog);
+  /* initially hide the destination, errors and progress widgets + close buttons */
+  gtk_widget_hide (data->destination_key_label);
+  gtk_widget_hide (data->destination_label);
+  gtk_widget_hide (data->copying_key_label);
+  gtk_widget_hide (data->copying_vbox);
+  gtk_widget_show (data->dialog);
+  gtk_widget_hide (data->close_button);
+  gtk_widget_hide (data->show_in_folder_button);
   gtk_window_present (GTK_WINDOW (data->dialog));
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]