[geary/wip/714217-offline: 12/13] Flesh out persistance code for serializing replay queue parameters



commit 24c67115bbb5ab4869543fd8456ff15ad512d585
Author: Jim Nelson <jim yorba org>
Date:   Thu Dec 12 17:36:36 2013 -0800

    Flesh out persistance code for serializing replay queue parameters

 sql/version-017.sql                                |   13 ++
 src/CMakeLists.txt                                 |   11 ++
 src/engine/persistance/persistance-activator.vala  |   21 +++
 .../persistance-data-flavor-deserializer.vala      |   40 +++++
 .../persistance-data-flavor-serializer.vala        |   40 +++++
 .../persistance/persistance-data-flavor.vala       |   28 ++++
 .../persistance/persistance-deserializer.vala      |   89 +++++++++++
 .../persistance/persistance-flavor-keyfile.vala    |  164 ++++++++++++++++++++
 .../persistance/persistance-serializable.vala      |   40 +++++
 .../persistance/persistance-serialized-type.vala   |   31 ++++
 src/engine/persistance/persistance-serializer.vala |   70 +++++++++
 src/engine/persistance/persistance.vala            |   40 +++++
 12 files changed, 587 insertions(+), 0 deletions(-)
---
diff --git a/sql/version-017.sql b/sql/version-017.sql
new file mode 100644
index 0000000..a1ca3b0
--- /dev/null
+++ b/sql/version-017.sql
@@ -0,0 +1,13 @@
+--
+-- Create the persistent replay queue.  This allows for operations on the server to be queued
+-- locally and replayed (executed) in order when the connection is available.
+--
+
+CREATE TABLE ReplayQueueTable (
+    id INTEGER PRIMARY KEY AUTOINCREMENT,
+    folder_id INTEGER REFERENCES FolderTable ON DELETE CASCADE
+    activation_record TEXT
+);
+
+CREATE INDEX ReplayQueueTableFolderIndex ON ReplayQueueTable(folder_id);
+
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index c64db30..39fef35 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -239,6 +239,17 @@ engine/nonblocking/nonblocking-mutex.vala
 engine/nonblocking/nonblocking-reporting-semaphore.vala
 engine/nonblocking/nonblocking-variants.vala
 
+engine/persistance/persistance.vala
+engine/persistance/persistance-activator.vala
+engine/persistance/persistance-data-flavor.vala
+engine/persistance/persistance-data-flavor-deserializer.vala
+engine/persistance/persistance-data-flavor-serializer.vala
+engine/persistance/persistance-deserializer.vala
+engine/persistance/persistance-flavor-keyfile.vala
+engine/persistance/persistance-serializable.vala
+engine/persistance/persistance-serialized-type.vala
+engine/persistance/persistance-serializer.vala
+
 engine/rfc822/rfc822.vala
 engine/rfc822/rfc822-error.vala
 engine/rfc822/rfc822-gmime-filter-flowed.vala
