[gnome-disk-utility/udisks2-port] Add SMART functionality



commit 9218e1f98dfed532dec91e63370d6cad304339e9
Author: David Zeuthen <davidz redhat com>
Date:   Fri Sep 2 16:07:59 2011 -0400

    Add SMART functionality
    
    Signed-off-by: David Zeuthen <davidz redhat com>

 data/ui/palimpsest.ui              |   15 +-
 src/palimpsest/Makefile.am         |    1 +
 src/palimpsest/gduatasmartdialog.c | 1524 ++++++++++++++++++++++++++++++++++++
 src/palimpsest/gduatasmartdialog.h |   40 +
 src/palimpsest/gduwindow.c         |  138 ++--
 src/palimpsest/gduwindow.h         |    4 +
 6 files changed, 1631 insertions(+), 91 deletions(-)
---
diff --git a/data/ui/palimpsest.ui b/data/ui/palimpsest.ui
index f2c6e68..e7e0118 100644
--- a/data/ui/palimpsest.ui
+++ b/data/ui/palimpsest.ui
@@ -61,6 +61,15 @@
     <property name="visible">True</property>
     <property name="can_focus">False</property>
     <child>
+      <object class="GtkMenuItem" id="generic-menu-item-view-smart">
+        <property name="visible">True</property>
+        <property name="can_focus">False</property>
+        <property name="use_action_appearance">False</property>
+        <property name="label" translatable="yes">View SMART data...</property>
+        <property name="use_underline">True</property>
+      </object>
+    </child>
+    <child>
       <object class="GtkMenuItem" id="generic-menu-item-configure-fstab">
         <property name="visible">True</property>
         <property name="can_focus">False</property>
@@ -308,7 +317,7 @@
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="xalign">1</property>
-                        <property name="label" translatable="yes">Serial</property>
+                        <property name="label" translatable="yes">Serial Number</property>
                         <attributes>
                           <attribute name="foreground" value="#555555555555"/>
                         </attributes>
@@ -395,7 +404,7 @@
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="xalign">1</property>
-                        <property name="label" translatable="yes">Size</property>
+                        <property name="label" translatable="yes">Disk Size</property>
                         <attributes>
                           <attribute name="foreground" value="#555555555555"/>
                         </attributes>
@@ -432,7 +441,7 @@
                         <property name="visible">True</property>
                         <property name="can_focus">False</property>
                         <property name="xalign">1</property>
-                        <property name="label" translatable="yes">Status</property>
+                        <property name="label" translatable="yes">Assessment</property>
                         <attributes>
                           <attribute name="foreground" value="#555555555555"/>
                         </attributes>
diff --git a/src/palimpsest/Makefile.am b/src/palimpsest/Makefile.am
index 9019a68..7b9cdc0 100644
--- a/src/palimpsest/Makefile.am
+++ b/src/palimpsest/Makefile.am
@@ -31,6 +31,7 @@ palimpsest_SOURCES = 					\
 	gduutils.h		gduutils.c		\
 	gduvolumegrid.h		gduvolumegrid.c		\
 	gduwindow.h		gduwindow.c		\
+	gduatasmartdialog.h	gduatasmartdialog.c	\
 	$(enum_built_sources)				\
 	$(NULL)
 
