[gnome-disk-utility/udisks2-port] Estimate speed and duration when creating/restoring disk images



commit a00ab1bee946a8929b838b7b1726a3015ca5d687
Author: David Zeuthen <davidz redhat com>
Date:   Tue Dec 6 16:58:21 2011 -0500

    Estimate speed and duration when creating/restoring disk images
    
    It's very frustrating to not have this, so here goes
    
     http://people.freedesktop.org/~david/gdu2-create-disk-image-progress.png
     http://people.freedesktop.org/~david/gdu2-restore-disk-image-progress.png
    
    Signed-off-by: David Zeuthen <davidz redhat com>

 data/ui/create-disk-image-dialog.ui        |   26 +++-
 data/ui/restore-disk-image-dialog.ui       |  141 +++++----------
 src/palimpsest/Makefile.am                 |    1 +
 src/palimpsest/gduatasmartdialog.c         |   43 +----
 src/palimpsest/gducreatediskimagedialog.c  |   47 +++++-
 src/palimpsest/gduestimator.c              |  270 ++++++++++++++++++++++++++++
 src/palimpsest/gduestimator.h              |   47 +++++
 src/palimpsest/gdurestorediskimagedialog.c |   50 +++++-
 src/palimpsest/gdutypes.h                  |    3 +
 src/palimpsest/gduutils.c                  |   42 +++++
 src/palimpsest/gduutils.h                  |    2 +
 11 files changed, 526 insertions(+), 146 deletions(-)
---
diff --git a/data/ui/create-disk-image-dialog.ui b/data/ui/create-disk-image-dialog.ui
index d0575a9..8a55fbd 100644
--- a/data/ui/create-disk-image-dialog.ui
+++ b/data/ui/create-disk-image-dialog.ui
@@ -193,9 +193,33 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkProgressBar" id="copying_progressbar">
+                  <object class="GtkVBox" id="vbox2">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkProgressBar" id="copying_progressbar">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="copying_progress_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>
diff --git a/data/ui/restore-disk-image-dialog.ui b/data/ui/restore-disk-image-dialog.ui
index e1fb547..0323d15 100644
--- a/data/ui/restore-disk-image-dialog.ui
+++ b/data/ui/restore-disk-image-dialog.ui
@@ -86,100 +86,6 @@
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
                     <property name="orientation">vertical</property>
-                    <child>
-                      <object class="GtkInfoBar" id="error_infobar">
-                        <property name="message_type">error</property>
-                        <property name="no_show_all">True</property>
-                        <child internal-child="content_area">
-                          <object class="GtkBox" id="vbox3">
-                            <property name="can_focus">False</property>
-                            <property name="orientation">horizontal</property>
-                            <property name="spacing">6</property>
-                            <child>
-                              <object class="GtkImage" id="image1">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="xalign">1.0</property>
-                                <property name="stock">gtk-dialog-error</property>
-                                <property name="icon-size">1</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="error_label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Error: Disk image is too big.</property>
-                                <property name="use_markup">True</property>
-                                <property name="xalign">0.0</property>
-                                <property name="selectable">True</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">0</property>
-                      </packing>
-                    </child>
-                    <child>
-                      <object class="GtkInfoBar" id="warning_infobar">
-                        <property name="message_type">warning</property>
-                        <property name="no_show_all">True</property>
-                        <child internal-child="content_area">
-                          <object class="GtkBox" id="vbox4">
-                            <property name="can_focus">False</property>
-                            <property name="orientation">horizontal</property>
-                            <property name="spacing">6</property>
-                            <child>
-                          <object class="GtkImage" id="image2">
-                            <property name="visible">True</property>
-                            <property name="can_focus">False</property>
-                            <property name="xalign">1.0</property>
-                            <property name="stock">gtk-dialog-warning</property>
-                            <property name="icon-size">1</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="warning_label">
-                                <property name="visible">True</property>
-                                <property name="can_focus">False</property>
-                                <property name="label" translatable="yes">Warning: Disk image is too small.</property>
-                                <property name="use_markup">True</property>
-                                <property name="xalign">0.0</property>
-                                <property name="selectable">True</property>
-                              </object>
-                              <packing>
-                                <property name="expand">False</property>
-                                <property name="fill">True</property>
-                                <property name="position">1</property>
-                              </packing>
-                            </child>
-                          </object>
-                        </child>
-                      </object>
-                      <packing>
-                        <property name="expand">False</property>
-                        <property name="fill">True</property>
-                        <property name="position">1</property>
-                      </packing>
-                    </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>
@@ -227,6 +133,27 @@
                         <property name="height">1</property>
                       </packing>
                     </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
