diff options
author | Matthew Barnes <mbarnes@redhat.com> | 2009-04-28 22:57:05 +0800 |
---|---|---|
committer | Matthew Barnes <mbarnes@redhat.com> | 2009-04-28 22:57:05 +0800 |
commit | 6f2f7292a7934a93e18d36594a8b9ef8dc4454e7 (patch) | |
tree | d2c915af4de525703ba9c7fef599a6ffb5a02fdc | |
parent | 9ec72b2da8880ca93ecff50cb83c5b2ce5ec206c (diff) | |
download | gsoc2013-evolution-6f2f7292a7934a93e18d36594a8b9ef8dc4454e7.tar.gz gsoc2013-evolution-6f2f7292a7934a93e18d36594a8b9ef8dc4454e7.tar.zst gsoc2013-evolution-6f2f7292a7934a93e18d36594a8b9ef8dc4454e7.zip |
Resolve some differences between this branch and master.
-rw-r--r-- | calendar/gui/dialogs/comp-editor.c | 129 | ||||
-rw-r--r-- | composer/e-msg-composer.c | 123 | ||||
-rw-r--r-- | configure.in | 2 | ||||
-rw-r--r-- | e-util/e-util.c | 30 | ||||
-rw-r--r-- | plugins/external-editor/external-editor.c | 2 | ||||
-rw-r--r-- | widgets/misc/e-attachment.c | 620 |
6 files changed, 706 insertions, 200 deletions
diff --git a/calendar/gui/dialogs/comp-editor.c b/calendar/gui/dialogs/comp-editor.c index ceab3d3068..466973d88e 100644 --- a/calendar/gui/dialogs/comp-editor.c +++ b/calendar/gui/dialogs/comp-editor.c @@ -194,37 +194,6 @@ comp_editor_weak_notify_cb (gpointer unused, active_editors = g_list_remove (active_editors, where_the_object_was); } -static void -drag_data_received (CompEditor *editor, - GdkDragContext *context, - gint x, - gint y, - GtkSelectionData *selection, - guint info, - guint time) -{ - EAttachmentView *view; - - view = E_ATTACHMENT_VIEW (editor->priv->attachment_view); - - e_attachment_view_drag_data_received ( - view, context, x, y, selection, info, time); -} - -static gboolean -drag_motion (CompEditor *editor, - GdkDragContext *context, - gint x, - gint y, - guint time) -{ - EAttachmentView *view; - - view = E_ATTACHMENT_VIEW (editor->priv->attachment_view); - - return e_attachment_view_drag_motion (view, context, x, y, time); -} - static GSList * get_attachment_list (CompEditor *editor) { @@ -232,7 +201,7 @@ get_attachment_list (CompEditor *editor) EAttachmentView *view; GtkTreeModel *model; GtkTreeIter iter; - GSList *parts = NULL, *list = NULL, *p = NULL; + GSList *parts = NULL, *list = NULL; const char *comp_uid = NULL; const char *local_store = e_cal_get_local_attachment_store (editor->priv->client); gboolean valid; @@ -266,9 +235,6 @@ get_attachment_list (CompEditor *editor) if (mime_part == NULL) continue; - if (mime_part == NULL) - continue; - wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); /* Extract the content from the stream and write it down @@ -663,6 +629,14 @@ action_attach_cb (GtkAction *action, } static void +action_classification_cb (GtkRadioAction *action, + GtkRadioAction *current, + CompEditor *editor) +{ + comp_editor_set_changed (editor, TRUE); +} + +static void action_close_cb (GtkAction *action, CompEditor *editor) { @@ -822,9 +796,11 @@ action_save_cb (GtkAction *action, } commit_all_fields (editor); - if (e_cal_component_is_instance (priv->comp)) + if (e_cal_component_has_recurrences (priv->comp)) { if (!recur_component_dialog (priv->client, priv->comp, &priv->mod, GTK_WINDOW (editor), delegated)) return; + } else if (e_cal_component_is_instance (priv->comp)) + priv->mod = CALOBJ_MOD_THIS; comp = comp_editor_get_current_comp (editor, &correct); e_cal_component_get_summary (comp, &text); @@ -1441,6 +1417,46 @@ comp_editor_key_press_event (GtkWidget *widget, key_press_event (widget, event); } +static gboolean +comp_editor_drag_motion (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + guint time) +{ + CompEditorPrivate *priv; + EAttachmentView *view; + + priv = COMP_EDITOR_GET_PRIVATE (widget); + view = E_ATTACHMENT_VIEW (priv->attachment_view); + + return e_attachment_view_drag_motion (view, context, x, y, time); +} + +static void +comp_editor_drag_data_received (GtkWidget *widget, + GdkDragContext *context, + gint x, + gint y, + GtkSelectionData *selection, + guint info, + guint time) +{ + CompEditorPrivate *priv; + EAttachmentView *view; + + priv = COMP_EDITOR_GET_PRIVATE (widget); + view = E_ATTACHMENT_VIEW (priv->attachment_view); + + /* Forward the data to the attachment view. Note that calling + * e_attachment_view_drag_data_received() will not work because + * that function only handles the case where all the other drag + * handlers have failed. */ + e_attachment_paned_drag_data_received ( + E_ATTACHMENT_PANED (view), + context, x, y, selection, info, time); +} + static void comp_editor_class_init (CompEditorClass *class) { @@ -1459,6 +1475,8 @@ comp_editor_class_init (CompEditorClass *class) widget_class->map = comp_editor_map; widget_class->delete_event = comp_editor_delete_event; widget_class->key_press_event = comp_editor_key_press_event; + widget_class->drag_motion = comp_editor_drag_motion; + widget_class->drag_data_received = comp_editor_drag_data_received; class->help_section = "usage-calendar"; class->edit_comp = real_edit_comp; @@ -1544,6 +1562,7 @@ comp_editor_init (CompEditor *editor) GtkWidget *container; GtkWidget *widget; EShell *shell; + gint n_targets; GError *error = NULL; editor->priv = priv = COMP_EDITOR_GET_PRIVATE (editor); @@ -1594,7 +1613,7 @@ comp_editor_init (CompEditor *editor) action_group, classification_radio_entries, G_N_ELEMENTS (classification_radio_entries), E_CAL_COMPONENT_CLASS_PUBLIC, - NULL, NULL); /* no callback */ + G_CALLBACK (action_classification_cb), editor); gtk_ui_manager_insert_action_group ( priv->ui_manager, action_group, 0); g_object_unref (action_group); @@ -1659,12 +1678,22 @@ comp_editor_init (CompEditor *editor) comp_editor_setup_recent_menu (editor); - /* DND support */ - gtk_drag_dest_set (GTK_WIDGET (editor), GTK_DEST_DEFAULT_ALL, drop_types, num_drop_types, GDK_ACTION_COPY|GDK_ACTION_ASK|GDK_ACTION_MOVE); - g_signal_connect(editor, "drag_data_received", G_CALLBACK (drag_data_received), NULL); - g_signal_connect(editor, "drag-motion", G_CALLBACK(drag_motion), NULL); + /* Drag-and-Drop Support */ + + view = E_ATTACHMENT_VIEW (priv->attachment_view); + target_list = e_attachment_view_get_target_list (view); + drag_actions = e_attachment_view_get_drag_actions (view); + + targets = gtk_target_table_new_from_list (target_list, &n_targets); + + gtk_drag_dest_set ( + GTK_WIDGET (editor), GTK_DEST_DEFAULT_ALL, + targets, n_targets, drag_actions); - gtk_window_set_type_hint (GTK_WINDOW (editor), GDK_WINDOW_TYPE_HINT_NORMAL); + gtk_target_table_free (targets, n_targets); + + gtk_window_set_type_hint ( + GTK_WINDOW (editor), GDK_WINDOW_TYPE_HINT_NORMAL); /* FIXME Shell should be passed in. */ shell = e_shell_get_default (); @@ -2318,7 +2347,8 @@ fill_widgets (CompEditor *editor) EAttachmentStore *store; EAttachmentView *view; CompEditorPrivate *priv; - GList *l; + GtkAction *action; + GList *iter; view = E_ATTACHMENT_VIEW (editor->priv->attachment_view); store = e_attachment_view_get_store (view); @@ -2336,15 +2366,22 @@ fill_widgets (CompEditor *editor) store, G_CALLBACK (attachment_store_changed_cb), editor); set_attachment_list (editor, attachment_list); - g_signal_handlers_unblock_by_func( + g_signal_handlers_unblock_by_func ( store, G_CALLBACK (attachment_store_changed_cb), editor); g_slist_foreach (attachment_list, (GFunc)g_free, NULL); g_slist_free (attachment_list); } - for (l = priv->pages; l != NULL; l = l->next) - comp_editor_page_fill_widgets (l->data, priv->comp); + action = comp_editor_get_action (editor, "classify-public"); + g_signal_handlers_block_by_func ( + action, G_CALLBACK (action_classification_cb), editor); + + for (iter = priv->pages; iter != NULL; iter = iter->next) + comp_editor_page_fill_widgets (iter->data, priv->comp); + + g_signal_handlers_unblock_by_func ( + action, G_CALLBACK (action_classification_cb), editor); } static void diff --git a/composer/e-msg-composer.c b/composer/e-msg-composer.c index 5e8943d478..d1a6abb222 100644 --- a/composer/e-msg-composer.c +++ b/composer/e-msg-composer.c @@ -1109,13 +1109,12 @@ get_signature_html (EMsgComposer *composer) "<!--+GtkHTML:<DATA class=\"ClueFlow\" key=\"signature_name\" value=\"uid:%s\">-->" "<TABLE WIDTH=\"100%%\" CELLSPACING=\"0\" CELLPADDING=\"0\"><TR><TD><BR>" "%s%s%s%s" - "%s</TD></TR></TABLE>", + "</TD></TR></TABLE>", encoded_uid ? encoded_uid : "", format_html ? "" : "<PRE>\n", format_html || (!strncmp ("-- \n", text, 4) || strstr (text, "\n-- \n")) ? "" : "-- \n", text, - format_html ? "" : "</PRE>\n", - is_top_signature () ? "<BR>" : ""); + format_html ? "" : "</PRE>\n"); g_free (text); g_free (encoded_uid); text = html; @@ -1440,117 +1439,6 @@ struct _drop_data { guint time; }; -#if 0 /* KILL-BONOBO */ -static void -drop_action (EMsgComposer *composer, - GdkDragContext *context, - guint32 action, - GtkSelectionData *selection, - guint info, - guint time, - gboolean html_dnd) -{ - CamelMimePart *mime_part; - CamelMimeMessage *msg; - int i, success = FALSE, delete = FALSE; - EMsgComposerPrivate *p = composer->priv; - - switch (info) { - case DND_TYPE_X_UID_LIST: { - GPtrArray *uids; - char *inptr, *inend; - CamelFolder *folder; - CamelException ex = CAMEL_EXCEPTION_INITIALISER; - - /* NB: This all runs synchronously, could be very slow/hang/block the ui */ - - uids = g_ptr_array_new (); - - inptr = (char*)selection->data; - inend = (char*)(selection->data + selection->length); - while (inptr < inend) { - char *start = inptr; - - while (inptr < inend && *inptr) - inptr++; - - if (start > (char *)selection->data) - g_ptr_array_add (uids, g_strndup (start, inptr-start)); - - inptr++; - } - - if (uids->len > 0) { - folder = mail_tool_uri_to_folder ((const gchar *)selection->data, 0, &ex); - if (folder) { - if (uids->len == 1) { - msg = camel_folder_get_message (folder, uids->pdata[0], &ex); - if (msg == NULL) - goto fail; - msg_composer_attach_message (composer, msg); - } else { - CamelMultipart *mp = camel_multipart_new (); - char *desc; - - camel_data_wrapper_set_mime_type ((CamelDataWrapper *)mp, "multipart/digest"); - camel_multipart_set_boundary (mp, NULL); - for (i=0;i<uids->len;i++) { - msg = camel_folder_get_message (folder, uids->pdata[i], &ex); - if (msg) { - mime_part = camel_mime_part_new (); - camel_mime_part_set_disposition (mime_part, "inline"); - camel_medium_set_content_object ((CamelMedium *)mime_part, (CamelDataWrapper *)msg); - camel_mime_part_set_content_type (mime_part, "message/rfc822"); - camel_multipart_add_part (mp, mime_part); - camel_object_unref (mime_part); - camel_object_unref (msg); - } else { - camel_object_unref (mp); - goto fail; - } - } - mime_part = camel_mime_part_new (); - camel_medium_set_content_object ((CamelMedium *)mime_part, (CamelDataWrapper *)mp); - /* translators, this count will always be >1 */ - desc = g_strdup_printf (ngettext ("Attached message", "%d attached messages", uids->len), uids->len); - camel_mime_part_set_description (mime_part, desc); - g_free (desc); - e_attachment_bar_attach_mime_part (E_ATTACHMENT_BAR(p->attachment_bar), mime_part); - camel_object_unref (mime_part); - camel_object_unref (mp); - } - success = TRUE; - delete = action == GDK_ACTION_MOVE; - fail: - if (camel_exception_is_set (&ex)) { - char *name; - - camel_object_get (folder, NULL, CAMEL_FOLDER_NAME, &name, NULL); - e_error_run ((GtkWindow *)composer, "mail-composer:attach-nomessages", - name?name:(char *)selection->data, camel_exception_get_description (&ex), NULL); - camel_object_free (folder, CAMEL_FOLDER_NAME, name); - } - camel_object_unref (folder); - } else { - e_error_run ((GtkWindow *)composer, "mail-composer:attach-nomessages", - (const gchar*)selection->data, camel_exception_get_description (&ex), NULL); - } - - camel_exception_clear (&ex); - } - - g_ptr_array_free (uids, TRUE); - - break; } - default: - d (printf ("dropping an unknown\n")); - break; - } - - gtk_drag_finish (context, success, delete, time); -} -#endif - static void msg_composer_notify_header_cb (EMsgComposer *composer) { @@ -3973,7 +3861,6 @@ e_msg_composer_show_sig_file (EMsgComposer *composer) GtkhtmlEditor *editor; GtkHTML *html; gchar *html_text; - gboolean top_signature; g_return_if_fail (E_IS_MSG_COMPOSER (composer)); @@ -4000,8 +3887,6 @@ e_msg_composer_show_sig_file (EMsgComposer *composer) } gtkhtml_editor_run_command (editor, "unblock-selection"); - top_signature = is_top_signature (); - html_text = get_signature_html (composer); if (html_text) { gtkhtml_editor_run_command (editor, "insert-paragraph"); @@ -4015,10 +3900,6 @@ e_msg_composer_show_sig_file (EMsgComposer *composer) gtkhtml_editor_run_command (editor, "style-normal"); gtkhtml_editor_insert_html (editor, html_text); g_free (html_text); - } else if (top_signature) { - /* insert paragraph after the signature ClueFlow things */ - gtkhtml_editor_run_command (editor, "cursor-forward"); - gtkhtml_editor_run_command (editor, "insert-paragraph"); } gtkhtml_editor_undo_end (editor); diff --git a/configure.in b/configure.in index 4df6e2cd4a..9827d2f606 100644 --- a/configure.in +++ b/configure.in @@ -1790,7 +1790,7 @@ plugins_experimental="$plugins_experimental_always $IPOD_SYNC $TNEF_ATTACHMENTS all_plugins_experimental="$plugins_experimental_always ipod-sync tnef-attachments" dnl Temporary KILL-BONOBO hack -enable_plugins="attachment-reminder addressbook-file audio-inline bbdb bogo-junk-plugin caldav calendar-file calendar-http copy-tool default-source external-editor google-account-setup hula-account-setup imap-features mail-notification mail-to-meeting mark-all-read plugin-manager profiler sa-junk-plugin save-calendar subject-thread $TNEF_ATTACHMENTS vcard-inline webdav-account-setup" +enable_plugins="attachment-reminder addressbook-file audio-inline bbdb bogo-junk-plugin caldav calendar-file calendar-http copy-tool default-source external-editor google-account-setup hula-account-setup imap-features mail-notification mark-all-read plugin-manager profiler sa-junk-plugin save-calendar subject-thread $TNEF_ATTACHMENTS vcard-inline webdav-account-setup" dnl PLUGINS NOT BUILDING YET dnl ------------------------ diff --git a/e-util/e-util.c b/e-util/e-util.c index 04be119657..071f5e58b8 100644 --- a/e-util/e-util.c +++ b/e-util/e-util.c @@ -1531,33 +1531,3 @@ e_camel_object_get_type (void) return type; } - -static gpointer -e_camel_object_copy (gpointer camel_object) -{ - if (CAMEL_IS_OBJECT (camel_object)) - camel_object_ref (camel_object); - - return camel_object; -} - -static void -e_camel_object_free (gpointer camel_object) -{ - if (CAMEL_IS_OBJECT (camel_object)) - camel_object_unref (camel_object); -} - -GType -e_camel_object_get_type (void) -{ - static GType type = 0; - - if (G_UNLIKELY (type == 0)) - type = g_boxed_type_register_static ( - "ECamelObject", - (GBoxedCopyFunc) e_camel_object_copy, - (GBoxedFreeFunc) e_camel_object_free); - - return type; -} diff --git a/plugins/external-editor/external-editor.c b/plugins/external-editor/external-editor.c index c541124e53..a2c86af392 100644 --- a/plugins/external-editor/external-editor.c +++ b/plugins/external-editor/external-editor.c @@ -183,8 +183,6 @@ read_file (char *filename) */ composer = e_msg_composer_new_with_message (message); - g_signal_connect (GTK_OBJECT (composer), "send", G_CALLBACK (em_utils_composer_send_cb), NULL); - g_signal_connect (GTK_OBJECT (composer), "save-draft", G_CALLBACK (em_utils_composer_save_draft_cb), NULL); gtk_widget_show (GTK_WIDGET (composer)); diff --git a/widgets/misc/e-attachment.c b/widgets/misc/e-attachment.c index b2c3e1579e..537a3470d7 100644 --- a/widgets/misc/e-attachment.c +++ b/widgets/misc/e-attachment.c @@ -1915,6 +1915,626 @@ attachment_open_context_free (OpenContext *open_context) g_slice_free (OpenContext, open_context); } +static gboolean +attachment_open_check_for_error (OpenContext *open_context, + GError *error) +{ + GSimpleAsyncResult *simple; + + if (error == NULL) + return FALSE; + + /* Steal the result. */ + simple = open_context->simple; + open_context->simple = NULL; + + g_simple_async_result_set_from_error (simple, error); + g_simple_async_result_complete (simple); + g_error_free (error); + + attachment_open_context_free (open_context); + + return TRUE; +} + +static void +attachment_open_file (GFile *file, + OpenContext *open_context) +{ + GdkAppLaunchContext *context; + GSimpleAsyncResult *simple; + GList *file_list; + gboolean success; + GError *error = NULL; + + /* Steal the result. */ + simple = open_context->simple; + open_context->simple = NULL; + + /* Find a default app based on content type. */ + if (open_context->app_info == NULL) { + EAttachment *attachment; + GFileInfo *file_info; + const gchar *content_type; + + attachment = open_context->attachment; + file_info = e_attachment_get_file_info (attachment); + if (file_info == NULL) + goto exit; + + content_type = g_file_info_get_content_type (file_info); + if (content_type == NULL) + goto exit; + + open_context->app_info = g_app_info_get_default_for_type ( + content_type, FALSE); + } + + if (open_context->app_info == NULL) + goto exit; + + context = gdk_app_launch_context_new (); + file_list = g_list_prepend (NULL, file); + + success = g_app_info_launch ( + open_context->app_info, file_list, + G_APP_LAUNCH_CONTEXT (context), &error); + + g_simple_async_result_set_op_res_gboolean (simple, success); + + g_list_free (file_list); + g_object_unref (context); + +exit: + if (error != NULL) { + g_simple_async_result_set_from_error (simple, error); + g_error_free (error); + } + + g_simple_async_result_complete (simple); + attachment_open_context_free (open_context); +} + +static void +attachment_open_save_finished_cb (EAttachment *attachment, + GAsyncResult *result, + OpenContext *open_context) +{ + GFile *file; + GError *error = NULL; + + file = e_attachment_save_finish (attachment, result, &error); + + if (attachment_open_check_for_error (open_context, error)) + return; + + attachment_open_file (file, open_context); + g_object_unref (file); +} + +static void +attachment_open_save_temporary (OpenContext *open_context) +{ + GFile *file; + gchar *template; + gchar *path; + GError *error = NULL; + + errno = 0; + + /* XXX This could trigger a blocking temp directory cleanup. */ + template = g_strdup_printf (PACKAGE "-%s-XXXXXX", g_get_user_name ()); + path = e_mktemp (template); + g_free (template); + + /* XXX Let's hope errno got set properly. */ + if (path == NULL) + g_set_error ( + &error, G_FILE_ERROR, + g_file_error_from_errno (errno), + "%s", g_strerror (errno)); + + /* We already know if there's an error, but this does the cleanup. */ + if (attachment_open_check_for_error (open_context, error)) + return; + + file = g_file_new_for_path (path); + + g_free (path); + + e_attachment_save_async ( + open_context->attachment, file, (GAsyncReadyCallback) + attachment_open_save_finished_cb, open_context); + + g_object_unref (file); +} + +void +e_attachment_open_async (EAttachment *attachment, + GAppInfo *app_info, + GAsyncReadyCallback callback, + gpointer user_data) +{ + OpenContext *open_context; + CamelMimePart *mime_part; + GFile *file; + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (callback != NULL); + + file = e_attachment_get_file (attachment); + mime_part = e_attachment_get_mime_part (attachment); + g_return_if_fail (file != NULL || mime_part != NULL); + + open_context = attachment_open_context_new ( + attachment, callback, user_data); + + if (G_IS_APP_INFO (app_info)) + open_context->app_info = g_object_ref (app_info); + + /* If the attachment already references a GFile, we can launch + * the application directly. Otherwise we have to save the MIME + * part to a temporary file and launch the application from that. */ + if (file != NULL) { + attachment_open_file (file, open_context); + + } else if (mime_part != NULL) + attachment_open_save_temporary (open_context); +} + +gboolean +e_attachment_open_finish (EAttachment *attachment, + GAsyncResult *result, + GError **error) +{ + GSimpleAsyncResult *simple; + gboolean success; + + g_return_val_if_fail (E_IS_ATTACHMENT (attachment), FALSE); + g_return_val_if_fail (G_IS_SIMPLE_ASYNC_RESULT (result), FALSE); + + simple = G_SIMPLE_ASYNC_RESULT (result); + success = g_simple_async_result_get_op_res_gboolean (simple); + g_simple_async_result_propagate_error (simple, error); + g_object_unref (simple); + + return success; +} + +void +e_attachment_open_handle_error (EAttachment *attachment, + GAsyncResult *result, + GtkWindow *parent) +{ + GtkWidget *dialog; + GFileInfo *file_info; + const gchar *display_name; + const gchar *primary_text; + GError *error = NULL; + + g_return_if_fail (E_IS_ATTACHMENT (attachment)); + g_return_if_fail (G_IS_ASYNC_RESULT (result)); + g_return_if_fail (GTK_IS_WINDOW (parent)); + + if (e_attachment_open_finish (attachment, result, &error)) + return; + + /* Ignore cancellations. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) + return; + + file_info = e_attachment_get_file_info (attachment); + + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + else + display_name = NULL; + + if (display_name != NULL) + primary_text = g_strdup_printf ( + _("Could not open '%s'"), display_name); + else + primary_text = g_strdup_printf ( + _("Could not open the attachment")); + + dialog = gtk_message_dialog_new_with_markup ( + 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); + g_error_free (error); +} + +/************************* e_attachment_save_async() *************************/ + +typedef struct _SaveContext SaveContext; + +struct _SaveContext { + EAttachment *attachment; + GSimpleAsyncResult *simple; + + GFile *directory; + GFile *destination; + GInputStream *input_stream; + GOutputStream *output_stream; + goffset total_num_bytes; + gssize bytes_read; + gchar buffer[4096]; + gint count; +}; + +/* Forward Declaration */ +static void +attachment_save_read_cb (GInputStream *input_stream, + GAsyncResult *result, + SaveContext *save_context); + +static SaveContext * +attachment_save_context_new (EAttachment *attachment, + GAsyncReadyCallback callback, + gpointer user_data) +{ + SaveContext *save_context; + GSimpleAsyncResult *simple; + + simple = g_simple_async_result_new ( + G_OBJECT (attachment), callback, + user_data, e_attachment_save_async); + + save_context = g_slice_new0 (SaveContext); + save_context->attachment = g_object_ref (attachment); + save_context->simple = simple; + + attachment_set_saving (save_context->attachment, TRUE); + + return save_context; +} + +static void +attachment_save_context_free (SaveContext *save_context) +{ + /* Do not free the GSimpleAsyncResult. */ + g_object_unref (save_context->attachment); + + if (save_context->directory != NULL) + g_object_unref (save_context->directory); + + if (save_context->destination != NULL) + g_object_unref (save_context->destination); + + if (save_context->input_stream != NULL) + g_object_unref (save_context->input_stream); + + if (save_context->output_stream != NULL) + g_object_unref (save_context->output_stream); + + g_slice_free (SaveContext, save_context); +} + +static gboolean +attachment_save_check_for_error (SaveContext *save_context, + GError *error) +{ + GSimpleAsyncResult *simple; + + if (error == NULL) + return FALSE; + + /* 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); + g_error_free (error); + + attachment_save_context_free (save_context); + + return TRUE; +} + +static GFile * +attachment_save_new_candidate (SaveContext *save_context) +{ + GFile *candidate; + GFileInfo *file_info; + EAttachment *attachment; + const gchar *display_name; + gchar *basename; + + attachment = save_context->attachment; + file_info = e_attachment_get_file_info (attachment); + + if (file_info != NULL) + display_name = g_file_info_get_display_name (file_info); + if (display_name == NULL) + /* Translators: Default attachment filename. */ + display_name = _("attachment.dat"); + + if (save_context->count == 0) + basename = g_strdup (display_name); + else { + GString *string; + const gchar *ext; + gsize length; + + string = g_string_sized_new (strlen (display_name)); + ext = g_utf8_strchr (display_name, -1, '.'); + + if (ext != NULL) + length = ext - display_name; + else + length = strlen (display_name); + + g_string_append_len (string, display_name, length); + g_string_append_printf (string, " (%d)", save_context->count); + g_string_append (string, (ext != NULL) ? ext : ""); + + basename = g_string_free (string, FALSE); + } + + save_context->count++; + + candidate = g_file_get_child (save_context->directory, basename); + + g_free (basename); + + return candidate; +} + +static void +attachment_save_write_cb (GOutputStream *output_stream, + GAsyncResult *result, + SaveContext *save_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GInputStream *input_stream; + gssize bytes_written; + GError *error = NULL; + + bytes_written = g_output_stream_write_finish ( + output_stream, result, &error); + + if (attachment_save_check_for_error (save_context, error)) + return; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + input_stream = save_context->input_stream; + + if (bytes_written < save_context->bytes_read) { + g_memmove ( + save_context->buffer, + save_context->buffer + bytes_written, + save_context->bytes_read - bytes_written); + save_context->bytes_read -= bytes_written; + + g_output_stream_write_async ( + output_stream, + save_context->buffer, + save_context->bytes_read, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_write_cb, + save_context); + } else + g_input_stream_read_async ( + input_stream, + save_context->buffer, + sizeof (save_context->buffer), + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_read_cb, + save_context); +} + +static void +attachment_save_read_cb (GInputStream *input_stream, + GAsyncResult *result, + SaveContext *save_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GOutputStream *output_stream; + gssize bytes_read; + GError *error = NULL; + + bytes_read = g_input_stream_read_finish ( + input_stream, result, &error); + + if (attachment_save_check_for_error (save_context, error)) + return; + + if (bytes_read == 0) { + GSimpleAsyncResult *simple; + GFile *destination; + + /* Steal the result. */ + simple = save_context->simple; + save_context->simple = NULL; + + /* Steal the destination. */ + destination = save_context->destination; + save_context->destination = NULL; + + g_simple_async_result_set_op_res_gpointer ( + simple, destination, (GDestroyNotify) g_object_unref); + g_simple_async_result_complete (simple); + + attachment_save_context_free (save_context); + + return; + } + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + output_stream = save_context->output_stream; + save_context->bytes_read = bytes_read; + + attachment_progress_cb ( + g_seekable_tell (G_SEEKABLE (input_stream)), + save_context->total_num_bytes, attachment); + + g_output_stream_write_async ( + output_stream, + save_context->buffer, + save_context->bytes_read, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_write_cb, + save_context); +} + +static void +attachment_save_got_output_stream (SaveContext *save_context) +{ + GCancellable *cancellable; + GInputStream *input_stream; + CamelDataWrapper *wrapper; + CamelMimePart *mime_part; + CamelStream *stream; + EAttachment *attachment; + GByteArray *buffer; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + mime_part = e_attachment_get_mime_part (attachment); + + /* Decode the MIME part to an in-memory buffer. We have to do + * this because CamelStream is synchronous-only, and using threads + * is dangerous because CamelDataWrapper is not reentrant. */ + buffer = g_byte_array_new (); + stream = camel_stream_mem_new (); + camel_stream_mem_set_byte_array (CAMEL_STREAM_MEM (stream), buffer); + wrapper = camel_medium_get_content_object (CAMEL_MEDIUM (mime_part)); + camel_data_wrapper_decode_to_stream (wrapper, stream); + camel_object_unref (stream); + + /* Load the buffer into a GMemoryInputStream. */ + input_stream = g_memory_input_stream_new_from_data ( + buffer->data, (gssize) buffer->len, + (GDestroyNotify) g_free); + save_context->input_stream = input_stream; + save_context->total_num_bytes = (goffset) buffer->len; + g_byte_array_free (buffer, FALSE); + + g_input_stream_read_async ( + input_stream, + save_context->buffer, + sizeof (save_context->buffer), + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_read_cb, + save_context); +} + +static void +attachment_save_create_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GFileOutputStream *output_stream; + GError *error = NULL; + + /* Output stream might be NULL, so don't use cast macro. */ + output_stream = g_file_create_finish (destination, result, &error); + save_context->output_stream = (GOutputStream *) output_stream; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_EXISTS)) { + destination = attachment_save_new_candidate (save_context); + + g_file_create_async ( + destination, G_FILE_CREATE_NONE, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_create_cb, + save_context); + + g_object_unref (destination); + g_error_free (error); + return; + } + + if (attachment_save_check_for_error (save_context, error)) + return; + + save_context->destination = g_object_ref (destination); + attachment_save_got_output_stream (save_context); +} + +static void +attachment_save_replace_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) +{ + GFileOutputStream *output_stream; + GError *error = NULL; + + /* Output stream might be NULL, so don't use cast macro. */ + output_stream = g_file_replace_finish (destination, result, &error); + save_context->output_stream = (GOutputStream *) output_stream; + + if (attachment_save_check_for_error (save_context, error)) + return; + + save_context->destination = g_object_ref (destination); + attachment_save_got_output_stream (save_context); +} + +static void +attachment_save_query_info_cb (GFile *destination, + GAsyncResult *result, + SaveContext *save_context) +{ + EAttachment *attachment; + GCancellable *cancellable; + GFileInfo *file_info; + GFileType file_type; + GError *error = NULL; + + attachment = save_context->attachment; + cancellable = attachment->priv->cancellable; + + file_info = g_file_query_info_finish (destination, result, &error); + + /* G_IO_ERROR_NOT_FOUND just means we're creating a new file. */ + if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_NOT_FOUND)) { + g_error_free (error); + goto replace; + } + + if (attachment_save_check_for_error (save_context, error)) + return; + + file_type = g_file_info_get_file_type (file_info); + g_object_unref (file_info); + + if (file_type == G_FILE_TYPE_DIRECTORY) { + save_context->directory = g_object_ref (destination); + destination = attachment_save_new_candidate (save_context); + + g_file_create_async ( + destination, G_FILE_CREATE_NONE, + G_PRIORITY_DEFAULT, cancellable, + (GAsyncReadyCallback) attachment_save_create_cb, + save_context); + + g_object_unref (destination); + + return; + } + +replace: + g_file_replace_async ( + destination, NULL, FALSE, + G_FILE_CREATE_REPLACE_DESTINATION, G_PRIORITY_DEFAULT, cancellable, (GAsyncReadyCallback) attachment_save_replace_cb, save_context); |