[almanah] core: Add support for versioning the formats used by entries in the database



commit b7649d949735b3f532a11d3f450468871e8eadbc
Author: Philip Withnall <philip tecnocode co uk>
Date:   Wed Apr 13 21:06:29 2011 +0100

    core: Add support for versioning the formats used by entries in the database
    
    This should allow for smoother upgrades between different formats in future,
    since we won't have to try and detect which one is in use in a given entry.

 po/POTFILES.in        |    1 +
 src/entry.c           |   90 +++++++++++++++++++++++++++++++++++-------------
 src/entry.h           |   11 +++++-
 src/storage-manager.c |   21 +++++++----
 4 files changed, 88 insertions(+), 35 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index ef23b6c..b00c4d8 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -5,6 +5,7 @@ data/org.gnome.almanah.gschema.xml.in.in
 [type: gettext/glade]data/almanah.ui
 src/add-definition-dialog.c
 src/date-entry-dialog.c
+src/entry.c
 src/events/calendar-appointment.c
 src/events/calendar-task.c
 src/export-operation.c
diff --git a/src/entry.c b/src/entry.c
index e2d4bd8..4b80127 100644
--- a/src/entry.c
+++ b/src/entry.c
@@ -19,11 +19,25 @@
 
 #include <config.h>
 #include <glib.h>
+#include <glib/gi18n.h>
 #include <gtk/gtk.h>
 
 #include "entry.h"
 #include "main.h"
 
+GQuark
+almanah_entry_error_quark (void)
+{
+	return g_quark_from_static_string ("almanah-entry-error-quark");
+}
+
+typedef enum {
+	/* Unset */
+	DATA_FORMAT_UNSET = 0,
+	/* Plain text or GtkTextBuffer's default serialisation format, as used in Almanah versions < 0.8.0 */
+	DATA_FORMAT_PLAIN_TEXT__GTK_TEXT_BUFFER = 1,
+} DataFormat;
+
 static void almanah_entry_finalize (GObject *object);
 static void almanah_entry_get_property (GObject *object, guint property_id, GValue *value, GParamSpec *pspec);
 static void almanah_entry_set_property (GObject *object, guint property_id, const GValue *value, GParamSpec *pspec);
@@ -32,6 +46,7 @@ struct _AlmanahEntryPrivate {
 	GDate date;
 	guint8 *data;
 	gsize length;
+	DataFormat version; /* version of the *format* used for ->data */
 	gboolean is_empty;
 	gboolean is_important;
 	GDate last_edited; /* date the entry was last edited *in the database*; e.g. this isn't updated when almanah_entry_set_content() is called */
@@ -103,6 +118,7 @@ almanah_entry_init (AlmanahEntry *self)
 	self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_ENTRY, AlmanahEntryPrivate);
 	self->priv->data = NULL;
 	self->priv->length = 0;
+	self->priv->version = DATA_FORMAT_UNSET;
 	g_date_clear (&(self->priv->date), 1);
 	g_date_clear (&(self->priv->last_edited), 1);
 }
@@ -198,17 +214,23 @@ almanah_entry_new (GDate *date)
 
 /* NOTE: There's a difference between content and data, as recognised by AlmanahEntry.
  * Content is deserialized, and handled in terms of GtkTextBuffers.
- * Data is serialized, and handled in terms of a guint8 *data and gsize length. */
+ * Data is serialized, and handled in terms of a guint8 *data and gsize length, as well as an associated data format version.
+ * Internally, the data format version is structured according to DataFormat; but externally it's just an opaque guint. */
 const guint8 *
-almanah_entry_get_data (AlmanahEntry *self, gsize *length)
+almanah_entry_get_data (AlmanahEntry *self, gsize *length, guint *version)
 {
 	if (length != NULL)
 		*length = self->priv->length;
+
+	if (version != NULL) {
+		*version = self->priv->version;
+	}
+
 	return self->priv->data;
 }
 
 void