+                    <child>
+                      <placeholder/>
+                    </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>
@@ -259,9 +186,33 @@
                   </packing>
                 </child>
                 <child>
-                  <object class="GtkProgressBar" id="copying_progressbar">
+                  <object class="GtkVBox" id="vbox3">
                     <property name="visible">True</property>
                     <property name="can_focus">False</property>
+                    <property name="spacing">6</property>
+                    <child>
+                      <object class="GtkProgressBar" id="copying_progressbar">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">0</property>
+                      </packing>
+                    </child>
+                    <child>
+                      <object class="GtkLabel" id="copying_progress_label">
+                        <property name="visible">True</property>
+                        <property name="can_focus">False</property>
+                        <property name="xalign">0</property>
+                      </object>
+                      <packing>
+                        <property name="expand">True</property>
+                        <property name="fill">True</property>
+                        <property name="position">1</property>
+                      </packing>
+                    </child>
                   </object>
                   <packing>
                     <property name="expand">False</property>
diff --git a/src/palimpsest/Makefile.am b/src/palimpsest/Makefile.am
index d15096e..8b5ca93 100644
--- a/src/palimpsest/Makefile.am
+++ b/src/palimpsest/Makefile.am
@@ -44,6 +44,7 @@ palimpsest_SOURCES = 							\
 	gducreatediskimagedialog.h	gducreatediskimagedialog.c	\
 	gdurestorediskimagedialog.h	gdurestorediskimagedialog.c	\
 	gdupasswordstrengthwidget.h	gdupasswordstrengthwidget.c	\
+	gduestimator.h			gduestimator.c			\
 	$(enum_built_sources)						\
 	$(NULL)
 
diff --git a/src/palimpsest/gduatasmartdialog.c b/src/palimpsest/gduatasmartdialog.c
index d699b24..b099db0 100644
--- a/src/palimpsest/gduatasmartdialog.c
+++ b/src/palimpsest/gduatasmartdialog.c
@@ -27,6 +27,7 @@
 #include "gduapplication.h"
 #include "gduwindow.h"
 #include "gduatasmartdialog.h"
+#include "gduutils.h"
 
 enum
 {
@@ -121,41 +122,6 @@ dialog_data_free (DialogData *data)
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-
-static gchar *
-age_to_string (guint age_seconds)
-{
-  gchar *s;
-
-  if (age_seconds < 60)
-    {
-      s = g_strdup_printf (_("Less than a minute ago"));
-      //next_update = 60 - age_seconds;
-    }
-  else if (age_seconds < 60 * 60)
-    {
-      s = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
-                                      N_("%d minute ago"),
-                                      N_("%d minutes ago"),
-                                      age_seconds / 60),
-                           age_seconds / 60);
-      //next_update = 60*(age_seconds/60 + 1) - age_seconds;
-    }
-  else
-    {
-      s = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
-                                      N_("%d hour ago"),
-                                      N_("%d hours ago"),
-                                      age_seconds / 60 / 60),
-                           age_seconds / 60 / 60);
-      //next_update = 60*60*(age_seconds/(60*60) + 1) - age_seconds;
-    }
-  return s;
-}
-
-
-/* ---------------------------------------------------------------------------------------------------- */
-
 typedef struct {
         const gchar *name;
         const gchar *pretty_name;
@@ -1020,11 +986,14 @@ update_updated_label (DialogData *data)
   time_t now;
   time_t updated;
   gchar *s;
+  gchar *s2;
 
   now = time (NULL);
   updated = udisks_drive_ata_get_smart_updated (data->ata);
-  s = age_to_string (now - updated);
-  gtk_label_set_text (GTK_LABEL (data->updated_label), s);
+  s = gdu_utils_duration_to_string (now - updated, FALSE);
+  s2 = g_strdup_printf (_("%s ago"), s);
+  gtk_label_set_text (GTK_LABEL (data->updated_label), s2);
+  g_free (s2);
   g_free (s);
 }
 
