store historical SMART data and add DriveSmartGetHistoricalData()
authorDavid Zeuthen <davidz@redhat.com>
Sun, 4 May 2008 15:42:48 +0000 (11:42 -0400)
committerDavid Zeuthen <davidz@redhat.com>
Sun, 4 May 2008 15:42:48 +0000 (11:42 -0400)
This add sqlite3 as a dependency.

configure.in
doc/TODO
src/Makefile.am
src/devkit-disks-daemon.c
src/devkit-disks-daemon.h
src/devkit-disks-device-private.h
src/devkit-disks-device.c
src/devkit-disks-device.h
src/devkit-disks-logger.c [new file with mode: 0644]
src/devkit-disks-logger.h [new file with mode: 0644]
src/org.freedesktop.DeviceKit.Disks.Device.xml

index a493c9e..d096bda 100644 (file)
@@ -125,6 +125,10 @@ PKG_CHECK_MODULES(LIBPARTED, [libparted >= 1.8.8])
 AC_SUBST(LIBPARTED_CFLAGS)
 AC_SUBST(LIBPARTED_LIBS)
 
+PKG_CHECK_MODULES(SQLITE3, [sqlite3])
+AC_SUBST(SQLITE3_CFLAGS)
+AC_SUBST(SQLITE3_LIBS)
+
 if test "x$GCC" = "xyes"; then
   LDFLAGS="-Wl,--as-needed $LDFLAGS"
 fi
index 65056e6..8d8006a 100644 (file)
--- a/doc/TODO
+++ b/doc/TODO
@@ -2,3 +2,7 @@
 - Need a way to stop polling for SMART data to avoid
   breaking boxes with thousands of disks
   - maybe default to polling if there's less than 20 drives
+    - Probably needs config file
+
+- Delete all SMART data older than N days
+  - Probably needs config file
index d9fa1b8..8aa6019 100644 (file)
@@ -40,6 +40,7 @@ dbusif_DATA = org.freedesktop.DeviceKit.Disks.xml org.freedesktop.DeviceKit.Disk
 
 devkit_disks_daemon_SOURCES =                                          \
        devkit-disks-daemon.h           devkit-disks-daemon.c           \
+       devkit-disks-logger.h           devkit-disks-logger.c           \
        devkit-disks-device.h           devkit-disks-device.c           \
        devkit-disks-device-private.h                                   \
        mounts-file.h                   mounts-file.c                   \
@@ -49,11 +50,13 @@ devkit_disks_daemon_SOURCES =                                               \
 devkit_disks_daemon_CPPFLAGS =                                 \
        -I$(top_srcdir)/src                             \
        -DG_LOG_DOMAIN=\"devkit-disks-daemon\"          \
+       $(SQLITE3_CFLAGS)                               \
        $(DISABLE_DEPRECATED)                           \
        $(AM_CPPFLAGS)
 
 devkit_disks_daemon_LDADD =                            \
        $(GIO_LIBS)                                     \
+       $(SQLITE3_LIBS)                                 \
        $(DBUS_GLIB_LIBS)                               \
        $(POLKIT_DBUS_LIBS)                             \
        $(DEVKIT_LIBS)
index f92fb37..03e89ea 100644 (file)
@@ -89,6 +89,8 @@ struct DevkitDisksDaemonPrivate
         GUnixMountMonitor *mount_monitor;
 
         guint              smart_refresh_timer_id;
+
+        DevkitDisksLogger *logger;
 };
 
 static void     devkit_disks_daemon_class_init  (DevkitDisksDaemonClass *klass);
@@ -355,6 +357,10 @@ devkit_disks_daemon_finalize (GObject *object)
                 g_source_remove (daemon->priv->smart_refresh_timer_id);
         }
 
+        if (daemon->priv->logger != NULL) {
+                g_object_unref (daemon->priv->logger);
+        }
+
         G_OBJECT_CLASS (devkit_disks_daemon_parent_class)->finalize (object);
 }
 
