[gnome-disk-utility] Introduce local jobs and use it in create/restore disk image operations



commit 992ce822069dfdb50706c9a788803920c9f96999
Author: David Zeuthen <zeuthen gmail com>
Date:   Thu Dec 13 13:31:42 2012 -0500

    Introduce local jobs and use it in create/restore disk image operations
    
    This way we can avoid the non-modal dialogs as seen here
    
     http://people.freedesktop.org/~david/gnome-disks-multiple-disk-image-dialogs.png
    
    since creating or restoring a disk image is now like any other
    long-running operation, see
    
     http://people.freedesktop.org/~david/gnome-disks-disk-image-ops-as-jobs-1.png
     http://people.freedesktop.org/~david/gnome-disks-disk-image-ops-as-jobs-2.png
    
    Signed-off-by: David Zeuthen <zeuthen gmail com>

 data/ui/create-disk-image-dialog.ui   |  120 +----------
 data/ui/restore-disk-image-dialog.ui  |  104 +---------
 src/disks/Makefile.am                 |    1 +
 src/disks/gduapplication.c            |  103 +++++++++
 src/disks/gduapplication.h            |    7 +
 src/disks/gducreatediskimagedialog.c  |  323 ++++++++++----------------
 src/disks/gdudevicetreemodel.c        |  102 +++++----
 src/disks/gdudevicetreemodel.h        |    4 +-
 src/disks/gdulocaljob.c               |  224 ++++++++++++++++++
 src/disks/gdulocaljob.h               |   35 +++
 src/disks/gdurestorediskimagedialog.c |  245 +++++++++-----------
 src/disks/gdutypes.h                  |    3 +
 src/disks/gduvolumegrid.c             |   30 ++-
 src/disks/gduvolumegrid.h             |    2 +-
 src/disks/gduwindow.c                 |  404 +++++++++++++++++++-------------
 15 files changed, 923 insertions(+), 784 deletions(-)
---
diff --git a/data/ui/create-disk-image-dialog.ui b/data/ui/create-disk-image-dialog.ui
index 66e0452..0948b69 100644
--- a/data/ui/create-disk-image-dialog.ui
+++ b/data/ui/create-disk-image-dialog.ui
@@ -7,6 +7,7 @@
     <property name="border_width">12</property>
     <property name="title" translatable="yes">Create Disk Image</property>
     <property name="resizable">False</property>
+    <property name="modal">True</property>
     <property name="type_hint">dialog</property>
     <child internal-child="vbox">
       <object class="GtkBox" id="dialog-vbox1">
@@ -91,38 +92,6 @@
               </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>
@@ -154,61 +123,6 @@
                 <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="GtkProgressBar" id="copying-progressbar">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="hexpand">True</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">0</property>
-                  </packing>
-                </child>
-                <child>
-                  <object class="GtkLabel" id="copying-label">
-                    <property name="visible">True</property>
-                    <property name="can_focus">False</property>
-                    <property name="xalign">0</property>
-                  </object>
-                  <packing>
-                    <property name="expand">False</property>
-                    <property name="fill">True</property>
-                    <property name="position">1</property>
-                  </packing>
-                </child>
-              </object>
-              <packing>
-                <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>
-              <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>
             <property name="expand">False</property>
@@ -249,36 +163,6 @@
                 <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>
@@ -292,8 +176,6 @@
     <action-widgets>
       <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/data/ui/restore-disk-image-dialog.ui b/data/ui/restore-disk-image-dialog.ui
index 2e79e79..6ba5e95 100644
--- a/data/ui/restore-disk-image-dialog.ui
+++ b/data/ui/restore-disk-image-dialog.ui
@@ -7,6 +7,7 @@
     <property name="border_width">12</property>
     <property name="title" translatable="yes">Restore Disk Image</property>
     <property name="resizable">False</property>
+    <property name="modal">True</property>
     <property name="type_hint">dialog</property>
     <child internal-child="vbox">
       <object class="GtkBox" id="dialog-vbox1">
@@ -80,7 +81,7 @@
               </object>
               <packing>
                 <property name="left_attach">0</property>
-                <property name="top_attach">2</property>
+                <property name="top_attach">1</property>
                 <property name="width">1</property>
                 <property name="height">1</property>
               </packing>
@@ -96,92 +97,6 @@
               </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="GtkVBox" id="copying-vbox">
-                <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-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>
-              </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="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">3</property>
-                <property name="width">1</property>
-                <property name="height">1</property>
-              </packing>
-            </child>
-            <child>
-              <object class="GtkLabel" id="source-key-label">
-                <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">1</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>
-                <property name="ellipsize">middle</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>
@@ -227,20 +142,6 @@
                 <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>
           </object>
           <packing>
             <property name="expand">False</property>
@@ -254,7 +155,6 @@
     <action-widgets>
       <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-widgets>
   </object>
 </interface>
diff --git a/src/disks/Makefile.am b/src/disks/Makefile.am
index a109aac..0c76be9 100644
--- a/src/disks/Makefile.am
+++ b/src/disks/Makefile.am
@@ -51,6 +51,7 @@ gnome_disks_SOURCES = 							\
 	gducreateraidarraydialog.h	gducreateraidarraydialog.c	\
 	gduerasemultipledisksdialog.h	gduerasemultipledisksdialog.c	\
 	gdudvdsupport.h			gdudvdsupport.c			\
+	gdulocaljob.h			gdulocaljob.c			\
 	$(enum_built_sources)						\
 	$(NULL)
 
diff --git a/src/disks/gduapplication.c b/src/disks/gduapplication.c
index 8bc37eb..85ad31d 100644
--- a/src/disks/gduapplication.c
+++ b/src/disks/gduapplication.c
@@ -17,6 +17,7 @@
 
 #include "gduapplication.h"
 #include "gduwindow.h"
+#include "gdulocaljob.h"
 
 struct _GduApplication
 {
@@ -26,6 +27,9 @@ struct _GduApplication
 
   UDisksClient *client;
   GduWindow *window;
+
+  /* Maps from UDisksObject* -> GList<GduLocalJob*> */
+  GHashTable *local_jobs;
 };
 
 typedef struct
@@ -38,6 +42,7 @@ G_DEFINE_TYPE (GduApplication, gdu_application, GTK_TYPE_APPLICATION);
 static void
 gdu_application_init (GduApplication *app)
 {
+  app->local_jobs = g_hash_table_new (g_direct_hash, g_direct_equal);
 }
 
 static void
