aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--calendar/gui/dialogs/comp-editor.c220
-rw-r--r--widgets/misc/e-attachment-store.c327
-rw-r--r--widgets/misc/e-attachment-store.h7
3 files changed, 472 insertions, 82 deletions
diff --git a/calendar/gui/dialogs/comp-editor.c b/calendar/gui/dialogs/comp-editor.c
index 598dbf4ff4..469c1c6e28 100644
--- a/calendar/gui/dialogs/comp-editor.c
+++ b/calendar/gui/dialogs/comp-editor.c
@@ -190,99 +190,109 @@ attachment_store_changed_cb (CompEditor *editor)
comp_editor_set_changed (editor, TRUE);
}
+static void
+attachment_save_finished (EAttachmentStore *store,
+ GAsyncResult *result,
+ gpointer user_data)
+{
+ GtkWidget *dialog;
+ const gchar *primary_text;
+ gchar **uris;
+ GError *error = NULL;
+
+ struct {
+ gchar **uris;
+ gboolean done;
+ GtkWindow *parent;
+ } *status = user_data;
+
+ uris = e_attachment_store_save_finish (store, result, &error);
+
+ status->uris = uris;
+ status->done = TRUE;
+
+ if (uris != NULL)
+ goto exit;
+
+ /* Ignore cancellations. */
+ if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ goto exit;
+
+ primary_text = _("Could not save attachments");
+
+ dialog = gtk_message_dialog_new_with_markup (
+ status->parent, GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_ERROR, GTK_BUTTONS_OK,
+ "<big><b>%s</b></big>", primary_text);
+
+ gtk_message_dialog_format_secondary_text (
+ GTK_MESSAGE_DIALOG (dialog), "%s", error->message);
+
+ gtk_dialog_run (GTK_DIALOG (dialog));
+
+ gtk_widget_destroy (dialog);
+
+exit:
+ if (error != NULL)
+ g_error_free (error);
+
+ g_object_unref (status->parent);
+}
+
static GSList *
get_attachment_list (CompEditor *editor)
{
EAttachmentStore *store;
EAttachmentView *view;
- GtkTreeModel *model;
- GtkTreeIter iter;
+ GFile *destination;
GSList *list = NULL;
- const gchar *comp_uid = NULL;
- const gchar *local_store = e_cal_get_local_attachment_store (editor->priv->client);
- gboolean valid;
- gint ticker=0;
+ const char *comp_uid = NULL;
+ const char *local_store;
+ gchar *uri;
+ gint ii;
- e_cal_component_get_uid (editor->priv->comp, &comp_uid);
+ struct {
+ gchar **uris;
+ gboolean done;
+ GtkWindow *parent;
+ } status;
+
+ status.uris = NULL;
+ status.done = FALSE;
+ status.parent = g_object_ref (editor);
view = E_ATTACHMENT_VIEW (editor->priv->attachment_view);
store = e_attachment_view_get_store (view);
- model = GTK_TREE_MODEL (store);
- valid = gtk_tree_model_get_iter_first (model, &iter);
-
- while (valid) {
- EAttachment *attachment;
- CamelDataWrapper *wrapper;
- CamelMimePart *mime_part;
- CamelStream *stream;
- gchar *attach_file_url;
- gchar *safe_fname, *utf8_safe_fname;
- gchar *filename;
- gint column_id;
-
- column_id = E_ATTACHMENT_STORE_COLUMN_ATTACHMENT;
- gtk_tree_model_get (model, &iter, column_id, &attachment, -1);
- mime_part = e_attachment_get_mime_part (attachment);
- g_object_unref (attachment);
-
- valid = gtk_tree_model_iter_next (model, &iter);
+ local_store = e_cal_get_local_attachment_store (editor->priv->client);
+ e_cal_component_get_uid (editor->priv->comp, &comp_uid);
+ uri = g_build_path ("/", local_store, comp_uid, NULL);
+ destination = g_file_new_for_uri (uri);
+ g_free (uri);
- if (mime_part == NULL)
- continue;
+ e_attachment_store_save_async (
+ store, destination, (GAsyncReadyCallback)
+ attachment_save_finished, &status);
- wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part));
+ g_object_unref (destination);
- /* Extract the content from the stream and write it down
- * as a mime part file into the directory denoting the
- * calendar source */
- utf8_safe_fname = camel_file_util_safe_filename (camel_mime_part_get_filename (mime_part));
-
- /* It is absolutely fine to get a NULL from the filename of
- * mime part. We assume that it is named "Attachment"
- * in mailer. I'll do that with a ticker */
- if (!utf8_safe_fname)
- safe_fname = g_strdup_printf ("%s-%d", _("attachment"), ticker++);
- else {
- safe_fname = g_filename_from_utf8 ((const gchar *) utf8_safe_fname, -1, NULL, NULL, NULL);
- g_free (utf8_safe_fname);
- }
- filename = g_strdup_printf ("%s-%s", comp_uid, safe_fname);
-
- attach_file_url = g_build_path ("/", local_store, filename, NULL);
-
- g_free (filename);
- g_free (safe_fname);
-
- /* do not overwrite existing files, this will result in truncation */
- filename = g_filename_from_uri (attach_file_url, NULL, NULL);
- if (!g_file_test (filename, G_FILE_TEST_EXISTS)) {
- stream = camel_stream_fs_new_with_name(filename, O_RDWR|O_CREAT|O_TRUNC, 0600);
- if (!stream) {
- /* TODO handle error conditions */
- g_message ("DEBUG: could not open the file to write\n");
- g_free (attach_file_url);
- g_free (filename);
- continue;
- }
+ /* We can't return until we have results, so crank
+ * the main loop until the callback gets triggered. */
+ while (!status.done)
+ gtk_main_iteration ();
- if (camel_data_wrapper_decode_to_stream (wrapper, (CamelStream *) stream) == -1) {
- g_free (attach_file_url);
- camel_stream_close (stream);
- camel_object_unref (stream);
- g_message ("DEBUG: could not write to file\n");
- }
+ if (status.uris == NULL)
+ return NULL;
- camel_stream_close (stream);
- camel_object_unref (stream);
- }
-
- list = g_slist_append (list, g_strdup (attach_file_url));
- g_free (attach_file_url);
- g_free (filename);
+ /* Transfer the URI strings to the GSList. */
+ for (ii = 0; status.uris[ii] != NULL; ii++) {
+ list = g_slist_prepend (list, status.uris[ii]);
+ status.uris[ii] = NULL;
}
- return list;
+ g_free (status.uris);
+
+ return g_slist_reverse (list);
}
/* This sets the focus to the toplevel, so any field being edited is committed.
@@ -2239,11 +2249,52 @@ comp_editor_get_client (CompEditor *editor)
}
static void
-set_attachment_list (CompEditor *editor, GSList *attach_list)
+attachment_loaded_cb (EAttachment *attachment,
+ GAsyncResult *result,
+ GtkWindow *parent)
+{
+ GFileInfo *file_info;
+ const gchar *display_name;
+ const gchar *uid;
+
+ /* Prior to 2.27.2, attachment files were named:
+ *
+ * <component-uid> '-' <actual-filename>
+ * -------------------------------------
+ * (one long filename)
+ *
+ * Here we fix the display name if this form is detected so we
+ * don't show the component UID in the user interface. If the
+ * user saves changes in the editor, the attachment will be
+ * written to disk as:
+ *
+ * <component-uid> / <actual-filename>
+ * --------------- -----------------
+ * (directory) (original name)
+ *
+ * So this is a lazy migration from the old form to the new.
+ */
+
+ file_info = e_attachment_get_file_info (attachment);
+ display_name = g_file_info_get_display_name (file_info);
+ uid = g_object_get_data (G_OBJECT (attachment), "uid");
+
+ if (g_str_has_prefix (display_name, uid)) {
+ g_file_info_set_display_name (
+ file_info, display_name + strlen (uid) + 1);
+ g_object_notify (G_OBJECT (attachment), "file-info");
+ }
+
+ e_attachment_load_handle_error (attachment, result, parent);
+}
+
+static void
+set_attachment_list (CompEditor *editor, GSList *uri_list)
{
EAttachmentStore *store;
EAttachmentView *view;
- GSList *iter = NULL;
+ const gchar *uid = NULL;
+ GSList *iter;
view = E_ATTACHMENT_VIEW (editor->priv->attachment_view);
store = e_attachment_view_get_store (view);
@@ -2256,15 +2307,22 @@ set_attachment_list (CompEditor *editor, GSList *attach_list)
return;
}
- for (iter = attach_list; iter != NULL; iter = iter->next) {
+ /* XXX What an awkward API this is. Takes a return location
+ * for a constant string instead of just returning it. */
+ e_cal_component_get_uid (editor->priv->comp, &uid);
+
+ for (iter = uri_list; iter != NULL; iter = iter->next) {
EAttachment *attachment;
- const gchar *uri = iter->data;
- attachment = e_attachment_new_for_uri (uri);
+ attachment = e_attachment_new_for_uri (iter->data);
e_attachment_store_add_attachment (store, attachment);
+ g_object_set_data_full (
+ G_OBJECT (attachment),
+ "uid", g_strdup (uid),
+ (GDestroyNotify) g_free);
e_attachment_load_async (
attachment, (GAsyncReadyCallback)
- e_attachment_load_handle_error, editor);
+ attachment_loaded_cb, editor);
g_object_unref (attachment);
}
}
diff --git a/widgets/misc/e-attachment-store.c b/widgets/misc/e-attachment-store.c
index baa1ef6c7f..c99bdeab3f 100644
--- a/widgets/misc/e-attachment-store.c
+++ b/widgets/misc/e-attachment-store.c
@@ -854,7 +854,7 @@ e_attachment_store_get_uris_async (EAttachmentStore *store,
path = e_mkdtemp (template);
g_free (template);
- /* XXX Let's hope errno got set property. */
+ /* XXX Let's hope errno got set properly. */
if (path == NULL) {
GSimpleAsyncResult *simple;
@@ -903,3 +903,328 @@ e_attachment_store_get_uris_finish (EAttachmentStore *store,
return uris;
}
+
+/********************** e_attachment_store_save_async() **********************/
+
+typedef struct _SaveContext SaveContext;
+
+struct _SaveContext {
+ GSimpleAsyncResult *simple;
+ GFile *destination;
+ GFile *fresh_directory;
+ GFile *trash_directory;
+ GList *attachment_list;
+ GError *error;
+ gchar **uris;
+ gint index;
+};
+
+static SaveContext *
+attachment_store_save_context_new (EAttachmentStore *store,
+ GFile *destination,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SaveContext *save_context;
+ GSimpleAsyncResult *simple;
+ GList *attachment_list;
+ guint length;
+ gchar **uris;
+
+ simple = g_simple_async_result_new (
+ G_OBJECT (store), callback, user_data,
+ e_attachment_store_save_async);
+
+ attachment_list = e_attachment_store_get_attachments (store);
+
+ /* Add one for NULL terminator. */
+ length = g_list_length (attachment_list) + 1;
+ uris = g_malloc0 (sizeof (gchar *) * length);
+
+ save_context = g_slice_new0 (SaveContext);
+ save_context->simple = simple;
+ save_context->destination = g_object_ref (destination);
+ save_context->attachment_list = attachment_list;
+ save_context->uris = uris;
+
+ return save_context;
+}
+
+static void
+attachment_store_save_context_free (SaveContext *save_context)
+{
+ /* Do not free the GSimpleAsyncResult. */
+
+ /* The attachment list should be empty now. */
+ g_warn_if_fail (save_context->attachment_list == NULL);
+
+ /* So should the error. */
+ g_warn_if_fail (save_context->error == NULL);
+
+ if (save_context->destination) {
+ g_object_unref (save_context->destination);
+ save_context->destination = NULL;
+ }
+
+ if (save_context->fresh_directory) {
+ g_object_unref (save_context->fresh_directory);
+ save_context->fresh_directory = NULL;
+ }
+
+ if (save_context->trash_directory) {
+ g_object_unref (save_context->trash_directory);
+ save_context->trash_directory = NULL;
+ }
+
+ g_strfreev (save_context->uris);
+
+ g_slice_free (SaveContext, save_context);
+}
+
+static void
+attachment_store_save_cb (EAttachment *attachment,
+ GAsyncResult *result,
+ SaveContext *save_context)
+{
+ GSimpleAsyncResult *simple;
+ GFile *file;
+ gchar **uris;
+ gchar *template;
+ gchar *path;
+ GError *error = NULL;
+
+ file = e_attachment_save_finish (attachment, result, &error);
+
+ /* Remove the attachment from the list. */
+ save_context->attachment_list = g_list_remove (
+ save_context->attachment_list, attachment);
+ g_object_unref (attachment);
+
+ if (file != NULL) {
+ /* Assemble the file's final URI from its basename. */
+ gchar *basename;
+ gchar *uri;
+
+ basename = g_file_get_basename (file);
+ g_object_unref (file);
+
+ file = save_context->destination;
+ file = g_file_get_child (file, basename);
+ uri = g_file_get_uri (file);
+ g_object_unref (file);
+
+ save_context->uris[save_context->index++] = uri;
+
+ } else if (error != NULL) {
+ /* If this is the first error, cancel the other jobs. */
+ if (save_context->error == NULL) {
+ g_propagate_error (&save_context->error, error);
+ g_list_foreach (
+ save_context->attachment_list,
+ (GFunc) e_attachment_cancel, NULL);
+ error = NULL;
+
+ /* Otherwise, we can only report back one error. So if
+ * this is something other than cancellation, dump it to
+ * the terminal. */
+ } else if (!g_error_matches (
+ error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
+ g_warning ("%s", error->message);
+ }
+
+ if (error != NULL)
+ g_error_free (error);
+
+ /* If there's still jobs running, let them finish. */
+ if (save_context->attachment_list != NULL)
+ return;
+
+ /* If an error occurred while saving, we're done. */
+ if (save_context->error != NULL) {
+ /* Steal the result. */
+ simple = save_context->simple;
+ save_context->simple = NULL;
+
+ /* And the error. */
+ error = save_context->error;
+ save_context->error = NULL;
+
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ attachment_store_save_context_free (save_context);
+ g_error_free (error);
+ return;
+ }
+
+ /* Attachments are all saved to a temporary directory.
+ * Now we need to move the existing destination directory
+ * out of the way (if it exists). Instead of testing for
+ * existence we'll just attempt the move and ignore any
+ * G_IO_ERROR_NOT_FOUND errors. */
+
+ /* First, however, we need another temporary directory to
+ * move the existing destination directory to. Note we're
+ * not actually creating the directory yet, just picking a
+ * name for it. The usual raciness with this approach
+ * applies here (read up on mktemp(3)), but worst case is
+ * we get a spurious G_IO_ERROR_WOULD_MERGE error and the
+ * user has to try saving attachments again. */
+ template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
+ path = e_mktemp (template);
+ g_free (template);
+
+ save_context->trash_directory = g_file_new_for_path (path);
+ g_free (path);
+
+ /* XXX No asynchronous move operation in GIO? */
+ g_file_move (
+ save_context->destination,
+ save_context->trash_directory,
+ G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
+
+ if (error != NULL && !g_error_matches (
+ error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) {
+
+ /* Steal the result. */
+ simple = save_context->simple;
+ save_context->simple = NULL;
+
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ attachment_store_save_context_free (save_context);
+ g_error_free (error);
+ return;
+ }
+
+ g_clear_error (&error);
+
+ /* Now we can move the first temporary directory containing
+ * the newly saved files to the user-specified destination. */
+ g_file_move (
+ save_context->fresh_directory,
+ save_context->destination,
+ G_FILE_COPY_NONE, NULL, NULL, NULL, &error);
+
+ if (error != NULL) {
+ /* Steal the result. */
+ simple = save_context->simple;
+ save_context->simple = NULL;
+
+ g_simple_async_result_set_from_error (simple, error);
+ g_simple_async_result_complete (simple);
+ attachment_store_save_context_free (save_context);
+ g_error_free (error);
+ return;
+ }
+
+ /* Steal the result. */
+ simple = save_context->simple;
+ save_context->simple = NULL;
+
+ /* And the URI list. */
+ uris = save_context->uris;
+ save_context->uris = NULL;
+
+ g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
+ g_simple_async_result_complete (simple);
+
+ attachment_store_save_context_free (save_context);
+}
+
+void
+e_attachment_store_save_async (EAttachmentStore *store,
+ GFile *destination,
+ GAsyncReadyCallback callback,
+ gpointer user_data)
+{
+ SaveContext *save_context;
+ GList *attachment_list, *iter;
+ GFile *temp_directory;
+ gchar *template;
+ gchar *path;
+
+ g_return_if_fail (E_IS_ATTACHMENT_STORE (store));
+ g_return_if_fail (G_IS_FILE (destination));
+ g_return_if_fail (callback != NULL);
+
+ save_context = attachment_store_save_context_new (
+ store, destination, callback, user_data);
+
+ attachment_list = save_context->attachment_list;
+
+ /* Deal with an empty attachment store. The caller will get
+ * an empty NULL-terminated list as opposed to NULL, to help
+ * distinguish it from an error. */
+ if (attachment_list == NULL) {
+ GSimpleAsyncResult *simple;
+ gchar **uris;
+
+ /* Steal the result. */
+ simple = save_context->simple;
+ save_context->simple = NULL;
+
+ /* And the URI list. */
+ uris = save_context->uris;
+ save_context->uris = NULL;
+
+ g_simple_async_result_set_op_res_gpointer (simple, uris, NULL);
+ g_simple_async_result_complete_in_idle (simple);
+ attachment_store_save_context_free (save_context);
+ return;
+ }
+
+ /* Save all attachments to a temporary directory, which we'll
+ * then move to its proper location. We use a directory so
+ * files can retain their basenames.
+ * XXX This could trigger a blocking temp directory cleanup. */
+ template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ());
+ path = e_mkdtemp (template);
+ g_free (template);
+
+ /* XXX Let's hope errno got set properly. */
+ if (path == NULL) {
+ GSimpleAsyncResult *simple;
+
+ /* Steal the result. */
+ simple = save_context->simple;
+ save_context->simple = NULL;
+
+ g_simple_async_result_set_error (
+ simple, G_FILE_ERROR,
+ g_file_error_from_errno (errno),
+ "%s", g_strerror (errno));
+
+ g_simple_async_result_complete_in_idle (simple);
+ attachment_store_save_context_free (save_context);
+ return;
+ }
+
+ temp_directory = g_file_new_for_path (path);
+ save_context->fresh_directory = temp_directory;
+ g_free (path);
+
+ for (iter = attachment_list; iter != NULL; iter = iter->next)
+ e_attachment_save_async (
+ E_ATTACHMENT (iter->data),
+ temp_directory, (GAsyncReadyCallback)
+ attachment_store_save_cb, save_context);
+}
+
+gchar **
+e_attachment_store_save_finish (EAttachmentStore *store,
+ GAsyncResult *result,
+ GError **error)
+{
+ GSimpleAsyncResult *simple;
+ gchar **uris;
+
+ g_return_val_if_fail (E_IS_ATTACHMENT_STORE (store), NULL);
+ g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), NULL);
+
+ simple = G_SIMPLE_ASYNC_RESULT (result);
+ uris = g_simple_async_result_get_op_res_gpointer (simple);
+ g_simple_async_result_propagate_error (simple, error);
+ g_object_unref (simple);
+
+ return uris;
+}
diff --git a/widgets/misc/e-attachment-store.h b/widgets/misc/e-attachment-store.h
index 46ca9e4aa6..1c3cb52731 100644
--- a/widgets/misc/e-attachment-store.h
+++ b/widgets/misc/e-attachment-store.h
@@ -118,6 +118,13 @@ gchar ** e_attachment_store_get_uris_finish
(EAttachmentStore *store,
GAsyncResult *result,
GError **error);
+void e_attachment_store_save_async (EAttachmentStore *store,
+ GFile *destination,
+ GAsyncReadyCallback callback,
+ gpointer user_data);
+gchar ** e_attachment_store_save_finish (EAttachmentStore *store,
+ GAsyncResult *result,
+ GError **error);
G_END_DECLS