@@ -767,6 +773,8 @@ register_disks_daemon (DevkitDisksDaemon *daemon)
         //g_unix_mount_monitor_set_rate_limit (daemon->priv->mount_monitor, 50);
         g_signal_connect (daemon->priv->mount_monitor, "mounts-changed", (GCallback) mounts_changed, daemon);
 
+        daemon->priv->logger = devkit_disks_logger_new ();
+
         return TRUE;
 error:
         return FALSE;
@@ -819,6 +827,12 @@ devkit_disks_daemon_new (void)
         return daemon;
 }
 
+DevkitDisksLogger *
+devkit_disks_daemon_local_get_logger (DevkitDisksDaemon *daemon)
+{
+        return daemon->priv->logger;
+}
+
 PolKitCaller *
 devkit_disks_damon_local_get_caller_for_context (DevkitDisksDaemon *daemon,
                                                  DBusGMethodInvocation *context)
index 7fd77cd..1a32519 100644 (file)
 #include <polkit-dbus/polkit-dbus.h>
 #include <dbus/dbus-glib.h>
 
+struct DevkitDisksDevice;
+typedef struct DevkitDisksDevice DevkitDisksDevice;
+
+#include "devkit-disks-logger.h"
+
 G_BEGIN_DECLS
 
 #define DEVKIT_TYPE_DISKS_DAEMON         (devkit_disks_daemon_get_type ())
@@ -66,9 +71,6 @@ DevkitDisksDaemon *devkit_disks_daemon_new                 (void);
 
 /* local methods */
 
-struct DevkitDisksDevice;
-typedef struct DevkitDisksDevice DevkitDisksDevice;
-
 GList             *devkit_disks_daemon_local_get_all_devices     (DevkitDisksDaemon       *daemon);
 DevkitDisksDevice *devkit_disks_daemon_local_find_by_native_path (DevkitDisksDaemon       *daemon,
                                                                   const char              *native_path);
@@ -88,6 +90,8 @@ void               devkit_disks_daemon_local_update_mount_state  (DevkitDisksDae
 void               devkit_disks_daemon_local_synthesize_changed  (DevkitDisksDaemon       *daemon,
                                                                   DevkitDevice            *d);
 
+DevkitDisksLogger *devkit_disks_daemon_local_get_logger (DevkitDisksDaemon *daemon);
+
 /* exported methods */
 
 gboolean devkit_disks_daemon_enumerate_devices (DevkitDisksDaemon     *daemon,
index cafba43..e1a7cc2 100644 (file)
@@ -31,6 +31,25 @@ G_BEGIN_DECLS
 struct Job;
 typedef struct Job Job;
 
+#define SMART_DATA_STRUCT_TYPE (dbus_g_type_get_struct ("GValueArray",   \
+                                                        G_TYPE_INT,      \
+                                                        G_TYPE_STRING,   \
+                                                        G_TYPE_INT,      \
+                                                        G_TYPE_INT,      \
+                                                        G_TYPE_INT,      \
+                                                        G_TYPE_INT,      \
+                                                        G_TYPE_STRING,   \
+                                                        G_TYPE_INVALID))
+
+#define HISTORICAL_SMART_DATA_STRUCT_TYPE (dbus_g_type_get_struct ("GValueArray",   \
+                                                                   G_TYPE_UINT64, \
+                                                                   G_TYPE_DOUBLE, \
+                                                                   G_TYPE_UINT64, \
+                                                                   G_TYPE_STRING, \
+                                                                   G_TYPE_BOOLEAN, \
+                                                                   dbus_g_type_get_collection ("GPtrArray", SMART_DATA_STRUCT_TYPE), \
+                                                                   G_TYPE_INVALID))
+
 struct DevkitDisksDevicePrivate
 {
         DBusGConnection *system_bus_connection;
index b9a8204..93975ea 100644 (file)
@@ -48,6 +48,7 @@
 #include <dbus/dbus-glib-lowlevel.h>
 #include <devkit-gobject.h>
 #include <polkit-dbus/polkit-dbus.h>
+#include <sqlite3.h>
 
 #include "devkit-disks-device.h"
 #include "devkit-disks-device-private.h"
@@ -555,16 +556,6 @@ get_property (GObject         *object,
         }
 }
 