@@ -45,6 +50,20 @@ gdu_application_finalize (GObject *object)
 {
   GduApplication *app = GDU_APPLICATION (object);
 
+  if (app->local_jobs != NULL)
+    {
+      GHashTableIter iter;
+      GList *local_jobs, *jobs_to_destroy = NULL, *l;
+
+      g_hash_table_iter_init (&iter, app->local_jobs);
+      while (g_hash_table_iter_next (&iter, NULL /* object*/, (gpointer) &local_jobs))
+        jobs_to_destroy = g_list_concat (jobs_to_destroy, g_list_copy (local_jobs));
+      for (l = jobs_to_destroy; l != NULL; l = l->next)
+        gdu_application_destroy_local_job (app, GDU_LOCAL_JOB (l->data));
+      g_list_free (jobs_to_destroy);
+      g_hash_table_destroy (app->local_jobs);
+    }
+
   if (app->client != NULL)
     g_object_unref (app->client);
 
@@ -380,3 +399,87 @@ gdu_application_new_widget (GduApplication  *application,
     }
   return ret;
 }
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+on_local_job_notify (GObject    *object,
+                     GParamSpec *pspec,
+                     gpointer    user_data)
+{
+  GduApplication *app = GDU_APPLICATION (user_data);
+  udisks_client_queue_changed (app->client);
+}
+
+
+GduLocalJob *
+gdu_application_create_local_job  (GduApplication *application,
+                                   UDisksObject   *object)
+{
+  GduLocalJob *job = NULL;
+  GList *local_jobs;
+
+  g_return_val_if_fail (GDU_IS_APPLICATION (application), NULL);
+  g_return_val_if_fail (UDISKS_IS_OBJECT (object), NULL);
+
+  job = gdu_local_job_new (object);
+
+  local_jobs = g_hash_table_lookup (application->local_jobs, object);
+  local_jobs = g_list_prepend (local_jobs, job);
+  g_hash_table_insert (application->local_jobs, object, local_jobs);
+
+  g_signal_connect (job, "notify", G_CALLBACK (on_local_job_notify), application);
+
+  udisks_client_queue_changed (application->client);
+
+  return job;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+void
+gdu_application_destroy_local_job (GduApplication *application,
+                                   GduLocalJob    *job)
+{
+  GList *local_jobs;
+  UDisksObject *object;
+
+  g_return_if_fail (GDU_IS_APPLICATION (application));
+  g_return_if_fail (GDU_IS_LOCAL_JOB (job));
+
+  object = gdu_local_job_get_object (job);
+
+  local_jobs = g_hash_table_lookup (application->local_jobs, object);
+  g_warn_if_fail (g_list_find (local_jobs, job) != NULL);
+  local_jobs = g_list_remove (local_jobs, job);
+  g_signal_handlers_disconnect_by_func (job, G_CALLBACK (on_local_job_notify), application);
+
+  if (local_jobs != NULL)
+    g_hash_table_insert (application->local_jobs, object, local_jobs);
+  else
+    g_hash_table_remove (application->local_jobs, object);
+
+  g_object_unref (job);
+
+  udisks_client_queue_changed (application->client);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+GList *
+gdu_application_get_local_jobs_for_object (GduApplication *application,
+                                           UDisksObject   *object)
+{
+  GList *ret;
+
+  g_return_val_if_fail (GDU_IS_APPLICATION (application), NULL);
+  g_return_val_if_fail (UDISKS_IS_OBJECT (object), NULL);
+
+  ret = g_list_copy_deep (g_hash_table_lookup (application->local_jobs, object),
+                          (GCopyFunc) g_object_ref,
+                          NULL);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/disks/gduapplication.h b/src/disks/gduapplication.h
index 2749939..b010845 100644
--- a/src/disks/gduapplication.h
+++ b/src/disks/gduapplication.h
@@ -27,6 +27,13 @@ GObject      *gdu_application_new_widget (GduApplication  *application,
                                           const gchar     *name,
                                           GtkBuilder     **out_builder);
 
+GduLocalJob  *gdu_application_create_local_job  (GduApplication *application,
+                                                 UDisksObject   *object);
+void          gdu_application_destroy_local_job (GduApplication *application,
+                                                 GduLocalJob    *job);
+GList        *gdu_application_get_local_jobs_for_object (GduApplication *application,
+                                                         UDisksObject   *object);
+
 
 G_END_DECLS
 
diff --git a/src/disks/gducreatediskimagedialog.c b/src/disks/gducreatediskimagedialog.c
index 822f8db..f9321eb 100644
--- a/src/disks/gducreatediskimagedialog.c
+++ b/src/disks/gducreatediskimagedialog.c
@@ -25,6 +25,7 @@
 #include "gduvolumegrid.h"
 #include "gducreatefilesystemwidget.h"
 #include "gduestimator.h"
+#include "gdulocaljob.h"
 
 #include "gdudvdsupport.h"
 
@@ -58,18 +59,9 @@ typedef struct
   GtkWidget *name_entry;
   GtkWidget *folder_label;
   GtkWidget *folder_fcbutton;
-  GtkWidget *destination_key_label;
-  GtkWidget *destination_label;
-
-  GtkWidget *copying_key_label;
-  GtkWidget *copying_vbox;
-  GtkWidget *copying_progressbar;
-  GtkWidget *copying_label;
 
   GtkWidget *start_copying_button;
   GtkWidget *cancel_button;
-  GtkWidget *close_button;
-  GtkWidget *show_in_folder_button;
 
   GCancellable *cancellable;
   GFile *output_file;
@@ -91,6 +83,8 @@ typedef struct
   gboolean completed;
 
   guint inhibit_cookie;
+
+  GduLocalJob *local_job;
 } DialogData;
 
 static const struct {
@@ -102,18 +96,9 @@ static const struct {
   {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}
 };
 
@@ -127,21 +112,51 @@ dialog_data_ref (DialogData *data)
 }
 
 static void
+dialog_data_terminate_job (DialogData *data)
+{
+  if (data->local_job != NULL)
+    {
+      gdu_application_destroy_local_job (gdu_window_get_application (data->window), data->local_job);
+      data->local_job = NULL;
+    }
+}
+
+static void
+dialog_data_uninhibit (DialogData *data)
+{
+  if (data->inhibit_cookie > 0)
+    {
+      gtk_application_uninhibit (GTK_APPLICATION (gdu_window_get_application (data->window)),
+                                 data->inhibit_cookie);
+      data->inhibit_cookie = 0;
+    }
+}
+
+static void
+dialog_data_hide (DialogData *data)
+{
+  if (data->dialog != NULL)
+    {
+      GtkWidget *dialog;
+      if (data->response_signal_handler_id != 0)
+        g_signal_handler_disconnect (data->dialog, data->response_signal_handler_id);
+      dialog = data->dialog;
+      data->dialog = NULL;
+      gtk_widget_hide (dialog);
+      gtk_widget_destroy (dialog);
+      data->dialog = NULL;
+    }
+}
+
+static void
 dialog_data_unref (DialogData *data)
 {
   if (g_atomic_int_dec_and_test (&data->ref_count))
     {
-      /* hide the dialog */
-      if (data->dialog != NULL)
-        {
-          GtkWidget *dialog;
-          if (data->response_signal_handler_id != 0)
-            g_signal_handler_disconnect (data->dialog, data->response_signal_handler_id);
-          dialog = data->dialog;
-          data->dialog = NULL;
-          gtk_widget_hide (dialog);
-          gtk_widget_destroy (dialog);
-        }
+      dialog_data_terminate_job (data);
+      dialog_data_uninhibit (data);
+      dialog_data_hide (data);
+
       g_clear_object (&data->cancellable);
       g_clear_object (&data->output_file_stream);
       g_object_unref (data->window);
@@ -180,13 +195,8 @@ dialog_data_complete_and_unref (DialogData *data)
       data->completed = TRUE;
       g_cancellable_cancel (data->cancellable);
     }
-  if (data->inhibit_cookie > 0)
-    {
-      gtk_application_uninhibit (GTK_APPLICATION (gdu_window_get_application (data->window)),
-                                 data->inhibit_cookie);
-      data->inhibit_cookie = 0;
-    }
-  gtk_widget_hide (data->dialog);
+  dialog_data_uninhibit (data);
+  dialog_data_hide (data);
   dialog_data_unref (data);
 }
 
@@ -279,16 +289,17 @@ create_disk_image_populate (DialogData *data)
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-update_gui (DialogData *data,
+update_job (DialogData *data,
             gboolean    done)
 {
+  gchar *extra_markup = NULL;
   guint64 bytes_completed = 0;
-  guint64 bytes_target = 1;
+  guint64 bytes_target = 0;
   guint64 bytes_per_sec = 0;
-  guint64 usec_remaining = 1;
+  guint64 usec_remaining = 0;
   guint64 num_error_bytes = 0;
-  gchar *s, *s2, *s3, *s4, *s5;
-  gdouble progress;
+  gdouble progress = 0.0;
+  gchar *s2, *s3;
 
   g_mutex_lock (&data->copy_lock);
   if (data->estimator != NULL)
@@ -302,55 +313,9 @@ update_gui (DialogData *data,
   data->update_id = 0;
   g_mutex_unlock (&data->copy_lock);
 
-  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 (data->retrieving_dvd_keys)
+  if (data->retrieving_dvd_keys)
     {
-      s = g_strdup (_("Retrieving DVD keys"));
-    }
-  else if (bytes_per_sec > 0 && usec_remaining > 0)
-    {
-      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 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);
-      g_free (s4);
-      g_free (s3);
-      g_free (s2);
-    }
-  else
-    {
-      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").
-       */
-      s = g_strdup_printf (_("%s of %s copied"), s2, s3);
-      g_free (s2);
-      g_free (s3);
+      extra_markup = g_strdup (_("Retrieving DVD keys"));
     }
 
   if (num_error_bytes > 0)
@@ -363,53 +328,63 @@ update_gui (DialogData *data,
       /* 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 (extra_markup);
+      extra_markup = g_strdup_printf ("<span foreground=\"#ff0000\">%s</span>", 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_label), s2);
-  g_free (s);
+  if (data->local_job != NULL)
+    {
+      udisks_job_set_bytes (UDISKS_JOB (data->local_job), bytes_target);
+      udisks_job_set_rate (UDISKS_JOB (data->local_job), bytes_per_sec);
+
+      if (done)
+        {
+          progress = 1.0;
+        }
+      else
+        {
+          if (bytes_target != 0)
+            progress = ((gdouble) bytes_completed) / ((gdouble) bytes_target);
+          else
+            progress = 0.0;
+        }
+      udisks_job_set_progress (UDISKS_JOB (data->local_job), progress);
 
-  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);
+      if (usec_remaining == 0)
+        udisks_job_set_expected_end_time (UDISKS_JOB (data->local_job), 0);
+      else
+        udisks_job_set_expected_end_time (UDISKS_JOB (data->local_job), usec_remaining + g_get_real_time ());
+
+      gdu_local_job_set_extra_markup (data->local_job, extra_markup);
+    }
+
+  g_free (extra_markup);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-play_complete_sound_and_uninhibit (DialogData *data)
+play_complete_sound (DialogData *data)
 {
   const gchar *sound_message;
 
   /* Translators: A descriptive string for the 'complete' sound, see CA_PROP_EVENT_DESCRIPTION */
   sound_message = _("Disk image copying complete");
-  ca_gtk_play_for_widget (GTK_WIDGET (data->dialog), 0,
+  ca_gtk_play_for_widget (GTK_WIDGET (data->window), 0,
                           CA_PROP_EVENT_ID, "complete",
                           CA_PROP_EVENT_DESCRIPTION, sound_message,
                           NULL);
-
-  if (data->inhibit_cookie > 0)
-    {
-      gtk_application_uninhibit (GTK_APPLICATION (gdu_window_get_application (data->window)),
-                                 data->inhibit_cookie);
-      data->inhibit_cookie = 0;
-    }
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
 static gboolean
-on_update_ui (gpointer user_data)
+on_update_job (gpointer user_data)
 {
   DialogData *data = user_data;
-  update_gui (data, FALSE);
+  update_job (data, FALSE);
   dialog_data_unref (data);
   return FALSE; /* remove source */
 }
@@ -421,10 +396,10 @@ on_show_error (gpointer user_data)
 {
   DialogData *data = user_data;
 
-  play_complete_sound_and_uninhibit (data);
+  dialog_data_uninhibit (data);
 
   g_assert (data->copy_error != NULL);
-  gdu_utils_show_error (GTK_WINDOW (data->dialog),
+  gdu_utils_show_error (GTK_WINDOW (data->window),
                         _("Error creating disk image"),
                         data->copy_error);
   g_clear_error (&data->copy_error);
@@ -442,13 +417,11 @@ on_success (gpointer user_data)
 {
   DialogData *data = user_data;
 
-  update_gui (data, TRUE);
-
-  gtk_widget_hide (data->cancel_button);
-  gtk_widget_show (data->close_button);
-  gtk_widget_show (data->show_in_folder_button);
+  update_job (data, TRUE);
 
-  play_complete_sound_and_uninhibit (data);
+  play_complete_sound (data);
+  dialog_data_uninhibit (data);
+  dialog_data_complete_and_unref (data);
 
   dialog_data_unref (data);
   return FALSE; /* remove source */
@@ -610,14 +583,14 @@ copy_thread_func (gpointer user_data)
           g_mutex_lock (&data->copy_lock);
           data->retrieving_dvd_keys = TRUE;
           g_mutex_unlock (&data->copy_lock);
-          g_idle_add (on_update_ui, dialog_data_ref (data));
+          g_idle_add (on_update_job, dialog_data_ref (data));
 
           dvd_support = gdu_dvd_support_new (device_file, udisks_block_get_size (data->block));
 
           g_mutex_lock (&data->copy_lock);
           data->retrieving_dvd_keys = FALSE;
           g_mutex_unlock (&data->copy_lock);
-          g_idle_add (on_update_ui, dialog_data_ref (data));
+          g_idle_add (on_update_job, dialog_data_ref (data));
         }
     }
 
@@ -702,7 +675,7 @@ copy_thread_func (gpointer user_data)
           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));
+            data->update_id = g_idle_add (on_update_job, dialog_data_ref (data));
           last_update_usec = now_usec;
         }
       g_mutex_unlock (&data->copy_lock);
@@ -844,6 +817,19 @@ check_overwrite (DialogData *data)
   return ret;
 }
 
+static void
+on_local_job_canceled (GduLocalJob  *job,
+                       gpointer      user_data)
+{
+  DialogData *data = user_data;
+  if (!data->completed)
+    {
+      dialog_data_terminate_job (data);
+      dialog_data_complete_and_unref (data);
+      update_job (data, FALSE);
+    }
+}
+
 static gboolean
 start_copying (DialogData *data)
 {
@@ -851,7 +837,6 @@ start_copying (DialogData *data)
   const gchar *name;
   GFile *folder;
   GError *error;
-  gchar *uri = NULL;
 
   name = gtk_entry_get_text (GTK_ENTRY (data->name_entry));
   folder = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->folder_fcbutton));
@@ -874,9 +859,6 @@ start_copying (DialogData *data)
       goto out;
     }
 
-  uri = gdu_utils_get_pretty_uri (data->output_file);
-  gtk_label_set_text (GTK_LABEL (data->destination_label), uri);
-
   /* 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));
 
@@ -887,67 +869,29 @@ start_copying (DialogData *data)
                                                   /* Translators: Reason why suspend/logout is being inhibited */
                                                   C_("create-inhibit-message", "Copying device to disk image"));
 
