Make adding MIME parts to a new RFC822 message async

This will allow us to schedule some long-running work on a background
thread.
This commit is contained in:
Michael Gratton 2019-07-18 16:27:33 +10:00 committed by Michael James Gratton
parent 8d6bffbb00
commit 7cc3e6633e
6 changed files with 136 additions and 55 deletions

View file

@ -1473,7 +1473,9 @@ public class ComposerWidget : Gtk.EventBox, Geary.BaseInterface {
try {
Geary.ComposedEmail draft = yield get_composed_email(null, true);
this.draft_manager.update(
draft.to_rfc822_message(), this.draft_flags, null
yield draft.to_rfc822_message(null, null),
this.draft_flags,
null
);
} catch (Error err) {
GLib.message("Unable to save draft: %s", err.message);

View file

@ -61,8 +61,11 @@ public class Geary.ComposedEmail : BaseObject {
this.body_html = body_html;
}
public Geary.RFC822.Message to_rfc822_message(string? message_id = null) {
return new RFC822.Message.from_composed_email(this, message_id);
public async Geary.RFC822.Message to_rfc822_message(string? message_id,
GLib.Cancellable? cancellable) {
return yield new RFC822.Message.from_composed_email(
this, message_id, cancellable
);
}
/**

View file

@ -504,9 +504,10 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
string domain = composed.sender != null
? composed.sender.domain
: this.information.primary_mailbox.domain;
Geary.RFC822.Message rfc822 = new Geary.RFC822.Message.from_composed_email(
composed, GMime.utils_generate_message_id(domain)
);
Geary.RFC822.Message rfc822 =
yield new Geary.RFC822.Message.from_composed_email(
composed, GMime.utils_generate_message_id(domain), cancellable
);
yield this.smtp.queue_email(rfc822, cancellable);
}

View file

@ -125,7 +125,9 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
stock_from_gmime();
}
public Message.from_composed_email(Geary.ComposedEmail email, string? message_id) {
public async Message.from_composed_email(Geary.ComposedEmail email,
string? message_id,
GLib.Cancellable? cancellable) {
this.message = new GMime.Message(true);
// Required headers
@ -205,12 +207,26 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
// Body: text format (optional)
if (email.body_text != null) {
GMime.Part? body_text = body_data_to_part(email.body_text.data,
ref body_charset,
ref body_encoding,
"text/plain",
true);
body_parts.add(body_text);
GMime.Part? body_text = null;
try {
body_text = yield body_data_to_part(
email.body_text.data,
null,
null,
"text/plain",
true,
cancellable
);
} catch (GLib.Error err) {
warning("Error creating text body part: %s", err.message);
}
if (body_text != null) {
body_charset = body_text.get_content_type().get_parameter(
"charset"
);
body_encoding = body_text.get_content_encoding();
body_parts.add(body_text);
}
}
// Body: HTML format (also optional)
@ -233,9 +249,20 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
foreach (string cid in email.cid_files.keys) {
if (email.contains_inline_img_src(CID_URL_PREFIX + cid)) {
File file = email.cid_files[cid];
GMime.Object? inline_part = get_file_part(
file, Geary.Mime.DispositionType.INLINE
);
GMime.Object? inline_part = null;
try {
inline_part = yield get_file_part(
file,
Geary.Mime.DispositionType.INLINE,
cancellable
);
} catch (GLib.Error err) {
warning(
"Error creating CID part %s: %s",
file.get_path(),
err.message
);
}
if (inline_part != null) {
inline_part.set_content_id(cid);
related_parts.add(inline_part);
@ -259,9 +286,20 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
if (email.replace_inline_img_src(name,
CID_URL_PREFIX + cid)) {
GMime.Object? inline_part = get_file_part(
inline_files[name], Geary.Mime.DispositionType.INLINE
);
GMime.Object? inline_part = null;
try {
inline_part = yield get_file_part(
inline_files[name],
Geary.Mime.DispositionType.INLINE,
cancellable
);
} catch (GLib.Error err) {
warning(
"Error creating inline file part %s: %s",
inline_files[name].get_path(),
err.message
);
}
if (inline_part != null) {
inline_part.set_content_id(cid);
related_parts.add(inline_part);
@ -270,11 +308,19 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
}
}
GMime.Object? body_html = body_data_to_part(email.body_html.data,
ref body_charset,
ref body_encoding,
"text/html",
false);
GMime.Object? body_html = null;
try {
body_html = yield body_data_to_part(
email.body_html.data,
body_charset,
body_encoding,
"text/html",
false,
cancellable
);
} catch (GLib.Error err) {
warning("Error creating html body part: %s", err.message);
}
// Assemble the HTML and inline images into a related
// part, if needed
@ -297,11 +343,23 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
Gee.List<GMime.Object> attachment_parts = new Gee.LinkedList<GMime.Object>();
foreach (File file in email.attached_files) {
GMime.Object? attachment_part = get_file_part(
file, Geary.Mime.DispositionType.ATTACHMENT
);
if (attachment_part != null)
GMime.Object? attachment_part = null;
try {
attachment_part = yield get_file_part(
file,
Geary.Mime.DispositionType.ATTACHMENT,
cancellable
);
} catch (GLib.Error err) {
warning(
"Error creating attachment file part %s: %s",
file.get_path(),
err.message
);
}
if (attachment_part != null) {
attachment_parts.add(attachment_part);
}
}
GMime.Object? attachment_part = coalesce_parts(attachment_parts, "mixed");
if (attachment_part != null)
@ -366,29 +424,39 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
}
}
private GMime.Part? get_file_part(File file,
Geary.Mime.DispositionType disposition) {
if (!file.query_exists())
return null;
FileInfo file_info;
try {
file_info = file.query_info(FileAttribute.STANDARD_CONTENT_TYPE, FileQueryInfoFlags.NONE);
} catch (Error err) {
debug("Error querying info from file: %s", err.message);
return null;
}
private async GMime.Part? get_file_part(File file,
Geary.Mime.DispositionType disposition,
GLib.Cancellable cancellable)
throws GLib.Error {
FileInfo file_info = yield file.query_info_async(
FileAttribute.STANDARD_CONTENT_TYPE,
FileQueryInfoFlags.NONE
);
GMime.Part part = new GMime.Part();
part.set_disposition(disposition.serialize());
part.set_filename(file.get_basename());
part.set_content_type(new GMime.ContentType.from_string(file_info.get_content_type()));
part.set_content_type(
new GMime.ContentType.from_string(file_info.get_content_type())
);
// This encoding is the initial encoding of the stream.
GMime.StreamGIO stream = new GMime.StreamGIO(file);
stream.set_owner(false);
part.set_content_object(new GMime.DataWrapper.with_stream(stream, GMime.ContentEncoding.BINARY));
part.set_content_encoding(Geary.RFC822.Utils.get_best_encoding(stream));
part.set_content_object(
new GMime.DataWrapper.with_stream(
stream, GMime.ContentEncoding.BINARY
)
);
part.set_content_encoding(
yield Utils.get_best_encoding(
stream,
// Determine this from the MTA's capabilities at send
// time
GMime.EncodingConstraint.7BIT,
cancellable
)
);
return part;
}
@ -993,11 +1061,13 @@ public class Geary.RFC822.Message : BaseObject, EmailHeaderSet {
* clean ASCII. So if we have guessed both already for a plain
* text body, it will still apply for any HTML part.
*/
private GMime.Part body_data_to_part(uint8[] content,
ref string? charset,
ref GMime.ContentEncoding? encoding,
string content_type,
bool is_flowed) {
private async GMime.Part body_data_to_part(uint8[] content,
string? charset,
GMime.ContentEncoding? encoding,
string content_type,
bool is_flowed,
GLib.Cancellable? cancellable)
throws GLib.Error {
GMime.Stream content_stream = new GMime.StreamMem.with_buffer(content);
if (charset == null) {
charset = Geary.RFC822.Utils.get_best_charset(content_stream);

View file

@ -43,7 +43,9 @@ async void main_async() throws Error {
composed_email.body_html = contents;
}
msg = new Geary.RFC822.Message.from_composed_email(composed_email, null);
msg = yield new Geary.RFC822.Message.from_composed_email(
composed_email, null, null
);
}
stdout.printf("\n\n%s\n\n", msg.to_string());

View file

@ -91,13 +91,15 @@ class Integration.Smtp.ClientSession : TestCase {
null, this.config.credentials.user
);
Geary.RFC822.Message message = new_message(
this.new_message.begin(
return_path,
new Geary.RFC822.MailboxAddress(
"Geary integration test",
this.config.credentials.user
)
),
async_complete_full
);
Geary.RFC822.Message message = new_message.end(async_result());
this.session.send_email_async.begin(
return_path,
@ -115,8 +117,8 @@ class Integration.Smtp.ClientSession : TestCase {
this.session.login_async.end(async_result());
}
private Geary.RFC822.Message new_message(Geary.RFC822.MailboxAddress from,
Geary.RFC822.MailboxAddress to) {
private async Geary.RFC822.Message new_message(Geary.RFC822.MailboxAddress from,
Geary.RFC822.MailboxAddress to) {
Geary.ComposedEmail composed = new Geary.ComposedEmail(
new GLib.DateTime.now_local(),
new Geary.RFC822.MailboxAddresses.single(from),
@ -128,9 +130,10 @@ class Integration.Smtp.ClientSession : TestCase {
null
);
return new Geary.RFC822.Message.from_composed_email(
return yield new Geary.RFC822.Message.from_composed_email(
composed,
GMime.utils_generate_message_id(from.domain)
GMime.utils_generate_message_id(from.domain),
null
);
}