[geary/wip/714217-offline: 12/13] Flesh out persistance code for serializing replay queue parameters
- From: Jim Nelson <jnelson src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/wip/714217-offline: 12/13] Flesh out persistance code for serializing replay queue parameters
- Date: Fri, 13 Dec 2013 01:38:52 +0000 (UTC)
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]