-almanah_entry_set_data (AlmanahEntry *self, const guint8 *data, gsize length)
+almanah_entry_set_data (AlmanahEntry *self, const guint8 *data, gsize length, guint version)
 {
 	AlmanahEntryPrivate *priv = self->priv;
 
@@ -216,39 +238,54 @@ almanah_entry_set_data (AlmanahEntry *self, const guint8 *data, gsize length)
 
 	priv->data = g_memdup (data, length * sizeof (*data));
 	priv->length = length;
+	priv->version = version;
 	priv->is_empty = FALSE;
 }
 
 gboolean
 almanah_entry_get_content (AlmanahEntry *self, GtkTextBuffer *text_buffer, gboolean create_tags, GError **error)
 {
-	GdkAtom format_atom;
-	GtkTextIter start_iter;
 	AlmanahEntryPrivate *priv = self->priv;
-	GError *deserialise_error = NULL;
-
-	format_atom = gtk_text_buffer_register_deserialize_tagset (text_buffer, PACKAGE_NAME);
-	gtk_text_buffer_deserialize_set_can_create_tags (text_buffer, format_atom, create_tags);
-	gtk_text_buffer_get_start_iter (text_buffer, &start_iter);
-
-	/* Try deserializing the (hopefully) serialized data first */
-	if (gtk_text_buffer_deserialize (text_buffer, text_buffer,
-					 format_atom,
-					 &start_iter,
-					 priv->data, priv->length,
-					 &deserialise_error) == FALSE) {
-		/* Since that failed, check the data's in the old format, and try to just load it as text */
-		if (g_strcmp0 ((gchar*) priv->data, "GTKTEXTBUFFERCONTENTS-0001") != 0) {
-			gtk_text_buffer_set_text (text_buffer, (gchar*) priv->data, priv->length);
-			g_error_free (deserialise_error);
+
+	/* Deserialise the data according to the version of the data format attached to the entry */
+	switch (priv->version) {
+		case DATA_FORMAT_PLAIN_TEXT__GTK_TEXT_BUFFER: {
+			GdkAtom format_atom;
+			GtkTextIter start_iter;
+			GError *deserialise_error = NULL;
+
+			format_atom = gtk_text_buffer_register_deserialize_tagset (text_buffer, PACKAGE_NAME);
+			gtk_text_buffer_deserialize_set_can_create_tags (text_buffer, format_atom, create_tags);
+			gtk_text_buffer_get_start_iter (text_buffer, &start_iter);
+
+			/* Try deserializing the (hopefully) serialized data first */
+			if (gtk_text_buffer_deserialize (text_buffer, text_buffer,
+			                                 format_atom,
+			                                 &start_iter,
+			                                 priv->data, priv->length,
+			                                 &deserialise_error) == FALSE) {
+				/* Since that failed, check the data's in the old format, and try to just load it as text */
+				if (g_strcmp0 ((gchar*) priv->data, "GTKTEXTBUFFERCONTENTS-0001") != 0) {
+					gtk_text_buffer_set_text (text_buffer, (gchar*) priv->data, priv->length);
+					g_error_free (deserialise_error);
+					return TRUE;
+				}
+
+				g_propagate_error (error, deserialise_error);
+				return FALSE;
+			}
+
 			return TRUE;
 		}
+		case DATA_FORMAT_UNSET:
+		default: {
+			/* Invalid/Unset version number */
+			g_set_error (error, ALMANAH_ENTRY_ERROR, ALMANAH_ENTRY_ERROR_INVALID_DATA_VERSION,
+			             _("Invalid data version number %u."), priv->version);
 
-		g_propagate_error (error, deserialise_error);
-		return FALSE;
+			return FALSE;
+		}
 	}
-
-	return TRUE;
 }
 
 void
@@ -269,6 +306,9 @@ almanah_entry_set_content (AlmanahEntry *self, GtkTextBuffer *text_buffer)
 						format_atom,
 						&start, &end,
 						&(priv->length));
+
+	/* Always serialise data in the latest format */
+	priv->version = DATA_FORMAT_PLAIN_TEXT__GTK_TEXT_BUFFER;
 }
 
 /* NOTE: Designed for use on the stack */