diff --git a/src/engine/persistance/persistance-activator.vala 
b/src/engine/persistance/persistance-activator.vala
new file mode 100644
index 0000000..4b67d70
--- /dev/null
+++ b/src/engine/persistance/persistance-activator.vala
@@ -0,0 +1,21 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * An Activator is responsible for mapping a class name to a class and activating (instantiating)
+ * it on-demand.
+ */
+
+public interface Geary.Persistance.Activator : Object {
+    /**
+     * Returns an instance of { link Serializable} that maps to the persisted classname and version
+     * number.
+     *
+     * If unknown or unable to create an instance for the version number, return null.
+     */
+    public abstract Serializable? activate(string classname, int version);
+}
+
diff --git a/src/engine/persistance/persistance-data-flavor-deserializer.vala 
b/src/engine/persistance/persistance-data-flavor-deserializer.vala
new file mode 100644
index 0000000..281d14e
--- /dev/null
+++ b/src/engine/persistance/persistance-data-flavor-deserializer.vala
@@ -0,0 +1,40 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * An interface to the basic deserializer facilities provided by the { link DataFlavor} itself.
+ *
+ * The DataFlavorDeserializer is responsible for maintaining little state, just enough to allow for
+ * its methods to be called in roughly any order without issue.  The deserialized state is expected
+ * to be held entirely in memory.
+ */
+
+public interface Geary.Persistance.DataFlavorDeserializer : BaseObject {
+    public abstract string get_classname() throws Error;
+    
+    public abstract int get_serialized_version() throws Error;
+    
+    public abstract bool has_value(string name) throws Error;
+    
+    public abstract SerializedType get_value_type(string name) throws Error;
+    
+    public abstract bool get_bool(string name) throws Error;
+    
+    public abstract int get_int(string name) throws Error;
+    
+    public abstract int64 get_int64(string name) throws Error;
+    
+    public abstract float get_float(string name) throws Error;
+    
+    public abstract double get_double(string name) throws Error;
+    
+    public abstract string get_utf8(string name) throws Error;
+    
+    public abstract int[] get_int_array(string name) throws Error;
+    
+    public abstract string[] get_utf8_array(string name) throws Error;
+}
+
diff --git a/src/engine/persistance/persistance-data-flavor-serializer.vala 
b/src/engine/persistance/persistance-data-flavor-serializer.vala
new file mode 100644
index 0000000..641bfc1
--- /dev/null
+++ b/src/engine/persistance/persistance-data-flavor-serializer.vala
@@ -0,0 +1,40 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * An interface to the basic serializer facilities provided by the { link DataFlavor} itself.
+ *
+ * The DataFlavorSerializer is responsible for maintaining little state, just enough to allow for
+ * its methods to be called in roughly any order without issue.  The serialized state is expected
+ * to be created in memory and returned when { link commit} is invoked.
+ */
+
+public interface Geary.Persistance.DataFlavorSerializer : BaseObject {
+    public abstract void set_bool(string name, bool b);
+    
+    public abstract void set_int(string name, int i);
+    
+    public abstract void set_int64(string name, int64 i64);
+    
+    public abstract void set_float(string name, float f);
+    
+    public abstract void set_double(string name, double d);
+    
+    public abstract void set_utf8(string name, string utf8);
+    
+    public abstract void set_int_array(string name, int[] iar);
+    
+    public abstract void set_utf8_array(string name, string[] utf8ar);
+    
+    /**
+     * Returns the serialized byte stream as a { link Geary.Memory.Buffer}.
+     *
+     * The DataFlavorSerializer is not required to reset its state after this call.  It should
+     * expect to be discarded soon after commit() is invoked.
+     */
+    internal abstract Geary.Memory.Buffer commit() throws Error;
+}
+
diff --git a/src/engine/persistance/persistance-data-flavor.vala 
b/src/engine/persistance/persistance-data-flavor.vala
new file mode 100644
index 0000000..e3f52ba
--- /dev/null
+++ b/src/engine/persistance/persistance-data-flavor.vala
@@ -0,0 +1,28 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * An interface a data flavor (some scheme for persistance of data) must implement to be usable
+ * by { link Serializer}.
+ */
+
+public interface Geary.Persistance.DataFlavor : BaseObject {
+    /**
+     * Human-readable name for the { link DataFlavor}, i.e. "KeyFile" or "JSON".
+     */
+    public abstract string name { get; }
+    
+    /**
+     * Create a new { link DataFlavorSerializer} for the { link Serializable} object.
+     *
+     * The DataFlavorSerializer is not responsible for holding a reference to the Serializable,
+     * although it may.
+     */
+    internal abstract DataFlavorSerializer create_serializer(Serializable sobj);
+    
+    internal abstract DataFlavorDeserializer create_deserializer(Geary.Memory.Buffer buffer) throws Error;
+}
+
diff --git a/src/engine/persistance/persistance-deserializer.vala 
b/src/engine/persistance/persistance-deserializer.vala
new file mode 100644
index 0000000..c860e79
--- /dev/null
+++ b/src/engine/persistance/persistance-deserializer.vala
@@ -0,0 +1,89 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * A Deserializer turns specially-formatted bytes streams (generated by { link Serializer}) into
+ * Objects which implement { link Serializable}.
+ *
+ * The { link DataFlavor} given to the Deserializer must match the DataFlavor used to generate
+ * the serialized stream.  There is no metadata in the stream to tell the Deserializer which to
+ * use.
+ *
+ * See notes at Serializer for more information on how the serialized stream must be maintained by
+ * the caller in order for Deserializer to operate properly.
+ */
+
+public class Geary.Persistance.Deserializer : BaseObject {
+    private DataFlavor flavor;
+    private Activator activator;
+    
+    public Deserializer(DataFlavor flavor, Activator activator) {
+        this.flavor = flavor;
+        this.activator = activator;
+    }
+    
+    public Serializable from_buffer(Geary.Memory.Buffer buffer) throws Error {
+        DataFlavorDeserializer deserializer = flavor.create_deserializer(buffer);
+        
+        return deserialize_properties(deserializer);
+    }
+    
+    public Serializable deserialize_properties(DataFlavorDeserializer deserializer)
+        throws Error {
+        Serializable? sobj = activator.activate(deserializer.get_classname(),
+            deserializer.get_serialized_version());
+        // TODO: Need Errors
+        assert(sobj != null);
+        
+        foreach (ParamSpec param_spec in sobj.get_class().list_properties()) {
+            if (!is_serializable(param_spec, false))
+                continue;
+            
+            if (!deserializer.has_value(param_spec.name)) {
+                debug("WARNING: Serialized stream does not contain parameter for %s",
+                    param_spec.name);
+                
+                continue;
+            }
+            
+            // Give the object the chance to manually deserialize the property
+            if (sobj.deserialize_property(param_spec.name, deserializer))
+                continue;
+            
+            Value value;
+            switch (deserializer.get_value_type(param_spec.name)) {
+                case SerializedType.BOOL:
+                    value = Value(typeof(bool));
+                    value.set_boolean(deserializer.get_bool(param_spec.name));
+                break;
+                
+                case SerializedType.INT:
+                    value = Value(typeof(int));
+                    value.set_int(deserializer.get_int(param_spec.name));
+                break;
+                
+                case SerializedType.INT64:
+                    value = Value(typeof(int64));
+                    value.set_int64(deserializer.get_int64(param_spec.name));
+                break;
+                
+                case SerializedType.INT_ARRAY:
+                case SerializedType.UTF8_ARRAY:
+                    debug("WARNING: int[] and string[] properties must be manually deserialized");
+                    
+                    continue;
+                
+                default:
+                    assert_not_reached();
+            }
+            
+            sobj.set_property(param_spec.name, value);
+        }
+        
+        return sobj;
+    }
+}
+
diff --git a/src/engine/persistance/persistance-flavor-keyfile.vala 
b/src/engine/persistance/persistance-flavor-keyfile.vala
new file mode 100644
index 0000000..bccc3a0
--- /dev/null
+++ b/src/engine/persistance/persistance-flavor-keyfile.vala
@@ -0,0 +1,164 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * A simple implementation to allow Object persistance in a GKeyFile format.
+ *
+ * Because this DataFlavor maintains no instance-specific state, it's a singleton class.  Use
+ * { link instance} to retrieve the global instance.
+ */
+
+public class Geary.Persistance.Flavor.GKeyFile : BaseObject, Geary.Persistance.DataFlavor {
+    private const string VERSION_NAME = "__version__";
+    
+    private class KeyFileSerializer :  BaseObject, DataFlavorSerializer {
+        private string groupname;
+        private KeyFile keyfile = new KeyFile();
+        
+        public KeyFileSerializer(Serializable sobj) {
+            groupname = sobj.serialize_classname();
+            
+            // set the object version number
+            keyfile.set_integer(groupname, VERSION_NAME, sobj.serialize_version());
+        }
+        
+        public void set_bool(string name, bool b) {
+            keyfile.set_integer(groupname, typename(name), SerializedType.BOOL.serialize());
+            keyfile.set_boolean(groupname, name, b);
+        }
+        
+        public void set_int(string name, int i) {
+            keyfile.set_integer(groupname, typename(name), SerializedType.INT.serialize());
+            keyfile.set_integer(groupname, name, i);
+        }
+        
+        public void set_int64(string name, int64 i64) {
+            keyfile.set_integer(groupname, typename(name), SerializedType.INT64.serialize());
+            keyfile.set_int64(groupname, name, i64);
+        }
+        
+        public void set_float(string name, float f) {
+            keyfile.set_integer(groupname, typename(name), SerializedType.FLOAT.serialize());
+            keyfile.set_double(groupname, name, f);
+        }
+        
+        public void set_double(string name, double d) {
+            keyfile.set_integer(groupname, typename(name), SerializedType.DOUBLE.serialize());
+            keyfile.set_double(groupname, name, d);
+        }
+        
+        public void set_utf8(string name, string utf8) {
+            keyfile.set_integer(groupname, typename(name), SerializedType.UTF8.serialize());
+            keyfile.set_string(groupname, name, utf8);
+        }
+        
+        public void set_int_array(string name, int[] iar) {
+            keyfile.set_integer(groupname, typename(name), SerializedType.INT_ARRAY.serialize());
+            keyfile.set_integer_list(groupname, name, iar);
+        }
+        
+        public void set_utf8_array(string name, string[] utf8ar) {
+            keyfile.set_integer(groupname, typename(name), SerializedType.UTF8_ARRAY.serialize());
+            keyfile.set_string_list(groupname, name, utf8ar);
+        }
+        
+        public Memory.Buffer commit() throws Error {
+            return new Memory.StringBuffer(keyfile.to_data());
+        }
+    }
+    
+    private class KeyFileDeserializer : BaseObject, DataFlavorDeserializer {
+        private string groupname;
+        private KeyFile keyfile = new KeyFile();
+        
+        public KeyFileDeserializer(Geary.Memory.Buffer buffer) throws Error {
+            string str = buffer.to_string();
+            keyfile.load_from_data(str, str.length, KeyFileFlags.NONE);
+            
+            groupname = keyfile.get_start_group();
+        }
+        
+        public string get_classname() {
+            return groupname;
+        }
+        
+        public int get_serialized_version() throws Error {
+            return keyfile.get_integer(groupname, VERSION_NAME);
+        }
+        
+        public bool has_value(string name) throws Error {
+            return keyfile.has_key(groupname, name);
+        }
+        
+        public SerializedType get_value_type(string name) throws Error {
+            return SerializedType.deserialize(keyfile.get_integer(groupname, typename(name)));
+        }
+        
+        public bool get_bool(string name) throws Error {
+            return keyfile.get_boolean(groupname, name);
+        }
+        
+        public int get_int(string name) throws Error {
+            return keyfile.get_integer(groupname, name);
+        }
+        
+        public int64 get_int64(string name) throws Error {
+            return keyfile.get_int64(groupname, name);
+        }
+        
+        public float get_float(string name) throws Error {
+            return (float) keyfile.get_double(groupname, name);
+        }
+        
+        public double get_double(string name) throws Error {
+            return keyfile.get_double(groupname, name);
+        }
+        
+        public string get_utf8(string name) throws Error {
+            return keyfile.get_string(groupname, name);
+        }
+        
+        public int[] get_int_array(string name) throws Error {
+            return keyfile.get_integer_list(groupname, name);
+        }
+        
+        public string[] get_utf8_array(string name) throws Error {
+            return keyfile.get_string_list(groupname, name);
+        }
+    }
+    
+    private static GKeyFile? _instance = null;
+    /**
+     * The global instance of GKeyFile.
+     */
+    public static GKeyFile instance {
+        get {
+            return (_instance != null) ? _instance : _instance = new GKeyFile();
+        }
+    }
+    
+    public string name {
+        get {
+            return "GKeyFile";
+        }
+    }
+    
+    private GKeyFile() {
+    }
+    
+    private static string typename(string name) {
+        return "__t_%s__".printf(name);
+    }
+    
+    internal DataFlavorSerializer create_serializer(Serializable sobj) {
+        return new KeyFileSerializer(sobj);
+    }
+    
+    internal DataFlavorDeserializer create_deserializer(Geary.Memory.Buffer buffer) throws Error {
+        return new KeyFileDeserializer(buffer);
+    }
+}
+
diff --git a/src/engine/persistance/persistance-serializable.vala 
b/src/engine/persistance/persistance-serializable.vala
new file mode 100644
index 0000000..b501b5d
--- /dev/null
+++ b/src/engine/persistance/persistance-serializable.vala
@@ -0,0 +1,40 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * A class which can be serialized by { link Serializer}.
+ *
+ * Serializer is only capable of serializing basic data types (int, bool, string, etc).  Complex
+ * data types must manually serialize their values in their { link serialize_property} method.
+ */
+
+public interface Geary.Persistance.Serializable : Object {
+    /**
+     * Utility method to return the object's serializable class name (GType name).
+     */
+    public string serialize_classname() {
+        return get_class().get_type().name();
+    }
+    
+    /**
+     * Returns the version number of this Object's properties signature.
+     *
+     * If the properties of the implementing class changes, this value should be incremented.
+     */
+    public abstract int serialize_version();
+    
+    /**
+     * Manual serialization of a property.
+     *
+     * If { link Serializer} is incapable of serializing a property, this method is called.
+     * The object must either manually serialize the property (use name to determine which) and
+     * return true, or return false.
+     */
+    public abstract bool serialize_property(string name, DataFlavorSerializer serializer) throws Error;
+    
+    public abstract bool deserialize_property(string name, DataFlavorDeserializer deserializer) throws Error;
+}
+
diff --git a/src/engine/persistance/persistance-serialized-type.vala 
b/src/engine/persistance/persistance-serialized-type.vala
new file mode 100644
index 0000000..30df04c
--- /dev/null
+++ b/src/engine/persistance/persistance-serialized-type.vala
@@ -0,0 +1,31 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * Type information about a serialized value.
+ *
+ * The integer values for this enumeration are immutable and will not change in the future.
+ */
+
+public enum SerializedType {
+    BOOL = 0,
+    INT,
+    INT64,
+    FLOAT,
+    DOUBLE,
+    UTF8,
+    INT_ARRAY,
+    UTF8_ARRAY;
+    
+    public int serialize() {
+        return (int) this;
+    }
+    
+    public static SerializedType deserialize(int value) throws Error {
+        return (SerializedType) value;
+    }
+}
+
diff --git a/src/engine/persistance/persistance-serializer.vala 
b/src/engine/persistance/persistance-serializer.vala
new file mode 100644
index 0000000..7139c29
--- /dev/null
+++ b/src/engine/persistance/persistance-serializer.vala
@@ -0,0 +1,70 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * Serializer turns Objects which implement { link Serializable} into a serialized byte stream
+ * that may be persisted or transmitted and reconstituted later by { link Deserializer}.
+ *
+ * The serialized stream the Serializer produces for each object cannot merely be appended together
+ * without some control mechanism to separate them (in separate files or database rows, a length
+ * marker between each in the stream, etc.)  That is, Deserializer will ''not'' break up an appended
+ * stream of serialized objects properly; that is up to the caller to do, and it must call the
+ * appropriate deserialization calls for each packet of bytes.
+ *
+ * Also, the serialized stream contains no metadata indicating which DataFlavor was utilized.  That
+ * must be decided by or maintained by the caller via appropriate means.
+ */
+
+public class Geary.Persistance.Serializer : BaseObject {
+    private DataFlavor flavor;
+    
+    public Serializer(DataFlavor flavor) {
+        this.flavor = flavor;
+    }
+    
+    /**
+     * Serialize the supplied Object into a byte stream using the persistance { link Flavor} given
+     * to the { link Serializer} and written to the OutputStream.
+     *
+     * The OutputStream is not closed when completed.
+     */
+    public Geary.Memory.Buffer to_buffer(Serializable sobj) throws Error {
+        DataFlavorSerializer serializer = flavor.create_serializer(sobj);
+        
+        serialize_properties(serializer, sobj);
+        
+        return serializer.commit();
+    }
+    
+    private void serialize_properties(DataFlavorSerializer serializer, Serializable sobj) throws Error {
+        foreach (ParamSpec param_spec in sobj.get_class().list_properties()) {
+            if (!is_serializable(param_spec, true))
+                continue;
+            
+            Value value = Value(param_spec.value_type);
+            sobj.get_property(param_spec.name, ref value);
+            
+            if (param_spec.value_type == typeof(bool)) {
+                serializer.set_bool(param_spec.name, (bool) value);
+            } else if (param_spec.value_type == typeof(int)) {
+                serializer.set_int(param_spec.name, (int) value);
+            } else if (param_spec.value_type == typeof(int64)) {
+                serializer.set_int64(param_spec.name, (int64) value);
+            } else if (param_spec.value_type == typeof(float)) {
+                serializer.set_float(param_spec.name, (float) value);
+            } else if (param_spec.value_type == typeof(double)) {
+                serializer.set_double(param_spec.name, (double) value);
+            } else if (param_spec.value_type == typeof(string)) {
+                serializer.set_utf8(param_spec.name, (string) value);
+            } else if (!sobj.serialize_property(param_spec.name, serializer)) {
+                debug("WARNING: %s type %s not supported by Serializer", param_spec.name,
+                    param_spec.value_type.name());
+            }
+        }
+    }
+}
+
+
diff --git a/src/engine/persistance/persistance.vala b/src/engine/persistance/persistance.vala
new file mode 100644
index 0000000..40f6f99
--- /dev/null
+++ b/src/engine/persistance/persistance.vala
@@ -0,0 +1,40 @@
+/* Copyright 2013 Yorba Foundation
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later).  See the COPYING file in this distribution.
+ */
+
+/**
+ * Object persistance is implemented for GObjects by serializing (saving) their properties
+ * and deserializing (loading) them later.  Only properties are serialized/deserialized in this
+ * implementation; a more sophisticated system could probably be coded later if necessary.
+ *
+ * NOTE: This is a simple first-step implementation that doesn't attempt full serialization of
+ * an object containment tree.  It only performs "flat" persistance of an object.  What's more,
+ * any properties it does not recognize must be manually persisted by the object itself.
+ */
+
+namespace Geary.Persistance {
+
+private bool is_serializable(ParamSpec param_spec, bool warn) {
+    if ((param_spec.flags & ParamFlags.READWRITE) == 0) {
+        if (warn) {
+            debug("WARNING: %s type %s not read/write, cannot be serialized", param_spec.name,
+                param_spec.value_type.name());
+        }
+        
+        return false;
+    } else if ((param_spec.flags & ParamFlags.CONSTRUCT_ONLY) != 0) {
+        if (warn) {
+            debug("WARNING: %s type %s is construct-only, cannot be serialized", param_spec.name,
+                param_spec.value_type.name());
+        }
+        
+        return false;
+    }
+    
+    return true;
+}
+
+}
+


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