diff --git a/src/palimpsest/gducreatediskimagedialog.c b/src/palimpsest/gducreatediskimagedialog.c
index f026144..0b1cff1 100644
--- a/src/palimpsest/gducreatediskimagedialog.c
+++ b/src/palimpsest/gducreatediskimagedialog.c
@@ -32,10 +32,12 @@
 #include "gduvolumegrid.h"
 #include "gduutils.h"
 #include "gducreatefilesystemwidget.h"
+#include "gduestimator.h"
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-#define BUFFER_SIZE (1024*1024)
+/* TODO: make dynamic? */
+#define BUFFER_SIZE (1*1024*1024)
 
 typedef struct
 {
@@ -53,8 +55,10 @@ typedef struct
   GtkWidget *start_copying_button;
   GtkWidget *destination_name_entry;
   GtkWidget *destination_name_fcbutton;
-  GtkWidget *copying_progressbar;
+
   GtkWidget *copying_label;
+  GtkWidget *copying_progressbar;
+  GtkWidget *copying_progress_label;
 
   GCancellable *cancellable;
   GInputStream *block_stream;
@@ -67,6 +71,8 @@ typedef struct
   guint64 total_bytes_read;
   guint64 buffer_bytes_written;
   guint64 buffer_bytes_to_write;
+
+  GduEstimator *estimator;
 } CreateDiskImageData;
 
 static CreateDiskImageData *
@@ -106,6 +112,7 @@ create_disk_image_data_unref (CreateDiskImageData *data)
       if (data->builder != NULL)
         g_object_unref (data->builder);
       g_free (data->buffer);
+      g_clear_object (&data->estimator);
       g_free (data);
     }
 }