diff --git a/src/entry.h b/src/entry.h
index 905e75c..1245641 100644
--- a/src/entry.h
+++ b/src/entry.h
@@ -34,6 +34,13 @@ typedef enum {
 /* The number of days after which a diary entry requires confirmation to be edited */
 #define ALMANAH_ENTRY_CUTOFF_AGE 14
 
+typedef enum {
+	ALMANAH_ENTRY_ERROR_INVALID_DATA_VERSION,
+} AlmanahEntryError;
+
+GQuark almanah_entry_error_quark (void) G_GNUC_CONST;
+#define ALMANAH_ENTRY_ERROR		(almanah_entry_error_quark ())
+
 #define ALMANAH_TYPE_ENTRY		(almanah_entry_get_type ())
 #define ALMANAH_ENTRY(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_ENTRY, AlmanahEntry))
 #define ALMANAH_ENTRY_CLASS(k)		(G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_ENTRY, AlmanahEntryClass))
@@ -55,8 +62,8 @@ typedef struct {
 GType almanah_entry_get_type (void);
 AlmanahEntry *almanah_entry_new (GDate *date);
 
-const guint8 *almanah_entry_get_data (AlmanahEntry *self, gsize *length);
-void almanah_entry_set_data (AlmanahEntry *self, const guint8 *data, gsize length);
+const guint8 *almanah_entry_get_data (AlmanahEntry *self, gsize *length, guint *version);
+void almanah_entry_set_data (AlmanahEntry *self, const guint8 *data, gsize length, guint version);
 gboolean almanah_entry_get_content (AlmanahEntry *self, GtkTextBuffer *text_buffer, gboolean create_tags, GError **error);
 void almanah_entry_set_content (AlmanahEntry *self, GtkTextBuffer *text_buffer);
 
diff --git a/src/storage-manager.c b/src/storage-manager.c
index 28f6dce..49ef227 100644
--- a/src/storage-manager.c
+++ b/src/storage-manager.c
@@ -229,6 +229,7 @@ create_tables (AlmanahStorageManager *self)
 		"ALTER TABLE entries ADD COLUMN edited_year INTEGER", /* added in 0.8.0 */
 		"ALTER TABLE entries ADD COLUMN edited_month INTEGER", /* added in 0.8.0 */
 		"ALTER TABLE entries ADD COLUMN edited_day INTEGER", /* added in 0.8.0 */
+		"ALTER TABLE entries ADD COLUMN version INTEGER DEFAULT 1", /* added in 0.8.0 */
 		NULL
 	};
 
@@ -726,7 +727,7 @@ build_entry_from_statement (sqlite3_stmt *statement)
 	GDate date, last_edited;
 	AlmanahEntry *entry;
 
-	/* Assumes query for SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, ... FROM entries ... */
+	/* Assumes query for SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, version, ... FROM entries ... */
 
 	/* Get the date */
 	g_date_set_dmy (&date,
@@ -736,7 +737,7 @@ build_entry_from_statement (sqlite3_stmt *statement)
 
 	/* Get the content */
 	entry = almanah_entry_new (&date);
-	almanah_entry_set_data (entry, sqlite3_column_blob (statement, 0), sqlite3_column_bytes (statement, 0));
+	almanah_entry_set_data (entry, sqlite3_column_blob (statement, 0), sqlite3_column_bytes (statement, 0), sqlite3_column_int (statement, 8));
 	almanah_entry_set_is_important (entry, (sqlite3_column_int (statement, 1) == 1) ? TRUE : FALSE);
 
 	/* Set the last-edited date if possible (for backwards-compatibility, we have to assume that not all entries have valid last-edited dates set,
@@ -771,7 +772,7 @@ almanah_storage_manager_get_entry (AlmanahStorageManager *self, GDate *date)
 
 	/* Prepare the statement */
 	if (sqlite3_prepare_v2 (self->priv->connection,
-	                        "SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year FROM entries "
+	                        "SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, version FROM entries "
 	                        "WHERE year = ? AND month = ? AND day = ?", -1,
 	                        &statement, NULL) != SQLITE_OK) {
 		return NULL;
@@ -831,13 +832,15 @@ almanah_storage_manager_set_entry (AlmanahStorageManager *self, AlmanahEntry *en
 		sqlite3_stmt *statement;
 		GDate last_edited;
 		gboolean existed_before;
+		guint version;
 
 		existed_before = almanah_storage_manager_entry_exists (self, &date);
 
 		/* Prepare the statement */
 		if (sqlite3_prepare_v2 (self->priv->connection,
-		                        "REPLACE INTO entries (year, month, day, content, is_important, edited_day, edited_month, edited_year) "
-		                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?)", -1,
+		                        "REPLACE INTO entries "
+		                        "(year, month, day, content, is_important, edited_day, edited_month, edited_year, version) "
+		                        "VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", -1,
 		                        &statement, NULL) != SQLITE_OK) {
 			return FALSE;
 		}
@@ -847,8 +850,9 @@ almanah_storage_manager_set_entry (AlmanahStorageManager *self, AlmanahEntry *en
 		sqlite3_bind_int (statement, 2, g_date_get_month (&date));
 		sqlite3_bind_int (statement, 3, g_date_get_day (&date));
 
-		data = almanah_entry_get_data (entry, &length);
+		data = almanah_entry_get_data (entry, &length, &version);
 		sqlite3_bind_blob (statement, 4, data, length, SQLITE_TRANSIENT);
+		sqlite3_bind_int (statement, 9, version);
 
 		sqlite3_bind_int (statement, 5, almanah_entry_is_important (entry));
 
@@ -936,7 +940,7 @@ almanah_storage_manager_search_entries (AlmanahStorageManager *self, const gchar
 
 		/* Prepare the statement. */
 		if (sqlite3_prepare_v2 (self->priv->connection,
-		                        "SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year FROM entries "
+		                        "SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, version FROM entries "
 		                        "ORDER BY year DESC, month DESC, day DESC", -1,
 		                        (sqlite3_stmt**) &(iter->statement), NULL) != SQLITE_OK) {
 			return NULL;
@@ -1031,7 +1035,8 @@ almanah_storage_manager_get_entries (AlmanahStorageManager *self, AlmanahStorage
 	if (iter->statement == NULL) {
 		/* Prepare the statement */
 		if (sqlite3_prepare_v2 (self->priv->connection,
-		                        "SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year FROM entries", -1,
+		                        "SELECT content, is_important, day, month, year, edited_day, edited_month, edited_year, version "
+		                        "FROM entries", -1,
 		                        (sqlite3_stmt**) &(iter->statement), NULL) != SQLITE_OK) {
 			return NULL;
 		}



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