-#define SMART_DATA_STRUCT_TYPE (dbus_g_type_get_struct ("GValueArray",   \
-                                                        G_TYPE_INT,      \
-                                                        G_TYPE_STRING,   \
-                                                        G_TYPE_INT,      \
-                                                        G_TYPE_INT,      \
-                                                        G_TYPE_INT,      \
-                                                        G_TYPE_INT,      \
-                                                        G_TYPE_STRING,   \
-                                                        G_TYPE_INVALID))
-
 static void
 devkit_disks_device_class_init (DevkitDisksDeviceClass *klass)
 {
@@ -2494,7 +2485,7 @@ job_read_out (GIOChannel *channel,
                 line = g_strndup (job->stdout_string->str + job->stdout_string_cursor, line_len);
                 job->stdout_string_cursor += line_len + 1;
 
-                g_print ("helper(pid %5d): '%s'\n", job->pid, line);
+                //g_print ("helper(pid %5d): '%s'\n", job->pid, line);
 
                 if (strlen (line) < 256) {
                         int cur_task;
@@ -5545,6 +5536,9 @@ drive_smart_refresh_data_completed_cb (DBusGMethodInvocation *context,
         /* emit change event since we've updated the smart data */
         emit_changed (device);
 
+        devkit_disks_logger_record_smart_values (devkit_disks_daemon_local_get_logger (device->priv->daemon),
+                                                 device);
+
         if (context != NULL)
                 dbus_g_method_return (context, passed, power_on_hours, temperature, last_self_test_result);
 out:
index 74c747c..35a77ef 100644 (file)
@@ -185,6 +185,11 @@ gboolean devkit_disks_device_drive_smart_refresh_data (DevkitDisksDevice     *de
                                                        char                 **options,
                                                        DBusGMethodInvocation *context);
 
+gboolean devkit_disks_device_drive_smart_get_historical_data (DevkitDisksDevice     *device,
+                                                              guint64                from,
+                                                              guint64                to,
+                                                              DBusGMethodInvocation *context);
+
 gboolean devkit_disks_device_drive_smart_initiate_selftest (DevkitDisksDevice     *device,
                                                             const char            *test,
                                                             gboolean               captive,
diff --git a/src/devkit-disks-logger.c b/src/devkit-disks-logger.c
new file mode 100644 (file)
index 0000000..148a431
--- /dev/null
@@ -0,0 +1,485 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <david@fubar.dk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include "config.h"
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <glib/gi18n-lib.h>
+#include <glib-object.h>
+#include <dbus/dbus-glib.h>
+#include <dbus/dbus-glib-lowlevel.h>
+#include <sqlite3.h>
+
+#include "devkit-disks-device.h"
+#include "devkit-disks-device-private.h"
+#include "devkit-disks-logger.h"
+
+struct DevkitDisksLoggerPrivate
+{
+        sqlite3 *db;
+};
+
+static GObjectClass *parent_class = NULL;
+
+G_DEFINE_TYPE (DevkitDisksLogger, devkit_disks_logger, G_TYPE_OBJECT)
+
+#define DEVKIT_DISKS_LOGGER_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), DEVKIT_TYPE_DISKS_DEVICE, DevkitDisksDevicePrivate))
+
+
+static void
+devkit_disks_logger_finalize (DevkitDisksLogger *logger)
+{
+        if (logger->priv->db != NULL)
+                sqlite3_close (logger->priv->db);
+
+        if (G_OBJECT_CLASS (parent_class)->finalize)
+                (* G_OBJECT_CLASS (parent_class)->finalize) (G_OBJECT (logger));
+}
+
+static void
+devkit_disks_logger_class_init (DevkitDisksLoggerClass *klass)
+{
+        GObjectClass *obj_class = (GObjectClass *) klass;
+
+        parent_class = g_type_class_peek_parent (klass);
+
+        obj_class->finalize = (GObjectFinalizeFunc) devkit_disks_logger_finalize;
+
+}
+
+static void
+devkit_disks_logger_init (DevkitDisksLogger *logger)
+{
+        logger->priv = g_new0 (DevkitDisksLoggerPrivate, 1);
+}
+
+DevkitDisksLogger *
+devkit_disks_logger_new (void)
+{
+        int ret;
+        char *err_msg;
+        DevkitDisksLogger * logger;
+
+        logger = DEVKIT_DISKS_LOGGER (g_object_new (DEVKIT_TYPE_DISKS_LOGGER, NULL));
+
+        ret = sqlite3_open_v2 (PACKAGE_LOCALSTATE_DIR "/lib/DeviceKit-disks/db.sqlite3",
+                               &logger->priv->db,
+                               SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE,
+                               NULL);
+        if (ret != SQLITE_OK) {
+                g_warning ("error opening db: %s", sqlite3_errmsg (logger->priv->db));
+                sqlite3_close (logger->priv->db);
+                goto error;
+        }
+
+        /* create tables */
+        ret = sqlite3_exec (logger->priv->db,
+                            "CREATE TABLE SmartEntry ("
+                            "  smart_entry_id INTEGER PRIMARY KEY, "
+                            "  disk_id TEXT, "
+                            "  time_collected INTEGER, "
+                            "  temperature INTEGER, "
+                            "  time_powered_on INTEGER, "
+                            "  last_self_test_result TEXT, "
+                            "  is_failing INTEGER"
+                            ");"
+                            "CREATE TABLE SmartAttr ("
+                            "  smart_entry_id INTEGER, "
+                            "  disk_id TEXT, "
+                            "  time_collected INTEGER, "
+                            "  id INTEGER, "
+                            "  name TEXT, "
+                            "  flags INTEGER, "
+                            "  value INTEGER, "
+                            "  worst INTEGER, "
+                            "  threshold INTEGER, "
+                            "  raw TEXT"
+                            ");",
+                            NULL,
+                            NULL,
+                            &err_msg);
+        if (ret != SQLITE_OK) {
+                g_warning ("SQL error: %s", err_msg);
+                sqlite3_free (err_msg);
+        }
+
+        return logger;
+
+error:
+        g_object_unref (logger);
+        return NULL;
+}
+
+static char *
+drive_get_safe_uuid (DevkitDisksDevice *device)
+{
+        char *s;
+        char *result;
+
+        result = NULL;
+
+        if (device->priv->info.drive_vendor == NULL || strlen (device->priv->info.drive_vendor) == 0)
+                goto out;
+        if (device->priv->info.drive_model == NULL || strlen (device->priv->info.drive_model) == 0)
+                goto out;
+        if (device->priv->info.drive_revision == NULL || strlen (device->priv->info.drive_revision) == 0)
+                goto out;
+        if (device->priv->info.drive_serial == NULL || strlen (device->priv->info.drive_serial) == 0)
+                goto out;
+
+        s = g_strdup_printf ("%s_%s_%s_%s",
+                             device->priv->info.drive_vendor,
+                             device->priv->info.drive_model,
+                             device->priv->info.drive_revision,
+                             device->priv->info.drive_serial);
+        result = g_uri_escape_string (s, NULL, FALSE);
+        g_free (s);
+
+out:
+        return result;
+}
+
+void
+devkit_disks_logger_record_smart_values (DevkitDisksLogger *logger,
+                                         DevkitDisksDevice *device)
+{
+        int n;
+        int ret;
+        char *err_msg;
+        char *s;
+        char *disk_id;
+        sqlite3_int64 row_id;
+        GString *str;
+
+        g_return_if_fail (device != NULL);
+        g_return_if_fail (logger != NULL);
+        g_return_if_fail (logger->priv->db != NULL);
+
+        disk_id = NULL;
+
+        disk_id = drive_get_safe_uuid (device);
+        if (disk_id == NULL) {
+                g_warning ("no drive uuid for %s", device->priv->native_path);
+                goto out;
+        }
+
+        s = sqlite3_mprintf (
+                "BEGIN TRANSACTION;"
+                "INSERT INTO SmartEntry "
+                "(disk_id, time_collected, temperature, time_powered_on, last_self_test_result, is_failing) "
+                "VALUES ('%q', %" G_GUINT64_FORMAT ", %d, %" G_GUINT64_FORMAT ", '%q', %d)",
+                disk_id,
+                device->priv->drive_smart_time_collected,
+                (int) device->priv->drive_smart_temperature,
+                device->priv->drive_smart_time_powered_on,
+                device->priv->drive_smart_last_self_test_result,
+                device->priv->drive_smart_is_failing ? 1 : 0);
+        ret = sqlite3_exec (logger->priv->db, s, NULL, NULL, &err_msg);
+        sqlite3_free (s);
+        if (ret != SQLITE_OK) {
+                g_warning ("SQL error: %s", err_msg);
+                sqlite3_free (err_msg);
+                goto out;
+        }
+
+        row_id = sqlite3_last_insert_rowid (logger->priv->db);
+
+        str = g_string_new (NULL);
+        for (n = 0; n < (int) device->priv->drive_smart_attributes->len; n++) {
+                GValue elem = {0};
+                int id;
+                char *name;
+                int flags;
+                int value;
+                int worst;
+                int threshold;
+                char *raw_string;
+
+                g_value_init (&elem, SMART_DATA_STRUCT_TYPE);
+                g_value_set_static_boxed (&elem, device->priv->drive_smart_attributes->pdata[n]);
+                dbus_g_type_struct_get (&elem,
+                                        0, &id,
+                                        1, &name,
+                                        2, &flags,
+                                        3, &value,
+                                        4, &worst,
+                                        5, &threshold,
+                                        6, &raw_string,
+                                        G_MAXUINT);
+
+                s = sqlite3_mprintf (
+                        "INSERT INTO SmartAttr "
+                        "VALUES (%" G_GINT64_FORMAT ", '%q', %" G_GUINT64_FORMAT ", %d, '%q', %d, %d, %d, %d, '%q');\n",
+                        row_id,
+                        disk_id,
+                        device->priv->drive_smart_time_collected,
+                        id,
+                        name,
+                        flags,
+                        value,
+                        worst,
+                        threshold,
+                        raw_string);
+                g_string_append (str, s);
+                sqlite3_free (s);
+        }
+
+        g_string_append_printf (str, "COMMIT;");
+
+        s = g_string_free (str, FALSE);
+        ret = sqlite3_exec (logger->priv->db, s, NULL, NULL, &err_msg);
+        g_free (s);
+        if (ret != SQLITE_OK) {
+                g_warning ("SQL error: %s", err_msg);
+                sqlite3_free (err_msg);
+        }
+out:
+        g_free (disk_id);
+}
+
+static gboolean
+throw_error (DBusGMethodInvocation *context, int error_code, const char *format, ...)
+{
+        GError *error;
+        va_list args;
+        char *message;
+
+        if (context == NULL)
+                return TRUE;
+
+        va_start (args, format);
+        message = g_strdup_vprintf (format, args);
+        va_end (args);
+
+        error = g_error_new (DEVKIT_DISKS_DEVICE_ERROR,
+                             error_code,
+                             message);
+        dbus_g_method_return_error (context, error);
+        g_error_free (error);
+        g_free (message);
+        return TRUE;
+}
+
+typedef struct {
+        GPtrArray *array;
+
+        gint64 cur_rowid;
+        gboolean needs_draining;
+
+        guint64 time_collected;
+        double temperature;
+        guint64 time_powered_on;
+        char last_self_test_result[256];
+        gboolean is_failing;
+        GPtrArray *attrs;
+} HistoricalData;
+
+static void
+historical_data_drain (HistoricalData *data)
+{
+        GValue elem = {0};
+
+        if (!data->needs_draining)
+                return;
+
+        g_value_init (&elem, HISTORICAL_SMART_DATA_STRUCT_TYPE);
+        g_value_take_boxed (&elem, dbus_g_type_specialized_construct (HISTORICAL_SMART_DATA_STRUCT_TYPE));
+        dbus_g_type_struct_set (&elem,
+                                0, data->time_collected,
+                                1, data->temperature,
+                                2, data->time_powered_on,
+                                3, data->last_self_test_result,
+                                4, data->is_failing,
+                                5, data->attrs,
+                                G_MAXUINT);
+        g_ptr_array_add (data->array, g_value_get_boxed (&elem));
+
+        g_ptr_array_foreach (data->attrs, (GFunc) g_value_array_free, NULL);
+        g_ptr_array_free (data->attrs, TRUE);
+        data->attrs = NULL;
+        data->needs_draining = FALSE;
+}
+
+static int
+historical_data_cb (void *user_data, int argc, char **argv, char **col_name)
+{
+        HistoricalData *data = (HistoricalData *) user_data;
+        gint64 rowid;
+        int id;
+        const char *name;
+        int flags;
+        int value;
+        int worst;
+        int threshold;
+        const char *raw;
+
+        if (argc != 13) {
+                g_warning ("expected 13 columns, got %d instead", argc);
+                goto out;
+        }
+
+        /* TODO: could add checks for the column types */
+
+        rowid = atoll (argv[0]);
+        if (rowid != data->cur_rowid) {
+                if (data->needs_draining) {
+                        historical_data_drain (data);
+                }
+
+                data->needs_draining = TRUE;
+                data->cur_rowid = rowid;
+                data->time_collected = atoll (argv[1]);
+                data->temperature = atof (argv[2]);
+                data->time_powered_on = atoll (argv[3]);
+                strncpy (data->last_self_test_result, argv[4], 256);
+                data->is_failing = (strcmp (argv[5], "0") != 0);
+                data->attrs = g_ptr_array_new ();
+
+                /*g_warning ("got time_collected=%lld temperature=%g time_powered_on=%lld lstr='%s' is_failing=%d",
+                           data->time_collected,
+                           data->temperature,
+                           data->time_powered_on,
+                           data->last_self_test_result,
+                           data->is_failing);*/
+        }
+
+        id = atoi (argv[6]);
+        name = argv[7];
+        flags = atoi (argv[8]);
+        value = atoi (argv[9]);
+        worst = atoi (argv[10]);
+        threshold = atoi (argv[11]);
+        raw = argv[12];
+
+        /*g_warning ("got id=%d name='%s' flags=0x%04x value=%d worst=%d threshold=%d raw='%s'",
+          id, name, flags, value, worst, threshold, raw);*/
+
+        GValue elem = {0};
+        g_value_init (&elem, SMART_DATA_STRUCT_TYPE);
+        g_value_take_boxed (&elem, dbus_g_type_specialized_construct (SMART_DATA_STRUCT_TYPE));
+        dbus_g_type_struct_set (&elem,
+                                0, id,
+                                1, name,
+                                2, flags,
+                                3, value,
+                                4, worst,
+                                5, threshold,
+                                6, raw,
+                                G_MAXUINT);
+        g_ptr_array_add (data->attrs, g_value_get_boxed (&elem));
+
+
+        /*
+        int n;
+        for (n = 0; n < argc; n++) {
+                printf ("%s = %s\n", col_name[n], argv[n] ? argv[n] : "NULL");
+        }
+        printf("\n");
+        */
+
+out:
+        return 0;
+}
+
+gboolean
+devkit_disks_device_drive_smart_get_historical_data (DevkitDisksDevice     *device,
+                                                     guint64                from,
+                                                     guint64                to,
+                                                     DBusGMethodInvocation *context)
+{
+        char *s;
+        char *disk_id;
+        GTimeVal now;
+        int ret;
+        char *err_msg;
+        DevkitDisksLogger *logger;
+        HistoricalData *data;
+
+        logger = devkit_disks_daemon_local_get_logger (device->priv->daemon);
+
+        disk_id = drive_get_safe_uuid (device);
+        if (disk_id == NULL) {
+                g_warning ("no drive uuid for %s", device->priv->native_path);
+                throw_error (context, DEVKIT_DISKS_DEVICE_ERROR_GENERAL, "No unique disk id for device");
+                goto out;
+        }
+
+        if (from > to) {
+                throw_error (context, DEVKIT_DISKS_DEVICE_ERROR_GENERAL, "Malformed time range (from > to)");
+                goto out;
+        }
+
+        if (to == 0) {
+                g_get_current_time (&now);
+                to = (guint64) now.tv_sec;
+        }
+
+        data = g_new0 (HistoricalData, 1);
+        data->array = g_ptr_array_new ();
+        data->cur_rowid = -1;
+
+        s = sqlite3_mprintf ("SELECT"
+                             " SmartEntry.smart_entry_id,"
+                             " SmartEntry.time_collected,"
+                             " SmartEntry.temperature,"
+                             " SmartEntry.time_powered_on,"
+                             " SmartEntry.last_self_test_result,"
+                             " SmartEntry.is_failing,"
+                             " SmartAttr.id,"
+                             " SmartAttr.name,"
+                             " Smartattr.flags, "
+                             " SmartAttr.value,"
+                             " SmartAttr.worst,"
+                             " SmartAttr.threshold,"
+                             " SmartAttr.raw "
+                             "FROM SmartEntry, SmartAttr "
+                             "WHERE"
+                             " SmartEntry.disk_id='%q' AND"
+                             " SmartEntry.smart_entry_id=SmartAttr.smart_entry_id AND"
+                             " SmartEntry.time_collected >= %" G_GUINT64_FORMAT " AND"
+                             " SmartEntry.time_collected <= %" G_GUINT64_FORMAT " "
+                             "ORDER BY SmartEntry.smart_entry_id, SmartAttr.id;",
+                             disk_id, from, to);
+        ret = sqlite3_exec (logger->priv->db,
+                            s,
+                            historical_data_cb,
+                            data,
+                            &err_msg);
+        if (ret != SQLITE_OK) {
+                g_warning ("SQL error: %s", err_msg);
+                sqlite3_free (err_msg);
+        }
+        sqlite3_free (s);
+
+        historical_data_drain (data);
+        dbus_g_method_return (context, data->array);
+        g_ptr_array_foreach (data->array, (GFunc) g_value_array_free, NULL);
+        g_ptr_array_free (data->array, TRUE);
+        g_free (data);
+
+out:
+        g_free (disk_id);
+        return TRUE;
+}
diff --git a/src/devkit-disks-logger.h b/src/devkit-disks-logger.h
new file mode 100644 (file)
index 0000000..44b1ec1
--- /dev/null
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2008 David Zeuthen <david@fubar.dk>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __DEVKIT_DISKS_LOGGER_H__
+#define __DEVKIT_DISKS_LOGGER_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define DEVKIT_TYPE_DISKS_LOGGER         (devkit_disks_logger_get_type ())
+#define DEVKIT_DISKS_LOGGER(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), DEVKIT_TYPE_DISKS_LOGGER, DevkitDisksLogger))
+#define DEVKIT_DISKS_LOGGER_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST((k), DEVKIT_TYPE_DISKS_LOGGER, DevkitDisksLoggerClass))
+#define DEVKIT_IS_DISKS_LOGGER(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), DEVKIT_TYPE_DISKS_LOGGER))
+#define DEVKIT_IS_DISKS_LOGGER_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), DEVKIT_TYPE_DISKS_LOGGER))
+#define DEVKIT_DISKS_LOGGER_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), DEVKIT_TYPE_DISKS_LOGGER, DevkitDisksLoggerClass))
+
+typedef struct DevkitDisksLoggerPrivate DevkitDisksLoggerPrivate;
+
+typedef struct
+{
+        GObject                   parent;
+        DevkitDisksLoggerPrivate *priv;
+} DevkitDisksLogger;
+
+typedef struct
+{
+        GObjectClass   parent_class;
+} DevkitDisksLoggerClass;
+
+GType              devkit_disks_logger_get_type              (void);
+DevkitDisksLogger *devkit_disks_logger_new                   (void);
+void               devkit_disks_logger_record_smart_values   (DevkitDisksLogger *logger,
+                                                              DevkitDisksDevice *device);
+
+G_END_DECLS
+
+#endif /* __DEVKIT_DISKS_LOGGER_H__ */
index ab08941..f961193 100644 (file)
       </doc:doc>
     </method>
 
+    <method name="DriveSmartGetHistoricalData">
+      <annotation name="org.freedesktop.DBus.GLib.Async" value=""/>
+      <arg name="since" direction="in" type="t">
+        <doc:doc><doc:summary>
+            Don't fetch S.M.A.R.T. data collected earlier than this point
+            in time (seconds since the Epoch Jan 1, 1970 0:00 UTC).
+        </doc:summary></doc:doc>
+      </arg>
+      <arg name="until" direction="in" type="t">
+        <doc:doc><doc:summary>
+            Don't fetch S.M.A.R.T. data collected later than this point in
+            time (seconds since the Epoch Jan 1, 1970 0:00 UTC). If 0 is
+            passed the current time will be used.
+        </doc:summary></doc:doc>
+      </arg>
+      <arg name="data" direction="out" type="a(tdtsba(isiiiis))">
+        <doc:doc><doc:summary>
+            An array of historical S.M.A.R.T. data. Each element contains
+            the following members:
+            <doc:list>
+              <doc:item>
+                <doc:term>time</doc:term>
+                <doc:definition>
+                  When the data was collected (seconds since the Epoch Jan 1, 1970 0:00 UTC). Similar to the
+                  <doc:ref type="property" to="Device:drive-smart-time-collected">drive-smart-time-collected</doc:ref>
+                  property.
+                </doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>temperature</doc:term>
+                <doc:definition>
+                  Temperature of the drive. Similar to the
+                  <doc:ref type="property" to="Device:drive-smart-temperature">drive-smart-temperature</doc:ref>
+                  property.
+                </doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>time-powered-on</doc:term>
+                <doc:definition>
+                  Number of the seconds the drive has been powered on. Similar to the
+                  <doc:ref type="property" to="Device:drive-smart-time-powered-on">drive-smart-time-powered-on</doc:ref>
+                  property.
+                </doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>last-self-test-result</doc:term>
+                <doc:definition>
+                  The result of the last self-test. Similar to the
+                  <doc:ref type="property" to="Device:drive-smart-last-self-test-result">drive-smart-last-self-test-result</doc:ref>
+                  property.
+                </doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>is-failing</doc:term>
+                <doc:definition>
+                  If the drive is assessed to be failing. Similar to the
+                  <doc:ref type="property" to="Device:drive-smart-is-failing">drive-smart-is-failing</doc:ref>
+                  property.
+                </doc:definition>
+              </doc:item>
+              <doc:item>
+                <doc:term>attributes</doc:term>
+                <doc:definition>
+                  The attributes of the drive. Similar to the
+                  <doc:ref type="property" to="Device:drive-smart-attributes">drive-smart-attributes</doc:ref>
+                  property.
+                </doc:definition>
+              </doc:item>
+            </doc:list>
+        </doc:summary></doc:doc>
+      </arg>
+      <doc:doc>
+        <doc:description>
+          <doc:para>
+            Retrieves historical S.M.A.R.T. data from the drive in the
+            given time interval. Note that this is data collected and
+            stored by the host as S.M.A.R.T. capable drives doesn't
+            store or return historical data.
+          </doc:para>
+        </doc:description>
+        <doc:errors>
+          <doc:error name="&ERROR_NOT_SMART_CAPABLE;">device is not S.M.A.R.T. capable</doc:error>
+          <doc:error name="&ERROR_NOT_DRIVE;">device is not a drive</doc:error>
+          <doc:error name="&ERROR_GENERAL;">if incoming parameters are invalid or an unknown error occured</doc:error>
+        </doc:errors>
+      </doc:doc>
+    </method>
 
     <!-- ************************************************************ -->
 
     </property>
     <property name="drive-smart-last-self-test-result" type="s" access="read">
       <doc:doc><doc:description><doc:para>
-            The result of the last self-test to run. The following values are known:
+            The result of the last self-test. The following values are known:
             <doc:list>
               <doc:item>
                 <doc:term>completed_ok</doc:term>