[evolution/kill-bonobo] Bug 581280 – Wrong attachment name in event



commit abf31aee9f84cc700f9947425bf575165d96c77b
Author: Matthew Barnes <mbarnes redhat com>
Date:   Thu May 14 13:39:23 2009 -0400

    Bug 581280 â?? Wrong attachment name in event
---
 calendar/gui/dialogs/comp-editor.c |  220 +++++++++++++++---------
 widgets/misc/e-attachment-store.c  |  327 +++++++++++++++++++++++++++++++++++-
 widgets/misc/e-attachment-store.h  |    7 +
 3 files changed, 472 insertions(+), 82 deletions(-)

diff --git a/calendar/gui/dialogs/comp-editor.c b/calendar/gui/dialogs/comp-editor.c
index e644a12..794184d 100644
--- a/calendar/gui/dialogs/comp-editor.c
+++ b/calendar/gui/dialogs/comp-editor.c
@@ -205,99 +205,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.
@@ -2338,11 +2348,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);
@@ -2355,15 +2406,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 baa1ef6..c99bdea 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 46ca9e4..1c3cb52 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
 



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