From b47a7e75cdb4297bfd727b2d09de7ec7d93e869d Mon Sep 17 00:00:00 2001 From: David Zeuthen Date: Tue, 26 Jun 2012 19:32:48 -0400 Subject: [PATCH] Set Job:ExpectedEndTime when erasing a device Do this by introducing some plumbing in UDisksBaseJob to estimate the end time based on the UDisksJob:progress property. We do this by using a 100-period moving average (actually, up to 100 periods). Works out great in practice. Also switch the format of the time used on the org.fd.UDisks.Job interface to be micro-seconds instead of seconds. This is not really a break as these properties were always zero in any released udisks2 version. http://people.freedesktop.org/~david/gnome-disks-estimated-time-remaining.png Signed-off-by: David Zeuthen --- data/org.freedesktop.UDisks2.xml | 4 +- src/udisksbasejob.c | 143 ++++++++++++++++++++++++++++++++++++++- src/udisksbasejob.h | 17 +++-- src/udiskslinuxblock.c | 4 +- 4 files changed, 157 insertions(+), 11 deletions(-) diff --git a/data/org.freedesktop.UDisks2.xml b/data/org.freedesktop.UDisks2.xml index 956a921..69802ae 100644 --- a/data/org.freedesktop.UDisks2.xml +++ b/data/org.freedesktop.UDisks2.xml @@ -1592,7 +1592,7 @@ @@ -1600,7 +1600,7 @@ diff --git a/src/udisksbasejob.c b/src/udisksbasejob.c index e113b67..c03a232 100644 --- a/src/udisksbasejob.c +++ b/src/udisksbasejob.c @@ -29,6 +29,14 @@ #include "udisksdaemonutil.h" #include "udisks-daemon-marshal.h" +#define MAX_SAMPLES 100 + +typedef struct +{ + gint64 time_usec; + gdouble value; +} Sample; + /** * SECTION:udisksbasejob * @title: UDisksBaseJob @@ -41,6 +49,12 @@ struct _UDisksBaseJobPrivate { GCancellable *cancellable; UDisksDaemon *daemon; + + gboolean auto_estimate; + gulong notify_progress_signal_handler_id; + + Sample *samples; + guint num_samples; }; static void job_iface_init (UDisksJobIface *iface); @@ -50,6 +64,7 @@ enum PROP_0, PROP_DAEMON, PROP_CANCELLABLE, + PROP_AUTO_ESTIMATE, }; G_DEFINE_ABSTRACT_TYPE_WITH_CODE (UDisksBaseJob, udisks_base_job, UDISKS_TYPE_JOB_SKELETON, @@ -60,6 +75,9 @@ udisks_base_job_finalize (GObject *object) { UDisksBaseJob *job = UDISKS_BASE_JOB (object); + + g_free (job->priv->samples); + if (job->priv->cancellable != NULL) { g_object_unref (job->priv->cancellable); @@ -88,6 +106,10 @@ udisks_base_job_get_property (GObject *object, g_value_set_object (value, job->priv->cancellable); break; + case PROP_AUTO_ESTIMATE: + g_value_set_boolean (value, job->priv->auto_estimate); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -115,6 +137,10 @@ udisks_base_job_set_property (GObject *object, job->priv->cancellable = g_value_dup_object (value); break; + case PROP_AUTO_ESTIMATE: + udisks_base_job_set_auto_estimate (job, g_value_get_boolean (value)); + break; + default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); break; @@ -145,7 +171,7 @@ udisks_base_job_init (UDisksBaseJob *job) job->priv = G_TYPE_INSTANCE_GET_PRIVATE (job, UDISKS_TYPE_BASE_JOB, UDisksBaseJobPrivate); now_usec = g_get_real_time (); - udisks_job_set_start_time (UDISKS_JOB (job), now_usec / G_USEC_PER_SEC); + udisks_job_set_start_time (UDISKS_JOB (job), now_usec); } static void @@ -191,6 +217,23 @@ udisks_base_job_class_init (UDisksBaseJobClass *klass) G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS)); + /** + * UDisksBaseJob:auto-estimate: + * + * If %TRUE, the #UDisksJob:expected-end-time property will be + * automatically updated every time the #UDisksJob:progress property + * is updated. + */ + g_object_class_install_property (gobject_class, + PROP_AUTO_ESTIMATE, + g_param_spec_boolean ("auto-estimate", + "Auto Estimate", + "Whether to automatically estimate end time", + FALSE, + G_PARAM_READABLE | + G_PARAM_WRITABLE | + G_PARAM_STATIC_STRINGS)); + g_type_class_add_private (klass, sizeof (UDisksBaseJobPrivate)); } @@ -389,3 +432,101 @@ job_iface_init (UDisksJobIface *iface) } /* ---------------------------------------------------------------------------------------------------- */ + +gboolean +udisks_base_job_get_auto_estimate (UDisksBaseJob *job) +{ + g_return_val_if_fail (UDISKS_IS_BASE_JOB (job), FALSE); + return job->priv->auto_estimate; +} + + +static void +on_notify_progress (GObject *object, + GParamSpec *spec, + gpointer user_data) +{ + UDisksBaseJob *job = UDISKS_BASE_JOB (user_data); + Sample *sample; + guint n; + gdouble sum_of_speeds; + guint num_speeds; + gdouble avg_speed; + gint64 usec_remaining; + gint64 now; + gdouble current_progress; + + now = g_get_real_time (); + current_progress = udisks_job_get_progress (UDISKS_JOB (job)); + + /* first add new sample... */ + if (job->priv->num_samples == MAX_SAMPLES) + { + memmove (job->priv->samples, job->priv->samples + 1, sizeof (Sample) * (MAX_SAMPLES - 1)); + job->priv->num_samples -= 1; + } + sample = &job->priv->samples[job->priv->num_samples++]; + sample->time_usec = now; + sample->value = current_progress; + + /* ... then update expected-end-time from samples - we want at + * least five samples before making an estimate... + */ + if (job->priv->num_samples < 5) + goto out; + + num_speeds = 0; + sum_of_speeds = 0.0; + for (n = 1; n < job->priv->num_samples; n++) + { + Sample *a = &job->priv->samples[n-1]; + Sample *b = &job->priv->samples[n]; + gdouble speed; + speed = (b->value - a->value) / (b->time_usec - a->time_usec); + sum_of_speeds += speed; + num_speeds++; + } + avg_speed = sum_of_speeds / num_speeds; + + usec_remaining = (1.0 - current_progress) / avg_speed; + + udisks_job_set_expected_end_time (UDISKS_JOB (job), now + usec_remaining); + + out: + ; +} + + +void +udisks_base_job_set_auto_estimate (UDisksBaseJob *job, + gboolean value) +{ + g_return_if_fail (UDISKS_IS_BASE_JOB (job)); + + if (!!value == !!job->priv->auto_estimate) + goto out; + + if (value) + { + if (job->priv->samples == NULL) + job->priv->samples = g_new0 (Sample, MAX_SAMPLES); + g_assert_cmpint (job->priv->notify_progress_signal_handler_id, ==, 0); + job->priv->notify_progress_signal_handler_id = g_signal_connect (job, + "notify::progress", + G_CALLBACK (on_notify_progress), + job); + g_assert_cmpint (job->priv->notify_progress_signal_handler_id, !=, 0); + } + else + { + g_assert_cmpint (job->priv->notify_progress_signal_handler_id, !=, 0); + g_signal_handler_disconnect (job, job->priv->notify_progress_signal_handler_id); + job->priv->notify_progress_signal_handler_id = 0; + } + + job->priv->auto_estimate = !!value; + g_object_notify (G_OBJECT (job), "auto-estimate"); + + out: + ; +} diff --git a/src/udisksbasejob.h b/src/udisksbasejob.h index c78eb30..a897816 100644 --- a/src/udisksbasejob.h +++ b/src/udisksbasejob.h @@ -61,14 +61,17 @@ struct _UDisksBaseJobClass gpointer padding[8]; }; -GType udisks_base_job_get_type (void) G_GNUC_CONST; -UDisksDaemon *udisks_base_job_get_daemon (UDisksBaseJob *job); -GCancellable *udisks_base_job_get_cancellable (UDisksBaseJob *job); +GType udisks_base_job_get_type (void) G_GNUC_CONST; +UDisksDaemon *udisks_base_job_get_daemon (UDisksBaseJob *job); +GCancellable *udisks_base_job_get_cancellable (UDisksBaseJob *job); +gboolean udisks_base_job_get_auto_estimate (UDisksBaseJob *job); +void udisks_base_job_set_auto_estimate (UDisksBaseJob *job, + gboolean value); -void udisks_base_job_add_object (UDisksBaseJob *job, - UDisksObject *object); -void udisks_base_job_remove_object (UDisksBaseJob *job, - UDisksObject *object); +void udisks_base_job_add_object (UDisksBaseJob *job, + UDisksObject *object); +void udisks_base_job_remove_object (UDisksBaseJob *job, + UDisksObject *object); G_END_DECLS diff --git a/src/udiskslinuxblock.c b/src/udiskslinuxblock.c index 2948050..f9c88ea 100644 --- a/src/udiskslinuxblock.c +++ b/src/udiskslinuxblock.c @@ -1757,6 +1757,7 @@ erase_device (UDisksBlock *block, } job = udisks_daemon_launch_simple_job (daemon, object, "format-erase", caller_uid, NULL); + udisks_base_job_set_auto_estimate (UDISKS_BASE_JOB (job), TRUE); udisks_job_set_progress_valid (UDISKS_JOB (job), TRUE); if (ioctl (fd, BLKGETSIZE64, &size) != 0) @@ -1816,7 +1817,8 @@ erase_device (UDisksBlock *block, else udisks_simple_job_complete (UDISKS_SIMPLE_JOB (job), TRUE, ""); } - g_propagate_error (error, local_error); + if (local_error != NULL) + g_propagate_error (error, local_error); g_free (buf); if (fd != -1) close (fd); -- 2.7.4