More efficient use of network resources

Previously, Geary could sometimes request elements of an email
message it already had in the database.  Often this was due to
the client requesting information already present as well as one
additional field; the old implementation required all the requested
fields be pulled down.

Now Geary will work out on a message-by-message basis what is
present and what is not and request only missing fields for each
one.  It will cluster messages missing the same fields into the
same request and it submits all requests simultaneously, to take
advantage of IMAP pipelining.

This work has some orthogonal benefits toward #5224, better
Dovecot support, by making the upper layers able to merge an email
using both locally-fetched data and remotely-fetched data.
This commit is contained in:
Jim Nelson 2012-05-14 17:55:06 -07:00
parent 0e1437ef27
commit ce73f610dc
9 changed files with 426 additions and 229 deletions

View file

@ -226,8 +226,8 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
}
public async Gee.List<Geary.Email>? list_email_async(int low, int count,
Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable)
throws Error {
Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, bool partial_ok,
Cancellable? cancellable) throws Error {
check_open();
Geary.Folder.normalize_span_specifiers(ref low, ref count,
@ -242,12 +242,13 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
Gee.List<MessageLocationRow>? list = yield location_table.list_async(transaction,
folder_row.id, low, count, false, cancellable);
return yield do_list_email_async(transaction, list, required_fields, false, cancellable);
return yield do_list_email_async(transaction, list, required_fields, false, partial_ok,
cancellable);
}
private async Gee.List<Geary.Email>? list_email_including_removed_async(int low, int count,
Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, Cancellable? cancellable)
throws Error {
Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, bool partial_ok,
Cancellable? cancellable) throws Error {
check_open();
Geary.Folder.normalize_span_specifiers(ref low, ref count,
@ -262,14 +263,16 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
Gee.List<MessageLocationRow>? list = yield location_table.list_async(transaction,
folder_row.id, low, count, true, cancellable);
return yield do_list_email_async(transaction, list, required_fields, true, cancellable);
return yield do_list_email_async(transaction, list, required_fields, true, partial_ok,
cancellable);
}
public async Gee.List<Geary.Email>? list_email_by_id_async(Geary.EmailIdentifier initial_id,
int count, Geary.Email.Field required_fields, Geary.Folder.ListFlags flags,
int count, Geary.Email.Field required_fields, Geary.Folder.ListFlags flags, bool partial_ok,
Cancellable? cancellable = null) throws Error {
if (count == 0 || count == 1) {
Geary.Email email = yield fetch_email_async(initial_id, required_fields, cancellable);
Geary.Email email = yield fetch_email_async(initial_id, required_fields, partial_ok,
cancellable);
Gee.List<Geary.Email> singleton = new Gee.ArrayList<Geary.Email>();
singleton.add(email);
@ -298,12 +301,13 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
Gee.List<MessageLocationRow>? list = yield location_table.list_ordering_async(transaction,
folder_row.id, low, high, cancellable);
return yield do_list_email_async(transaction, list, required_fields, false, cancellable);
return yield do_list_email_async(transaction, list, required_fields, false, partial_ok,
cancellable);
}
private async Gee.List<Geary.Email>? do_list_email_async(Transaction transaction,
Gee.List<MessageLocationRow>? list, Geary.Email.Field required_fields,
bool include_removed, Cancellable? cancellable) throws Error {
bool include_removed, bool partial_ok, Cancellable? cancellable) throws Error {
check_open();
if (list == null || list.size == 0)
@ -323,7 +327,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
// only add to the list if the email contains all the required fields (because
// properties comes out of a separate table, skip this if properties are requested)
if (!message_row.fields.fulfills(required_fields.clear(Geary.Email.Field.PROPERTIES)))
if (!partial_ok && !message_row.fields.fulfills(required_fields.clear(Geary.Email.Field.PROPERTIES)))
continue;
}
@ -331,7 +335,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
if (required_fields.require(Geary.Email.Field.PROPERTIES)) {
properties = yield imap_message_properties_table.fetch_async(transaction,
location_row.message_id, cancellable);
if (properties == null)
if (!partial_ok && properties == null)
continue;
}
@ -339,7 +343,8 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
int position = yield location_row.get_position_async(transaction, include_removed,
cancellable);
if (position == -1) {
debug("Unable to locate position of email during list of %s, dropping", to_string());
debug("WARNING: Unable to locate position of email during list of %s, dropping",
to_string());
continue;
}
@ -360,7 +365,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
}
public async Geary.Email fetch_email_async(Geary.EmailIdentifier id,
Geary.Email.Field required_fields, Cancellable? cancellable = null) throws Error {
Geary.Email.Field required_fields, bool partial_ok, Cancellable? cancellable = null) throws Error {
check_open();
Geary.Imap.UID uid = ((Imap.EmailIdentifier) id).uid;
@ -397,7 +402,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
// see if the message row fulfills everything but properties, which are held in
// separate table
if (!message_row.fields.fulfills(required_fields.clear(Geary.Email.Field.PROPERTIES))) {
if (!partial_ok && !message_row.fields.fulfills(required_fields.clear(Geary.Email.Field.PROPERTIES))) {
throw new EngineError.INCOMPLETE_MESSAGE(
"Message %s in folder %s only fulfills %Xh fields (required: %Xh)", id.to_string(),
to_string(), message_row.fields, required_fields);
@ -408,7 +413,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
if (required_fields.require(Geary.Email.Field.PROPERTIES)) {
properties = yield imap_message_properties_table.fetch_async(transaction,
location_row.message_id, cancellable);
if (properties == null) {
if (!partial_ok && properties == null) {
throw new EngineError.INCOMPLETE_MESSAGE(
"Message %s in folder %s does not have PROPERTIES field", id.to_string(),
to_string());
@ -632,7 +637,7 @@ private class Geary.Sqlite.Folder : Object, Geary.ReferenceSemantics {
if (local_position >= 1) {
// get EmailIdentifier
Gee.List<Geary.Email>? local = yield list_email_including_removed_async(local_position, 1,
Geary.Email.Field.NONE, Geary.Folder.ListFlags.NONE, null);
Geary.Email.Field.NONE, Geary.Folder.ListFlags.NONE, false, null);
if (local != null && local.size == 1) {
id = local[0].id;
} else {