[almanah] Make import/export operations asynchronous
- From: Philip Withnall <pwithnall src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [almanah] Make import/export operations asynchronous
- Date: Mon, 17 May 2010 23:38:50 +0000 (UTC)
commit 2f9fd8078783368beb3744e79c25269a76836503
Author: Philip Withnall <philip tecnocode co uk>
Date: Sun May 16 16:24:02 2010 +0100
Make import/export operations asynchronous
configure.ac | 2 +-
data/almanah.ui | 7 +-
src/Makefile.am | 4 +
src/export-operation.c | 257 ++++++++++++++++++++
src/export-operation.h | 60 +++++
src/import-export-dialog.c | 558 +++++++------------------------------------
src/import-export-dialog.h | 11 +-
src/import-operation.c | 510 ++++++++++++++++++++++++++++++++++++++++
src/import-operation.h | 72 ++++++
src/main.c | 1 +
10 files changed, 1004 insertions(+), 478 deletions(-)
---
diff --git a/configure.ac b/configure.ac
index 275e8a5..ed81fcf 100644
--- a/configure.ac
+++ b/configure.ac
@@ -79,7 +79,7 @@ dnl Dependencies
dnl ***************************************************************************
dnl Required dependencies
-PKG_CHECK_MODULES(STANDARD, glib-2.0 gtk+-2.0 >= 2.14 gmodule-2.0 gio-2.0 sqlite3 cairo gconf-2.0 atk dbus-glib-1)
+PKG_CHECK_MODULES(STANDARD, glib-2.0 gtk+-2.0 >= 2.14 gmodule-2.0 gthread-2.0 gio-2.0 sqlite3 cairo gconf-2.0 atk dbus-glib-1)
AC_SUBST(STANDARD_CFLAGS)
AC_SUBST(STANDARD_LIBS)
diff --git a/data/almanah.ui b/data/almanah.ui
index c3fe756..a58af93 100644
--- a/data/almanah.ui
+++ b/data/almanah.ui
@@ -937,8 +937,11 @@
<object class="GtkListStore" id="almanah_ied_mode_store">
<columns>
- <column type="gchararray"/><!-- Name -->
<column type="guint"/><!-- Type -->
+ <column type="gchararray"/><!-- Name -->
+ <column type="gchararray"/><!-- Description -->
+ <column type="guint"/><!-- Action -->
+ <column type="gchararray"/><!-- Title -->
</columns>
</object>
@@ -978,7 +981,7 @@
<child>
<object class="GtkCellRendererText" id="almanah_ied_mode_renderer"/>
<attributes>
- <attribute name="text">0</attribute>
+ <attribute name="text">1</attribute>
</attributes>
</child>
</object>
diff --git a/src/Makefile.am b/src/Makefile.am
index 6ed8ea7..93e9f92 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -33,6 +33,10 @@ almanah_SOURCES = \
date-entry-dialog.h \
import-export-dialog.c \
import-export-dialog.h \
+ import-operation.c \
+ import-operation.h \
+ export-operation.c \
+ export-operation.h \
definitions/file.c \
definitions/file.h \
definitions/note.c \
diff --git a/src/export-operation.c b/src/export-operation.c
new file mode 100644
index 0000000..032f55f
--- /dev/null
+++ b/src/export-operation.c
@@ -0,0 +1,257 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Philip Withnall 2010 <philip tecnocode co uk>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "export-operation.h"
+#include "entry.h"
+#include "storage-manager.h"
+#include "main.h"
+
+typedef gboolean (*ExportFunc) (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error);
+
+typedef struct {
+ const gchar *name; /* translatable */
+ const gchar *description; /* translatable */
+ GtkFileChooserAction action;
+ const gchar *file_chooser_title; /* translatable */
+ ExportFunc export_func;
+} ExportModeDetails;
+
+static gboolean export_text_files (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error);
+static gboolean export_database (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error);
+
+static const ExportModeDetails export_modes[] = {
+ { N_("Text Files"),
+ N_("Select a _folder to export the entries to as text files, one per entry, with names in the format 'yyyy-mm-dd', and no extension. "
+ "All entries will be exported, unencrypted in plain text format."),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ N_("Select a Folder"),
+ export_text_files },
+ { N_("Database"),
+ N_("Select a _filename for a complete copy of the unencrypted Almanah Diary database to be given."),
+ GTK_FILE_CHOOSER_ACTION_SAVE, /* TODO: Need to change the file chooser for this */
+ N_("Select a File"),
+ export_database }
+};
+
+static void almanah_export_operation_dispose (GObject *object);
+
+struct _AlmanahExportOperationPrivate {
+ gint current_mode; /* index into export_modes */
+ GFile *destination;
+};
+
+G_DEFINE_TYPE (AlmanahExportOperation, almanah_export_operation, G_TYPE_OBJECT)
+#define ALMANAH_EXPORT_OPERATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ALMANAH_TYPE_EXPORT_OPERATION, AlmanahExportOperationPrivate))
+
+static void
+almanah_export_operation_class_init (AlmanahExportOperationClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (AlmanahExportOperationPrivate));
+
+ gobject_class->dispose = almanah_export_operation_dispose;
+}
+
+static void
+almanah_export_operation_init (AlmanahExportOperation *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_EXPORT_OPERATION, AlmanahExportOperationPrivate);
+ self->priv->current_mode = -1; /* no mode selected */
+}
+
+static void
+almanah_export_operation_dispose (GObject *object)
+{
+ AlmanahExportOperationPrivate *priv = ALMANAH_EXPORT_OPERATION (object)->priv;
+
+ if (priv->destination != NULL)
+ g_object_unref (priv->destination);
+ priv->destination = NULL;
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (almanah_export_operation_parent_class)->dispose (object);
+}
+
+AlmanahExportOperation *
+almanah_export_operation_new (AlmanahExportOperationType type_id, GFile *destination)
+{
+ AlmanahExportOperation *export_operation = g_object_new (ALMANAH_TYPE_EXPORT_OPERATION, NULL);
+ export_operation->priv->current_mode = type_id;
+ export_operation->priv->destination = g_object_ref (destination);
+
+ return export_operation;
+}
+
+static gboolean
+export_text_files (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error)
+{
+ AlmanahStorageManagerIter iter;
+ AlmanahEntry *entry;
+ GtkTextBuffer *buffer;
+ gboolean success = FALSE;
+ GError *child_error = NULL;
+
+ /* Build a text buffer to use when getting all the entries */
+ buffer = gtk_text_buffer_new (NULL);
+
+ /* Iterate through the entries */
+ almanah_storage_manager_iter_init (&iter);
+ while ((entry = almanah_storage_manager_get_entries (almanah->storage_manager, &iter)) != NULL) {
+ GDate date;
+ gchar *filename, *content;
+ GFile *file;
+ GtkTextIter start_iter, end_iter;
+
+ /* Get the filename */
+ almanah_entry_get_date (entry, &date);
+ filename = g_strdup_printf ("%04u-%02u-%02u", g_date_get_year (&date), g_date_get_month (&date), g_date_get_day (&date));
+ file = g_file_get_child (destination, filename);
+ g_free (filename);
+
+ /* Get the entry contents */
+ if (almanah_entry_get_content (entry, buffer, TRUE, &child_error) == FALSE) {
+ /* Error */
+ g_object_unref (file);
+ g_object_unref (entry);
+ break;
+ }
+
+ g_object_unref (entry);
+
+ gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter);
+ content = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
+
+ /* Create the file */
+ if (g_file_replace_contents (file, content, strlen (content), NULL, FALSE,
+ G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, &child_error) == FALSE) {
+ /* Error */
+ g_object_unref (file);
+ g_free (content);
+ break;
+ }
+
+ g_object_unref (file);
+ g_free (content);
+ }
+
+ /* Check if the loop was broken due to an error */
+ if (child_error != NULL) {
+ g_propagate_error (error, child_error);
+ goto finish;
+ }
+
+ success = TRUE;
+
+finish:
+ g_object_unref (buffer);
+
+ return success;
+}
+
+static gboolean
+export_database (AlmanahExportOperation *self, GFile *destination, GCancellable *cancellable, GError **error)
+{
+ GFile *source;
+ gboolean success;
+
+ /* Get the input file (current unencrypted database) */
+ source = g_file_new_for_path (almanah_storage_manager_get_filename (almanah->storage_manager, TRUE));
+
+ /* Copy the current database to that location */
+ success = g_file_copy (source, destination, G_FILE_COPY_OVERWRITE, cancellable, NULL, NULL, error);
+
+ g_object_unref (source);
+
+ return success;
+}
+
+static void
+export_thread (GSimpleAsyncResult *result, AlmanahExportOperation *operation, GCancellable *cancellable)
+{
+ GError *error = NULL;
+
+ /* Check to see if the operation's been cancelled already */
+ if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* Export and return */
+ if (export_modes[operation->priv->current_mode].export_func (operation, operation->priv->destination, cancellable, &error) == FALSE) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ }
+}
+
+void
+almanah_export_operation_run (AlmanahExportOperation *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+
+ g_return_if_fail (ALMANAH_IS_EXPORT_OPERATION (self));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, almanah_export_operation_run);
+ g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) export_thread, G_PRIORITY_DEFAULT, cancellable);
+ g_object_unref (result);
+}
+
+gboolean
+almanah_export_operation_finish (AlmanahExportOperation *self, GAsyncResult *async_result, GError **error)
+{
+ GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
+
+ g_return_val_if_fail (ALMANAH_IS_EXPORT_OPERATION (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (result) == almanah_export_operation_run);
+
+ if (g_simple_async_result_propagate_error (result, error) == TRUE)
+ return FALSE;
+
+ return TRUE;
+}
+
+void
+almanah_export_operation_populate_model (GtkListStore *store, guint type_id_column, guint name_column, guint description_column, guint action_column,
+ guint file_chooser_title_column)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (export_modes); i++) {
+ GtkTreeIter iter;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ type_id_column, i,
+ name_column, _(export_modes[i].name),
+ description_column, _(export_modes[i].description),
+ action_column, export_modes[i].action,
+ file_chooser_title_column, _(export_modes[i].file_chooser_title),
+ -1);
+ }
+}
diff --git a/src/export-operation.h b/src/export-operation.h
new file mode 100644
index 0000000..f0990c2
--- /dev/null
+++ b/src/export-operation.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Philip Withnall 2010 <philip tecnocode co uk>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ALMANAH_EXPORT_OPERATION_H
+#define ALMANAH_EXPORT_OPERATION_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+typedef guint AlmanahExportOperationType;
+
+#define ALMANAH_TYPE_EXPORT_OPERATION (almanah_export_operation_get_type ())
+#define ALMANAH_EXPORT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_EXPORT_OPERATION, AlmanahExportOperation))
+#define ALMANAH_EXPORT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_EXPORT_OPERATION, AlmanahExportOperationClass))
+#define ALMANAH_IS_EXPORT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALMANAH_TYPE_EXPORT_OPERATION))
+#define ALMANAH_IS_EXPORT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ALMANAH_TYPE_EXPORT_OPERATION))
+#define ALMANAH_EXPORT_OPERATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ALMANAH_TYPE_EXPORT_OPERATION, AlmanahExportOperationClass))
+
+typedef struct _AlmanahExportOperationPrivate AlmanahExportOperationPrivate;
+
+typedef struct {
+ GObject parent;
+ AlmanahExportOperationPrivate *priv;
+} AlmanahExportOperation;
+
+typedef struct {
+ GObjectClass parent;
+} AlmanahExportOperationClass;
+
+GType almanah_export_operation_get_type (void) G_GNUC_CONST;
+
+AlmanahExportOperation *almanah_export_operation_new (AlmanahExportOperationType type_id, GFile *destination) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+void almanah_export_operation_run (AlmanahExportOperation *self, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data);
+gboolean almanah_export_operation_finish (AlmanahExportOperation *self, GAsyncResult *async_result, GError **error);
+
+void almanah_export_operation_populate_model (GtkListStore *list_store, guint type_id_column, guint name_column, guint description_column,
+ guint action_column, guint file_chooser_title_column);
+
+G_END_DECLS
+
+#endif /* !ALMANAH_EXPORT_OPERATION_H */
diff --git a/src/import-export-dialog.c b/src/import-export-dialog.c
index 48af0c9..4883c55 100644
--- a/src/import-export-dialog.c
+++ b/src/import-export-dialog.c
@@ -23,48 +23,12 @@
#include <gtk/gtk.h>
#include "import-export-dialog.h"
+#include "import-operation.h"
+#include "export-operation.h"
#include "interface.h"
#include "main.h"
#include "main-window.h"
-typedef gboolean (*ImportFunc) (AlmanahImportExportDialog *self, AlmanahImportResultsDialog *results_dialog, GError **error);
-typedef gboolean (*ExportFunc) (AlmanahImportExportDialog *self, GError **error);
-
-typedef struct {
- const gchar *name; /* translatable */
- const gchar *import_description; /* translatable */
- const gchar *export_description; /* translatable */
- GtkFileChooserAction action;
- const gchar *file_chooser_title; /* translatable */
- ImportFunc import_func;
- ExportFunc export_func;
-} ImportExportModeDetails;
-
-static gboolean import_text_files (AlmanahImportExportDialog *self, AlmanahImportResultsDialog *results_dialog, GError **error);
-static gboolean export_text_files (AlmanahImportExportDialog *self, GError **error);
-
-static gboolean import_database (AlmanahImportExportDialog *self, AlmanahImportResultsDialog *results_dialog, GError **error);
-static gboolean export_database (AlmanahImportExportDialog *self, GError **error);
-
-static const ImportExportModeDetails import_export_modes[] = {
- { N_("Text Files"),
- N_("Select a _folder containing text files, one per entry, with names in the format 'yyyy-mm-dd', and no extension. "
- "Any and all such files will be imported."),
- N_("Select a _folder to export the entries to as text files, one per entry, with names in the format 'yyyy-mm-dd', and no extension. "
- "All entries will be exported, unencrypted in plain text format."),
- GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
- N_("Select a Folder"),
- import_text_files,
- export_text_files },
- { N_("Database"),
- N_("Select a database _file created by Almanah Diary to import."),
- N_("Select a _filename for a complete copy of the unencrypted Almanah Diary database to be given."),
- GTK_FILE_CHOOSER_ACTION_OPEN,
- N_("Select a File"),
- import_database,
- export_database }
-};
-
static void response_cb (GtkDialog *dialog, gint response_id, AlmanahImportExportDialog *self);
/* GtkBuilder callbacks */
@@ -76,7 +40,7 @@ struct _AlmanahImportExportDialogPrivate {
gboolean import; /* TRUE if we're in import mode, FALSE otherwise */
GtkComboBox *mode_combo_box;
GtkListStore *mode_store;
- guint current_mode; /* index into import_export_modes */
+ gint current_mode;
GtkFileChooser *file_chooser;
GtkWidget *import_export_button;
GtkLabel *description_label;
@@ -104,20 +68,6 @@ almanah_import_export_dialog_init (AlmanahImportExportDialog *self)
gtk_window_set_transient_for (GTK_WINDOW (self), GTK_WINDOW (almanah->main_window));
}
-static void
-populate_mode_store (GtkListStore *store)
-{
- guint i;
-
- /* Add all the available import/export modes to the combo box */
- for (i = 0; i < G_N_ELEMENTS (import_export_modes); i++) {
- GtkTreeIter iter;
-
- gtk_list_store_append (store, &iter);
- gtk_list_store_set (store, &iter, 0, _(import_export_modes[i].name), 1, i, -1);
- }
-}
-
/**
* almanah_import_export_dialog_new:
* @import: %TRUE to set the dialog up for importing, %FALSE to set it up for exporting
@@ -186,7 +136,10 @@ almanah_import_export_dialog_new (gboolean import)
gtk_button_set_label (GTK_BUTTON (priv->import_export_button), (import == TRUE) ? _("_Import") : _("_Export"));
/* Populate the mode combo box */
- populate_mode_store (priv->mode_store);
+ if (import == TRUE)
+ almanah_import_operation_populate_model (priv->mode_store, 0, 1, 2, 3, 4);
+ else
+ almanah_export_operation_populate_model (priv->mode_store, 0, 1, 2, 3, 4);
gtk_combo_box_set_active (priv->mode_combo_box, 0);
g_object_unref (builder);
@@ -194,398 +147,77 @@ almanah_import_export_dialog_new (gboolean import)
return import_export_dialog;
}
-/**
- * set_entry:
- * @self: an #AlmanahImportExportDialog
- * @imported_entry: an #AlmanahEntry created for an entry being imported
- * @import_source: a string representing the source of the imported entry
- * @message: return location for an error or informational message, or %NULL; free with g_free()
- *
- * Stores the given entry in the database, merging it with any existing one where appropriate.
- *
- * @import_source should represent where the imported entry came from, so it could be the filename of a text file which held the entry, or of
- * the entry's previous database.
- *
- * If @message is non-%NULL, error or informational messages can be allocated and returned in it. They will be returned irrespective of the return
- * value of the function, as some errors can be overcome to result in a successful import. The message should be freed with g_free() by the caller.
- *
- * Return value: %ALMANAH_IMPORT_STATUS_MERGED if the entry was merged with an existing one, %ALMANAH_IMPORT_STATUS_IMPORTED if it was imported
- * without merging, %ALMANAH_IMPORT_STATUS_FAILED otherwise
- **/
-static AlmanahImportStatus
-set_entry (AlmanahImportExportDialog *self, AlmanahEntry *imported_entry, const gchar *import_source, gchar **message)
+static void
+import_progress_cb (const GDate *date, AlmanahImportStatus status, const gchar *message, AlmanahImportResultsDialog *results_dialog)
{
- GDate entry_date, existing_last_edited, imported_last_edited;
- AlmanahEntry *existing_entry;
- GtkTextBuffer *existing_buffer, *imported_buffer;
- GtkTextIter existing_start, existing_end, imported_start, imported_end;
- gchar *header_string;
- GError *error = NULL;
-
- /* Check to see if there's a conflict first */
- almanah_entry_get_date (imported_entry, &entry_date);
- existing_entry = almanah_storage_manager_get_entry (almanah->storage_manager, &entry_date);
-
- if (existing_entry == NULL) {
- /* Add the entry to the proper database and return, ignoring failure */
- almanah_storage_manager_set_entry (almanah->storage_manager, imported_entry);
-
- return ALMANAH_IMPORT_STATUS_IMPORTED;
- }
-
- /* Load both entries into buffers */
- imported_buffer = gtk_text_buffer_new (NULL);
- if (almanah_entry_get_content (imported_entry, imported_buffer, TRUE, &error) == FALSE) {
- if (message != NULL)
- *message = g_strdup_printf (_("Error deserializing imported entry into buffer: %s"), (error != NULL) ? error->message : NULL);
-
- g_error_free (error);
- g_object_unref (imported_buffer);
- g_object_unref (existing_entry);
-
- return ALMANAH_IMPORT_STATUS_FAILED;
- }
-
- /* The two buffers have to use the same tag table for gtk_text_buffer_insert_range() to work */
- existing_buffer = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (imported_buffer));
- if (almanah_entry_get_content (existing_entry, existing_buffer, FALSE, NULL) == FALSE) {
- /* Deserialising the existing entry failed; use the imported entry instead */
- almanah_storage_manager_set_entry (almanah->storage_manager, imported_entry);
-
- if (message != NULL) {
- *message = g_strdup_printf (_("Error deserializing existing entry into buffer; overwriting with imported entry: %s"),
- (error != NULL) ? error->message : NULL);
- }
-
- g_error_free (error);
- g_object_unref (imported_buffer);
- g_object_unref (existing_buffer);
- g_object_unref (existing_entry);
-
- return ALMANAH_IMPORT_STATUS_IMPORTED;
- }
-
- /* Get the bounds for later use */
- gtk_text_buffer_get_bounds (existing_buffer, &existing_start, &existing_end);
- gtk_text_buffer_get_bounds (imported_buffer, &imported_start, &imported_end);
-
- /* Compare the two buffers --- if they're identical, leave the current entry alone and mark the entries as merged.
- * Compare the character counts first so that the comparison is less expensive in the case they aren't identical .*/
- if (gtk_text_buffer_get_char_count (existing_buffer) == gtk_text_buffer_get_char_count (imported_buffer)) {
- gchar *existing_text, *imported_text;
-
- existing_text = gtk_text_buffer_get_text (existing_buffer, &existing_start, &existing_end, FALSE);
- imported_text = gtk_text_buffer_get_text (imported_buffer, &imported_start, &imported_end, FALSE);
-
- /* If they're the same, no modifications are required */
- if (strcmp (existing_text, imported_text) == 0) {
- g_free (existing_text);
- g_free (imported_text);
- g_object_unref (imported_buffer);
- g_object_unref (existing_buffer);
- g_object_unref (existing_entry);
- return ALMANAH_IMPORT_STATUS_MERGED;
- }
-
- g_free (existing_text);
- g_free (imported_text);
- }
-
- /* Append some header text for the imported entry */
- /* Translators: This text is appended to an existing entry when an entry is being imported to the same date.
- * The imported entry is appended to this text. */
- header_string = g_strdup_printf (_("\n\nEntry imported from \"%s\":\n\n"), import_source);
- gtk_text_buffer_insert (existing_buffer, &existing_end, header_string, -1);
- g_free (header_string);
-
- /* Append the imported entry to the end of the existing one */
- gtk_text_buffer_insert_range (existing_buffer, &existing_end, &imported_start, &imported_end);
- g_object_unref (imported_buffer);
-
- /* Store the buffer back in the existing entry and save the entry */
- almanah_entry_set_content (existing_entry, existing_buffer);
- g_object_unref (existing_buffer);
-
- /* Update the last-edited time for the merged entry to be the more recent of the last-edited times for the existing and imported entries */
- almanah_entry_get_last_edited (existing_entry, &existing_last_edited);
- almanah_entry_get_last_edited (imported_entry, &imported_last_edited);
-
- if (g_date_valid (&existing_last_edited) == FALSE || g_date_compare (&existing_last_edited, &imported_last_edited) < 0)
- almanah_entry_set_last_edited (existing_entry, &imported_last_edited);
-
- almanah_storage_manager_set_entry (almanah->storage_manager, existing_entry);
- g_object_unref (existing_entry);
-
- return ALMANAH_IMPORT_STATUS_MERGED;
+ almanah_import_results_dialog_add_result (results_dialog, date, status, message);
}
-static gboolean
-import_text_files (AlmanahImportExportDialog *self, AlmanahImportResultsDialog *results_dialog, GError **error)
+static void
+import_cb (AlmanahImportOperation *operation, GAsyncResult *async_result, AlmanahImportResultsDialog *results_dialog)
{
- gboolean retval = FALSE;
- GFile *folder;
- GFileInfo *file_info;
- GFileEnumerator *enumerator;
- GtkTextBuffer *buffer;
- GError *child_error = NULL;
- AlmanahImportExportDialogPrivate *priv = self->priv;
-
- /* Get the folder containing all the files to import */
- folder = gtk_file_chooser_get_file (priv->file_chooser);
- g_assert (folder != NULL);
-
- enumerator = g_file_enumerate_children (folder, "standard::name,standard::display-name,standard::is-hidden,time::modified",
- G_FILE_QUERY_INFO_NONE, NULL, error);
- g_object_unref (folder);
- if (enumerator == NULL)
- return FALSE;
-
- /* Build a text buffer to use when setting all the entries */
- buffer = gtk_text_buffer_new (NULL);
-
- /* Enumerate all the children of the folder */
- while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &child_error)) != NULL) {
- AlmanahEntry *entry;
- GDate parsed_date, last_edited;
- GTimeVal modification_time;
- GFile *file;
- gchar *contents, *message = NULL;
- gsize length;
- AlmanahImportStatus status;
- const gchar *file_name = g_file_info_get_name (file_info);
- const gchar *display_name = g_file_info_get_display_name (file_info);
-
- /* Skip the file if it's hidden */
- if (g_file_info_get_is_hidden (file_info) == TRUE || file_name[strlen (file_name) - 1] == '~') {
- g_object_unref (file_info);
- continue;
- }
-
- /* Heuristically parse the date, though we recommend using the format: yyyy-mm-dd */
- g_date_set_parse (&parsed_date, file_name);
- if (g_date_valid (&parsed_date) == FALSE) {
- g_object_unref (file_info);
- continue;
- }
-
- /* Get the file */
- file = g_file_get_child (folder, file_name);
- g_assert (file != NULL);
-
- /* Load the content */
- if (g_file_load_contents (file, NULL, &contents, &length, NULL, &child_error) == FALSE) {
- g_object_unref (file);
- g_object_unref (file_info);
- break; /* let the error get handled by the code just after the loop */
- }
- g_object_unref (file);
-
- /* Create the relevant entry */
- entry = almanah_entry_new (&parsed_date);
-
- /* Set the content on the entry */
- gtk_text_buffer_set_text (buffer, contents, length);
- almanah_entry_set_content (entry, buffer);
- g_free (contents);
-
- /* Set the entry's last-edited date */
- g_file_info_get_modification_time (file_info, &modification_time);
- g_date_set_time_val (&last_edited, &modification_time);
- almanah_entry_set_last_edited (entry, &last_edited);
-
- /* Store the entry */
- status = set_entry (self, entry, display_name, &message);
- almanah_import_results_dialog_add_result (results_dialog, &parsed_date, status, message);
- g_free (message);
-
- g_object_unref (entry);
- g_object_unref (file_info);
- }
-
- /* Check if the loop was broken due to an error */
- if (child_error != NULL) {
- g_propagate_error (error, child_error);
- goto finish;
- }
-
- /* Success! */
- retval = TRUE;
+ AlmanahImportExportDialog *self;
+ GError *error = NULL;
-finish:
- g_object_unref (folder);
- g_object_unref (enumerator);
- g_object_unref (buffer);
+ self = ALMANAH_IMPORT_EXPORT_DIALOG (gtk_window_get_transient_for (GTK_WINDOW (results_dialog))); /* set in response_cb() */
- return retval;
-}
+ /* Check for errors (e.g. errors opening databases or files; not errors importing individual entries once we have the content to import) */
+ if (almanah_import_operation_finish (operation, async_result, &error) == FALSE) {
+ /* Show an error */
+ GtkWidget *error_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK, _("Import failed"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
+ gtk_dialog_run (GTK_DIALOG (error_dialog));
+ gtk_widget_destroy (error_dialog);
-static gboolean
-export_text_files (AlmanahImportExportDialog *self, GError **error)
-{
- AlmanahImportExportDialogPrivate *priv = self->priv;
- AlmanahStorageManagerIter iter;
- GFile *folder;
- AlmanahEntry *entry;
- GtkTextBuffer *buffer;
- gboolean success = FALSE;
- GError *child_error = NULL;
-
- /* Get the output folder */
- folder = gtk_file_chooser_get_file (priv->file_chooser);
- g_assert (folder != NULL);
-
- /* Build a text buffer to use when getting all the entries */
- buffer = gtk_text_buffer_new (NULL);
-
- /* Iterate through the entries */
- almanah_storage_manager_iter_init (&iter);
- while ((entry = almanah_storage_manager_get_entries (almanah->storage_manager, &iter)) != NULL) {
- GDate date;
- gchar *filename, *content;
- GFile *file;
- GtkTextIter start_iter, end_iter;
-
- /* Get the filename */
- almanah_entry_get_date (entry, &date);
- filename = g_strdup_printf ("%04u-%02u-%02u", g_date_get_year (&date), g_date_get_month (&date), g_date_get_day (&date));
- file = g_file_get_child (folder, filename);
- g_free (filename);
-
- /* Get the entry contents */
- if (almanah_entry_get_content (entry, buffer, TRUE, &child_error) == FALSE) {
- /* Error */
- g_object_unref (file);
- g_object_unref (entry);
- break;
- }
-
- g_object_unref (entry);
-
- gtk_text_buffer_get_bounds (buffer, &start_iter, &end_iter);
- content = gtk_text_buffer_get_text (buffer, &start_iter, &end_iter, FALSE);
-
- /* Create the file */
- if (g_file_replace_contents (file, content, strlen (content), NULL, FALSE,
- G_FILE_CREATE_PRIVATE | G_FILE_CREATE_REPLACE_DESTINATION, NULL, NULL, &child_error) == FALSE) {
- /* Error */
- g_object_unref (file);
- g_free (content);
- break;
- }
-
- g_object_unref (file);
- g_free (content);
- }
+ g_error_free (error);
- /* Check if the loop was broken due to an error */
- if (child_error != NULL) {
- g_propagate_error (error, child_error);
- goto finish;
+ gtk_widget_hide (GTK_WIDGET (self));
+ } else {
+ /* Show the results dialogue */
+ gtk_widget_hide (GTK_WIDGET (self));
+ gtk_widget_show_all (GTK_WIDGET (results_dialog));
+ gtk_dialog_run (GTK_DIALOG (results_dialog));
}
- success = TRUE;
-
-finish:
- g_object_unref (folder);
- g_object_unref (buffer);
-
- return success;
+ gtk_widget_destroy (GTK_WIDGET (results_dialog));
}
-static gboolean
-import_database (AlmanahImportExportDialog *self, AlmanahImportResultsDialog *results_dialog, GError **error)
+static void
+export_cb (AlmanahExportOperation *operation, GAsyncResult *async_result, AlmanahImportExportDialog *self)
{
- GFile *file;
- GFileInfo *file_info;
- gchar *path;
- const gchar *display_name;
- GSList *i, *definitions;
- AlmanahEntry *entry;
- AlmanahStorageManager *database;
- AlmanahStorageManagerIter iter;
- AlmanahImportExportDialogPrivate *priv = self->priv;
-
- /* Get the database file to import */
- file = gtk_file_chooser_get_file (priv->file_chooser);
- g_assert (file != NULL);
-
- /* Get the display name for use with set_entry(), below */
- file_info = g_file_query_info (file, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL);
- display_name = g_file_info_get_display_name (file_info);
-
- /* Open the database */
- path = g_file_get_path (file);
- database = almanah_storage_manager_new (path);
- g_free (path);
- g_object_unref (file);
-
- /* Connect to the database */
- if (almanah_storage_manager_connect (database, error) == FALSE) {
- g_object_unref (database);
- return FALSE;
- }
-
- /* Iterate through every entry */
- almanah_storage_manager_iter_init (&iter);
- while ((entry = almanah_storage_manager_get_entries (database, &iter)) != NULL) {
- GDate date;
- gchar *message = NULL;
- AlmanahImportStatus status;
+ GError *error = NULL;
- almanah_entry_get_date (entry, &date);
+ /* Check for errors (e.g. errors opening databases or files; not errors importing individual entries once we have the content to import) */
+ if (almanah_export_operation_finish (operation, async_result, &error) == FALSE) {
+ /* Show an error */
+ GtkWidget *error_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
+ GTK_BUTTONS_OK, _("Export failed"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
+ gtk_dialog_run (GTK_DIALOG (error_dialog));
+ gtk_widget_destroy (error_dialog);
- status = set_entry (self, entry, display_name, &message);
- almanah_import_results_dialog_add_result (results_dialog, &date, status, message);
- g_free (message);
+ g_error_free (error);
- g_object_unref (entry);
- }
+ gtk_widget_hide (GTK_WIDGET (self));
+ } else {
+ /* Show a success message */
+ GtkWidget *message_dialog;
- /* Query for every definition */
- definitions = almanah_storage_manager_get_definitions (database);
- for (i = definitions; i != NULL; i = i->next) {
- /* Add the definition to the proper database, ignoring failure */
- almanah_storage_manager_add_definition (almanah->storage_manager, ALMANAH_DEFINITION (i->data));
- g_object_unref (i->data);
+ gtk_widget_hide (GTK_WIDGET (self));
+ message_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO,
+ GTK_BUTTONS_OK, _("Export successful"));
+ gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message_dialog), _("The diary was successfully exported."));
+ gtk_dialog_run (GTK_DIALOG (message_dialog));
+ gtk_widget_destroy (message_dialog);
}
- g_slist_free (definitions);
-
- almanah_storage_manager_disconnect (database, NULL);
- g_object_unref (database);
- g_object_unref (file_info);
-
- return TRUE;
-}
-
-static gboolean
-export_database (AlmanahImportExportDialog *self, GError **error)
-{
- AlmanahImportExportDialogPrivate *priv = self->priv;
- GFile *source, *destination;
- gboolean success;
-
- /* Get the output file */
- destination = gtk_file_chooser_get_file (priv->file_chooser);
- g_assert (destination != NULL);
-
- /* Get the input file (current unencrypted database) */
- source = g_file_new_for_path (almanah_storage_manager_get_filename (almanah->storage_manager, TRUE));
-
- /* Copy the current database to that location */
- success = g_file_copy (source, destination, G_FILE_COPY_OVERWRITE, NULL, NULL, NULL, error);
-
- g_object_unref (source);
- g_object_unref (destination);
-
- return success;
}
static void
response_cb (GtkDialog *dialog, gint response_id, AlmanahImportExportDialog *self)
{
- GError *error = NULL;
AlmanahImportExportDialogPrivate *priv = self->priv;
- AlmanahImportResultsDialog *results_dialog = NULL; /* shut up, gcc */
+ GFile *file;
/* Just return if the user pressed Cancel */
if (response_id != GTK_RESPONSE_OK) {
@@ -593,49 +225,29 @@ response_cb (GtkDialog *dialog, gint response_id, AlmanahImportExportDialog *sel
return;
}
+ file = gtk_file_chooser_get_file (priv->file_chooser);
+ g_assert (file != NULL);
+
if (priv->import == TRUE) {
- /* Import the entries according to the selected method.
- * It's OK if we block, since the dialogue should be running in its own main loop. */
- results_dialog = almanah_import_results_dialog_new ();
- import_export_modes[priv->current_mode].import_func (self, results_dialog, &error);
+ /* Import the entries according to the selected method.*/
+ AlmanahImportOperation *operation;
+ AlmanahImportResultsDialog *results_dialog = almanah_import_results_dialog_new ();
+ gtk_window_set_transient_for (GTK_WINDOW (results_dialog), GTK_WINDOW (self)); /* this is required in import_cb() */
+
+ operation = almanah_import_operation_new (priv->current_mode, file);
+ almanah_import_operation_run (operation, NULL, (AlmanahImportProgressCallback) import_progress_cb, results_dialog,
+ (GAsyncReadyCallback) import_cb, results_dialog);
+ g_object_unref (operation);
} else {
/* Export the entries according to the selected method. */
- import_export_modes[priv->current_mode].export_func (self, &error);
- }
-
- /* Check for errors (e.g. errors opening databases or files; not errors importing individual entries once we have the content to import) */
- if (error != NULL) {
- /* Show an error */
- GtkWidget *error_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_ERROR,
- GTK_BUTTONS_OK, (priv->import == TRUE) ? _("Import failed") : _("Export failed"));
- gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (error_dialog), "%s", error->message);
- gtk_dialog_run (GTK_DIALOG (error_dialog));
- gtk_widget_destroy (error_dialog);
+ AlmanahExportOperation *operation;
- g_error_free (error);
-
- gtk_widget_hide (GTK_WIDGET (self));
- } else {
- if (priv->import == TRUE) {
- /* Show the results dialogue */
- gtk_widget_hide (GTK_WIDGET (self));
- gtk_widget_show_all (GTK_WIDGET (results_dialog));
- gtk_dialog_run (GTK_DIALOG (results_dialog));
- } else {
- /* Show a success message */
- GtkWidget *message_dialog;
-
- gtk_widget_hide (GTK_WIDGET (self));
- message_dialog = gtk_message_dialog_new (GTK_WINDOW (self), GTK_DIALOG_MODAL, GTK_MESSAGE_INFO,
- GTK_BUTTONS_OK, _("Export successful"));
- gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (message_dialog), _("The diary was successfully exported."));
- gtk_dialog_run (GTK_DIALOG (message_dialog));
- gtk_widget_destroy (message_dialog);
- }
+ operation = almanah_export_operation_new (priv->current_mode, file);
+ almanah_export_operation_run (operation, NULL, (GAsyncReadyCallback) export_cb, self);
+ g_object_unref (operation);
}
- if (priv->import == TRUE)
- gtk_widget_destroy (GTK_WIDGET (results_dialog));
+ g_object_unref (file);
}
void
@@ -643,20 +255,32 @@ ied_mode_combo_box_changed_cb (GtkComboBox *combo_box, AlmanahImportExportDialog
{
AlmanahImportExportDialogPrivate *priv = self->priv;
gint new_mode;
+ GtkTreeIter iter;
+ GtkTreeModel *model;
+ gchar *description, *title;
+ GtkFileChooserAction action;
new_mode = gtk_combo_box_get_active (combo_box);
- if (new_mode == -1 || new_mode == (gint) priv->current_mode)
+ if (new_mode == -1 || new_mode == priv->current_mode)
return;
priv->current_mode = new_mode;
/* Change the dialogue */
- gtk_file_chooser_set_action (priv->file_chooser, import_export_modes[priv->current_mode].action);
- gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (priv->file_chooser),
- _(import_export_modes[priv->current_mode].file_chooser_title));
- gtk_label_set_text_with_mnemonic (priv->description_label,
- (priv->import == TRUE) ? _(import_export_modes[priv->current_mode].import_description)
- : _(import_export_modes[priv->current_mode].export_description));
+ gtk_combo_box_get_active_iter (combo_box, &iter);
+ model = gtk_combo_box_get_model (combo_box);
+ gtk_tree_model_get (model, &iter,
+ 2, &description,
+ 3, &action,
+ 4, &title,
+ -1);
+
+ gtk_file_chooser_set_action (priv->file_chooser, action);
+ gtk_file_chooser_button_set_title (GTK_FILE_CHOOSER_BUTTON (priv->file_chooser), title);
+ gtk_label_set_text_with_mnemonic (priv->description_label, description);
+
+ g_free (title);
+ g_free (description);
}
void
@@ -803,7 +427,7 @@ results_selection_changed_cb (GtkTreeSelection *tree_selection, GtkWidget *butto
}
void
-almanah_import_results_dialog_add_result (AlmanahImportResultsDialog *self, GDate *date, AlmanahImportStatus status, const gchar *message)
+almanah_import_results_dialog_add_result (AlmanahImportResultsDialog *self, const GDate *date, AlmanahImportStatus status, const gchar *message)
{
GtkTreeIter iter;
gchar formatted_date[100];
diff --git a/src/import-export-dialog.h b/src/import-export-dialog.h
index c7c5666..7cf4203 100644
--- a/src/import-export-dialog.h
+++ b/src/import-export-dialog.h
@@ -24,14 +24,9 @@
#include <glib-object.h>
#include <gtk/gtk.h>
-G_BEGIN_DECLS
+#include "import-operation.h"
-/* These must be kept in synchrony with the rows in almanah_ird_view_store in almanah.ui */
-typedef enum {
- ALMANAH_IMPORT_STATUS_IMPORTED = 0,
- ALMANAH_IMPORT_STATUS_MERGED = 1,
- ALMANAH_IMPORT_STATUS_FAILED = 2
-} AlmanahImportStatus;
+G_BEGIN_DECLS
#define ALMANAH_TYPE_IMPORT_EXPORT_DIALOG (almanah_import_export_dialog_get_type ())
#define ALMANAH_IMPORT_EXPORT_DIALOG(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_IMPORT_EXPORT_DIALOG, AlmanahImportExportDialog))
@@ -76,7 +71,7 @@ typedef struct {
GType almanah_import_results_dialog_get_type (void) G_GNUC_CONST;
AlmanahImportResultsDialog *almanah_import_results_dialog_new (void) G_GNUC_WARN_UNUSED_RESULT;
-void almanah_import_results_dialog_add_result (AlmanahImportResultsDialog *self, GDate *date, AlmanahImportStatus status, const gchar *message);
+void almanah_import_results_dialog_add_result (AlmanahImportResultsDialog *self, const GDate *date, AlmanahImportStatus status, const gchar *message);
G_END_DECLS
diff --git a/src/import-operation.c b/src/import-operation.c
new file mode 100644
index 0000000..87d9b5a
--- /dev/null
+++ b/src/import-operation.c
@@ -0,0 +1,510 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Philip Withnall 2010 <philip tecnocode co uk>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <config.h>
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include "import-operation.h"
+#include "entry.h"
+#include "storage-manager.h"
+#include "main.h"
+
+typedef gboolean (*ImportFunc) (AlmanahImportOperation *self, GFile *source, AlmanahImportProgressCallback callback, gpointer user_data,
+ GCancellable *cancellable, GError **error);
+
+typedef struct {
+ const gchar *name; /* translatable */
+ const gchar *description; /* translatable */
+ GtkFileChooserAction action;
+ const gchar *file_chooser_title; /* translatable */
+ ImportFunc import_func;
+} ImportModeDetails;
+
+static gboolean import_text_files (AlmanahImportOperation *self, GFile *source, AlmanahImportProgressCallback callback, gpointer user_data,
+ GCancellable *cancellable, GError **error);
+static gboolean import_database (AlmanahImportOperation *self, GFile *source, AlmanahImportProgressCallback callback, gpointer user_data,
+ GCancellable *cancellable, GError **error);
+
+static const ImportModeDetails import_modes[] = {
+ { N_("Text Files"),
+ N_("Select a _folder containing text files, one per entry, with names in the format 'yyyy-mm-dd', and no extension. "
+ "Any and all such files will be imported."),
+ GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER,
+ N_("Select a Folder"),
+ import_text_files },
+ { N_("Database"),
+ N_("Select a database _file created by Almanah Diary to import."),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ N_("Select a File"),
+ import_database }
+};
+
+static void almanah_import_operation_dispose (GObject *object);
+
+struct _AlmanahImportOperationPrivate {
+ gint current_mode; /* index into import_modes */
+ GFile *source;
+};
+
+G_DEFINE_TYPE (AlmanahImportOperation, almanah_import_operation, G_TYPE_OBJECT)
+#define ALMANAH_IMPORT_OPERATION_GET_PRIVATE(obj) (G_TYPE_INSTANCE_GET_PRIVATE ((obj), ALMANAH_TYPE_IMPORT_OPERATION, AlmanahImportOperationPrivate))
+
+static void
+almanah_import_operation_class_init (AlmanahImportOperationClass *klass)
+{
+ GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+
+ g_type_class_add_private (klass, sizeof (AlmanahImportOperationPrivate));
+
+ gobject_class->dispose = almanah_import_operation_dispose;
+}
+
+static void
+almanah_import_operation_init (AlmanahImportOperation *self)
+{
+ self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, ALMANAH_TYPE_IMPORT_OPERATION, AlmanahImportOperationPrivate);
+ self->priv->current_mode = -1; /* no mode selected */
+}
+
+static void
+almanah_import_operation_dispose (GObject *object)
+{
+ AlmanahImportOperationPrivate *priv = ALMANAH_IMPORT_OPERATION (object)->priv;
+
+ if (priv->source != NULL)
+ g_object_unref (priv->source);
+ priv->source = NULL;
+
+ /* Chain up to the parent class */
+ G_OBJECT_CLASS (almanah_import_operation_parent_class)->dispose (object);
+}
+
+AlmanahImportOperation *
+almanah_import_operation_new (AlmanahImportOperationType type_id, GFile *source)
+{
+ AlmanahImportOperation *import_operation = g_object_new (ALMANAH_TYPE_IMPORT_OPERATION, NULL);
+ import_operation->priv->current_mode = type_id;
+ import_operation->priv->source = g_object_ref (source);
+
+ return import_operation;
+}
+
+typedef struct {
+ AlmanahImportProgressCallback callback;
+ gpointer user_data;
+ GDate *date;
+ AlmanahImportStatus status;
+ gchar *message;
+} ProgressData;
+
+static gboolean
+progress_idle_callback_cb (ProgressData *data)
+{
+ g_assert (data->callback != NULL);
+ data->callback (data->date, data->status, data->message, data->user_data);
+
+ /* Free the data */
+ g_free (data->message);
+ g_date_free (data->date);
+ g_slice_free (ProgressData, data);
+
+ return FALSE;
+}
+
+static void
+progress_idle_callback (AlmanahImportProgressCallback callback, gpointer user_data, const GDate *date, AlmanahImportStatus status,
+ const gchar *message)
+{
+ ProgressData *data = g_slice_new (ProgressData);
+ data->callback = callback;
+ data->user_data = user_data;
+ data->date = g_date_new_dmy (g_date_get_day (date), g_date_get_month (date), g_date_get_year (date));
+ data->status = status;
+ data->message = g_strdup (message);
+
+ g_idle_add ((GSourceFunc) progress_idle_callback_cb, data);
+}
+
+/**
+ * set_entry:
+ * @self: an #AlmanahImportOperation
+ * @imported_entry: an #AlmanahEntry created for an entry being imported
+ * @import_source: a string representing the source of the imported entry
+ * @message: return location for an error or informational message, or %NULL; free with g_free()
+ *
+ * Stores the given entry in the database, merging it with any existing one where appropriate.
+ *
+ * @import_source should represent where the imported entry came from, so it could be the filename of a text file which held the entry, or of
+ * the entry's previous database.
+ *
+ * If @message is non-%NULL, error or informational messages can be allocated and returned in it. They will be returned irrespective of the return
+ * value of the function, as some errors can be overcome to result in a successful import. The message should be freed with g_free() by the caller.
+ *
+ * Return value: %ALMANAH_IMPORT_STATUS_MERGED if the entry was merged with an existing one, %ALMANAH_IMPORT_STATUS_IMPORTED if it was imported
+ * without merging, %ALMANAH_IMPORT_STATUS_FAILED otherwise
+ **/
+static AlmanahImportStatus
+set_entry (AlmanahImportOperation *self, AlmanahEntry *imported_entry, const gchar *import_source, gchar **message)
+{
+ GDate entry_date, existing_last_edited, imported_last_edited;
+ AlmanahEntry *existing_entry;
+ GtkTextBuffer *existing_buffer, *imported_buffer;
+ GtkTextIter existing_start, existing_end, imported_start, imported_end;
+ gchar *header_string;
+ GError *error = NULL;
+
+ /* Check to see if there's a conflict first */
+ almanah_entry_get_date (imported_entry, &entry_date);
+ existing_entry = almanah_storage_manager_get_entry (almanah->storage_manager, &entry_date);
+
+ if (existing_entry == NULL) {
+ /* Add the entry to the proper database and return, ignoring failure */
+ almanah_storage_manager_set_entry (almanah->storage_manager, imported_entry);
+
+ return ALMANAH_IMPORT_STATUS_IMPORTED;
+ }
+
+ /* Load both entries into buffers */
+ imported_buffer = gtk_text_buffer_new (NULL);
+ if (almanah_entry_get_content (imported_entry, imported_buffer, TRUE, &error) == FALSE) {
+ if (message != NULL)
+ *message = g_strdup_printf (_("Error deserializing imported entry into buffer: %s"), (error != NULL) ? error->message : NULL);
+
+ g_error_free (error);
+ g_object_unref (imported_buffer);
+ g_object_unref (existing_entry);
+
+ return ALMANAH_IMPORT_STATUS_FAILED;
+ }
+
+ /* The two buffers have to use the same tag table for gtk_text_buffer_insert_range() to work */
+ existing_buffer = gtk_text_buffer_new (gtk_text_buffer_get_tag_table (imported_buffer));
+ if (almanah_entry_get_content (existing_entry, existing_buffer, FALSE, NULL) == FALSE) {
+ /* Deserialising the existing entry failed; use the imported entry instead */
+ almanah_storage_manager_set_entry (almanah->storage_manager, imported_entry);
+
+ if (message != NULL) {
+ *message = g_strdup_printf (_("Error deserializing existing entry into buffer; overwriting with imported entry: %s"),
+ (error != NULL) ? error->message : NULL);
+ }
+
+ g_error_free (error);
+ g_object_unref (imported_buffer);
+ g_object_unref (existing_buffer);
+ g_object_unref (existing_entry);
+
+ return ALMANAH_IMPORT_STATUS_IMPORTED;
+ }
+
+ /* Get the bounds for later use */
+ gtk_text_buffer_get_bounds (existing_buffer, &existing_start, &existing_end);
+ gtk_text_buffer_get_bounds (imported_buffer, &imported_start, &imported_end);
+
+ /* Compare the two buffers --- if they're identical, leave the current entry alone and mark the entries as merged.
+ * Compare the character counts first so that the comparison is less expensive in the case they aren't identical .*/
+ if (gtk_text_buffer_get_char_count (existing_buffer) == gtk_text_buffer_get_char_count (imported_buffer)) {
+ gchar *existing_text, *imported_text;
+
+ existing_text = gtk_text_buffer_get_text (existing_buffer, &existing_start, &existing_end, FALSE);
+ imported_text = gtk_text_buffer_get_text (imported_buffer, &imported_start, &imported_end, FALSE);
+
+ /* If they're the same, no modifications are required */
+ if (strcmp (existing_text, imported_text) == 0) {
+ g_free (existing_text);
+ g_free (imported_text);
+ g_object_unref (imported_buffer);
+ g_object_unref (existing_buffer);
+ g_object_unref (existing_entry);
+ return ALMANAH_IMPORT_STATUS_MERGED;
+ }
+
+ g_free (existing_text);
+ g_free (imported_text);
+ }
+
+ /* Append some header text for the imported entry */
+ /* Translators: This text is appended to an existing entry when an entry is being imported to the same date.
+ * The imported entry is appended to this text. */
+ header_string = g_strdup_printf (_("\n\nEntry imported from \"%s\":\n\n"), import_source);
+ gtk_text_buffer_insert (existing_buffer, &existing_end, header_string, -1);
+ g_free (header_string);
+
+ /* Append the imported entry to the end of the existing one */
+ gtk_text_buffer_insert_range (existing_buffer, &existing_end, &imported_start, &imported_end);
+ g_object_unref (imported_buffer);
+
+ /* Store the buffer back in the existing entry and save the entry */
+ almanah_entry_set_content (existing_entry, existing_buffer);
+ g_object_unref (existing_buffer);
+
+ /* Update the last-edited time for the merged entry to be the more recent of the last-edited times for the existing and imported entries */
+ almanah_entry_get_last_edited (existing_entry, &existing_last_edited);
+ almanah_entry_get_last_edited (imported_entry, &imported_last_edited);
+
+ if (g_date_valid (&existing_last_edited) == FALSE || g_date_compare (&existing_last_edited, &imported_last_edited) < 0)
+ almanah_entry_set_last_edited (existing_entry, &imported_last_edited);
+
+ almanah_storage_manager_set_entry (almanah->storage_manager, existing_entry);
+ g_object_unref (existing_entry);
+
+ return ALMANAH_IMPORT_STATUS_MERGED;
+}
+
+static gboolean
+import_text_files (AlmanahImportOperation *self, GFile *source, AlmanahImportProgressCallback progress_callback, gpointer progress_user_data,
+ GCancellable *cancellable, GError **error)
+{
+ gboolean retval = FALSE;
+ GFileInfo *file_info;
+ GFileEnumerator *enumerator;
+ GtkTextBuffer *buffer;
+ GError *child_error = NULL;
+
+ enumerator = g_file_enumerate_children (source, "standard::name,standard::display-name,standard::is-hidden,time::modified",
+ G_FILE_QUERY_INFO_NONE, NULL, error);
+ if (enumerator == NULL)
+ return FALSE;
+
+ /* Build a text buffer to use when setting all the entries */
+ buffer = gtk_text_buffer_new (NULL);
+
+ /* Enumerate all the children of the folder */
+ while ((file_info = g_file_enumerator_next_file (enumerator, NULL, &child_error)) != NULL) {
+ AlmanahEntry *entry;
+ GDate parsed_date, last_edited;
+ GTimeVal modification_time;
+ GFile *file;
+ gchar *contents, *message = NULL;
+ gsize length;
+ AlmanahImportStatus status;
+ const gchar *file_name = g_file_info_get_name (file_info);
+ const gchar *display_name = g_file_info_get_display_name (file_info);
+
+ /* Skip the file if it's hidden */
+ if (g_file_info_get_is_hidden (file_info) == TRUE || file_name[strlen (file_name) - 1] == '~') {
+ g_object_unref (file_info);
+ continue;
+ }
+
+ /* Heuristically parse the date, though we recommend using the format: yyyy-mm-dd */
+ g_date_set_parse (&parsed_date, file_name);
+ if (g_date_valid (&parsed_date) == FALSE) {
+ g_object_unref (file_info);
+ continue;
+ }
+
+ /* Get the file */
+ file = g_file_get_child (source, file_name);
+ g_assert (file != NULL);
+
+ /* Load the content */
+ if (g_file_load_contents (file, NULL, &contents, &length, NULL, &child_error) == FALSE) {
+ g_object_unref (file);
+ g_object_unref (file_info);
+ break; /* let the error get handled by the code just after the loop */
+ }
+ g_object_unref (file);
+
+ /* Create the relevant entry */
+ entry = almanah_entry_new (&parsed_date);
+
+ /* Set the content on the entry */
+ gtk_text_buffer_set_text (buffer, contents, length);
+ almanah_entry_set_content (entry, buffer);
+ g_free (contents);
+
+ /* Set the entry's last-edited date */
+ g_file_info_get_modification_time (file_info, &modification_time);
+ g_date_set_time_val (&last_edited, &modification_time);
+ almanah_entry_set_last_edited (entry, &last_edited);
+
+ /* Store the entry */
+ status = set_entry (self, entry, display_name, &message);
+ progress_idle_callback (progress_callback, progress_user_data, &parsed_date, status, message);
+ g_free (message);
+
+ g_object_unref (entry);
+ g_object_unref (file_info);
+ }
+
+ /* Check if the loop was broken due to an error */
+ if (child_error != NULL) {
+ g_propagate_error (error, child_error);
+ goto finish;
+ }
+
+ /* Success! */
+ retval = TRUE;
+
+finish:
+ g_object_unref (enumerator);
+ g_object_unref (buffer);
+
+ return retval;
+}
+
+static gboolean
+import_database (AlmanahImportOperation *self, GFile *source, AlmanahImportProgressCallback progress_callback, gpointer progress_user_data,
+ GCancellable *cancellable, GError **error)
+{
+ GFileInfo *file_info;
+ gchar *path;
+ const gchar *display_name;
+ GSList *i, *definitions;
+ AlmanahEntry *entry;
+ AlmanahStorageManager *database;
+ AlmanahStorageManagerIter iter;
+
+ /* Get the display name for use with set_entry(), below */
+ file_info = g_file_query_info (source, G_FILE_ATTRIBUTE_STANDARD_DISPLAY_NAME, G_FILE_QUERY_INFO_NONE, NULL, NULL);
+ display_name = g_file_info_get_display_name (file_info);
+
+ /* Open the database */
+ path = g_file_get_path (source);
+ database = almanah_storage_manager_new (path);
+ g_free (path);
+
+ /* Connect to the database */
+ if (almanah_storage_manager_connect (database, error) == FALSE) {
+ g_object_unref (database);
+ return FALSE;
+ }
+
+ /* Iterate through every entry */
+ almanah_storage_manager_iter_init (&iter);
+ while ((entry = almanah_storage_manager_get_entries (database, &iter)) != NULL) {
+ GDate date;
+ gchar *message = NULL;
+ AlmanahImportStatus status;
+
+ almanah_entry_get_date (entry, &date);
+
+ status = set_entry (self, entry, display_name, &message);
+ progress_idle_callback (progress_callback, progress_user_data, &date, status, message);
+ g_free (message);
+
+ g_object_unref (entry);
+ }
+
+ /* Query for every definition */
+ definitions = almanah_storage_manager_get_definitions (database);
+ for (i = definitions; i != NULL; i = i->next) {
+ /* Add the definition to the proper database, ignoring failure */
+ almanah_storage_manager_add_definition (almanah->storage_manager, ALMANAH_DEFINITION (i->data));
+ g_object_unref (i->data);
+ }
+ g_slist_free (definitions);
+
+ almanah_storage_manager_disconnect (database, NULL);
+ g_object_unref (database);
+ g_object_unref (file_info);
+
+ return TRUE;
+}
+
+typedef struct {
+ AlmanahImportProgressCallback progress_callback;
+ gpointer progress_user_data;
+} ImportData;
+
+static void
+import_data_free (ImportData *data)
+{
+ g_slice_free (ImportData, data);
+}
+
+static void
+import_thread (GSimpleAsyncResult *result, AlmanahImportOperation *operation, GCancellable *cancellable)
+{
+ GError *error = NULL;
+ ImportData *data = g_simple_async_result_get_op_res_gpointer (result);
+
+ /* Check to see if the operation's been cancelled already */
+ if (g_cancellable_set_error_if_cancelled (cancellable, &error) == TRUE) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ return;
+ }
+
+ /* Import and return */
+ if (import_modes[operation->priv->current_mode].import_func (operation, operation->priv->source, data->progress_callback,
+ data->progress_user_data, cancellable, &error) == FALSE) {
+ g_simple_async_result_set_from_error (result, error);
+ g_error_free (error);
+ }
+}
+
+void
+almanah_import_operation_run (AlmanahImportOperation *self, GCancellable *cancellable, AlmanahImportProgressCallback progress_callback,
+ gpointer progress_user_data, GAsyncReadyCallback callback, gpointer user_data)
+{
+ GSimpleAsyncResult *result;
+ ImportData *data;
+
+ g_return_if_fail (ALMANAH_IS_IMPORT_OPERATION (self));
+ g_return_if_fail (cancellable == NULL || G_IS_CANCELLABLE (cancellable));
+
+ data = g_slice_new (ImportData);
+ data->progress_callback = progress_callback;
+ data->progress_user_data = progress_user_data;
+
+ result = g_simple_async_result_new (G_OBJECT (self), callback, user_data, almanah_import_operation_run);
+ g_simple_async_result_set_op_res_gpointer (result, data, (GDestroyNotify) import_data_free);
+ g_simple_async_result_run_in_thread (result, (GSimpleAsyncThreadFunc) import_thread, G_PRIORITY_DEFAULT, cancellable);
+ g_object_unref (result);
+}
+
+gboolean
+almanah_import_operation_finish (AlmanahImportOperation *self, GAsyncResult *async_result, GError **error)
+{
+ GSimpleAsyncResult *result = G_SIMPLE_ASYNC_RESULT (async_result);
+
+ g_return_val_if_fail (ALMANAH_IS_IMPORT_OPERATION (self), FALSE);
+ g_return_val_if_fail (G_IS_ASYNC_RESULT (async_result), FALSE);
+ g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
+
+ g_warn_if_fail (g_simple_async_result_get_source_tag (result) == almanah_import_operation_run);
+
+ if (g_simple_async_result_propagate_error (result, error) == TRUE)
+ return FALSE;
+
+ return TRUE;
+}
+
+void
+almanah_import_operation_populate_model (GtkListStore *store, guint type_id_column, guint name_column, guint description_column, guint action_column,
+ guint file_chooser_title_column)
+{
+ guint i;
+
+ for (i = 0; i < G_N_ELEMENTS (import_modes); i++) {
+ GtkTreeIter iter;
+
+ gtk_list_store_append (store, &iter);
+ gtk_list_store_set (store, &iter,
+ type_id_column, i,
+ name_column, _(import_modes[i].name),
+ description_column, _(import_modes[i].description),
+ action_column, import_modes[i].action,
+ file_chooser_title_column, _(import_modes[i].file_chooser_title),
+ -1);
+ }
+}
diff --git a/src/import-operation.h b/src/import-operation.h
new file mode 100644
index 0000000..72c2c80
--- /dev/null
+++ b/src/import-operation.h
@@ -0,0 +1,72 @@
+/* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
+/*
+ * Almanah
+ * Copyright (C) Philip Withnall 2010 <philip tecnocode co uk>
+ *
+ * Almanah 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 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Almanah 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 Almanah. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef ALMANAH_IMPORT_OPERATION_H
+#define ALMANAH_IMPORT_OPERATION_H
+
+#include <glib.h>
+#include <glib-object.h>
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+/* TODO: These must be kept in synchrony with the rows in almanah_ird_view_store in almanah.ui */
+typedef enum {
+ ALMANAH_IMPORT_STATUS_IMPORTED = 0,
+ ALMANAH_IMPORT_STATUS_MERGED = 1,
+ ALMANAH_IMPORT_STATUS_FAILED = 2
+} AlmanahImportStatus;
+
+typedef guint AlmanahImportOperationType;
+
+typedef void (*AlmanahImportProgressCallback) (const GDate *date, AlmanahImportStatus status, const gchar *message, gpointer user_data);
+
+#define ALMANAH_TYPE_IMPORT_OPERATION (almanah_import_operation_get_type ())
+#define ALMANAH_IMPORT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_CAST ((o), ALMANAH_TYPE_IMPORT_OPERATION, AlmanahImportOperation))
+#define ALMANAH_IMPORT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_CAST((k), ALMANAH_TYPE_IMPORT_OPERATION, AlmanahImportOperationClass))
+#define ALMANAH_IS_IMPORT_OPERATION(o) (G_TYPE_CHECK_INSTANCE_TYPE ((o), ALMANAH_TYPE_IMPORT_OPERATION))
+#define ALMANAH_IS_IMPORT_OPERATION_CLASS(k) (G_TYPE_CHECK_CLASS_TYPE ((k), ALMANAH_TYPE_IMPORT_OPERATION))
+#define ALMANAH_IMPORT_OPERATION_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), ALMANAH_TYPE_IMPORT_OPERATION, AlmanahImportOperationClass))
+
+typedef struct _AlmanahImportOperationPrivate AlmanahImportOperationPrivate;
+
+typedef struct {
+ GObject parent;
+ AlmanahImportOperationPrivate *priv;
+} AlmanahImportOperation;
+
+typedef struct {
+ GObjectClass parent;
+} AlmanahImportOperationClass;
+
+GType almanah_import_operation_get_type (void) G_GNUC_CONST;
+
+AlmanahImportOperation *almanah_import_operation_new (AlmanahImportOperationType type_id, GFile *source) G_GNUC_WARN_UNUSED_RESULT G_GNUC_MALLOC;
+
+void almanah_import_operation_run (AlmanahImportOperation *self, GCancellable *cancellable,
+ AlmanahImportProgressCallback progress_callback, gpointer progress_user_data,
+ GAsyncReadyCallback callback, gpointer user_data);
+gboolean almanah_import_operation_finish (AlmanahImportOperation *self, GAsyncResult *async_result, GError **error);
+
+void almanah_import_operation_populate_model (GtkListStore *list_store, guint type_id_column, guint name_column, guint description_column,
+ guint action_column, guint file_chooser_title_column);
+
+G_END_DECLS
+
+#endif /* !ALMANAH_IMPORT_OPERATION_H */
diff --git a/src/main.c b/src/main.c
index b208397..77001fa 100644
--- a/src/main.c
+++ b/src/main.c
@@ -111,6 +111,7 @@ main (int argc, char *argv[])
textdomain (GETTEXT_PACKAGE);
#endif
+ g_thread_init (NULL);
gtk_set_locale ();
gtk_init (&argc, &argv);
g_set_application_name (_("Almanah Diary"));
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]