@@ -198,6 +205,9 @@ write_cb (GOutputStream  *output_stream,
   CreateDiskImageData *data = user_data;
   GError *error;
   gssize bytes_written;
+  guint64 bytes_per_sec;
+  guint64 usec_remaining;
+  gchar *s, *s2, *s3, *s4, *s5;
 
   error = NULL;
   bytes_written = g_output_stream_write_finish (output_stream, res, &error);
@@ -214,11 +224,36 @@ write_cb (GOutputStream  *output_stream,
   data->buffer_bytes_written += bytes_written;
   data->buffer_bytes_to_write -= bytes_written;
 
-  /* update progress bar */
+  /* update progress bar and estimator */
   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (data->copying_progressbar),
                                  ((gdouble) data->total_bytes_read) / ((gdouble) data->block_size));
 
-  /* TODO: we should do fancy stuff like printing the estimated speed and time remaining */
+  gdu_estimator_add_sample (data->estimator, data->total_bytes_read);
+  bytes_per_sec = gdu_estimator_get_bytes_per_sec (data->estimator);
+  usec_remaining = gdu_estimator_get_usec_remaining (data->estimator);
+  if (bytes_per_sec > 0 && usec_remaining > 0)
+    {
+      s2 = g_format_size (data->total_bytes_read);
+      s3 = g_format_size (data->block_size);
+      s4 = gdu_utils_duration_to_string (usec_remaining / G_USEC_PER_SEC, TRUE);
+      s5 = g_format_size (bytes_per_sec);
+      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 (data->total_bytes_read);
+      s3 = g_format_size (data->block_size);
+      s = g_strdup_printf ("%s of %s copied", s2, s3);
+      g_free (s2);
+      g_free (s3);
+    }
+  s2 = g_strconcat ("<small>", s, "</small>", NULL);
+  gtk_label_set_markup (GTK_LABEL (data->copying_progress_label), s2);
+  g_free (s);
 
   write_more (data);
 
@@ -340,6 +375,7 @@ open_cb (UDisksBlock  *block,
   /* Alright, time to start copying! */
   data->cancellable = g_cancellable_new ();
   data->buffer = g_new0 (guchar, BUFFER_SIZE);
+  data->estimator = gdu_estimator_new (data->block_size);
   copy_more (data);
 
  out:
@@ -474,8 +510,9 @@ gdu_create_disk_image_dialog_show (GduWindow    *window,
   data->destination_name_entry = GTK_WIDGET (gtk_builder_get_object (data->builder, "destination_name_entry"));
   g_signal_connect (data->destination_name_entry, "notify::text", G_CALLBACK (on_notify), data);
   data->destination_name_fcbutton = GTK_WIDGET (gtk_builder_get_object (data->builder, "destination_folder_fcbutton"));
-  data->copying_progressbar = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_progressbar"));
   data->copying_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_label"));
+  data->copying_progressbar = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_progressbar"));
+  data->copying_progress_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_progress_label"));
 
   create_disk_image_populate (data);
   create_disk_image_update (data);
diff --git a/src/palimpsest/gduestimator.c b/src/palimpsest/gduestimator.c
new file mode 100644
index 0000000..824bf75
--- /dev/null
+++ b/src/palimpsest/gduestimator.c
@@ -0,0 +1,270 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#include "config.h"
+#include <glib/gi18n-lib.h>
+
+#include <math.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkx.h>
+#include <stdlib.h>
+
+#include "gduestimator.h"
+
+#define MAX_SAMPLES 100
+
+typedef struct
+{
+  gint64 time_usec;
+  guint64 value;
+} Sample;
+
+typedef struct _GduEstimatorClass GduEstimatorClass;
+struct _GduEstimator
+{
+  GObject parent;
+
+  guint64 target_bytes;
+  guint64 completed_bytes;
+  guint64 bytes_per_sec;
+  guint64 usec_remaining;
+
+  Sample samples[MAX_SAMPLES];
+  guint num_samples;
+};
+
+struct _GduEstimatorClass
+{
+  GObjectClass parent_class;
+};
+
+enum
+{
+  PROP_0,
+  PROP_TARGET_BYTES,
+  PROP_COMPLETED_BYTES,
+  PROP_BYTES_PER_SEC,
+  PROP_USEC_REMAINING,
+};
+
+G_DEFINE_TYPE (GduEstimator, gdu_estimator, G_TYPE_OBJECT)
+
+static void
+gdu_estimator_finalize (GObject *object)
+{
+  //GduEstimator *estimator = GDU_ESTIMATOR (object);
+
+  G_OBJECT_CLASS (gdu_estimator_parent_class)->finalize (object);
+}
+
+static void
+gdu_estimator_get_property (GObject    *object,
+                            guint       property_id,
+                            GValue     *value,
+                            GParamSpec *pspec)
+{
+  GduEstimator *estimator = GDU_ESTIMATOR (object);
+
+  switch (property_id)
+    {
+    case PROP_TARGET_BYTES:
+      g_value_set_uint64 (value, gdu_estimator_get_target_bytes (estimator));
+      break;
+
+    case PROP_COMPLETED_BYTES:
+      g_value_set_uint64 (value, gdu_estimator_get_completed_bytes (estimator));
+      break;
+
+    case PROP_BYTES_PER_SEC:
+      g_value_set_uint64 (value, gdu_estimator_get_bytes_per_sec (estimator));
+      break;
+
+    case PROP_USEC_REMAINING:
+      g_value_set_uint64 (value, gdu_estimator_get_usec_remaining (estimator));
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+static void
+gdu_estimator_set_property (GObject      *object,
+                            guint         property_id,
+                            const GValue *value,
+                            GParamSpec   *pspec)
+{
+  GduEstimator *estimator = GDU_ESTIMATOR (object);
+
+  switch (property_id)
+    {
+    case PROP_TARGET_BYTES:
+      estimator->target_bytes = g_value_get_uint64 (value);
+      break;
+
+    default:
+      G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
+      break;
+    }
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+update (GduEstimator *estimator)
+{
+  guint n;
+  gdouble sum_of_speeds;
+  guint num_speeds;
+  gdouble speed;
+
+  num_speeds = 0;
+  sum_of_speeds = 0.0;
+  for (n = 1; n < estimator->num_samples; n++)
+    {
+      Sample *a = &estimator->samples[n-1];
+      Sample *b = &estimator->samples[n];
+      gdouble speed;
+      speed = (b->value - a->value) / (((gdouble) (b->time_usec - a->time_usec)) / G_USEC_PER_SEC);
+      sum_of_speeds += speed;
+      num_speeds++;
+    }
+  estimator->bytes_per_sec = 0;
+  estimator->usec_remaining = 0;
+  if (num_speeds > 0)
+    {
+      speed = sum_of_speeds / num_speeds;
+      estimator->bytes_per_sec = speed;
+      if (estimator->bytes_per_sec > 0)
+        {
+          guint64 remaining_bytes = estimator->target_bytes - estimator->completed_bytes;
+          estimator->usec_remaining = G_USEC_PER_SEC * remaining_bytes / estimator->bytes_per_sec;
+        }
+    }
+
+  g_object_freeze_notify (G_OBJECT (estimator));
+  g_object_notify (G_OBJECT (estimator), "bytes-per-sec");
+  g_object_notify (G_OBJECT (estimator), "usec-remaining");
+  g_object_thaw_notify (G_OBJECT (estimator));
+}
+
+static void
+gdu_estimator_class_init (GduEstimatorClass *klass)
+{
+  GObjectClass *gobject_class;
+
+  gobject_class = G_OBJECT_CLASS (klass);
+  gobject_class->get_property = gdu_estimator_get_property;
+  gobject_class->set_property = gdu_estimator_set_property;
+  gobject_class->finalize     = gdu_estimator_finalize;
+
+  g_object_class_install_property (gobject_class, PROP_TARGET_BYTES,
+                                   g_param_spec_uint64 ("target-bytes", NULL, NULL,
+                                                        0, G_MAXUINT64, 0,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_WRITABLE |
+                                                        G_PARAM_CONSTRUCT_ONLY |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_COMPLETED_BYTES,
+                                   g_param_spec_uint64 ("completed-bytes", NULL, NULL,
+                                                        0, G_MAXUINT64, 0,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_BYTES_PER_SEC,
+                                   g_param_spec_uint64 ("bytes-per-sec", NULL, NULL,
+                                                        0, G_MAXUINT64, 0,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_STRINGS));
+
+  g_object_class_install_property (gobject_class, PROP_USEC_REMAINING,
+                                   g_param_spec_uint64 ("usec-remaining", NULL, NULL,
+                                                        0, G_MAXUINT64, 0,
+                                                        G_PARAM_READABLE |
+                                                        G_PARAM_STATIC_STRINGS));
+}
+
+static void
+gdu_estimator_init (GduEstimator *estimator)
+{
+}
+
+GduEstimator *
+gdu_estimator_new (guint64  target_bytes)
+{
+  return GDU_ESTIMATOR (g_object_new (GDU_TYPE_ESTIMATOR,
+                                      "target-bytes", target_bytes,
+                                      NULL));
+}
+
+guint64
+gdu_estimator_get_target_bytes (GduEstimator *estimator)
+{
+  g_return_val_if_fail (GDU_IS_ESTIMATOR (estimator), 0);
+  return estimator->target_bytes;
+}
+
+guint64
+gdu_estimator_get_completed_bytes (GduEstimator *estimator)
+{
+  g_return_val_if_fail (GDU_IS_ESTIMATOR (estimator), 0);
+  return estimator->completed_bytes;
+}
+
+guint64
+gdu_estimator_get_bytes_per_sec (GduEstimator *estimator)
+{
+  g_return_val_if_fail (GDU_IS_ESTIMATOR (estimator), 0);
+  return estimator->bytes_per_sec;
+}
+
+guint64
+gdu_estimator_get_usec_remaining (GduEstimator *estimator)
+{
+  g_return_val_if_fail (GDU_IS_ESTIMATOR (estimator), 0);
+  return estimator->usec_remaining;
+}
+
+void
+gdu_estimator_add_sample (GduEstimator    *estimator,
+                          guint64          completed_bytes)
+{
+  Sample *sample;
+  g_return_if_fail (GDU_IS_ESTIMATOR (estimator));
+  g_return_if_fail (completed_bytes >= estimator->completed_bytes);
+
+  estimator->completed_bytes = completed_bytes;
+
+  if (estimator->num_samples == MAX_SAMPLES)
+    {
+      memmove (estimator->samples, estimator->samples + 1, sizeof (Sample) * (MAX_SAMPLES - 1));
+      estimator->num_samples -= 1;
+    }
+  sample = &estimator->samples[estimator->num_samples++];
+
+  sample->time_usec = g_get_real_time ();
+  sample->value = completed_bytes;
+
+  update (estimator);
+}
diff --git a/src/palimpsest/gduestimator.h b/src/palimpsest/gduestimator.h
new file mode 100644
index 0000000..48d802b
--- /dev/null
+++ b/src/palimpsest/gduestimator.h
@@ -0,0 +1,47 @@
+/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
+ *
+ * Copyright (C) 2008-2011 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General
+ * Public License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place, Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Author: David Zeuthen <davidz redhat com>
+ */
+
+#ifndef __GDU_ESTIMATOR_H__
+#define __GDU_ESTIMATOR_H__
+
+#include <gtk/gtk.h>
+#include "gdutypes.h"
+
+G_BEGIN_DECLS
+
+#define GDU_TYPE_ESTIMATOR   gdu_estimator_get_type()
+#define GDU_ESTIMATOR(o)     (G_TYPE_CHECK_INSTANCE_CAST ((o), GDU_TYPE_ESTIMATOR, GduEstimator))
+#define GDU_IS_ESTIMATOR(o)  (G_TYPE_CHECK_INSTANCE_TYPE ((o), GDU_TYPE_ESTIMATOR))
+
+GType          gdu_estimator_get_type            (void) G_GNUC_CONST;
+GduEstimator  *gdu_estimator_new                 (guint64         target_bytes);
+void           gdu_estimator_add_sample          (GduEstimator    *estimator,
+                                                  guint64          completed_bytes);
+guint64        gdu_estimator_get_target_bytes    (GduEstimator    *estimator);
+guint64        gdu_estimator_get_completed_bytes (GduEstimator    *estimator);
+
+guint64        gdu_estimator_get_bytes_per_sec   (GduEstimator    *estimator);
+guint64        gdu_estimator_get_usec_remaining  (GduEstimator    *estimator);
+
+G_END_DECLS
+
+#endif /* __GDU_ESTIMATOR_H__ */
diff --git a/src/palimpsest/gdurestorediskimagedialog.c b/src/palimpsest/gdurestorediskimagedialog.c
index 8ccef48..ed71235 100644
--- a/src/palimpsest/gdurestorediskimagedialog.c
+++ b/src/palimpsest/gdurestorediskimagedialog.c
@@ -31,10 +31,12 @@
 #include "gdurestorediskimagedialog.h"
 #include "gduvolumegrid.h"
 #include "gduutils.h"
+#include "gduestimator.h"
 
 /* ---------------------------------------------------------------------------------------------------- */
 
-#define BUFFER_SIZE (1024*1024)
+/* TODO: make dynamic? */
+#define BUFFER_SIZE (1*1024*1024)
 
 typedef struct
 {
@@ -56,8 +58,9 @@ typedef struct
   GtkWidget *error_infobar;
   GtkWidget *error_label;
 
-  GtkWidget *copying_progressbar;
   GtkWidget *copying_label;
+  GtkWidget *copying_progressbar;
+  GtkWidget *copying_progress_label;
 
   GCancellable *cancellable;
   GOutputStream *block_stream;
@@ -68,6 +71,8 @@ typedef struct
   guint64 total_bytes_read;
   guint64 buffer_bytes_written;
   guint64 buffer_bytes_to_write;
+
+  GduEstimator *estimator;
 } RestoreDiskImageData;
 
 static RestoreDiskImageData *
@@ -97,6 +102,7 @@ restore_disk_image_data_unref (RestoreDiskImageData *data)
       if (data->builder != NULL)
         g_object_unref (data->builder);
       g_free (data->buffer);
+      g_clear_object (&data->estimator);
       g_free (data);
     }
 }
@@ -226,6 +232,9 @@ write_cb (GOutputStream  *output_stream,
   RestoreDiskImageData *data = user_data;
   GError *error;
   gssize bytes_written;
+  guint64 bytes_per_sec;
+  guint64 usec_remaining;
+  gchar *s, *s2, *s3, *s4, *s5;
 
   error = NULL;
   bytes_written = g_output_stream_write_finish (output_stream, res, &error);
@@ -242,11 +251,35 @@ write_cb (GOutputStream  *output_stream,
   data->buffer_bytes_written += bytes_written;
   data->buffer_bytes_to_write -= bytes_written;
 
-  /* update progress bar */
+  /* update progress bar and estimator */
   gtk_progress_bar_set_fraction (GTK_PROGRESS_BAR (data->copying_progressbar),
                                  ((gdouble) data->total_bytes_read) / ((gdouble) data->file_size));
-
-  /* TODO: we should do fancy stuff like printing the estimated speed and time remaining */
+  gdu_estimator_add_sample (data->estimator, data->total_bytes_read);
+  bytes_per_sec = gdu_estimator_get_bytes_per_sec (data->estimator);
+  usec_remaining = gdu_estimator_get_usec_remaining (data->estimator);
+  if (bytes_per_sec > 0 && usec_remaining > 0)
+    {
+      s2 = g_format_size (data->total_bytes_read);
+      s3 = g_format_size (data->file_size);
+      s4 = gdu_utils_duration_to_string (usec_remaining / G_USEC_PER_SEC, TRUE);
+      s5 = g_format_size (bytes_per_sec);
+      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 (data->total_bytes_read);
+      s3 = g_format_size (data->file_size);
+      s = g_strdup_printf ("%s of %s copied", s2, s3);
+      g_free (s2);
+      g_free (s3);
+    }
+  s2 = g_strconcat ("<small>", s, "</small>", NULL);
+  gtk_label_set_markup (GTK_LABEL (data->copying_progress_label), s2);
+  g_free (s);
 
   write_more (data);
 
@@ -263,7 +296,6 @@ write_more (RestoreDiskImageData *data)
     }
   else
     {
-      g_debug ("Writing %d bytes", (gint) data->buffer_bytes_to_write);
       g_output_stream_write_async (data->block_stream,
                                    data->buffer + data->buffer_bytes_written,
                                    data->buffer_bytes_to_write,
@@ -367,6 +399,7 @@ open_cb (UDisksBlock  *block,
   /* Alright, time to start copying! */
   data->cancellable = g_cancellable_new ();
   data->buffer = g_new0 (guchar, BUFFER_SIZE);
+  data->estimator = gdu_estimator_new (data->file_size);
   copy_more (data);
 
  out:
@@ -451,12 +484,13 @@ gdu_restore_disk_image_dialog_show (GduWindow    *window,
   data->source_file_fcbutton = GTK_WIDGET (gtk_builder_get_object (data->builder, "source_file_fcbutton"));
   g_signal_connect (data->source_file_fcbutton, "file-set",
                     G_CALLBACK (on_file_set), data);
-  data->copying_progressbar = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_progressbar"));
-  data->copying_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_label"));
   data->warning_infobar = GTK_WIDGET (gtk_builder_get_object (data->builder, "warning_infobar"));
   data->warning_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "warning_label"));
   data->error_infobar = GTK_WIDGET (gtk_builder_get_object (data->builder, "error_infobar"));
   data->error_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "error_label"));
+  data->copying_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_label"));
+  data->copying_progressbar = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_progressbar"));
+  data->copying_progress_label = GTK_WIDGET (gtk_builder_get_object (data->builder, "copying_progress_label"));
 
   restore_disk_image_populate (data);
   restore_disk_image_update (data);
diff --git a/src/palimpsest/gdutypes.h b/src/palimpsest/gdutypes.h
index 588b206..917908a 100644
--- a/src/palimpsest/gdutypes.h
+++ b/src/palimpsest/gdutypes.h
@@ -49,6 +49,9 @@ typedef struct _GduCreateFilesystemWidget GduCreateFilesystemWidget;
 struct _GduPasswordStrengthWidget;
 typedef struct _GduPasswordStrengthWidget GduPasswordStrengthWidget;
 
+struct _GduEstimator;
+typedef struct _GduEstimator GduEstimator;
+
 G_END_DECLS
 
 #endif /* __GDU_TYPES_H__ */
diff --git a/src/palimpsest/gduutils.c b/src/palimpsest/gduutils.c
index c27e9f0..c7530ad 100644
--- a/src/palimpsest/gduutils.c
+++ b/src/palimpsest/gduutils.c
@@ -88,3 +88,45 @@ gdu_utils_configure_file_chooser_for_disk_images (GtkFileChooser *file_chooser)
   gtk_file_chooser_add_filter (file_chooser, filter); /* adopts filter */
   gtk_file_chooser_set_filter (file_chooser, filter);
 }
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+gchar *
+gdu_utils_duration_to_string (guint    duration_sec,
+                              gboolean include_second_precision)
+{
+  gchar *s;
+
+  if (duration_sec < 60)
+    {
+      if (include_second_precision)
+        {
+          s = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
+                                          N_("%d second"),
+                                          N_("%d seconds"),
+                                          duration_sec),
+                               duration_sec);
+        }
+      else
+        {
+          s = g_strdup (_("Less than a minute"));
+        }
+    }
+  else if (duration_sec < 3600)
+    {
+      s = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
+                                      N_("%d minute"),
+                                      N_("%d minutes"),
+                                      duration_sec / 60),
+                           duration_sec / 60);
+    }
+  else
+    {
+      s = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
+                                      N_("%d hour"),
+                                      N_("%d hours"),
+                                      duration_sec / 3600),
+                           duration_sec / 3600);
+    }
+  return s;
+}
diff --git a/src/palimpsest/gduutils.h b/src/palimpsest/gduutils.h
index 4f1c9c1..ce9df52 100644
--- a/src/palimpsest/gduutils.h
+++ b/src/palimpsest/gduutils.h
@@ -34,6 +34,8 @@ gboolean gdu_utils_has_configuration (UDisksBlock  *block,
 
 void gdu_utils_configure_file_chooser_for_disk_images (GtkFileChooser *file_chooser);
 
+gchar *gdu_utils_duration_to_string (guint    duration_sec,
+                                     gboolean include_second_precision);
 
 G_END_DECLS
 



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