diff options
-rw-r--r-- | calendar/gui/dialogs/comp-editor.c | 220 | ||||
-rw-r--r-- | widgets/misc/e-attachment-store.c | 327 | ||||
-rw-r--r-- | 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 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 |