+  data->local_job = gdu_application_create_local_job (gdu_window_get_application (data->window),
+                                                      data->object);
+  udisks_job_set_operation (UDISKS_JOB (data->local_job), "x-gdu-create-disk-image");
+  /* Translators: this is the description of the job */
+  gdu_local_job_set_description (data->local_job, _("Creating Disk Image"));
+  udisks_job_set_progress_valid (UDISKS_JOB (data->local_job), TRUE);
+  udisks_job_set_cancelable (UDISKS_JOB (data->local_job), TRUE);
+  g_signal_connect (data->local_job, "canceled",
+                    G_CALLBACK (on_local_job_canceled),
+                    data);
+
+  dialog_data_hide (data);
+
   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
 ensure_unused_cb (GduWindow     *window,
                   GAsyncResult  *res,
                   gpointer       user_data)
@@ -975,18 +919,6 @@ on_dialog_response (GtkDialog     *dialog,
     case GTK_RESPONSE_OK:
       if (check_overwrite (data))
         {
-          /* 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);
-
-          gtk_widget_hide (data->start_copying_button);
-
           /* ensure the device is unused (e.g. unmounted) before copying data from it... */
           gdu_window_ensure_unused (data->window,
                                     data->object,
@@ -1000,10 +932,6 @@ on_dialog_response (GtkDialog     *dialog,
       dialog_data_complete_and_unref (data);
       break;
 
-    case 1: /* show_in_folder */
-      show_in_folder (data);
-      break;
-
     default: /* explicit fallthrough */
     case GTK_RESPONSE_CANCEL:
       dialog_data_complete_and_unref (data);
@@ -1049,14 +977,7 @@ gdu_create_disk_image_dialog_show (GduWindow    *window,
                                                        G_CALLBACK (on_dialog_response),
                                                        data);
 
-  /* 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_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (window));
   gtk_window_present (GTK_WINDOW (data->dialog));
 
   /* Only select the precomputed filename, not the .img / .iso extension */
diff --git a/src/disks/gdudevicetreemodel.c b/src/disks/gdudevicetreemodel.c
index 122351d..ec2abef 100644
--- a/src/disks/gdudevicetreemodel.c
+++ b/src/disks/gdudevicetreemodel.c
@@ -13,6 +13,7 @@
 #include <glib/gi18n.h>
 
 #include "gdudevicetreemodel.h"
+#include "gduapplication.h"
 #include "gduatasmartdialog.h"
 #include "gduenumtypes.h"
 
@@ -20,6 +21,7 @@ struct _GduDeviceTreeModel
 {
   GtkTreeStore parent_instance;
 
+  GduApplication *application;
   UDisksClient *client;
 
   GList *current_drives;
@@ -50,7 +52,7 @@ typedef struct
 enum
 {
   PROP_0,
-  PROP_CLIENT
+  PROP_APPLICATION
 };
 
 G_DEFINE_TYPE (GduDeviceTreeModel, gdu_device_tree_model, GTK_TYPE_TREE_STORE);
@@ -113,8 +115,8 @@ gdu_device_tree_model_get_property (GObject    *object,
 
   switch (prop_id)
     {
-    case PROP_CLIENT:
-      g_value_set_object (value, gdu_device_tree_model_get_client (model));
+    case PROP_APPLICATION:
+      g_value_set_object (value, gdu_device_tree_model_get_application (model));
       break;
 
     default:
@@ -133,8 +135,9 @@ gdu_device_tree_model_set_property (GObject      *object,
 
   switch (prop_id)
     {
-    case PROP_CLIENT:
-      model->client = g_value_dup_object (value);
+    case PROP_APPLICATION:
+      model->application = g_value_dup_object (value);
+      model->client = gdu_application_get_client (model->application);
       break;
 
     default:
@@ -496,16 +499,14 @@ gdu_device_tree_model_class_init (GduDeviceTreeModelClass *klass)
   gobject_class->set_property = gdu_device_tree_model_set_property;
 
   /**
-   * GduDeviceTreeModel:client:
+   * GduDeviceTreeModel:application:
    *
-   * The #UDisksClient used by the #GduDeviceTreeModel instance.
+   * The #GduApplication used by the #GduDeviceTreeModel instance.
    */
   g_object_class_install_property (gobject_class,
-                                   PROP_CLIENT,
-                                   g_param_spec_object ("client",
-                                                        "Client",
-                                                        "The client used by the tree model",
-                                                        UDISKS_TYPE_CLIENT,
+                                   PROP_APPLICATION,
+                                   g_param_spec_object ("application", NULL, NULL,
+                                                        GDU_TYPE_APPLICATION,
                                                         G_PARAM_READABLE |
                                                         G_PARAM_WRITABLE |
                                                         G_PARAM_CONSTRUCT_ONLY |
@@ -514,35 +515,35 @@ gdu_device_tree_model_class_init (GduDeviceTreeModelClass *klass)
 
 /**
  * gdu_device_tree_model_new:
- * @client: A #UDisksClient.
+ * @application: A #GduApplication.
  *
  * Creates a new #GduDeviceTreeModel for viewing the devices belonging to
- * @client.
+ * @application.
  *
  * Returns: A #GduDeviceTreeModel. Free with g_object_unref().
  */
 GduDeviceTreeModel *
-gdu_device_tree_model_new (UDisksClient *client)
+gdu_device_tree_model_new (GduApplication *application)
 {
   return GDU_DEVICE_TREE_MODEL (g_object_new (GDU_TYPE_DEVICE_TREE_MODEL,
-                                       "client", client,
-                                       NULL));
+                                              "application", application,
+                                              NULL));
 }
 
 /**
- * gdu_device_tree_model_get_client:
+ * gdu_device_tree_model_get_application:
  * @model: A #GduDeviceTreeModel.
  *
- * Gets the #UDisksClient used by @model.
+ * Gets the #GduApplication used by @model.
  *
- * Returns: (transfer none): A #UDisksClient. Do not free, the object
- * belongs to @model.
+ * Returns: (transfer none): A #GduApplication. Do not free, the
+ * object belongs to @model.
  */
-UDisksClient *
-gdu_device_tree_model_get_client (GduDeviceTreeModel *model)
+GduApplication *
+gdu_device_tree_model_get_application (GduDeviceTreeModel *model)
 {
   g_return_val_if_fail (GDU_IS_DEVICE_TREE_MODEL (model), NULL);
-  return model->client;
+  return model->application;
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -694,13 +695,14 @@ remove_mdraid (GduDeviceTreeModel *model,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static gboolean
-object_has_jobs (UDisksClient   *client,
-                 UDisksObject   *object)
+object_has_jobs (GduDeviceTreeModel *model,
+                 UDisksObject       *object)
 {
   GList *jobs;
   gboolean ret;
 
-  jobs = udisks_client_get_jobs_for_object (client, object);
+  jobs = udisks_client_get_jobs_for_object (model->client, object);
+  jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (model->application, object));
   ret = (jobs != NULL);
   g_list_foreach (jobs, (GFunc) g_object_unref, NULL);
   g_list_free (jobs);
@@ -709,20 +711,20 @@ object_has_jobs (UDisksClient   *client,
 }
 
 static gboolean
-iface_has_jobs (UDisksClient   *client,
-                GDBusInterface *iface)
+iface_has_jobs (GduDeviceTreeModel *model,
+                GDBusInterface     *iface)
 {
   GDBusObject *object;
   object = g_dbus_interface_get_object (G_DBUS_INTERFACE (iface));
   if (object != NULL)
-    return object_has_jobs (client, UDISKS_OBJECT (object));
+    return object_has_jobs (model, UDISKS_OBJECT (object));
   else
     return FALSE;
 }
 
 static gboolean
-block_has_jobs (UDisksClient   *client,
-                UDisksBlock    *block)
+block_has_jobs (GduDeviceTreeModel *model,
+                UDisksBlock        *block)
 {
   gboolean ret = FALSE;
   GDBusObject *block_object;
@@ -731,7 +733,7 @@ block_has_jobs (UDisksClient   *client,
   GList *partitions = NULL, *l;
   UDisksBlock *cleartext_block = NULL;
 
-  if (iface_has_jobs (client, G_DBUS_INTERFACE (block)))
+  if (iface_has_jobs (model, G_DBUS_INTERFACE (block)))
     {
       ret = TRUE;
       goto out;
@@ -744,7 +746,7 @@ block_has_jobs (UDisksClient   *client,
   part_table = udisks_object_get_partition_table (UDISKS_OBJECT (block_object));
   if (part_table != NULL)
     {
-      partitions = udisks_client_get_partitions (client, part_table);
+      partitions = udisks_client_get_partitions (model->client, part_table);
       for (l = partitions; l != NULL; l = l->next)
         {
           UDisksPartition *partition = UDISKS_PARTITION (l->data);
@@ -755,7 +757,7 @@ block_has_jobs (UDisksClient   *client,
           if (partition_object != NULL)
             {
               partition_block = udisks_object_get_block (UDISKS_OBJECT (partition_object));
-              if (block_has_jobs (client, partition_block))
+              if (block_has_jobs (model, partition_block))
                 {
                   ret = TRUE;
                   goto out;
@@ -767,10 +769,10 @@ block_has_jobs (UDisksClient   *client,
   encrypted = udisks_object_get_encrypted (UDISKS_OBJECT (block_object));
   if (encrypted != NULL)
     {
-      cleartext_block = udisks_client_get_cleartext_block (client, block);
+      cleartext_block = udisks_client_get_cleartext_block (model->client, block);
       if (cleartext_block != NULL)
         {
-          if (block_has_jobs (client, cleartext_block))
+          if (block_has_jobs (model, cleartext_block))
             {
               ret = TRUE;
               goto out;
@@ -790,20 +792,20 @@ block_has_jobs (UDisksClient   *client,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static gboolean
-drive_has_jobs (UDisksClient   *client,
-                UDisksDrive    *drive)
+drive_has_jobs (GduDeviceTreeModel *model,
+                UDisksDrive        *drive)
 {
   gboolean ret = FALSE;
   UDisksBlock *block = NULL;
 
-  if (iface_has_jobs (client, G_DBUS_INTERFACE (drive)))
+  if (iface_has_jobs (model, G_DBUS_INTERFACE (drive)))
     {
       ret = TRUE;
       goto out;
     }
 
-  block = udisks_client_get_block_for_drive (client, drive, FALSE); /* get_physical */
-  if (block_has_jobs (client, block))
+  block = udisks_client_get_block_for_drive (model->client, drive, FALSE); /* get_physical */
+  if (block_has_jobs (model, block))
     {
       ret = TRUE;
       goto out;
@@ -817,20 +819,20 @@ drive_has_jobs (UDisksClient   *client,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static gboolean
-mdraid_has_jobs (UDisksClient   *client,
-                 UDisksMDRaid   *mdraid)
+mdraid_has_jobs (GduDeviceTreeModel *model,
+                 UDisksMDRaid       *mdraid)
 {
   gboolean ret = FALSE;
   UDisksBlock *block = NULL;
 
-  if (iface_has_jobs (client, G_DBUS_INTERFACE (mdraid)))
+  if (iface_has_jobs (model, G_DBUS_INTERFACE (mdraid)))
     {
       ret = TRUE;
       goto out;
     }
 
-  block = udisks_client_get_block_for_mdraid (client, mdraid);
-  if (block != NULL && block_has_jobs (client, block))
+  block = udisks_client_get_block_for_mdraid (model->client, mdraid);
+  if (block != NULL && block_has_jobs (model, block))
     {
       ret = TRUE;
       goto out;
@@ -940,7 +942,7 @@ update_drive (GduDeviceTreeModel *model,
                            udisks_object_info_get_name (info));
     }
 
-  jobs_running = drive_has_jobs (model->client, drive);
+  jobs_running = drive_has_jobs (model, drive);
 
   size = udisks_drive_get_size (drive);
 
@@ -1142,7 +1144,7 @@ update_mdraid (GduDeviceTreeModel *model,
   if (block != NULL)
     size = udisks_block_get_size (block);
 
-  jobs_running = mdraid_has_jobs (model->client, mdraid);
+  jobs_running = mdraid_has_jobs (model, mdraid);
 
   gtk_tree_model_get (GTK_TREE_MODEL (model),
                       &iter,
@@ -1375,7 +1377,7 @@ update_block (GduDeviceTreeModel  *model,
                            preferred_device);
     }
 
-  jobs_running = block_has_jobs (model->client, block);
+  jobs_running = block_has_jobs (model, block);
 
   gtk_tree_model_get (GTK_TREE_MODEL (model),
                       &iter,
diff --git a/src/disks/gdudevicetreemodel.h b/src/disks/gdudevicetreemodel.h
index 99768db..6b46dc7 100644
--- a/src/disks/gdudevicetreemodel.h
+++ b/src/disks/gdudevicetreemodel.h
@@ -38,8 +38,8 @@ enum
 };
 
 GType               gdu_device_tree_model_get_type            (void) G_GNUC_CONST;
-GduDeviceTreeModel *gdu_device_tree_model_new                 (UDisksClient       *client);
-UDisksClient       *gdu_device_tree_model_get_client          (GduDeviceTreeModel *model);
+GduDeviceTreeModel *gdu_device_tree_model_new                 (GduApplication     *application);
+GduApplication     *gdu_device_tree_model_get_application     (GduDeviceTreeModel *model);
 gboolean            gdu_device_tree_model_get_iter_for_object (GduDeviceTreeModel *model,
                                                                UDisksObject       *object,
                                                                GtkTreeIter        *iter);
diff --git a/src/disks/gdulocaljob.c b/src/disks/gdulocaljob.c
new file mode 100644
index 0000000..4392df2
--- /dev/null
+++ b/src/disks/gdulocaljob.c
@@ -0,0 +1,224 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2012 Red Hat, Inc.
+ *
+ * Licensed under GPL version 2 or later.
+ *
+ * Author: David Zeuthen <zeuthen gmail com>
+ */
+
+#include "config.h"
+#include <glib/gi18n.h>
+
+#include "gduenums.h"
+#include "gdulocaljob.h"
+
+typedef struct GduLocalJobClass GduLocalJobClass;
+
+struct GduLocalJob
+{
+  UDisksJobSkeleton parent;
+
+  UDisksObject *object;
+  gchar *description;
+  gchar *extra_markup;
+};
+
+struct GduLocalJobClass
+{
+  UDisksJobSkeletonClass parent_class;
+
+  /* signals */
+  void (*canceled) (GduLocalJob *job);
+};
+
+enum
+{
+  PROP_0,
+  PROP_OBJECT,
+  PROP_DESCRIPTION,
+  PROP_EXTRA_MARKUP,
+};
+
+enum
+{
+  CANCELED_SIGNAL,
+  LAST_SIGNAL
+};
+
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE (GduLocalJob, gdu_local_job, UDISKS_TYPE_JOB_SKELETON)
+
+static void
+gdu_local_job_finalize (GObject *object)
+{
+  GduLocalJob *job = GDU_LOCAL_JOB (object);
+
+  g_object_unref (job->object);
+  g_free (job->description);
+  g_free (job->extra_markup);
+
+  G_OBJECT_CLASS (gdu_local_job_parent_class)->finalize (object);
+}
+
+static void
+gdu_local_job_get_property (GObject    *object,
+                            guint       property_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GduLocalJob *job = GDU_LOCAL_JOB (object);
+
+  switch (property_id)
+    {
+    case PROP_OBJECT:
+      g_value_set_object (value, gdu_local_job_get_object (job));
+      break;
+
+    case PROP_DESCRIPTION:
+      g_value_set_string (value, gdu_local_job_get_description (job));
+      break;
+
+    case PROP_EXTRA_MARKUP:
+      g_value_set_string (value, gdu_local_job_get_extra_markup (job));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gdu_local_job_set_property (GObject      *object,
+                            guint         property_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GduLocalJob *job = GDU_LOCAL_JOB (object);
+
+  switch (property_id)
+    {
+    case PROP_OBJECT:
+      g_assert (job->object == NULL);
+      job->object = g_value_dup_object (value);
+      break;
+
+    case PROP_DESCRIPTION:
+      gdu_local_job_set_description (job, g_value_get_string (value));
+      break;
+
+    case PROP_EXTRA_MARKUP:
+      gdu_local_job_set_extra_markup (job, g_value_get_string (value));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gdu_local_job_class_init (GduLocalJobClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->get_property = gdu_local_job_get_property;
+  gobject_class->set_property = gdu_local_job_set_property;
+  gobject_class->finalize     = gdu_local_job_finalize;
+
+  g_object_class_install_property (gobject_class, PROP_DESCRIPTION,
+                                   g_param_spec_string ("description", NULL, NULL,
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_EXTRA_MARKUP,
+                                   g_param_spec_string ("extra-markup", NULL, NULL,
+                                                        NULL,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_OBJECT,
+                                   g_param_spec_object ("object", NULL, NULL,
+                                                        UDISKS_TYPE_OBJECT,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  signals[CANCELED_SIGNAL] = g_signal_new ("canceled",
+                                           G_TYPE_FROM_CLASS (klass),
+                                           G_SIGNAL_RUN_LAST,
+                                           G_STRUCT_OFFSET (GduLocalJobClass, canceled),
+                                           NULL,
+                                           NULL,
+                                           g_cclosure_marshal_generic,
+                                           G_TYPE_NONE,
+                                           0);
+}
+
+static void
+gdu_local_job_init (GduLocalJob *widget)
+{
+}
+
+GduLocalJob *
+gdu_local_job_new (UDisksObject *object)
+{
+  return GDU_LOCAL_JOB (g_object_new (GDU_TYPE_LOCAL_JOB,
+                                      "object", object,
+                                      NULL));
+}
+
+void
+gdu_local_job_set_description (GduLocalJob *job,
+                               const gchar *description)
+{
+  g_return_if_fail (GDU_IS_LOCAL_JOB (job));
+  g_free (job->description);
+  job->description = g_strdup (description);
+  g_object_notify (G_OBJECT (job), "description");
+}
+
+const gchar *
+gdu_local_job_get_description (GduLocalJob *job)
+{
+  g_return_val_if_fail (GDU_IS_LOCAL_JOB (job), NULL);
+  return job->description;
+}
+
+void
+gdu_local_job_set_extra_markup (GduLocalJob *job,
+                                const gchar *markup)
+{
+  g_return_if_fail (GDU_IS_LOCAL_JOB (job));
+  g_free (job->extra_markup);
+  job->extra_markup = g_strdup (markup);
+  g_object_notify (G_OBJECT (job), "extra-markup");
+}
+
+const gchar *
+gdu_local_job_get_extra_markup (GduLocalJob *job)
+{
+  g_return_val_if_fail (GDU_IS_LOCAL_JOB (job), NULL);
+  return job->extra_markup;
+}
+
+UDisksObject *
+gdu_local_job_get_object (GduLocalJob *job)
+{
+  g_return_val_if_fail (GDU_IS_LOCAL_JOB (job), NULL);
+  return job->object;
+}
+
+void
+gdu_local_job_canceled (GduLocalJob  *job)
+{
+  g_return_if_fail (GDU_IS_LOCAL_JOB (job));
+  g_signal_emit (job, signals[CANCELED_SIGNAL], 0);
+}
+
+
diff --git a/src/disks/gdulocaljob.h b/src/disks/gdulocaljob.h
new file mode 100644
index 0000000..6f24442
--- /dev/null
+++ b/src/disks/gdulocaljob.h
@@ -0,0 +1,35 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2012 Red Hat, Inc.
+ *
+ * Licensed under GPL version 2 or later.
+ *
+ * Author: David Zeuthen <zeuthen gmail com>
+ */
+
+#ifndef __GDU_LOCAL_JOB_H__
+#define __GDU_LOCAL_JOB_H__
+
+#include <gtk/gtk.h>
+#include "gdutypes.h"
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_LOCAL_JOB  (gdu_local_job_get_type())
+#define GDU_LOCAL_JOB(o)    (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_LOCAL_JOB, GduLocalJob))
+#define GDU_IS_LOCAL_JOB(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_LOCAL_JOB))
+
+GType         gdu_local_job_get_type            (void) G_GNUC_CONST;
+GduLocalJob  *gdu_local_job_new                 (UDisksObject *object);
+UDisksObject *gdu_local_job_get_object          (GduLocalJob  *job);
+void          gdu_local_job_set_description     (GduLocalJob  *job,
+                                                 const gchar  *description);
+const gchar  *gdu_local_job_get_description     (GduLocalJob  *job);
+void          gdu_local_job_set_extra_markup    (GduLocalJob  *job,
+                                                 const gchar  *markup);
+const gchar  *gdu_local_job_get_extra_markup    (GduLocalJob  *job);
+void          gdu_local_job_canceled            (GduLocalJob  *job);
+
+G_END_DECLS
+
+#endif /* __GDU_LOCAL_JOB_H__ */
diff --git a/src/disks/gdurestorediskimagedialog.c b/src/disks/gdurestorediskimagedialog.c
index 2c7c90e..5389c57 100644
--- a/src/disks/gdurestorediskimagedialog.c
+++ b/src/disks/gdurestorediskimagedialog.c
@@ -24,6 +24,7 @@
 #include "gdurestorediskimagedialog.h"
 #include "gduvolumegrid.h"
 #include "gduestimator.h"
+#include "gdulocaljob.h"
 
 /* ---------------------------------------------------------------------------------------------------- */
 
@@ -48,18 +49,10 @@ typedef struct
   GtkWidget *image_key_label;
   GtkWidget *image_fcbutton;
 
-  GtkWidget *source_key_label;
-  GtkWidget *source_label;
   GtkWidget *destination_key_label;
   GtkWidget *destination_label;
 
-  GtkWidget *copying_key_label;
-  GtkWidget *copying_vbox;
-  GtkWidget *copying_progressbar;
-  GtkWidget *copying_label;
-
   GtkWidget *start_copying_button;
-  GtkWidget *close_button;
   GtkWidget *cancel_button;
 
   guint64 block_size;
@@ -84,7 +77,10 @@ typedef struct
 
   guint inhibit_cookie;
 
+  gulong response_signal_handler_id;
   gboolean completed;
+
+  GduLocalJob *local_job;
 } DialogData;
 
 
@@ -96,19 +92,11 @@ static const struct {
 
   {G_STRUCT_OFFSET (DialogData, image_key_label), "image-key-label"},
   {G_STRUCT_OFFSET (DialogData, image_fcbutton), "image-fcbutton"},
-  {G_STRUCT_OFFSET (DialogData, source_key_label), "source-key-label"},
-  {G_STRUCT_OFFSET (DialogData, source_label), "source-label"},
   {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"},
   {0, NULL}
 };
 
@@ -122,22 +110,53 @@ dialog_data_ref (DialogData *data)
 }
 
 static void
+dialog_data_terminate_job (DialogData *data)
+{
+  if (data->local_job != NULL)
+    {
+      gdu_application_destroy_local_job (gdu_window_get_application (data->window), data->local_job);
+      data->local_job = NULL;
+    }
+}
+
+static void
+dialog_data_uninhibit (DialogData *data)
+{
+  if (data->inhibit_cookie > 0)
+    {
+      gtk_application_uninhibit (GTK_APPLICATION (gdu_window_get_application (data->window)),
+                                 data->inhibit_cookie);
+      data->inhibit_cookie = 0;
+    }
+}
+
+static void
+dialog_data_hide (DialogData *data)
+{
+  if (data->dialog != NULL)
+    {
+      GtkWidget *dialog;
+      if (data->response_signal_handler_id != 0)
+        g_signal_handler_disconnect (data->dialog, data->response_signal_handler_id);
+      dialog = data->dialog;
+      data->dialog = NULL;
+      gtk_widget_hide (dialog);
+      gtk_widget_destroy (dialog);
+      data->dialog = NULL;
+    }
+}
+
+static void
 dialog_data_unref (DialogData *data)
 {
   if (g_atomic_int_dec_and_test (&data->ref_count))
     {
-      /* hide the dialog */
-      if (data->dialog != NULL)
-        {
-          GtkWidget *dialog;
-          dialog = data->dialog;
-          data->dialog = NULL;
-          gtk_widget_hide (dialog);
-          gtk_widget_destroy (dialog);
-        }
+      dialog_data_terminate_job (data);
+      dialog_data_uninhibit (data);
+      dialog_data_hide (data);
+
       g_object_unref (data->warning_infobar);
       g_object_unref (data->error_infobar);
-
       g_object_unref (data->window);
       g_object_unref (data->object);
       g_object_unref (data->block);
@@ -181,13 +200,8 @@ dialog_data_complete_and_unref (DialogData *data)
       data->completed = TRUE;
       g_cancellable_cancel (data->cancellable);
     }
-  if (data->inhibit_cookie > 0)
-    {
-      gtk_application_uninhibit (GTK_APPLICATION (gdu_window_get_application (data->window)),
-                                 data->inhibit_cookie);
-      data->inhibit_cookie = 0;
-    }
-  gtk_widget_hide (data->dialog);
+  dialog_data_uninhibit (data);
+  dialog_data_hide (data);
   dialog_data_unref (data);
 }
 
@@ -328,15 +342,14 @@ restore_disk_image_populate (DialogData *data)
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-update_gui (DialogData *data,
+update_job (DialogData *data,
             gboolean    done)
 {
   guint64 bytes_completed = 0;
-  guint64 bytes_target = 1;
+  guint64 bytes_target = 0;
   guint64 bytes_per_sec = 0;
-  guint64 usec_remaining = 1;
-  gchar *s, *s2, *s3, *s4, *s5;
-  gdouble progress;
+  guint64 usec_remaining = 0;
+  gdouble progress = 0.0;
 
   g_mutex_lock (&data->copy_lock);
   if (data->estimator != NULL)
@@ -349,68 +362,35 @@ update_gui (DialogData *data,
   data->update_id = 0;
   g_mutex_unlock (&data->copy_lock);
 
-  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)
+  if (data->local_job != NULL)
     {
-      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 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);
-      g_free (s4);
-      g_free (s3);
-      g_free (s2);
-    }
-  else
-    {
-      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").
-       */
-      s = g_strdup_printf (_("%s of %s copied"), s2, s3);
-      g_free (s2);
-      g_free (s3);
-    }
+      udisks_job_set_bytes (UDISKS_JOB (data->local_job), bytes_target);
+      udisks_job_set_rate (UDISKS_JOB (data->local_job), bytes_per_sec);
 
-  s2 = g_strconcat ("<small>", s, "</small>", NULL);
-  gtk_label_set_markup (GTK_LABEL (data->copying_label), s2);
-  g_free (s);
+      if (done)
+        {
+          progress = 1.0;
+        }
+      else
+        {
+          if (bytes_target != 0)
+            progress = ((gdouble) bytes_completed) / ((gdouble) bytes_target);
+          else
+            progress = 0.0;
+        }
+      udisks_job_set_progress (UDISKS_JOB (data->local_job), progress);
 
-  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);
+      if (usec_remaining == 0)
+        udisks_job_set_expected_end_time (UDISKS_JOB (data->local_job), 0);
+      else
+        udisks_job_set_expected_end_time (UDISKS_JOB (data->local_job), usec_remaining + g_get_real_time ());
+    }
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-play_complete_sound_and_uninhibit (DialogData *data)
+play_complete_sound (DialogData *data)
 {
   const gchar *sound_message;
 
@@ -432,10 +412,10 @@ play_complete_sound_and_uninhibit (DialogData *data)
 /* ---------------------------------------------------------------------------------------------------- */
 
 static gboolean
-on_update_ui (gpointer user_data)
+on_update_job (gpointer user_data)
 {
   DialogData *data = user_data;
-  update_gui (data, FALSE);
+  update_job (data, FALSE);
   dialog_data_unref (data);
   return FALSE; /* remove source */
 }
@@ -447,10 +427,11 @@ on_show_error (gpointer user_data)
 {
   DialogData *data = user_data;
 
-  play_complete_sound_and_uninhibit (data);
+  play_complete_sound (data);
+  dialog_data_uninhibit (data);
 
   g_assert (data->copy_error != NULL);
-  gdu_utils_show_error (GTK_WINDOW (data->dialog),
+  gdu_utils_show_error (GTK_WINDOW (data->window),
                         _("Error restoring disk image"),
                         data->copy_error);
   g_clear_error (&data->copy_error);
@@ -468,12 +449,11 @@ on_success (gpointer user_data)
 {
   DialogData *data = user_data;
 
-  update_gui (data, TRUE);
-
-  gtk_widget_hide (data->cancel_button);
-  gtk_widget_show (data->close_button);
+  update_job (data, TRUE);
 
-  play_complete_sound_and_uninhibit (data);
+  play_complete_sound (data);
+  dialog_data_uninhibit (data);
+  dialog_data_complete_and_unref (data);
 
   dialog_data_unref (data);
   return FALSE; /* remove source */
@@ -594,7 +574,7 @@ copy_thread_func (gpointer user_data)
           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));
+            data->update_id = g_idle_add (on_update_job, dialog_data_ref (data));
           last_update_usec = now_usec;
         }
       g_mutex_unlock (&data->copy_lock);
@@ -710,6 +690,19 @@ copy_thread_func (gpointer user_data)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static void
+on_local_job_canceled (GduLocalJob  *job,
+                       gpointer      user_data)
+{
+  DialogData *data = user_data;
+  if (!data->completed)
+    {
+      dialog_data_terminate_job (data);
+      dialog_data_complete_and_unref (data);
+      update_job (data, FALSE);
+    }
+}
+
 static gboolean
 start_copying (DialogData *data)
 {
@@ -717,7 +710,6 @@ start_copying (DialogData *data)
   gboolean ret = FALSE;
   GFileInfo *info;
   GError *error;
-  gchar *uri = NULL;
 
   error = NULL;
   file = gtk_file_chooser_get_file (GTK_FILE_CHOOSER (data->image_fcbutton));
@@ -749,9 +741,6 @@ start_copying (DialogData *data)
   data->file_size = g_file_info_get_size (info);
   g_object_unref (info);
 
-  uri = gdu_utils_get_pretty_uri (file);
-  gtk_label_set_text (GTK_LABEL (data->source_label), uri);
-
   data->inhibit_cookie = gtk_application_inhibit (GTK_APPLICATION (gdu_window_get_application (data->window)),
                                                   GTK_WINDOW (data->dialog),
                                                   GTK_APPLICATION_INHIBIT_SUSPEND |
@@ -759,6 +748,19 @@ start_copying (DialogData *data)
                                                   /* Translators: Reason why suspend/logout is being inhibited */
                                                   C_("restore-inhibit-message", "Copying disk image to device"));
 
+  data->local_job = gdu_application_create_local_job (gdu_window_get_application (data->window),
+                                                      data->object);
+  udisks_job_set_operation (UDISKS_JOB (data->local_job), "x-gdu-restore-disk-image");
+  /* Translators: this is the description of the job */
+  gdu_local_job_set_description (data->local_job, _("Restoring Disk Image"));
+  udisks_job_set_progress_valid (UDISKS_JOB (data->local_job), TRUE);
+  udisks_job_set_cancelable (UDISKS_JOB (data->local_job), TRUE);
+  g_signal_connect (data->local_job, "canceled",
+                    G_CALLBACK (on_local_job_canceled),
+                    data);
+
+  dialog_data_hide (data);
+
   g_thread_new ("copy-disk-image-thread",
                 copy_thread_func,
                 dialog_data_ref (data));
@@ -766,7 +768,6 @@ start_copying (DialogData *data)
 
  out:
   g_clear_object (&file);
-  g_free (uri);
   return ret;
 }
 
@@ -816,16 +817,6 @@ on_dialog_response (GtkDialog     *dialog,
       /* 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->image_fcbutton));
 
-      /* now that we advance to the "copy stage", hide/show some more widgets */
-      gtk_widget_hide (data->image_key_label);
-      gtk_widget_hide (data->image_fcbutton);
-      gtk_widget_hide (data->start_copying_button);
-      gtk_widget_show (data->source_key_label);
-      gtk_widget_show (data->source_label);
-      gtk_widget_show (data->copying_key_label);
-      gtk_widget_show (data->copying_vbox);
-      gtk_widget_hide (data->infobar_vbox);
-
       /* ensure the device is unused (e.g. unmounted) before copying data to it... */
       gdu_window_ensure_unused (data->window,
                                 data->object,
@@ -834,10 +825,6 @@ on_dialog_response (GtkDialog     *dialog,
                                 data);
       break;
 
-    case GTK_RESPONSE_CLOSE:
-      dialog_data_complete_and_unref (data);
-      break;
-
     default: /* explicit fallthrough */
     case GTK_RESPONSE_CANCEL:
       dialog_data_complete_and_unref (data);
@@ -897,18 +884,12 @@ gdu_restore_disk_image_dialog_show (GduWindow    *window,
   g_signal_connect (data->image_fcbutton, "notify",
                     G_CALLBACK (on_notify), data);
 
-  /* hide widgets not to be shown initially */
-  gtk_widget_hide (data->source_key_label);
-  gtk_widget_hide (data->source_label);
-  gtk_widget_hide (data->copying_key_label);
-  gtk_widget_hide (data->copying_vbox);
-  gtk_widget_hide (data->close_button);
+  data->response_signal_handler_id = g_signal_connect (data->dialog,
+                                                       "response",
+                                                       G_CALLBACK (on_dialog_response),
+                                                       data);
 
-  g_signal_connect (data->dialog,
-                    "response",
-                    G_CALLBACK (on_dialog_response),
-                    data);
-  gtk_widget_show (data->dialog);
+  gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (window));
   gtk_window_present (GTK_WINDOW (data->dialog));
 }
 
diff --git a/src/disks/gdutypes.h b/src/disks/gdutypes.h
index ec62f36..4d0e75b 100644
--- a/src/disks/gdutypes.h
+++ b/src/disks/gdutypes.h
@@ -43,6 +43,9 @@ typedef struct _GduEstimator GduEstimator;
 struct GduDVDSupport;
 typedef struct GduDVDSupport GduDVDSupport;
 
+struct GduLocalJob;
+typedef struct GduLocalJob GduLocalJob;
+
 G_END_DECLS
 
 #endif /* __GDU_TYPES_H__ */
diff --git a/src/disks/gduvolumegrid.c b/src/disks/gduvolumegrid.c
index 3ba5c62..effde28 100644
--- a/src/disks/gduvolumegrid.c
+++ b/src/disks/gduvolumegrid.c
@@ -16,6 +16,7 @@
 #include <stdlib.h>
 
 #include "gduvolumegrid.h"
+#include "gduapplication.h"
 
 /* ---------------------------------------------------------------------------------------------------- */
 
@@ -83,6 +84,7 @@ struct _GduVolumeGrid
 {
   GtkWidget parent;
 
+  GduApplication *application;
   UDisksClient *client;
   UDisksObject *block_object;
 
@@ -107,7 +109,7 @@ struct _GduVolumeGridClass
 enum
 {
   PROP_0,
-  PROP_CLIENT,
+  PROP_APPLICATION,
   PROP_BLOCK_OBJECT,
   PROP_NO_MEDIA_STRING,
 };
@@ -173,8 +175,8 @@ gdu_volume_grid_get_property (GObject    *object,
 
   switch (property_id)
     {
-    case PROP_CLIENT:
-      g_value_set_object (value, grid->client);
+    case PROP_APPLICATION:
+      g_value_set_object (value, grid->application);
       break;
 
     case PROP_BLOCK_OBJECT:
@@ -201,8 +203,9 @@ gdu_volume_grid_set_property (GObject      *object,
 
   switch (property_id)
     {
-    case PROP_CLIENT:
-      grid->client = g_value_dup_object (value);
+    case PROP_APPLICATION:
+      grid->application = g_value_dup_object (value);
+      grid->client = gdu_application_get_client (grid->application);
       break;
 
     case PROP_BLOCK_OBJECT:
@@ -504,11 +507,11 @@ gdu_volume_grid_class_init (GduVolumeGridClass *klass)
   gtkwidget_class->draw                 = gdu_volume_grid_draw;
 
   g_object_class_install_property (gobject_class,
-                                   PROP_CLIENT,
-                                   g_param_spec_object ("client",
-                                                        "Client",
-                                                        "The UDisksClient to use",
-                                                        UDISKS_TYPE_CLIENT,
+                                   PROP_APPLICATION,
+                                   g_param_spec_object ("application",
+                                                        "Application",
+                                                        "The GduApplication to use",
+                                                        GDU_TYPE_APPLICATION,
                                                         G_PARAM_READABLE |
                                                         G_PARAM_WRITABLE |
                                                         G_PARAM_CONSTRUCT_ONLY |
@@ -554,11 +557,11 @@ gdu_volume_grid_init (GduVolumeGrid *grid)
 }
 
 GtkWidget *
-gdu_volume_grid_new (UDisksClient *client)
+gdu_volume_grid_new (GduApplication *application)
 {
-  g_return_val_if_fail (UDISKS_IS_CLIENT (client), NULL);
+  g_return_val_if_fail (GDU_IS_APPLICATION (application), NULL);
   return GTK_WIDGET (g_object_new (GDU_TYPE_VOLUME_GRID,
-                                   "client", client,
+                                   "application", application,
                                    NULL));
 }
 
@@ -1613,6 +1616,7 @@ grid_element_set_details (GduVolumeGrid  *grid,
           element->show_configured = TRUE;
 
         jobs = udisks_client_get_jobs_for_object (grid->client, element->object);
+        jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (grid->application, element->object));
         element->show_spinner = (jobs != NULL);
         g_list_foreach (jobs, (GFunc) g_object_unref, NULL);
         g_list_free (jobs);
diff --git a/src/disks/gduvolumegrid.h b/src/disks/gduvolumegrid.h
index 0489d48..de24e64 100644
--- a/src/disks/gduvolumegrid.h
+++ b/src/disks/gduvolumegrid.h
@@ -20,7 +20,7 @@ G_BEGIN_DECLS
 #define GDU_IS_VOLUME_GRID(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_VOLUME_GRID))
 
 GType                     gdu_volume_grid_get_type              (void) G_GNUC_CONST;
-GtkWidget*                gdu_volume_grid_new                   (UDisksClient        *client);
+GtkWidget*                gdu_volume_grid_new                   (GduApplication      *application);
 void                      gdu_volume_grid_set_block_object      (GduVolumeGrid       *grid,
                                                                  UDisksObject        *block_device);
 UDisksObject             *gdu_volume_grid_get_block_object      (GduVolumeGrid      *grid);
diff --git a/src/disks/gduwindow.c b/src/disks/gduwindow.c
index 821a0a7..d0840f7 100644
--- a/src/disks/gduwindow.c
+++ b/src/disks/gduwindow.c
@@ -40,6 +40,7 @@
 #include "gdumdraiddisksdialog.h"
 #include "gducreateraidarraydialog.h"
 #include "gduerasemultipledisksdialog.h"
+#include "gdulocaljob.h"
 
 struct _GduWindow
 {
@@ -1287,7 +1288,7 @@ gdu_window_constructed (GObject *object)
   //gtk_style_context_add_class (context, GTK_STYLE_CLASS_INLINE_TOOLBAR);
   gtk_style_context_set_junction_sides (context, GTK_JUNCTION_BOTTOM);
 
-  window->model = gdu_device_tree_model_new (window->client);
+  window->model = gdu_device_tree_model_new (window->application);
 
   gtk_tree_view_set_model (GTK_TREE_VIEW (window->device_tree_treeview), GTK_TREE_MODEL (window->model));
   gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (window->model),
@@ -1398,7 +1399,7 @@ gdu_window_constructed (GObject *object)
 
   /* set up non-standard widgets that isn't in the .ui file */
 
-  window->volume_grid = gdu_volume_grid_new (window->client);
+  window->volume_grid = gdu_volume_grid_new (window->application);
   gtk_widget_show (window->volume_grid);
   gtk_box_pack_start (GTK_BOX (window->devtab_grid_hbox),
                       window->volume_grid,
@@ -1574,13 +1575,13 @@ gdu_window_constructed (GObject *object)
 
   /* cancel-button for drive job */
   g_signal_connect (window->devtab_drive_job_cancel_button,
-                    "clicked",
+                    "pressed",
                     G_CALLBACK (on_drive_job_cancel_button_clicked),
                     window);
 
   /* cancel-button for job */
   g_signal_connect (window->devtab_job_cancel_button,
-                    "clicked",
+                    "pressed",
                     G_CALLBACK (on_job_cancel_button_clicked),
                     window);
 
@@ -1982,41 +1983,84 @@ static gchar *
 get_job_progress_text (GduWindow *window,
                        UDisksJob *job)
 {
-  gchar *s;
-  gchar *desc;
+  gchar *s = NULL, *tmp;
   gint64 expected_end_time_usec;
-
-  desc = udisks_client_get_job_description (window->client, job);
+  guint64 rate;
+  guint64 bytes;
 
   expected_end_time_usec = udisks_job_get_expected_end_time (job);
+  rate = udisks_job_get_rate (job);
+  bytes = udisks_job_get_bytes (job);
   if (expected_end_time_usec > 0)
     {
       gint64 usec_left;
       gchar *s2, *s3;
 
       usec_left = expected_end_time_usec - g_get_real_time ();
-      if (usec_left < 0)
+      if (usec_left < 1)
+        usec_left = 1;
+      s2 = gdu_utils_format_duration_usec (usec_left, GDU_FORMAT_DURATION_FLAGS_NO_SECONDS);
+      if (rate > 0)
         {
-          /* Translators: Shown instead of e.g. "10 seconds remaining" when we've passed
-           * the expected end time...
+          s3 = g_format_size (rate);
+          /* Translators: Used for job progress.
+           *              The first %s is the estimated amount of time remaining (ex. "1 minute" or "5 minutes").
+           *              The second %s is the average amount of bytes transfered per second (ex. "8.9 MB").
            */
-          s3 = g_strdup_printf (C_("job-remaining-exceeded", "Almost doneâ"));
+          s = g_strdup_printf (C_("job-remaining-with-rate", "%s remaining (%s/sec)"), s2, s3);
+          g_free (s3);
         }
       else
         {
-          s2 = gdu_utils_format_duration_usec (usec_left, GDU_FORMAT_DURATION_FLAGS_NONE);
-          s3 = g_strdup_printf (C_("job-remaining", "%s remaining"), s2);
+          /* Translators: Used for job progress.
+           *              The first %s is the estimated amount of time remaining (ex. "1 minute" or "5 minutes").
+           */
+          s = g_strdup_printf (C_("job-remaining", "%s remaining"), s2);
+        }
+      g_free (s2);
+
+      if (bytes > 0 && udisks_job_get_progress_valid (job))
+        {
+          guint64 bytes_done = bytes * udisks_job_get_progress (job);
+          s2 = g_format_size (bytes_done);
+          s3 = g_format_size (bytes);
+          tmp = s;
+          /* Translators: Used to convey job progress where the amount of bytes to process is known.
+           *              The first %s is the amount of bytes processed (ex. "650 MB").
+           *              The second %s is the total amount of bytes to process (ex. "8.5 GB").
+           *              The third %s is the estimated amount of time remaining including speed (if known) (ex. "1 minute remaining", "5 minutes remaining (42.3 MB/s)", "Less than a minute remaining").
+           */
+          s = g_strdup_printf (_("%s of %s â %s"), s2, s3, s);
+          g_free (tmp);
+          g_free (s3);
           g_free (s2);
         }
-      s = g_strdup_printf ("<small>%s â %s</small>", desc, s3);
-      g_free (s3);
     }
-  else
+
+  if (GDU_IS_LOCAL_JOB (job))
     {
-      s = g_strdup_printf ("<small>%s</small>", desc);
+      const gchar *extra_markup = gdu_local_job_get_extra_markup (GDU_LOCAL_JOB (job));
+      if (extra_markup != NULL)
+        {
+          if (s != NULL)
+            {
+              tmp = s;
+              s = g_strdup_printf ("%s\n%s", s, extra_markup);
+              g_free (tmp);
+            }
+          else
+            {
+              s = g_strdup (extra_markup);
+            }
+        }
     }
 
-  g_free (desc);
+  if (s != NULL)
+    {
+      tmp = s;
+      s = g_strdup_printf ("<small>%s</small>", s);
+      g_free (tmp);
+    }
 
   return s;
 }
@@ -2024,108 +2068,178 @@ get_job_progress_text (GduWindow *window,
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-update_drive_jobs (GduWindow *window,
-                   GList     *jobs)
+update_jobs (GduWindow *window,
+             GList     *jobs,
+             gboolean   is_volume)
 {
+  GtkWidget *label = window->devtab_drive_job_label;
+  GtkWidget *grid = window->devtab_drive_job_grid;
+  GtkWidget *progressbar = window->devtab_drive_job_progressbar;
+  GtkWidget *remaining_label = window->devtab_drive_job_remaining_label;
+  GtkWidget *no_progress_label = window->devtab_drive_job_no_progress_label;
+  GtkWidget *cancel_button = window->devtab_drive_job_cancel_button;
+
+  if (is_volume)
+    {
+      label = window->devtab_job_label;
+      grid = window->devtab_job_grid;
+      progressbar = window->devtab_job_progressbar;
+      remaining_label = window->devtab_job_remaining_label;
+      no_progress_label = window->devtab_job_no_progress_label;
+      cancel_button = window->devtab_job_cancel_button;
+    }
+
   if (jobs == NULL)
     {
-      gtk_widget_hide (window->devtab_drive_job_label);
-      gtk_widget_hide (window->devtab_drive_job_grid);
+      gtk_widget_hide (label);
+      gtk_widget_hide (grid);
     }
   else
     {
       UDisksJob *job = UDISKS_JOB (jobs->data);
-      gchar *s;
+      gchar *s, *s2;
 
-      gtk_widget_show (window->devtab_drive_job_label);
-      gtk_widget_show (window->devtab_drive_job_grid);
+      gtk_widget_show (label);
+      gtk_widget_show (grid);
       if (udisks_job_get_progress_valid (job))
         {
           gdouble progress = udisks_job_get_progress (job);
-          gtk_widget_show (window->devtab_drive_job_progressbar);
-          gtk_widget_show (window->devtab_drive_job_remaining_label);
-          gtk_widget_hide (window->devtab_drive_job_no_progress_label);
+          gtk_widget_show (progressbar);
+          gtk_widget_hide (no_progress_label);
 
-          gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (window->devtab_drive_job_progressbar), progress);
+          gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (progressbar), progress);
 
-          s = g_strdup_printf ("%2.1f%%", 100.0 * progress);
-          gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR (window->devtab_drive_job_progressbar), TRUE);
-          gtk_progress_bar_set_text (GTK_PROGRESS_BAR (window->devtab_drive_job_progressbar), s);
+          if (GDU_IS_LOCAL_JOB (job))
+            s2 = g_strdup (gdu_local_job_get_description (GDU_LOCAL_JOB (job)));
+          else
+            s2 = udisks_client_get_job_description (window->client, job);
+          /* Translators: Used in job progress bar.
+           *              The %s is the job description (e.g. "Erasing Device").
+           *              The %f is the completion percentage (between 0.0 and 100.0).
+           */
+          s = g_strdup_printf (_("%s: %2.1f%%"),
+                                s2,
+                                100.0 * progress);
+          g_free (s2);
+          gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR (progressbar), TRUE);
+          gtk_progress_bar_set_text (GTK_PROGRESS_BAR (progressbar), s);
           g_free (s);
 
           s = get_job_progress_text (window, job);
-          gtk_label_set_markup (GTK_LABEL (window->devtab_drive_job_remaining_label), s);
-          g_free (s);
+          if (s != NULL)
+            {
+              gtk_widget_show (remaining_label);
+              gtk_label_set_markup (GTK_LABEL (remaining_label), s);
+              g_free (s);
+            }
+          else
+            {
+              gtk_widget_hide (remaining_label);
+            }
         }
       else
         {
-          gtk_widget_hide (window->devtab_drive_job_progressbar);
-          gtk_widget_hide (window->devtab_drive_job_remaining_label);
-          gtk_widget_show (window->devtab_drive_job_no_progress_label);
-          s = udisks_client_get_job_description (window->client, job);
-          gtk_label_set_text (GTK_LABEL (window->devtab_drive_job_no_progress_label), s);
+          gtk_widget_hide (progressbar);
+          gtk_widget_hide (remaining_label);
+          gtk_widget_show (no_progress_label);
+          if (GDU_IS_LOCAL_JOB (job))
+            s = g_strdup (gdu_local_job_get_description (GDU_LOCAL_JOB (job)));
+          else
+            s = udisks_client_get_job_description (window->client, job);
+          gtk_label_set_text (GTK_LABEL (no_progress_label), s);
           g_free (s);
         }
       if (udisks_job_get_cancelable (job))
-        gtk_widget_show (window->devtab_drive_job_cancel_button);
+        gtk_widget_show (cancel_button);
       else
-        gtk_widget_hide (window->devtab_drive_job_cancel_button);
+        gtk_widget_hide (cancel_button);
     }
 }
 
+static void
+update_drive_jobs (GduWindow *window,
+                   GList     *jobs)
+{
+  update_jobs (window, jobs, FALSE);
+}
+
+static void
+update_volume_jobs (GduWindow *window,
+                    GList     *jobs)
+{
+  update_jobs (window, jobs, TRUE);
+}
+
 /* ---------------------------------------------------------------------------------------------------- */
 
 static void
-update_drive_part_for_block (GduWindow      *window,
-                             UDisksBlock    *block,       /* should be the whole disk */
-                             ShowFlags      *show_flags)
+update_generic_drive_bits (GduWindow      *window,
+                           UDisksBlock    *block,          /* should be the whole disk */
+                           GList          *jobs,           /* jobs not specific to @block */
+                           ShowFlags      *show_flags)
 {
-  gchar *s = NULL;
-  guint64 size = 0;
-  UDisksObject *object = NULL;
-  UDisksPartitionTable *partition_table = NULL;
+  if (block != NULL)
+    {
+      gchar *s = NULL;
+      guint64 size = 0;
+      UDisksObject *object = NULL;
+      UDisksPartitionTable *partition_table = NULL;
 
-  object = (UDisksObject *) g_dbus_interface_get_object (G_DBUS_INTERFACE (block));
-  if (object == NULL)
-    goto out;
-  partition_table = udisks_object_get_partition_table (object);
+      object = (UDisksObject *) g_dbus_interface_get_object (G_DBUS_INTERFACE (block));
+      partition_table = udisks_object_get_partition_table (object);
 
-  gdu_volume_grid_set_no_media_string (GDU_VOLUME_GRID (window->volume_grid),
-                                       _("Block device is empty"));
+      gdu_volume_grid_set_no_media_string (GDU_VOLUME_GRID (window->volume_grid),
+                                           _("Block device is empty"));
 
-  size = udisks_block_get_size (block);
+      size = udisks_block_get_size (block);
 
-  /* -------------------------------------------------- */
-  /* 'Size' field */
+      /* -------------------------------------------------- */
+      /* 'Size' field */
 
-  set_size (window,
-            "devtab-drive-size-label",
-            "devtab-drive-size-value-label",
-            size, SET_MARKUP_FLAGS_HYPHEN_IF_EMPTY);
+      set_size (window,
+                "devtab-drive-size-label",
+                "devtab-drive-size-value-label",
+                size, SET_MARKUP_FLAGS_HYPHEN_IF_EMPTY);
 
-  /* -------------------------------------------------- */
-  /* 'Partitioning' field - only show if actually partitioned */
+      /* -------------------------------------------------- */
+      /* 'Partitioning' field - only show if actually partitioned */
 
-  s = NULL;
-  if (partition_table != NULL)
-    {
-      const gchar *table_type = udisks_partition_table_get_type_ (partition_table);
-      s = g_strdup (udisks_client_get_partition_table_type_for_display (window->client, table_type));
-      if (s == NULL)
+      s = NULL;
+      if (partition_table != NULL)
         {
-          /* Translators: Shown for unknown partitioning type. The first %s is the low-level type. */
-          s = g_strdup_printf (C_("partitioning", "Unknown (%s)"), table_type);
+          const gchar *table_type = udisks_partition_table_get_type_ (partition_table);
+          s = g_strdup (udisks_client_get_partition_table_type_for_display (window->client, table_type));
+          if (s == NULL)
+            {
+              /* Translators: Shown for unknown partitioning type. The first %s is the low-level type. */
+              s = g_strdup_printf (C_("partitioning", "Unknown (%s)"), table_type);
+            }
         }
+      set_markup (window,
+                  "devtab-drive-partitioning-label",
+                  "devtab-drive-partitioning-value-label",
+                  s, SET_MARKUP_FLAGS_NONE);
+      g_free (s);
+
+      g_clear_object (&partition_table);
     }
-  set_markup (window,
-              "devtab-drive-partitioning-label",
-              "devtab-drive-partitioning-value-label",
-              s, SET_MARKUP_FLAGS_NONE);
-  g_free (s);
 
- out:
-  /* cleanup */
-  g_clear_object (&partition_table);
+  /* -------------------------------------------------- */
+  /* 'Job' field - only shown if a job is running */
+
+  /* if there are no given jobs, look at the block object */
+  if (jobs == NULL && block != NULL)
+    {
+      UDisksObject *block_object = (UDisksObject *) g_dbus_interface_get_object (G_DBUS_INTERFACE (block));
+      jobs = udisks_client_get_jobs_for_object (window->client, block_object);
+      jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (window->application, block_object));
+      update_drive_jobs (window, jobs);
+      g_list_free_full (jobs, g_object_unref);
+    }
+  else
+    {
+      update_drive_jobs (window, jobs);
+    }
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -2232,8 +2346,9 @@ update_device_page_for_mdraid (GduWindow      *window,
         show_flags->drive_buttons |= SHOW_FLAGS_DRIVE_BUTTONS_RAID_START;
     }
 
-  if (block != NULL)
-    update_drive_part_for_block (window, block, show_flags);
+  jobs = udisks_client_get_jobs_for_object (window->client, object);
+  jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (window->application, object));
+  update_generic_drive_bits (window, block, jobs, show_flags);
 
   gtk_image_set_from_gicon (GTK_IMAGE (window->devtab_drive_image),
                             udisks_object_info_get_icon (info),
@@ -2511,23 +2626,6 @@ update_device_page_for_mdraid (GduWindow      *window,
     }
 
   /* -------------------------------------------------- */
-  /* 'Job' field - only shown if a job is running */
-
-  jobs = udisks_client_get_jobs_for_object (window->client, object);
-  /* if there are no jobs on the RAID array, look at the block object if it's partitioned
-   * (because: if it's not partitioned, we'll see the job in Volumes below so no need to show it here)
-   */
-  if (jobs == NULL && block != NULL)
-    {
-      UDisksObject *block_object = (UDisksObject *) g_dbus_interface_get_object (G_DBUS_INTERFACE (block));
-      if (udisks_object_peek_partition_table (block_object) != NULL)
-        {
-          jobs = udisks_client_get_jobs_for_object (window->client, block_object);
-        }
-    }
-  update_drive_jobs (window, jobs);
-
-  /* -------------------------------------------------- */
 
   /* Show MDRaid-specific items */
   gtk_widget_show (GTK_WIDGET (window->generic_drive_menu_item_mdraid_sep_1));
@@ -2583,8 +2681,9 @@ update_device_page_for_drive (GduWindow      *window,
   if (blocks != NULL)
     block = udisks_object_peek_block (UDISKS_OBJECT (blocks->data));
 
-  if (block != NULL)
-    update_drive_part_for_block (window, block, show_flags);
+  jobs = udisks_client_get_jobs_for_object (window->client, object);
+  jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (window->application, object));
+  update_generic_drive_bits (window, block, jobs, show_flags);
 
   gdu_volume_grid_set_no_media_string (GDU_VOLUME_GRID (window->volume_grid),
                                        _("No Media"));
@@ -2782,14 +2881,6 @@ update_device_page_for_drive (GduWindow      *window,
                   SET_MARKUP_FLAGS_HYPHEN_IF_EMPTY);
     }
 
-  jobs = udisks_client_get_jobs_for_object (window->client, object);
-  if (jobs != NULL)
-    {
-      update_drive_jobs (window, jobs);
-      g_list_foreach (jobs, (GFunc) g_object_unref, NULL);
-      g_list_free (jobs);
-    }
-
   if (udisks_drive_get_ejectable (drive))
     {
       show_flags->drive_buttons |= SHOW_FLAGS_DRIVE_BUTTONS_EJECT;
@@ -2821,6 +2912,9 @@ update_device_page_for_drive (GduWindow      *window,
   g_list_foreach (blocks, (GFunc) g_object_unref, NULL);
   g_list_free (blocks);
   g_clear_object (&info);
+
+  g_list_foreach (jobs, (GFunc) g_object_unref, NULL);
+  g_list_free (jobs);
 }
 
 /* ---------------------------------------------------------------------------------------------------- */
@@ -2861,7 +2955,7 @@ update_device_page_for_loop (GduWindow      *window,
   gtk_widget_show (window->devtab_drive_buttonbox);
   gtk_widget_show (window->devtab_drive_generic_button);
 
-  update_drive_part_for_block (window, block, show_flags);
+  update_generic_drive_bits (window, block, NULL, show_flags);
 
   /* -------------------------------------------------- */
   /* 'Auto-clear' and 'Backing File' fields */
@@ -2928,7 +3022,7 @@ update_device_page_for_fake_block (GduWindow      *window,
   gtk_widget_show (window->devtab_drive_buttonbox);
   gtk_widget_show (window->devtab_drive_generic_button);
 
-  update_drive_part_for_block (window, block, show_flags);
+  update_generic_drive_bits (window, block, NULL, show_flags);
 
   /* cleanup */
   g_clear_object (&info);
@@ -2991,7 +3085,7 @@ update_device_page_for_block (GduWindow          *window,
   gchar *in_use_markup = NULL;
   UDisksObject *drive_object;
   UDisksDrive *drive = NULL;
-  GList *jobs;
+  GList *jobs = NULL;
 
   read_only = udisks_block_get_read_only (block);
   partition = udisks_object_peek_partition (object);
@@ -3238,50 +3332,15 @@ update_device_page_for_block (GduWindow          *window,
         show_flags->drive_buttons |= SHOW_FLAGS_DRIVE_BUTTONS_EJECT;
     }
 
-  jobs = udisks_client_get_jobs_for_object (window->client, object);
-  if (jobs == NULL)
-    {
-      gtk_widget_hide (window->devtab_job_label);
-      gtk_widget_hide (window->devtab_job_grid);
-    }
-  else
+  /* Only show jobs if the volume is a partition (if it's not, we're already showing
+   * the jobs in the drive section)
+   */
+  if (partition != NULL)
     {
-      UDisksJob *job = UDISKS_JOB (jobs->data);
-
-      gtk_widget_show (window->devtab_job_label);
-      gtk_widget_show (window->devtab_job_grid);
-      if (udisks_job_get_progress_valid (job))
-        {
-          gdouble progress = udisks_job_get_progress (job);
-          gtk_widget_show (window->devtab_job_progressbar);
-          gtk_widget_show (window->devtab_job_remaining_label);
-          gtk_widget_hide (window->devtab_job_no_progress_label);
-
-          gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (window->devtab_job_progressbar), progress);
-
-          s = g_strdup_printf ("%2.1f%%", 100.0 * progress);
-          gtk_progress_bar_set_show_text (GTK_PROGRESS_BAR (window->devtab_job_progressbar), TRUE);
-          gtk_progress_bar_set_text (GTK_PROGRESS_BAR (window->devtab_job_progressbar), s);
-          g_free (s);
-
-          s = get_job_progress_text (window, job);
-          gtk_label_set_markup (GTK_LABEL (window->devtab_job_remaining_label), s);
-          g_free (s);
-        }
-      else
-        {
-          gtk_widget_hide (window->devtab_job_progressbar);
-          gtk_widget_hide (window->devtab_job_remaining_label);
-          gtk_widget_show (window->devtab_job_no_progress_label);
-          s = udisks_client_get_job_description (window->client, job);
-          gtk_label_set_text (GTK_LABEL (window->devtab_job_no_progress_label), s);
-          g_free (s);
-        }
-      if (udisks_job_get_cancelable (job))
-        gtk_widget_show (window->devtab_job_cancel_button);
-      else
-        gtk_widget_hide (window->devtab_job_cancel_button);
+      jobs = udisks_client_get_jobs_for_object (window->client, object);
+      jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (window->application, object));
     }
+  update_volume_jobs (window, jobs);
   g_list_foreach (jobs, (GFunc) g_object_unref, NULL);
   g_list_free (jobs);
   g_clear_object (&partition_table);
@@ -4651,6 +4710,7 @@ on_drive_job_cancel_button_clicked (GtkButton   *button,
   GList *jobs;
 
   jobs = udisks_client_get_jobs_for_object (window->client, window->current_object);
+  jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (window->application, window->current_object));
   /* if there are no jobs on the drive, look at the first block object */
   if (jobs == NULL)
     {
@@ -4661,6 +4721,7 @@ on_drive_job_cancel_button_clicked (GtkButton   *button,
         {
           UDisksObject *block_object = UDISKS_OBJECT (blocks->data);
           jobs = udisks_client_get_jobs_for_object (window->client, block_object);
+          jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (window->application, block_object));
         }
       g_list_foreach (blocks, (GFunc) g_object_unref, NULL);
       g_list_free (blocks);
@@ -4668,11 +4729,18 @@ on_drive_job_cancel_button_clicked (GtkButton   *button,
   if (jobs != NULL)
     {
       UDisksJob *job = UDISKS_JOB (jobs->data);
-      udisks_job_call_cancel (job,
-                              g_variant_new ("a{sv}", NULL), /* options */
-                              NULL, /* cancellable */
-                              (GAsyncReadyCallback) drive_job_cancel_cb,
-                              g_object_ref (window));
+      if (GDU_IS_LOCAL_JOB (job))
+        {
+          gdu_local_job_canceled (GDU_LOCAL_JOB (job));
+        }
+      else
+        {
+          udisks_job_call_cancel (job,
+                                  g_variant_new ("a{sv}", NULL), /* options */
+                                  NULL, /* cancellable */
+                                  (GAsyncReadyCallback) drive_job_cancel_cb,
+                                  g_object_ref (window));
+        }
     }
   g_list_foreach (jobs, (GFunc) g_object_unref, NULL);
   g_list_free (jobs);
@@ -4710,14 +4778,22 @@ on_job_cancel_button_clicked (GtkButton    *button,
   g_assert (object != NULL);
 
   jobs = udisks_client_get_jobs_for_object (window->client, object);
+  jobs = g_list_concat (jobs, gdu_application_get_local_jobs_for_object (window->application, object));
   if (jobs != NULL)
     {
       UDisksJob *job = UDISKS_JOB (jobs->data);
-      udisks_job_call_cancel (job,
-                              g_variant_new ("a{sv}", NULL), /* options */
-                              NULL, /* cancellable */
-                              (GAsyncReadyCallback) job_cancel_cb,
-                              g_object_ref (window));
+      if (GDU_IS_LOCAL_JOB (job))
+        {
+          gdu_local_job_canceled (GDU_LOCAL_JOB (job));
+        }
+      else
+        {
+          udisks_job_call_cancel (job,
+                                  g_variant_new ("a{sv}", NULL), /* options */
+                                  NULL, /* cancellable */
+                                  (GAsyncReadyCallback) job_cancel_cb,
+                                  g_object_ref (window));
+        }
     }
   g_list_foreach (jobs, (GFunc) g_object_unref, NULL);
   g_list_free (jobs);



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