diff --git a/src/palimpsest/gduatasmartdialog.c b/src/palimpsest/gduatasmartdialog.c
new file mode 100644
index 0000000..3b654e2
--- /dev/null
+++ b/src/palimpsest/gduatasmartdialog.c
@@ -0,0 +1,1524 @@
+/* -*- 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.h>
+
+#include "gduapplication.h"
+#include "gduwindow.h"
+#include "gduatasmartdialog.h"
+
+typedef struct
+{
+  UDisksObject *object;
+  UDisksDriveAta *ata;
+
+  GduWindow *window;
+  GtkBuilder *builder;
+
+  GtkListStore *attributes_list;
+
+  GtkWidget *dialog;
+  GtkWidget *updated_label;
+  GtkWidget *temperature_label;
+  GtkWidget *powered_on_label;
+  GtkWidget *self_test_label;
+  GtkWidget *self_assessment_label;
+  GtkWidget *overall_assessment_label;
+
+  GtkWidget *attributes_treeview;
+
+  GtkWidget *start_selftest_button;
+  GtkWidget *stop_selftest_button;
+
+  GtkWidget *selftest_menu;
+  GtkWidget *selftest_short_menuitem;
+  GtkWidget *selftest_extended_menuitem;
+  GtkWidget *selftest_conveyance_menuitem;
+} DialogData;
+
+static const struct {
+  goffset offset;
+  const gchar *name;
+} widget_mapping[] = {
+  {G_STRUCT_OFFSET (DialogData, updated_label), "updated-label"},
+  {G_STRUCT_OFFSET (DialogData, temperature_label), "temperature-label"},
+  {G_STRUCT_OFFSET (DialogData, powered_on_label), "powered-on-label"},
+  {G_STRUCT_OFFSET (DialogData, self_test_label), "self-test-label"},
+  {G_STRUCT_OFFSET (DialogData, self_assessment_label), "self-assessment-label"},
+  {G_STRUCT_OFFSET (DialogData, overall_assessment_label), "overall-assessment-label"},
+  {G_STRUCT_OFFSET (DialogData, attributes_treeview), "attributes-treeview"},
+  {G_STRUCT_OFFSET (DialogData, selftest_menu), "selftest-menu"},
+  {G_STRUCT_OFFSET (DialogData, selftest_short_menuitem), "selftest-short-menuitem"},
+  {G_STRUCT_OFFSET (DialogData, selftest_extended_menuitem), "selftest-extended-menuitem"},
+  {G_STRUCT_OFFSET (DialogData, selftest_conveyance_menuitem), "selftest-conveyance-menuitem"},
+  {0, NULL}
+};
+
+static void
+dialog_data_free (DialogData *data)
+{
+  if (data->dialog != NULL)
+    {
+      gtk_widget_hide (data->dialog);
+      gtk_widget_destroy (data->dialog);
+    }
+  if (data->object != NULL)
+    g_object_unref (data->object);
+  if (data->window != NULL)
+    g_object_unref (data->window);
+  if (data->builder != NULL)
+    g_object_unref (data->builder);
+
+  if (data->attributes_list != NULL)
+    g_object_unref (data->attributes_list);
+
+  g_free (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;
+        const gchar *desc;
+} SmartDetails;
+
+/* See http://smartmontools.sourceforge.net/doc.html
+ *     http://en.wikipedia.org/wiki/S.M.A.R.T
+ *     http://www.t13.org/Documents/UploadedDocuments/docs2005/e05148r0-ACS-ATA_SMARTAttributesAnnex.pdf
+ *
+ *     Keep in sync with libatasmart. Last sync: Thu Aug 20 2009
+ */
+static const SmartDetails smart_details[] = {
+{
+  "raw-read-error-rate",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Read Error Rate"),
+  N_("Frequency of errors while reading raw data from the disk. "
+     "A non-zero value indicates a problem with "
+     "either the disk surface or read/write heads")
+},
+{
+  "throughput-performance",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Throughput Performance"),
+  N_("Average efficiency of the disk")
+},
+{
+  "spin-up-time",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Spinup Time"),
+  N_("Time needed to spin up the disk")
+},
+{
+  "start-stop-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Start/Stop Count"),
+  N_("Number of spindle start/stop cycles")
+},
+{
+  "reallocated-sector-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Reallocated Sector Count"),
+  N_("Count of remapped sectors. "
+     "When the hard drive finds a read/write/verification error, it marks the sector "
+     "as \"reallocated\" and transfers data to a special reserved area (spare area)")
+},
+{
+  "read-channel-margin",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Read Channel Margin"),
+  N_("Margin of a channel while reading data.")
+},
+{
+  "seek-error-rate",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Seek Error Rate"),
+  N_("Frequency of errors while positioning")
+},
+{
+  "seek-time-performance",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Seek Timer Performance"),
+  N_("Average efficiency of operations while positioning")
+},
+{
+  "power-on-hours",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Power-On Hours"),
+  N_("Number of hours elapsed in the power-on state")
+},
+{
+  "spin-retry-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Spinup Retry Count"),
+  N_("Number of retry attempts to spin up")
+},
+{
+  "calibration-retry-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Calibration Retry Count"),
+  N_("Number of attempts to calibrate the device")
+},
+{
+  "power-cycle-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Power Cycle Count"),
+  N_("Number of power-on events")
+},
+{
+  "read-soft-error-rate",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Soft read error rate"),
+  N_("Frequency of 'program' errors while reading from the disk")
+},
+{
+  "reported-uncorrect",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Reported Uncorrectable Errors"),
+  N_("Number of errors that could not be recovered using hardware ECC")
+},
+{
+  "high-fly-writes",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("High Fly Writes"),
+  N_("Number of times a recording head is flying outside its normal operating range")
+},
+{
+  "airflow-temperature-celsius",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Airflow Temperature"),
+  N_("Airflow temperature of the drive")
+},
+{
+  "g-sense-error-rate",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("G-sense Error Rate"),
+  N_("Frequency of mistakes as a result of impact loads")
+},
+{
+  "power-off-retract-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Power-off Retract Count"),
+  N_("Number of power-off or emergency retract cycles")
+},
+{
+  "load-cycle-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Load/Unload Cycle Count"),
+  N_("Number of cycles into landing zone position")
+},
+{
+  "temperature-celsius-2",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Temperature"),
+  N_("Current internal temperature of the drive")
+},
+{
+  "hardware-ecc-recovered",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Hardware ECC Recovered"),
+  N_("Number of ECC on-the-fly errors")
+},
+{
+  "reallocated-event-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Reallocation Count"),
+  N_("Number of remapping operations. "
+     "The raw value of this attribute shows the total number of (successful "
+     "and unsuccessful) attempts to transfer data from reallocated sectors "
+     "to a spare area")
+},
+{
+  "current-pending-sector",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Current Pending Sector Count"),
+  N_("Number of sectors waiting to be remapped. "
+     "If the sector waiting to be remapped is subsequently written or read "
+     "successfully, this value is decreased and the sector is not remapped. Read "
+     "errors on the sector will not remap the sector, it will only be remapped on "
+     "a failed write attempt")
+},
+{
+  "offline-uncorrectable",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Uncorrectable Sector Count"),
+  N_("The total number of uncorrectable errors when reading/writing a sector. "
+     "A rise in the value of this attribute indicates defects of the "
+     "disk surface and/or problems in the mechanical subsystem")
+},
+{
+  "udma-crc-error-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("UDMA CRC Error Rate"),
+  N_("Number of CRC errors during UDMA mode")
+},
+{
+  "multi-zone-error-rate",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Write Error Rate"),
+  N_("Number of errors while writing to disk (or) multi-zone error rate (or) flying-height")
+},
+{
+  "soft-read-error-rate",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Soft Read Error Rate"),
+  N_("Number of off-track errors")
+},
+{
+  "ta-increase-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Data Address Mark Errors"),
+  N_("Number of Data Address Mark (DAM) errors (or) vendor-specific")
+},
+{
+  "run-out-cancel",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Run Out Cancel"),
+  N_("Number of ECC errors")
+},
+{
+  "shock-count-write-open",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Soft ECC correction"),
+  N_("Number of errors corrected by software ECC")
+},
+{
+  "shock-rate-write-open",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Thermal Asperity Rate"),
+  N_("Number of Thermal Asperity Rate errors")
+},
+{
+  "flying-height",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Flying Height"),
+  N_("Height of heads above the disk surface")
+},
+{
+  "spin-high-current",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Spin High Current"),
+  N_("Amount of high current used to spin up the drive")
+},
+{
+  "spin-buzz",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Spin Buzz"),
+  N_("Number of buzz routines to spin up the drive")
+},
+{
+  "offline-seek-performance",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Offline Seek Performance"),
+  N_("Drive's seek performance during offline operations")
+},
+{
+  "disk-shift",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Disk Shift"),
+  N_("Shift of disk is possible as a result of strong shock loading in the store, "
+     "as a result of falling (or) temperature")
+},
+{
+  "g-sense-error-rate-2",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("G-sense Error Rate"),
+  N_("Number of errors as a result of impact loads as detected by a shock sensor")
+},
+{
+  "loaded-hours",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Loaded Hours"),
+  N_("Number of hours in general operational state")
+},
+{
+  "load-retry-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Load/Unload Retry Count"),
+  N_("Loading on drive caused by numerous recurrences of operations, like reading, "
+     "recording, positioning of heads, etc")
+},
+{
+  "load-friction",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Load Friction"),
+  N_("Load on drive caused by friction in mechanical parts of the store")
+},
+{
+  "load-cycle-count-2",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Load/Unload Cycle Count"),
+  N_("Total number of load cycles")
+},
+{
+  "load-in-time",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Load-in Time"),
+  N_("General time for loading in a drive")
+},
+{
+  "torq-amp-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Torque Amplification Count"),
+  N_("Quantity efforts of the rotating moment of a drive")
+},
+{
+  "power-off-retract-count-2",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Power-off Retract Count"),
+  N_("Number of power-off retract events")
+},
+{
+  "head-amplitude",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("GMR Head Amplitude"),
+  N_("Amplitude of heads trembling (GMR-head) in running mode")
+},
+{
+  "temperature-celsius",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Temperature"),
+  N_("Temperature of the drive")
+},
+{
+  "endurance-remaining",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Endurance Remaining"),
+  N_("Number of physical erase cycles completed on the drive as "
+     "a percentage of the maximum physical erase cycles the drive supports")
+},
+{
+  "power-on-seconds-2",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Power-On Hours"),
+  N_("Number of hours elapsed in the power-on state")
+},
+{
+  "uncorrectable-ecc-count",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Uncorrectable ECC Count"),
+  N_("Number of uncorrectable ECC errors")
+},
+{
+  "good-block-rate",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Good Block Rate"),
+  N_("Number of available reserved blocks as a percentage "
+     "of the total number of reserved blocks"),
+},
+{
+  "head-flying-hours",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Head Flying Hours"),
+  N_("Time while head is positioning")
+},
+{
+  "read-error-retry-rate",
+  /* Translators: SMART attribute, see http://smartmontools.sourceforge.net/doc.html
+   * or the next string for a longer explanation.
+   */
+  N_("Read Error Retry Rate"),
+  N_("Number of errors while reading from a disk")
+},
+{
+  NULL,
+  NULL,
+  NULL
+}
+};
+
+/* TODO: move to libudisks2 */
+static gboolean
+attribute_get_details (const gchar  *name,
+                       const gchar **out_name,
+                       const gchar **out_description)
+{
+  SmartDetails *details;
+  static volatile gsize have_hash = 0;
+  static GHashTable *smart_details_map = NULL;
+  gboolean ret;
+
+  if (g_once_init_enter (&have_hash))
+    {
+      guint n;
+      smart_details_map = g_hash_table_new (g_str_hash, g_str_equal);
+      for (n = 0; smart_details[n].name != NULL; n++)
+        {
+          g_hash_table_insert (smart_details_map,
+                               (gpointer) smart_details[n].name,
+                               (gpointer) &(smart_details[n]));
+        }
+      g_once_init_leave (&have_hash, 1);
+    }
+
+  ret = FALSE;
+
+  details = g_hash_table_lookup (smart_details_map, name);
+  if (details != NULL)
+    {
+      if (out_name != NULL)
+        *out_name = gettext (details->pretty_name);
+      if (out_description != NULL)
+        *out_description = gettext (details->desc);;
+      ret = TRUE;
+    }
+
+  return ret;
+}
+
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+attr_format_desc (gint id, const gchar *name)
+{
+  const gchar *localized_name;
+  const gchar *localized_desc;
+  const gchar *n;
+  gchar *ret;
+
+  if (attribute_get_details (name, &localized_name, &localized_desc))
+    n = localized_name;
+  else
+    n = name;
+
+  ret = g_strdup_printf ("<b>%s</b>", n);
+
+  return ret;
+}
+
+static gchar *
+attr_format_assessment (gint     current,
+                        gint     worst,
+                        gint     threshold,
+                        guint16  flags)
+{
+  gchar *ret;
+  gboolean failed = FALSE;
+  gboolean failed_in_the_past = FALSE;
+
+  if (current > 0 && threshold > 0 && current <= threshold)
+    failed = TRUE;
+
+  if (worst > 0 && threshold > 0 && worst <= threshold)
+    failed_in_the_past = TRUE;
+
+  if (failed)
+    {
+      ret = g_strdup_printf ("<span foreground=\"#ff0000\"><b>%s</b></span>",
+                             /* Translators: Shown in the treeview for a failing attribute */
+                             _("FAILING"));
+    }
+  else if (failed_in_the_past)
+    {
+      ret = g_strdup_printf ("<span foreground=\"#ff0000\">%s</span>",
+                             /* Translators: Shown in the treeview for an attribute that failed in the past */
+                             _("Failed in the past"));
+    }
+  else
+    {
+      ret = g_strdup (_("OK"));
+    }
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+format_duration_msec (guint64 msec)
+{
+  gchar *ret;
+  if (msec > 1000 * 60 * 60 * 24 * 365.25)
+    {
+      /* Translators: Used for a time-based unit that exceed one year */
+      ret = g_strdup_printf (_("%.1f years"), msec / 1000.0 / 60.0 / 60.0 / 24.0 / 365.25);
+    }
+  else if (msec > 1000 * 60 * 60 * 24)
+    {
+      /* Translators: Used for a time-based unit that exceed one day but not one year */
+      ret = g_strdup_printf (_("%.1f days"), msec / 1000.0 / 60.0 / 60.0 / 24.0);
+    }
+  else if (msec > 1000 * 60 * 60)
+    {
+      /* Translators: Used for a time-based unit that exceed one hour but not one day */
+      ret = g_strdup_printf (_("%.1f hours"), msec / 1000.0 / 60.0 / 60.0);
+    }
+  else if (msec > 1000 * 60)
+    {
+      /* Translators: Used for a time-based unit that exceed one minute but not one hour */
+      ret = g_strdup_printf (_("%.1f minutes"), msec / 1000.0 / 60.0);
+    }
+  else if (msec > 1000)
+    {
+      /* Translators: Used for a time-based unit that exceed one second but not one minute */
+      ret = g_strdup_printf (_("%.1f seconds"), msec / 1000.0);
+    }
+  else
+    {
+      /* Translators: Used for a time-based unit that is counted in milliseconds and doesn't exceed one second */
+      ret = g_strdup_printf (_("%d msec"), (gint) msec);
+    }
+  return ret;
+}
+
+static gchar *
+pretty_to_string (guint64 pretty,
+                  gint    pretty_unit)
+{
+  gchar *ret;
+  gdouble celcius;
+  gdouble fahrenheit;
+
+  switch (pretty_unit)
+    {
+    case 2: /* SK_SMART_ATTRIBUTE_UNIT_MSECONDS */
+      ret = format_duration_msec (pretty);
+      break;
+
+    case 3: /* SK_SMART_ATTRIBUTE_UNIT_SECTORS */
+      /* Translators: Used in the treeview for the pretty/interpreted value of an attribute
+       * for a sector-based unit */
+      ret = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
+                                        "%d sector",
+                                        "%d sectors",
+                                        (gint) pretty),
+                             (gint) pretty);
+      break;
+
+    case 4: /* SK_SMART_ATTRIBUTE_UNIT_MKELVIN */
+      celcius = pretty / 1000.0 - 273.15;
+      fahrenheit = 9.0 * celcius / 5.0 + 32.0;
+      /* Translators: Used in the treeview for the pretty/interpreted value of an attribute
+       * for a temperature-based unit - first %f is the temperature in degrees Celcius, second %f
+       * is the temperature in degrees Fahrenheit */
+      ret = g_strdup_printf (_("%.0f C / %.0f F"), celcius, fahrenheit);
+      break;
+
+    case 1: /* SK_SMART_ATTRIBUTE_UNIT_NONE */
+      ret = g_strdup_printf ("%" G_GUINT64_FORMAT, pretty);
+      break;
+
+    default:
+    case 0: /* SK_SMART_ATTRIBUTE_UNIT_UNKNOWN */
+      /* Translators: Used in the treeview for the pretty/interpreted value of an attribute
+       * where the value cannot be interpreted */
+      ret = g_strdup (_("N/A"));
+      break;
+    }
+
+  return ret;
+}
+
+
+static gchar *
+attr_format_value (gint    current,
+                   gint    worst,
+                   gint    threshold,
+                   guint64 pretty,
+                   gint    pretty_unit,
+                   gchar **out_value2)
+{
+  gchar *ret;
+  gchar *current_str;
+  gchar *worst_str;
+  gchar *threshold_str;
+  gchar *pretty_str;
+
+  if (current >= 0)
+    current_str = g_strdup_printf ("%d", current);
+  else
+    current_str = g_strdup ("â");
+
+  if (worst >= 0)
+    worst_str = g_strdup_printf ("%d", worst);
+  else
+    worst_str = g_strdup ("â");
+
+  if (threshold >= 0)
+    threshold_str = g_strdup_printf ("%d", threshold);
+  else
+    threshold_str = g_strdup ("â");
+
+  pretty_str = pretty_to_string (pretty, pretty_unit);
+
+  ret = g_strdup_printf ("<small>"
+                         "%s: %s\n"
+                         "%s: %s"
+                         "</small>",
+                         /* Translators: Shown in the tree view for the normalized value of an attribute (0-254) */
+                         _("Normalized"), current_str,
+                         /* Translators: Shown in the tree view for the worst value of an attribute (0-254) */
+                         _("Worst"), worst_str);
+
+  *out_value2 = g_strdup_printf ("<small>"
+                                 "%s: %s\n"
+                                 "%s: %s"
+                                 "</small>",
+                                 /* Translators: Shown in the tree view for the threshold of an attribute (0-254) */
+                                 _("Threshold"), threshold_str,
+                                 /* Translators: Shown in the tree view for the interpreted/pretty value of an attribute */
+                                 _("Value"), pretty_str);
+  g_free (current_str);
+  g_free (worst_str);
+  g_free (threshold_str);
+  g_free (pretty_str);
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+attr_format_tooltip (gint         id,
+                     const gchar *name,
+                     guint16      flags)
+{
+  gboolean prefail;
+  gboolean online;
+  const gchar *type_str;
+  const gchar *updates_str;
+  gchar *ret;
+  const gchar *localized_name;
+  const gchar *localized_desc;
+  gchar *n;
+  gchar *d;
+
+  if (attribute_get_details (name, &localized_name, &localized_desc))
+    {
+      n = g_strdup (localized_name);
+      d = g_strdup (localized_desc);
+    }
+  else
+    {
+      n = g_strdup (name);
+      d = g_strdup_printf (_("No description for attribute %d"), id);
+    }
+
+  prefail = (flags & 0x0001);
+  online = (flags & 0x0002);
+
+  if (prefail)
+    {
+      /* Translators: Used in the tooltip for a row in the attribute treeview - please keep
+       * "(Pre-Fail)" in English
+       */
+      type_str = _("Failure is a sign the disk will fail within 24 hours (Pre-Fail)");
+    }
+  else
+    {
+      /* Translators: Used in the tooltip for a row in the attribute treeview - please keep
+       * "(Old-Age)" in English
+       */
+      type_str = _("Failure is a sign the disk exceeded its intended design life period (Old-Age)");
+    }
+
+  if (online)
+    {
+      /* Translators: Used in the tooltip for a row in the attribute treeview - please keep
+       * "(Online)" in English
+       */
+      updates_str = _("Every time data is collected (Online)");
+    }
+  else
+    {
+      /* Translators: Used in the tooltip for a row in the attribute treeview - please keep
+       * "(Not Online)" in English
+       */
+      updates_str = _("Only during off-line activities (Not Online)");
+    }
+
+  ret = g_strdup_printf ("<b>%s</b>: %s\n\n"
+                         "<b>%s</b>: %s\n\n"
+                         "<b>%s</b>: %s\n\n"
+                         "<b>%s</b>: %s",
+                         /* Translators: Used in the tooltip for a SMART attribute */
+                         _("Name"), n,
+                         /* Translators: Used in the tooltip for a SMART attribute */
+                         _("Type"), type_str,
+                         /* Translators: Used in the tooltip for a SMART attribute */
+                         _("Updates"), updates_str,
+                         /* Translators: Used in the tooltip for a SMART attribute */
+                         _("Description"), d);
+
+  g_free (n);
+  g_free (d);
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static gchar *
+calculate_self_test (DialogData *data,
+                     gboolean   *out_selftest_running)
+{
+  const gchar *s;
+  gchar *ret;
+  gboolean selftest_running = FALSE;
+
+  s = udisks_drive_ata_get_smart_selftest_status (data->ata);
+  if (g_strcmp0 (s, "success") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test completed successfully"));
+  else if (g_strcmp0 (s, "aborted") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test was aborted"));
+  else if (g_strcmp0 (s, "interrupted") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test was interrupted"));
+  else if (g_strcmp0 (s, "fatal") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test did not complete"));
+  else if (g_strcmp0 (s, "error_unknown") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test failed"));
+  else if (g_strcmp0 (s, "error_electrical") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test failed (electrical)"));
+  else if (g_strcmp0 (s, "error_servo") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test failed (servo)"));
+  else if (g_strcmp0 (s, "error_read") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test failed (read)"));
+  else if (g_strcmp0 (s, "error_handling") == 0)
+    ret = g_strdup (C_("smart-self-test-result", "Last self-test failed (handling)"));
+  else if (g_strcmp0 (s, "inprogress") == 0)
+    {
+      ret = g_strdup_printf (C_("smart-self-test-result", "A self-test is in progress (%d%% remaining)"),
+                             udisks_drive_ata_get_smart_selftest_percent_remaining (data->ata));
+      selftest_running = TRUE;
+    }
+  else
+    ret = g_strdup_printf (C_("smart-self-test-result", "Unknown (%s)"), s);
+
+  if (out_selftest_running)
+    *out_selftest_running = selftest_running;
+
+  return ret;
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+enum
+{
+  ID_COLUMN,
+  DESC_COLUMN,
+  ASSESSMENT_COLUMN,
+  VALUE_COLUMN,
+  VALUE2_COLUMN,
+  TOOLTIP_COLUMN,
+  N_COLUMNS,
+};
+
+static void
+update_updated_label (DialogData *data)
+{
+  time_t now;
+  time_t updated;
+  gchar *s;
+
+  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);
+  g_free (s);
+}
+
+static gchar *
+format_temp (UDisksDriveAta *ata)
+{
+  gdouble temp;
+  gchar *ret = NULL;
+
+  temp = udisks_drive_ata_get_smart_temperature (ata);
+  if (temp > 1.0)
+    {
+      gdouble celcius;
+      gdouble fahrenheit;
+
+      celcius = temp - 273.15;
+      fahrenheit = 9.0 * celcius / 5.0 + 32.0;
+      /* Translators: Used to format a temperature.
+       * The first %f is the temperature in degrees Celcius and
+       * the second %f is the temperature in degrees Fahrenheit.
+       */
+      ret = g_strdup_printf (_("%.0f C / %.0f F"), celcius, fahrenheit);
+    }
+  return ret;
+}
+
+static gchar *
+format_powered_on (UDisksDriveAta *ata)
+{
+  guint64 secs;
+  gchar *ret = NULL;
+
+  secs = udisks_drive_ata_get_smart_power_on_seconds (ata);
+  if (secs > 0)
+    ret = format_duration_msec (secs * 1000);
+  return ret;
+}
+
+gchar *
+gdu_ata_smart_get_overall_assessment (UDisksDriveAta *ata,
+                                      gboolean        include_temperature,
+                                      gboolean       *out_smart_is_supported)
+{
+  gchar *ret;
+  gint num_failing;
+  gint num_failed_in_the_past;
+  gint num_bad_sectors;
+  gboolean smart_is_supported = FALSE;
+
+  if (!udisks_drive_ata_get_smart_supported (ata))
+    {
+      /* Translators: XXX */
+      ret = g_strdup (_("SMART is not supported"));
+      goto out_no_smart;
+    }
+
+  if (!udisks_drive_ata_get_smart_enabled (ata))
+    {
+      /* Translators: XXX */
+      ret = g_strdup (_("SMART is not enabled"));
+      goto out_no_smart;
+    }
+
+  smart_is_supported = TRUE;
+
+  num_failing = udisks_drive_ata_get_smart_num_attributes_failing (ata);
+  num_failed_in_the_past = udisks_drive_ata_get_smart_num_attributes_failed_in_the_past (ata);
+  num_bad_sectors = udisks_drive_ata_get_smart_num_bad_sectors (ata);
+
+  /* If self-assessment indicates failure, just return that */
+  if (udisks_drive_ata_get_smart_failing (ata))
+    {
+      ret = g_strdup_printf ("<span foreground=\"#ff0000\"><b>%s</b></span>",
+                             /* Translators: XXX */
+                             _("DISK IS LIKELY TO FAIL SOON"));
+      goto out;
+    }
+
+  /* Otherwise, if an attribute is failing, return that */
+  if (num_failing > 0)
+    {
+      /* Translators: XXX */
+      ret = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
+                                        "Disk is OK, one failing attribute is failing",
+                                        "Disk is OK, %d attributes are failing",
+                                        num_failing),
+                             num_failing);
+      goto out;
+    }
+
+  /* Otherwise, if bad sectors have been detected, return that */
+  if (num_bad_sectors > 0)
+    {
+      /* Translators: XXX */
+      ret = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
+                                        "Disk is OK, one bad sector",
+                                        "Disk is OK, %d bad sectors",
+                                        num_bad_sectors),
+                             num_bad_sectors);
+      goto out;
+    }
+
+  /* Otherwise, if an attribute has failed in the past return that */
+  if (num_failed_in_the_past > 0)
+    {
+      /* Translators: XXX */
+      ret = g_strdup_printf (dngettext (GETTEXT_PACKAGE,
+                                        "Disk is OK, one attribute failed in the past",
+                                        "Disk is OK, %d attributes failed in the past",
+                                        num_failed_in_the_past),
+                             num_failed_in_the_past);
+      goto out;
+    }
+
+  /* Otherwise, it's all honky dory */
+
+  /* Translators: XXX */
+  ret = g_strdup ("Disk is OK");
+
+ out:
+
+  if (include_temperature)
+    {
+      gchar *s, *s1;
+      s = format_temp (ata);
+      if (s != NULL)
+        {
+          /* Translators: Used to convey the status and temperature in one line.
+           * The first %s is the status of the drive.
+           * The second %s is the temperature of the drive.
+           */
+          s1 = g_strdup_printf (_("%s (%s)"), ret, s);
+          g_free (ret);
+          ret = s1;
+        }
+    }
+
+ out_no_smart:
+  if (out_smart_is_supported != NULL)
+    *out_smart_is_supported = smart_is_supported;
+  return ret;
+}
+
+static void
+update_dialog (DialogData *data)
+{
+  gchar *s;
+  GVariant *attributes = NULL;
+  GError *error;
+  GtkTreeIter tree_iter;
+  GtkTreeIter *tree_iter_to_select;
+  gint selected_id;
+  gboolean selftest_running;
+
+  /* TODO: do it async and show spinner while call is pending */
+  error = NULL;
+  if (!udisks_drive_ata_call_smart_get_attributes_sync (udisks_object_peek_drive_ata (data->object),
+                                                        g_variant_new ("a{sv}", NULL), /* options */
+                                                        &attributes,
+                                                        NULL, /* GCancellable */
+                                                        &error))
+    {
+      g_warning ("Error getting ATA SMART information: %s (%s, %d)",
+                 error->message, g_quark_to_string (error->domain), error->code);
+      g_error_free (error);
+    }
+
+  update_updated_label (data);
+
+  s = calculate_self_test (data, &selftest_running);
+  gtk_label_set_text (GTK_LABEL (data->self_test_label), s);
+  g_free (s);
+
+  if (selftest_running)
+    {
+      gtk_widget_set_visible (data->start_selftest_button, FALSE);
+      gtk_widget_set_visible (data->stop_selftest_button, TRUE);
+    }
+  else
+    {
+      gtk_widget_set_visible (data->start_selftest_button, TRUE);
+      gtk_widget_set_visible (data->stop_selftest_button, FALSE);
+    }
+
+  /* record currently selected row so we can reselect it */
+  selected_id = -1;
+  tree_iter_to_select = NULL;
+  if (gtk_tree_selection_get_selected (gtk_tree_view_get_selection (GTK_TREE_VIEW (data->attributes_treeview)),
+                                       NULL,
+                                       &tree_iter))
+    {
+      gtk_tree_model_get (GTK_TREE_MODEL (data->attributes_list),
+                          &tree_iter,
+                          ID_COLUMN, &selected_id,
+                          -1);
+    }
+
+  gtk_list_store_clear (data->attributes_list);
+  if (attributes != NULL)
+    {
+      GVariantIter iter;
+      guchar id;
+      const gchar *name;
+      guint16 flags;
+      gint current, worst, threshold;
+      guint64 pretty; gint pretty_unit;
+      GVariant *expansion;
+
+      g_variant_iter_init (&iter, attributes);
+      while (g_variant_iter_next (&iter,
+                                  "(y&sqiiixi a{sv})",
+                                  &id,
+                                  &name,
+                                  &flags,
+                                  &current, &worst, &threshold,
+                                  &pretty, &pretty_unit,
+                                  &expansion))
+        {
+          GtkTreeIter titer;
+          gchar *desc_str;
+          gchar *assessment_str;
+          gchar *value_str;
+          gchar *value2_str;
+          gchar *tooltip_str;
+
+          desc_str = attr_format_desc (id, name);
+          assessment_str = attr_format_assessment (current, worst, threshold, flags);
+          value_str = attr_format_value (current, worst, threshold, pretty, pretty_unit, &value2_str);
+          tooltip_str = attr_format_tooltip (id, name, flags);
+
+          gtk_list_store_append (data->attributes_list, &titer);
+          gtk_list_store_set (data->attributes_list, &titer,
+                              ID_COLUMN, (gint) id,
+                              DESC_COLUMN, desc_str,
+                              ASSESSMENT_COLUMN, assessment_str,
+                              VALUE_COLUMN, value_str,
+                              VALUE2_COLUMN, value2_str,
+                              TOOLTIP_COLUMN, tooltip_str,
+                              -1);
+
+          if (id == selected_id)
+            tree_iter_to_select = gtk_tree_iter_copy (&titer);
+
+          g_free (desc_str);
+          g_free (assessment_str);
+          g_free (value_str);
+          g_free (value2_str);
+          g_free (tooltip_str);
+
+          g_variant_unref (expansion);
+        }
+    }
+
+  /* reselect the previously selected row */
+  if (tree_iter_to_select != NULL)
+    {
+      gtk_tree_selection_select_iter (gtk_tree_view_get_selection (GTK_TREE_VIEW (data->attributes_treeview)),
+                                      tree_iter_to_select);
+      gtk_tree_iter_free (tree_iter_to_select);
+    }
+  else
+    {
+      GtkTreeIter titer;
+      /* or the first row, if the previously selected one does not exist anymore */
+      if (gtk_tree_model_get_iter_first (GTK_TREE_MODEL (data->attributes_list), &titer))
+        {
+          gtk_tree_selection_select_iter (gtk_tree_view_get_selection (GTK_TREE_VIEW (data->attributes_treeview)),
+                                          &titer);
+        }
+    }
+
+  if (udisks_drive_ata_get_smart_failing (data->ata))
+    {
+      /* Translators: XXX */
+      s = g_strdup (_("Threshold exceeded"));
+    }
+  else
+    {
+      /* Translators: XXX */
+      s = g_strdup (_("Threshold not exceeded"));
+    }
+  gtk_label_set_markup (GTK_LABEL (data->self_assessment_label), s);
+  g_free (s);
+
+  s = format_powered_on (data->ata);
+  if (s == NULL)
+    s = g_strdup ("â");
+  gtk_label_set_markup (GTK_LABEL (data->powered_on_label), s);
+  g_free (s);
+
+  s = format_temp (data->ata);
+  if (s == NULL)
+    s = g_strdup ("â");
+  gtk_label_set_markup (GTK_LABEL (data->temperature_label), s);
+  g_free (s);
+
+  s = gdu_ata_smart_get_overall_assessment (data->ata, FALSE, NULL);
+  gtk_label_set_markup (GTK_LABEL (data->overall_assessment_label), s);
+  g_free (s);
+
+  if (attributes != NULL)
+    g_variant_unref (attributes);
+}
+
+/* called when properties on the Drive.Ata object changes */
+static void
+on_ata_notify (GObject     *object,
+               GParamSpec  *pspec,
+               gpointer     user_data)
+{
+  DialogData *data = user_data;
+  update_dialog (data);
+}
+
+/* called every second */
+static gboolean
+on_timeout (gpointer user_data)
+{
+  DialogData *data = user_data;
+  update_updated_label (data);
+  return TRUE; /* keep timeout around */
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
+static void
+smart_cancel_cb (UDisksDriveAta  *ata,
+                 GAsyncResult    *res,
+                 gpointer         user_data)
+{
+  GduWindow *window = GDU_WINDOW (user_data);
+  GError *error;
+
+  error = NULL;
+  if (!udisks_drive_ata_call_smart_selftest_abort_finish (ata, res, &error))
+    {
+      gdu_window_show_error (window,
+                             _("Error aborting SMART self-test"),
+                             error);
+      g_error_free (error);
+    }
+  g_object_unref (window);
+}
+
+static void
+smart_start_cb (UDisksDriveAta  *ata,
+                GAsyncResult    *res,
+                gpointer         user_data)
+{
+  GduWindow *window = GDU_WINDOW (user_data);
+  GError *error;
+
+  error = NULL;
+  if (!udisks_drive_ata_call_smart_selftest_start_finish (ata, res, &error))
+    {
+      gdu_window_show_error (window,
+                             _("Error starting SMART self-test"),
+                             error);
+      g_error_free (error);
+    }
+  g_object_unref (window);
+}
+
+
+static void
+selftest_do (DialogData  *data,
+             const gchar *type)
+{
+  if (g_strcmp0 (type, "abort") == 0)
+    {
+      udisks_drive_ata_call_smart_selftest_abort (data->ata,
+                                                  g_variant_new ("a{sv}", NULL), /* options */
+                                                  NULL, /* GCancellable */
+                                                  (GAsyncReadyCallback) smart_cancel_cb,
+                                                  g_object_ref (data->window));
+    }
+  else
+    {
+      udisks_drive_ata_call_smart_selftest_start (data->ata,
+                                                  type,
+                                                  g_variant_new ("a{sv}", NULL), /* options */
+                                                  NULL, /* GCancellable */
+                                                  (GAsyncReadyCallback) smart_start_cb,
+                                                  g_object_ref (data->window));
+    }
+}
+
+static void
+on_selftest_short (GtkMenuItem *menu_item,
+                   gpointer     user_data)
+{
+  DialogData *data = user_data;
+  selftest_do (data, "short");
+}
+
+static void
+on_selftest_extended (GtkMenuItem *menu_item,
+                      gpointer     user_data)
+{
+  DialogData *data = user_data;
+  selftest_do (data, "extended");
+}
+
+static void
+on_selftest_conveyance (GtkMenuItem *menu_item,
+                        gpointer     user_data)
+{
+  DialogData *data = user_data;
+  selftest_do (data, "conveyance");
+}
+
+void
+gdu_ata_smart_dialog_show (GduWindow    *window,
+                           UDisksObject *object)
+{
+  DialogData *data;
+  guint n;
+  GtkTreeViewColumn *column;
+  GtkCellRenderer *renderer;
+  gulong notify_id;
+  guint timeout_id;
+
+  data = g_new0 (DialogData, 1);
+  data->object = g_object_ref (object);
+  data->ata = udisks_object_peek_drive_ata (data->object);
+  data->window = g_object_ref (window);
+
+  data->dialog = gdu_application_new_widget (gdu_window_get_application (window),
+                                             "smart-dialog.ui",
+                                             "dialog1",
+                                             &data->builder);
+  for (n = 0; widget_mapping[n].name != NULL; n++)
+    {
+      gpointer *p = (gpointer *) ((char *) data + widget_mapping[n].offset);
+      *p = GTK_WIDGET (gtk_builder_get_object (data->builder, widget_mapping[n].name));
+    }
+
+  data->attributes_list = gtk_list_store_new (N_COLUMNS,
+                                              G_TYPE_INT,         /* id */
+                                              G_TYPE_STRING,      /* name */
+                                              G_TYPE_STRING,      /* assessment */
+                                              G_TYPE_STRING,      /* value */
+                                              G_TYPE_STRING,      /* value2 */
+                                              G_TYPE_STRING);     /* tooltip */
+  gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (data->attributes_list),
+                                        ID_COLUMN,
+                                        GTK_SORT_ASCENDING);
+  gtk_tree_view_set_model (GTK_TREE_VIEW (data->attributes_treeview),
+                           GTK_TREE_MODEL (data->attributes_list));
+
+  gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (data->attributes_treeview), TRUE);
+  gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (data->attributes_treeview), TOOLTIP_COLUMN);
+
+  column = gtk_tree_view_column_new ();
+  gtk_tree_view_append_column (GTK_TREE_VIEW (data->attributes_treeview), column);
+  /* Translators: This string is used as the column title in the treeview for the Attribute ID (0-255) */
+  gtk_tree_view_column_set_title (column, _("ID"));
+  renderer = gtk_cell_renderer_text_new ();
+  gtk_tree_view_column_pack_start (column, renderer, TRUE);
+  g_object_set (G_OBJECT (renderer),
+                "yalign", 0.5,
+                NULL);
+  gtk_tree_view_column_set_attributes (column, renderer,
+                                       "markup", ID_COLUMN, NULL);
+
+  column = gtk_tree_view_column_new ();
+  gtk_tree_view_append_column (GTK_TREE_VIEW (data->attributes_treeview), column);
+  /* Translators: This string is used as the column title in the treeview for the attribute name and description */
+  gtk_tree_view_column_set_title (column, _("Attribute"));
+  gtk_tree_view_column_set_expand (column, TRUE);
+  renderer = gtk_cell_renderer_text_new ();
+  g_object_set (G_OBJECT (renderer),
+                "yalign", 0.5,
+                "ellipsize", PANGO_ELLIPSIZE_MIDDLE,
+                NULL);
+  gtk_tree_view_column_pack_start (column, renderer, TRUE);
+  gtk_tree_view_column_set_attributes (column, renderer,
+                                       "markup", DESC_COLUMN, NULL);
+
+  column = gtk_tree_view_column_new ();
+  gtk_tree_view_append_column (GTK_TREE_VIEW (data->attributes_treeview), column);
+  /* Translators: This string is used as the column title in the treeview for the assessment of the attribute */
+  gtk_tree_view_column_set_title (column, _("Assessment"));
+  renderer = gtk_cell_renderer_text_new ();
+  gtk_tree_view_column_pack_start (column, renderer, FALSE);
+  g_object_set (G_OBJECT (renderer),
+                "yalign", 0.5,
+                NULL);
+  gtk_tree_view_column_set_attributes (column, renderer,
+                                       "markup", ASSESSMENT_COLUMN, NULL);
+
+  column = gtk_tree_view_column_new ();
+
+  gtk_tree_view_append_column (GTK_TREE_VIEW (data->attributes_treeview), column);
+  /* Translators: This string is used as the column title in the treeview for the value of the attribute */
+  gtk_tree_view_column_set_title (column, _("Value"));
+  renderer = gtk_cell_renderer_text_new ();
+  gtk_tree_view_column_pack_start (column, renderer, FALSE);
+  g_object_set (G_OBJECT (renderer),
+                "yalign", 0.0,
+                NULL);
+  gtk_tree_view_column_set_attributes (column, renderer,
+                                       "markup", VALUE_COLUMN, NULL);
+  renderer = gtk_cell_renderer_text_new ();
+  gtk_tree_view_column_pack_start (column, renderer, FALSE);
+  g_object_set (G_OBJECT (renderer),
+                "yalign", 0.0,
+                NULL);
+  gtk_tree_view_column_set_attributes (column, renderer,
+                                       "markup", VALUE2_COLUMN, NULL);
+
+
+  gtk_window_set_transient_for (GTK_WINDOW (data->dialog), GTK_WINDOW (window));
+
+  notify_id = g_signal_connect (data->ata, "notify", G_CALLBACK (on_ata_notify), data);
+  timeout_id = g_timeout_add_seconds (1, on_timeout, data);
+
+  g_signal_connect (data->selftest_short_menuitem, "activate", G_CALLBACK (on_selftest_short), data);
+  g_signal_connect (data->selftest_extended_menuitem, "activate", G_CALLBACK (on_selftest_extended), data);
+  g_signal_connect (data->selftest_conveyance_menuitem, "activate", G_CALLBACK (on_selftest_conveyance), data);
+
+  data->start_selftest_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), 0);
+  data->stop_selftest_button = gtk_dialog_get_widget_for_response (GTK_DIALOG (data->dialog), 1);
+
+  update_dialog (data);
+
+  while (TRUE)
+    {
+      gint response;
+      response = gtk_dialog_run (GTK_DIALOG (data->dialog));
+      switch (response)
+        {
+        case 0:
+          gtk_menu_popup (GTK_MENU (data->selftest_menu), NULL, NULL, NULL, NULL, 1, 0);
+          break;
+        case 1:
+          selftest_do (data, "abort");
+          break;
+        }
+
+      if (response < 0)
+        break;
+    }
+
+  g_source_remove (timeout_id);
+  g_signal_handler_disconnect (data->ata, notify_id);
+
+  dialog_data_free (data);
+}
diff --git a/src/palimpsest/gduatasmartdialog.h b/src/palimpsest/gduatasmartdialog.h
new file mode 100644
index 0000000..dce1d67
--- /dev/null
+++ b/src/palimpsest/gduatasmartdialog.h
@@ -0,0 +1,40 @@
+/* -*- 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_ATA_SMART_DIALOG_H_H__
+#define __GDU_ATA_SMART_DIALOG_H_H__
+
+#include <gtk/gtk.h>
+#include "gdutypes.h"
+
+G_BEGIN_DECLS
+
+void   gdu_ata_smart_dialog_show (GduWindow    *window,
+                                  UDisksObject *object);
+
+gchar *gdu_ata_smart_get_overall_assessment (UDisksDriveAta *ata,
+                                             gboolean        include_temperature,
+                                             gboolean       *out_smart_is_supported);
+
+G_END_DECLS
+
+#endif /* __GDU_ATA_SMART_DIALOG_H__ */
diff --git a/src/palimpsest/gduwindow.c b/src/palimpsest/gduwindow.c
index 13d19fd..bef6108 100644
--- a/src/palimpsest/gduwindow.c
+++ b/src/palimpsest/gduwindow.c
@@ -37,6 +37,7 @@
 #include "gdudevicetreemodel.h"
 #include "gduutils.h"
 #include "gduvolumegrid.h"
+#include "gduatasmartdialog.h"
 
 /* Keep in sync with tabs in palimpsest.ui file */
 typedef enum
@@ -87,6 +88,7 @@ struct _GduWindow
   GtkWidget *devtab_toolbar_deactivate_swap_button;
 
   GtkWidget *generic_menu;
+  GtkWidget *generic_menu_item_view_smart;
   GtkWidget *generic_menu_item_configure_fstab;
   GtkWidget *generic_menu_item_configure_crypttab;
   GtkWidget *generic_menu_item_edit_label;
@@ -123,6 +125,7 @@ static const struct {
   {G_STRUCT_OFFSET (GduWindow, devtab_toolbar_activate_swap_button), "devtab-action-activate-swap"},
   {G_STRUCT_OFFSET (GduWindow, devtab_toolbar_deactivate_swap_button), "devtab-action-deactivate-swap"},
   {G_STRUCT_OFFSET (GduWindow, generic_menu), "generic-menu"},
+  {G_STRUCT_OFFSET (GduWindow, generic_menu_item_view_smart), "generic-menu-item-view-smart"},
   {G_STRUCT_OFFSET (GduWindow, generic_menu_item_configure_fstab), "generic-menu-item-configure-fstab"},
   {G_STRUCT_OFFSET (GduWindow, generic_menu_item_configure_crypttab), "generic-menu-item-configure-crypttab"},
   {G_STRUCT_OFFSET (GduWindow, generic_menu_item_edit_label), "generic-menu-item-edit-label"},
@@ -155,16 +158,13 @@ typedef enum
   SHOW_FLAGS_ENCRYPTED_UNLOCK_BUTTON = (1<<7),
   SHOW_FLAGS_ENCRYPTED_LOCK_BUTTON   = (1<<8),
 
-  SHOW_FLAGS_POPUP_MENU_CONFIGURE_FSTAB    = (1<<9),
-  SHOW_FLAGS_POPUP_MENU_CONFIGURE_CRYPTTAB = (1<<10),
-  SHOW_FLAGS_POPUP_MENU_EDIT_LABEL         = (1<<11),
-  SHOW_FLAGS_POPUP_MENU_EDIT_PARTITION     = (1<<12),
+  SHOW_FLAGS_POPUP_MENU_VIEW_SMART         = (1<<9),
+  SHOW_FLAGS_POPUP_MENU_CONFIGURE_FSTAB    = (1<<10),
+  SHOW_FLAGS_POPUP_MENU_CONFIGURE_CRYPTTAB = (1<<11),
+  SHOW_FLAGS_POPUP_MENU_EDIT_LABEL         = (1<<12),
+  SHOW_FLAGS_POPUP_MENU_EDIT_PARTITION     = (1<<13),
 } ShowFlags;
 
-static void gdu_window_show_error (GduWindow   *window,
-                                   const gchar *message,
-                                   GError      *orig_error);
-
 static void setup_device_page (GduWindow *window, UDisksObject *object);
 static void update_device_page (GduWindow *window, ShowFlags *show_flags);
 static void teardown_device_page (GduWindow *window);
@@ -182,6 +182,8 @@ static void on_devtab_action_lock_activated (GtkAction *action, gpointer user_da
 static void on_devtab_action_activate_swap_activated (GtkAction *action, gpointer user_data);
 static void on_devtab_action_deactivate_swap_activated (GtkAction *action, gpointer user_data);
 
+static void on_generic_menu_item_view_smart (GtkMenuItem *menu_item,
+                                             gpointer   user_data);
 static void on_generic_menu_item_configure_fstab (GtkMenuItem *menu_item,
                                                   gpointer   user_data);
 static void on_generic_menu_item_configure_crypttab (GtkMenuItem *menu_item,
@@ -325,6 +327,8 @@ update_for_show_flags (GduWindow *window,
   gtk_action_set_visible (GTK_ACTION (window->devtab_toolbar_lock_button),
                           show_flags & SHOW_FLAGS_ENCRYPTED_LOCK_BUTTON);
 
+  gtk_widget_set_visible (GTK_WIDGET (window->generic_menu_item_view_smart),
+                          show_flags & SHOW_FLAGS_POPUP_MENU_VIEW_SMART);
   gtk_widget_set_visible (GTK_WIDGET (window->generic_menu_item_configure_fstab),
                           show_flags & SHOW_FLAGS_POPUP_MENU_CONFIGURE_FSTAB);
   gtk_widget_set_visible (GTK_WIDGET (window->generic_menu_item_configure_crypttab),
@@ -872,6 +876,10 @@ gdu_window_constructed (GObject *object)
                     G_CALLBACK (on_devtab_action_deactivate_swap_activated),
                     window);
 
+  g_signal_connect (window->generic_menu_item_view_smart,
+                    "activate",
+                    G_CALLBACK (on_generic_menu_item_view_smart),
+                    window);
   g_signal_connect (window->generic_menu_item_configure_fstab,
                     "activate",
                     G_CALLBACK (on_generic_menu_item_configure_fstab),
@@ -1213,12 +1221,16 @@ update_all (GduWindow     *window,
 
     case DETAILS_PAGE_DEVICE:
       /* this is a little too inclusive.. */
-      if (object != NULL && gdu_volume_grid_includes_object (GDU_VOLUME_GRID (window->volume_grid), object))
+      if (object != NULL)
         {
-          ShowFlags show_flags;
-          show_flags = SHOW_FLAGS_NONE;
-          update_details_page (window, window->current_page, &show_flags);
-          update_for_show_flags (window, show_flags);
+          if (object == window->current_object ||
+              gdu_volume_grid_includes_object (GDU_VOLUME_GRID (window->volume_grid), object))
+            {
+              ShowFlags show_flags;
+              show_flags = SHOW_FLAGS_NONE;
+              update_details_page (window, window->current_page, &show_flags);
+              update_for_show_flags (window, show_flags);
+            }
         }
       break;
     }
@@ -1423,74 +1435,14 @@ update_device_page_for_drive (GduWindow      *window,
 
   if (ata != NULL && !udisks_drive_get_media_removable (drive))
     {
-      gchar *s2 = NULL;
-      gchar *s3 = NULL;
-      if (!udisks_drive_ata_get_smart_supported (ata))
-        {
-          s = g_strdup (_("S.M.A.R.T. not supported"));
-        }
-      else if (!udisks_drive_ata_get_smart_enabled (ata))
-        {
-          s = g_strdup (_("S.M.A.R.T. not enabled"));
-        }
-      else
-        {
-          guint64 updated;
-
-          updated = udisks_drive_ata_get_smart_updated (ata);
-          if (updated == 0)
-            {
-              s = g_strdup (_("S.M.A.R.T. data not collected"));
-            }
-          else
-            {
-              gboolean failing;
-              gdouble temp;
-
-              failing = udisks_drive_ata_get_smart_failing (ata);
-              if (failing)
-                {
-                  s = g_strdup_printf ("<b><span foreground=\"#ff0000\">%s</span></b>",
-                                        _("ABOUT TO FAIL"));
-                }
-              else
-                {
-                  s = g_strdup (_("No problems detected"));
-                }
-              /* TODO: Also show
-               * - if one or more prefail attrs are exceeding threshold
-               * - if a self-test is in progress
-               */
-
-              temp = udisks_drive_ata_get_smart_temperature (ata);
-              if (temp > 1.0)
-                {
-                  gdouble celcius;
-                  gdouble fahrenheit;
-                  celcius = temp - 273.15;
-                  fahrenheit = 9.0 * celcius / 5.0 + 32.0;
-                  /* Translators: Used to convey the temperature of a drive.
-                   * The first %f is the temperature in degrees Celcius and the second %f
-                   * is the temperature in degrees Fahrenheit
-                   */
-                  s2 = g_strdup_printf (_("%.0f C / %.0f F"), celcius, fahrenheit);
-                }
-            }
-        }
-      if (s2 != NULL)
-        {
-          s3 = g_strdup_printf ("%s (%s)", s, s2);
-        }
-      else
-        {
-          s3 = g_strdup (s);
-        }
+      gboolean smart_is_supported;
+      s = gdu_ata_smart_get_overall_assessment (ata, TRUE, &smart_is_supported);
       set_markup (window,
                   "devtab-drive-smart-label",
                   "devtab-drive-smart-value-label",
-                  s3, SET_MARKUP_FLAGS_NONE);
-      g_free (s3);
-      g_free (s2);
+                  s, SET_MARKUP_FLAGS_NONE);
+      if (smart_is_supported)
+        *show_flags |= SHOW_FLAGS_POPUP_MENU_VIEW_SMART;
       g_free (s);
     }
 
@@ -2111,24 +2063,24 @@ teardown_device_page (GduWindow *window)
 /* ---------------------------------------------------------------------------------------------------- */
 
 /* TODO: right now we show a MessageDialog but we could do things like an InfoBar etc */
-static void
+void
 gdu_window_show_error (GduWindow   *window,
                        const gchar *message,
-                       GError      *orig_error)
+                       GError      *error)
 {
   GtkWidget *dialog;
-  GError *error;
+  GError *fixed_up_error;
 
   /* Never show an error if it's because the user dismissed the
    * authentication dialog himself
    */
-  if (orig_error->domain == UDISKS_ERROR &&
-      orig_error->code == UDISKS_ERROR_NOT_AUTHORIZED_DISMISSED)
+  if (error->domain == UDISKS_ERROR &&
+      error->code == UDISKS_ERROR_NOT_AUTHORIZED_DISMISSED)
     goto no_dialog;
 
-  error = g_error_copy (orig_error);
-  if (g_dbus_error_is_remote_error (error))
-    g_dbus_error_strip_remote_error (error);
+  fixed_up_error = g_error_copy (error);
+  if (g_dbus_error_is_remote_error (fixed_up_error))
+    g_dbus_error_strip_remote_error (fixed_up_error);
 
   dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW (window),
                                                GTK_DIALOG_MODAL,
@@ -2138,8 +2090,8 @@ gdu_window_show_error (GduWindow   *window,
                                                message);
   gtk_message_dialog_format_secondary_markup (GTK_MESSAGE_DIALOG (dialog),
                                               "%s",
-                                              error->message);
-  g_error_free (error);
+                                              fixed_up_error->message);
+  g_error_free (fixed_up_error);
   gtk_dialog_run (GTK_DIALOG (dialog));
   gtk_widget_destroy (dialog);
 
@@ -2993,6 +2945,16 @@ on_generic_menu_item_configure_fstab (GtkMenuItem *menu_item,
 
 /* ---------------------------------------------------------------------------------------------------- */
 
+static void
+on_generic_menu_item_view_smart (GtkMenuItem *menu_item,
+                                 gpointer     user_data)
+{
+  GduWindow *window = GDU_WINDOW (user_data);
+  gdu_ata_smart_dialog_show (window, window->current_object);
+}
+
+/* ---------------------------------------------------------------------------------------------------- */
+
 typedef struct
 {
   UDisksDrive *drive;
diff --git a/src/palimpsest/gduwindow.h b/src/palimpsest/gduwindow.h
index 8190eb0..da47aed 100644
--- a/src/palimpsest/gduwindow.h
+++ b/src/palimpsest/gduwindow.h
@@ -38,6 +38,10 @@ GduWindow      *gdu_window_new             (GduApplication *application,
 GduApplication *gdu_window_get_application (GduWindow      *window);
 UDisksClient   *gdu_window_get_client      (GduWindow      *window);
 
+void            gdu_window_show_error      (GduWindow      *window,
+                                            const gchar    *message,
+                                            GError         *error);
+
 G_END_DECLS
 
 #endif /* __GDU_WINDOW_H__ */



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