Commit aa355f54 authored by Jim Nelson's avatar Jim Nelson

Merge branch 'master' into feature/attachments

parents 3598a08d d472b9e4
......@@ -41,7 +41,7 @@ public class Geary.FolderPath : BaseObject, Gee.Hashable<Geary.FolderPath>,
}
/**
* Returns true if this {@link FolderPath} is the root folder.
* Returns true if this {@link FolderPath} is a root folder.
*
* This means that the FolderPath ''should'' be castable into {@link FolderRoot}, which is
* enforced through the constructor and accessor styles of this class. However, this test
......
......@@ -367,6 +367,7 @@ public class Geary.App.ConversationMonitor : BaseObject {
folder.email_appended.disconnect(on_folder_email_appended);
folder.email_removed.disconnect(on_folder_email_removed);
folder.email_flags_changed.disconnect(on_folder_email_flags_changed);
folder.email_count_changed.disconnect(on_folder_email_count_changed);
folder.opened.disconnect(on_folder_opened);
folder.closed.disconnect(on_folder_closed);
folder.account.email_locally_complete.disconnect(on_account_email_locally_complete);
......
......@@ -157,10 +157,11 @@ private class Geary.ImapDB.Account : BaseObject {
/**
* Only updates folder's STATUS message count, attributes, recent, and unseen; UIDVALIDITY and UIDNEXT
* updated when the folder is SELECT/EXAMINED (see update_folder_select_examine_async())
* updated when the folder is SELECT/EXAMINED (see update_folder_select_examine_async()) unless
* update_uid_info is true.
*/
public async void update_folder_status_async(Geary.Imap.Folder imap_folder, Cancellable? cancellable)
throws Error {
public async void update_folder_status_async(Geary.Imap.Folder imap_folder, bool update_uid_info,
Cancellable? cancellable) throws Error {
check_open();
Geary.Imap.FolderProperties properties = imap_folder.properties;
......@@ -190,7 +191,10 @@ private class Geary.ImapDB.Account : BaseObject {
stmt.bind_string(2, path.basename);
}
stmt.exec();
stmt.exec(cancellable);
if (update_uid_info)
do_update_uid_info(cx, properties, parent_id, path, cancellable);
if (properties.status_messages >= 0) {
do_update_last_seen_status_total(cx, parent_id, path.basename, properties.status_messages,
......@@ -209,13 +213,18 @@ private class Geary.ImapDB.Account : BaseObject {
local_properties.recent = properties.recent;
local_properties.attrs = properties.attrs;
if (update_uid_info) {
local_properties.uid_validity = properties.uid_validity;
local_properties.uid_next = properties.uid_next;
}
if (properties.status_messages >= 0)
local_properties.set_status_message_count(properties.status_messages, false);
}
}
/**
* Only updates folder's SELECT/EXAMINE message count, UIDVALIDITY, UIDNEXT, unseen, and recent.
* Updates folder's SELECT/EXAMINE message count, UIDVALIDITY, UIDNEXT, unseen, and recent.
* See also update_folder_status_async().
*/
public async void update_folder_select_examine_async(Geary.Imap.Folder imap_folder, Cancellable? cancellable)
......@@ -233,28 +242,7 @@ private class Geary.ImapDB.Account : BaseObject {
return Db.TransactionOutcome.ROLLBACK;
}
int64 uid_validity = (properties.uid_validity != null) ? properties.uid_validity.value
: Imap.UIDValidity.INVALID;
int64 uid_next = (properties.uid_next != null) ? properties.uid_next.value
: Imap.UID.INVALID;
Db.Statement stmt;
if (parent_id != Db.INVALID_ROWID) {
stmt = cx.prepare(
"UPDATE FolderTable SET uid_validity=?, uid_next=? WHERE parent_id=? AND name=?");
stmt.bind_int64(0, uid_validity);
stmt.bind_int64(1, uid_next);
stmt.bind_rowid(2, parent_id);
stmt.bind_string(3, path.basename);
} else {
stmt = cx.prepare(
"UPDATE FolderTable SET uid_validity=?, uid_next=? WHERE parent_id IS NULL AND name=?");
stmt.bind_int64(0, uid_validity);
stmt.bind_int64(1, uid_next);
stmt.bind_string(2, path.basename);
}
stmt.exec();
do_update_uid_info(cx, properties, parent_id, path, cancellable);
if (properties.select_examine_messages >= 0) {
do_update_last_seen_select_examine_total(cx, parent_id, path.basename,
......@@ -825,7 +813,7 @@ private class Geary.ImapDB.Account : BaseObject {
}
private async void populate_search_table_async(Cancellable? cancellable) {
debug("Populating search table");
debug("%s: Populating search table", account_information.email);
try {
int total = yield get_email_count_async(cancellable);
search_index_monitor.set_interval(0, total);
......@@ -840,17 +828,19 @@ private class Geary.ImapDB.Account : BaseObject {
yield Geary.Scheduler.sleep_ms_async(50);
}
} catch (Error e) {
debug("Error populating search table: %s", e.message);
debug("Error populating %s search table: %s", account_information.email, e.message);
}
if (search_index_monitor.is_in_progress)
search_index_monitor.notify_finish();
debug("Done populating search table");
debug("%s: Done populating search table", account_information.email);
}
private async bool populate_search_table_batch_async(int limit = 100,
Cancellable? cancellable) throws Error {
debug("%s: Searching for missing indexed messages...", account_information.email);
int count = 0;
yield db.exec_transaction_async(Db.TransactionType.RW, (cx, cancellable) => {
Db.Statement stmt = cx.prepare("""
......@@ -897,6 +887,9 @@ private class Geary.ImapDB.Account : BaseObject {
search_index_monitor.increment(count);
if (count > 0)
debug("%s: Found %d/%d missing indexed messages...", account_information.email, count, limit);
return (count < limit);
}
......@@ -1147,6 +1140,32 @@ private class Geary.ImapDB.Account : BaseObject {
stmt.exec(cancellable);
}
private void do_update_uid_info(Db.Connection cx, Imap.FolderProperties properties,
int64 parent_id, FolderPath path, Cancellable? cancellable) throws Error {
int64 uid_validity = (properties.uid_validity != null) ? properties.uid_validity.value
: Imap.UIDValidity.INVALID;
int64 uid_next = (properties.uid_next != null) ? properties.uid_next.value
: Imap.UID.INVALID;
Db.Statement stmt;
if (parent_id != Db.INVALID_ROWID) {
stmt = cx.prepare(
"UPDATE FolderTable SET uid_validity=?, uid_next=? WHERE parent_id=? AND name=?");
stmt.bind_int64(0, uid_validity);
stmt.bind_int64(1, uid_next);
stmt.bind_rowid(2, parent_id);
stmt.bind_string(3, path.basename);
} else {
stmt = cx.prepare(
"UPDATE FolderTable SET uid_validity=?, uid_next=? WHERE parent_id IS NULL AND name=?");
stmt.bind_int64(0, uid_validity);
stmt.bind_int64(1, uid_next);
stmt.bind_string(2, path.basename);
}
stmt.exec(cancellable);
}
private int do_get_email_count(Db.Connection cx, Cancellable? cancellable)
throws Error {
Db.Statement stmt = cx.prepare(
......
......@@ -16,7 +16,8 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
PARTIAL_OK,
INCLUDE_MARKED_FOR_REMOVE,
INCLUDING_ID,
OLDEST_TO_NEWEST;
OLDEST_TO_NEWEST,
ONLY_INCOMPLETE;
public bool is_all_set(ListFlags flags) {
return (this & flags) == flags;
......@@ -281,6 +282,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
bool including_id = flags.is_all_set(ListFlags.INCLUDING_ID);
bool oldest_to_newest = flags.is_all_set(ListFlags.OLDEST_TO_NEWEST);
bool only_incomplete = flags.is_all_set(ListFlags.ONLY_INCOMPLETE);
int64 start;
if (initial_id != null) {
......@@ -301,24 +303,35 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
// database ... first, gather locations of all emails in database
Gee.List<LocationIdentifier> ids = new Gee.ArrayList<LocationIdentifier>();
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
Db.Statement stmt;
if (oldest_to_newest) {
stmt = cx.prepare("""
SELECT message_id, ordering
FROM MessageLocationTable
WHERE folder_id = ? AND ordering >= ?
ORDER BY ordering ASC
LIMIT ?
""");
} else {
stmt = cx.prepare("""
SELECT message_id, ordering
FROM MessageLocationTable
WHERE folder_id = ? AND ordering <= ?
ORDER BY ordering DESC
LIMIT ?
""");
StringBuilder sql = new StringBuilder("""
SELECT MessageLocationTable.message_id, ordering
FROM MessageLocationTable
""");
if (only_incomplete) {
sql.append("""
INNER JOIN MessageTable
ON MessageTable.id = MessageLocationTable.message_id
""");
}
sql.append("WHERE folder_id = ? ");
if (oldest_to_newest)
sql.append("AND ordering >= ? ");
else
sql.append("AND ordering <= ? ");
if (only_incomplete)
sql.append_printf("AND fields != %d ", Geary.Email.Field.ALL);
if (oldest_to_newest)
sql.append("ORDER BY ordering ASC ");
else
sql.append("ORDER BY ordering DESC ");
sql.append("LIMIT ?");
Db.Statement stmt = cx.prepare(sql.str);
stmt.bind_rowid(0, folder_id);
stmt.bind_int64(1, start);
stmt.bind_int(2, count);
......@@ -351,6 +364,7 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
Geary.EmailIdentifier end_id, Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable)
throws Error {
bool including_id = flags.is_all_set(ListFlags.INCLUDING_ID);
bool only_incomplete = flags.is_all_set(ListFlags.ONLY_INCOMPLETE);
int64 start = ((Geary.Imap.EmailIdentifier) start_id).uid.value;
if (!including_id)
......@@ -367,11 +381,23 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
// database ... first, gather locations of all emails in database
Gee.List<LocationIdentifier> ids = new Gee.ArrayList<LocationIdentifier>();
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
Db.Statement stmt = cx.prepare("""
SELECT message_id, ordering
StringBuilder sql = new StringBuilder("""
SELECT MessageLocationTable.message_id, ordering
FROM MessageLocationTable
WHERE folder_id = ? AND ordering >= ? AND ordering <= ?
""");
if (only_incomplete) {
sql.append("""
INNER JOIN MessageTable
ON MessageTable.id = MessageLocationTable.message_id
""");
}
sql.append("WHERE folder_id = ? AND ordering >= ? AND ordering <= ? ");
if (only_incomplete)
sql.append_printf(" AND fields != %d ", Geary.Email.Field.ALL);
Db.Statement stmt = cx.prepare(sql.str);
stmt.bind_rowid(0, folder_id);
stmt.bind_int64(1, start);
stmt.bind_int64(2, end);
......@@ -399,6 +425,70 @@ private class Geary.ImapDB.Folder : BaseObject, Geary.ReferenceSemantics {
return yield list_email_in_chunks_async(ids, required_fields, flags, cancellable);
}
public async Gee.List<Geary.Email>? list_email_by_sparse_id_async(Gee.Collection<Geary.EmailIdentifier> ids,
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error {
if (ids.size == 0)
return null;
bool only_incomplete = flags.is_all_set(ListFlags.ONLY_INCOMPLETE);
// Break up work so all reading isn't done in single transaction that locks up the
// database ... first, gather locations of all emails in database
Gee.List<LocationIdentifier> locations = new Gee.ArrayList<LocationIdentifier>();
yield db.exec_transaction_async(Db.TransactionType.RO, (cx) => {
StringBuilder sql = new StringBuilder("""
SELECT MessageLocationTable.message_id, ordering
FROM MessageLocationTable
""");
if (only_incomplete) {
sql.append("""
INNER JOIN MessageTable
ON MessageTable.id = MessageLocationTable.message_id
""");
}
sql.append("WHERE folder_id = ? ");
if (only_incomplete)
sql.append_printf(" AND fields != %d ", Geary.Email.Field.ALL);
sql.append("AND ordering IN (");
bool first = true;
foreach (Geary.EmailIdentifier id in ids) {
if (!first)
sql.append(", ");
sql.append(id.ordering.to_string());
first = false;
}
sql.append(")");
Db.Statement stmt = cx.prepare(sql.str);
stmt.bind_rowid(0, folder_id);
Db.Result results = stmt.exec(cancellable);
if (results.finished)
return Db.TransactionOutcome.SUCCESS;
do {
int64 ordering = results.int64_at(1);
Geary.EmailIdentifier email_id = new Imap.EmailIdentifier(new Imap.UID(ordering), path);
LocationIdentifier location = new LocationIdentifier(results.rowid_at(0), ordering,
path, email_id);
if (!flags.include_marked_for_remove() && is_marked_removed(location.email_id))
continue;
locations.add(location);
} while (results.next(cancellable));
return Db.TransactionOutcome.SUCCESS;
}, cancellable);
// Next, read in email in chunks
return yield list_email_in_chunks_async(locations, required_fields, flags, cancellable);
}
private async Gee.List<Geary.Email>? list_email_in_chunks_async(Gee.List<LocationIdentifier> ids,
Geary.Email.Field required_fields, ListFlags flags, Cancellable? cancellable) throws Error {
int length = ids.size;
......
......@@ -43,8 +43,7 @@ private class Geary.ImapEngine.GmailAccount : Geary.ImapEngine.GenericAccount {
if (path_type_map == null) {
path_type_map = new Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType>();
path_type_map.set(new Geary.FolderRoot(Imap.Account.INBOX_NAME, null,
Imap.Folder.CASE_SENSITIVE), SpecialFolderType.INBOX);
path_type_map.set(Imap.MailboxSpecifier.inbox.to_folder_path(), SpecialFolderType.INBOX);
Geary.FolderPath gmail_root = new Geary.FolderRoot(GMAIL_FOLDER, null,
Imap.Folder.CASE_SENSITIVE);
......
......@@ -20,6 +20,11 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
public AccountSynchronizer(GenericAccount account) {
this.account = account;
// don't allow duplicates because it's possible for a Folder to change several times
// before finally opened and synchronized, which we only want to do once
bg_queue.allow_duplicates = false;
bg_queue.requeue_duplicate = false;
account.opened.connect(on_account_opened);
account.closed.connect(on_account_closed);
account.folders_available_unavailable.connect(on_folders_available_unavailable);
......@@ -285,6 +290,8 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
} else {
debug("No oldest message found for %s, synchronizing...", folder.to_string());
}
} else {
debug("Folder %s changed, synchronizing...", folder.to_string());
}
try {
......
......@@ -16,9 +16,9 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
// Don't fetch FLAGS; those are fetched by the FlagWatcher and during normalization when a
// standard open_async() is invoked on the Folder
private const Geary.Email.Field PREFETCH_FIELDS = Geary.Email.Field.ALL & ~(Geary.Email.MUTABLE_FIELDS);
private const Geary.Email.Field PREFETCH_FIELDS = Geary.Email.Field.ALL;
private const int PREFETCH_IDS_CHUNKS = 500;
private const int PREFETCH_CHUNK_BYTES = 64 * 1024;
private const int PREFETCH_CHUNK_BYTES = 8 * 1024;
public Nonblocking.CountingSemaphore active_sem { get; private set;
default = new Nonblocking.CountingSemaphore(null); }
......@@ -81,6 +81,8 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
// emails should include PROPERTIES
private void schedule_prefetch(Gee.Collection<Geary.Email> emails) {
debug("%s: scheduling %d emails for prefetching", folder.to_string(), emails.size);
prefetch_emails.add_all(emails);
// only increment active state if not rescheduling
......@@ -105,8 +107,8 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
for (;;) {
Gee.List<Geary.Email>? list = null;
try {
list = yield folder.list_email_by_id_async(lowest, PREFETCH_IDS_CHUNKS,
Geary.Email.Field.PROPERTIES, Geary.Folder.ListFlags.LOCAL_ONLY, cancellable);
list = yield folder.local_folder.list_email_by_id_async(lowest, PREFETCH_IDS_CHUNKS,
Geary.Email.Field.PROPERTIES, ImapDB.Folder.ListFlags.ONLY_INCOMPLETE, cancellable);
} catch (Error err) {
debug("Error while list local emails for %s: %s", folder.to_string(), err.message);
}
......@@ -129,8 +131,9 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
private async void do_prepare_new_async(Gee.Collection<Geary.EmailIdentifier> ids) {
Gee.List<Geary.Email>? list = null;
try {
list = yield folder.list_email_by_sparse_id_async(ids, Geary.Email.Field.PROPERTIES,
Geary.Folder.ListFlags.LOCAL_ONLY, cancellable);
list = yield folder.local_folder.list_email_by_sparse_id_async(
(Gee.Collection<ImapDB.EmailIdentifier>) ids,
Geary.Email.Field.PROPERTIES, ImapDB.Folder.ListFlags.ONLY_INCOMPLETE, cancellable);
} catch (Error err) {
debug("Error while list local emails for %s: %s", folder.to_string(), err.message);
}
......@@ -168,30 +171,6 @@ private class Geary.ImapEngine.EmailPrefetcher : Object {
Gee.TreeSet<Geary.Email> emails = prefetch_emails;
prefetch_emails = new Collection.FixedTreeSet<Geary.Email>(Email.compare_date_received_descending);
if (emails.size == 0)
return;
// Remove anything that is fully prefetched
Gee.Map<Geary.EmailIdentifier, Geary.Email.Field>? fields = null;
try {
fields = yield folder.list_local_email_fields_async(Email.emails_to_map(emails).keys,
cancellable);
} catch (Error err) {
debug("do_prefetch_batch_async: Unable to list local fields for %s prefetch: %s",
folder.to_string(), err.message);
return;
}
Collection.filtered_remove<Geary.Email>(emails, (email) => {
// if not present, don't prefetch
if (fields == null || !fields.has_key(email.id))
return false;
// only prefetch if missing fields
return !fields.get(email.id).fulfills(PREFETCH_FIELDS);
});
if (emails.size == 0)
return;
......
......@@ -5,7 +5,7 @@
*/
private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
private const int REFRESH_FOLDER_LIST_SEC = 10 * 60;
private const int REFRESH_FOLDER_LIST_SEC = 2 * 60;
private static Geary.FolderPath? outbox_path = null;
private static Geary.FolderPath? search_path = null;
......@@ -406,13 +406,19 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
// only worry about alterations if the remote is openable
if (remote_folder.properties.is_openable.is_possible()) {
ImapDB.Folder local_folder = generic_folder.local_folder;
if (remote_folder.properties.have_contents_changed(local_folder.get_properties()).is_possible())
if (remote_folder.properties.have_contents_changed(local_folder.get_properties(),
generic_folder.to_string())) {
altered_paths.add(remote_folder.path);
}
}
// always update, openable or not
// always update, openable or not; update UIDs if already open, otherwise will keep
// signalling that it's changed (because the only time UIDNEXT/UIDValidity is updated
// is when the folder is first opened)
try {
yield local.update_folder_status_async(remote_folder, cancellable);
yield local.update_folder_status_async(remote_folder,
generic_folder.get_open_state() != Geary.Folder.OpenState.CLOSED, cancellable);
} catch (Error update_error) {
debug("Unable to update local folder %s with remote properties: %s",
remote_folder.to_string(), update_error.message);
......
......@@ -317,7 +317,6 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
if (replay_queue.query_local_writebehind_operation(ReplayOperation.WritebehindOperation.CREATE,
remote_email.id, null)) {
to_create_or_merge.add(remote_email);
appended_ids.add(remote_email.id);
Logging.debug(Logging.Flag.FOLDER_NORMALIZATION, "%s: appending inside remote ID %s",
to_string(), remote_email.id.to_string());
......@@ -798,11 +797,18 @@ private class Geary.ImapEngine.GenericFolder : Geary.AbstractFolder, Geary.Folde
// marked for removal, which that helper function doesn't like
local_position = remote_position - (remote_count - local_count);
debug("do_replay_remove_message: local_count=%d local_position=%d", local_count, local_position);
Imap.UID? uid = yield local_folder.get_uid_at_async(local_position, null);
if (uid != null)
owned_id = new Imap.EmailIdentifier(uid, path);
// zero or negative means the message exists beyond the local vector's range, so
// nothing to do there
if (local_position > 0) {
debug("do_replay_remove_message: local_count=%d local_position=%d", local_count, local_position);
Imap.UID? uid = yield local_folder.get_uid_at_async(local_position, null);
if (uid != null)
owned_id = new Imap.EmailIdentifier(uid, path);
} else {
debug("do_replay_remove_message: message not stored locally (local_count=%d local_position=%d)",
local_count, local_position);
}
} catch (Error err) {
debug("Unable to determine ID of removed message #%d from %s: %s", remote_position,
to_string(), err.message);
......
......@@ -13,7 +13,7 @@ private class Geary.ImapEngine.OtherAccount : Geary.ImapEngine.GenericAccount {
protected override GenericFolder new_folder(Geary.FolderPath path, Imap.Account remote_account,
ImapDB.Account local_account, ImapDB.Folder local_folder) {
SpecialFolderType type;
if (path.basename == Imap.Account.INBOX_NAME)
if (Imap.MailboxSpecifier.folder_path_is_inbox(path))
type = SpecialFolderType.INBOX;
else
type = local_folder.get_properties().attrs.get_special_folder_type();
......
......@@ -43,11 +43,16 @@ private class Geary.ImapEngine.ListEmailByID : Geary.ImapEngine.AbstractListEmai
}
}
// verify that the initial_id was found; if not, then want to get it from the remote
// (this will force a vector expansion, if required)
if (initial_id != null && !initial_id_found) {
unfulfilled.set(required_fields | ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION,
initial_id);
// If INCLUDING_ID specified, verify that the initial_id was found; if not, then want to
// get it from the remote (this will force a vector expansion, if required)
if (flags.is_including_id()) {
if (initial_id != null && !initial_id_found) {
unfulfilled.set(required_fields | ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION,
initial_id);
}
} else {
// fake it, as this flag is used later to determine vector expansion
initial_id_found = true;
}
// report fulfilled items
......@@ -216,6 +221,7 @@ private class Geary.ImapEngine.ListEmailByID : Geary.ImapEngine.AbstractListEmai
// merely count backwards from the top of the vector
if (initial_id == null || local_count == 0) {
low_pos = new Imap.SequenceNumber(Numeric.int_floor((remote_count - count) + 1, 1));
// don't set high_pos, leave null to use symbolic "highest" in MessageSet
high_pos = null;
} else {
......@@ -243,13 +249,17 @@ private class Geary.ImapEngine.ListEmailByID : Geary.ImapEngine.AbstractListEmai
}
Imap.MessageSet msg_set;
if (high_pos != null)
int actual_count = -1;
if (high_pos != null) {
msg_set = new Imap.MessageSet.range_by_first_last(low_pos, high_pos);
else
actual_count = (high_pos.value - low_pos.value) + 1;
} else {
msg_set = new Imap.MessageSet.range_to_highest(low_pos);
}
debug("%s: Performing vector expansion using %s for %s/%u", owner.to_string(), msg_set.to_string(),
(initial_id != null) ? initial_id.to_string() : "(null)", count);
debug("%s: Performing vector expansion using %s for initial_id=%s count=%d actual_count=%d",
owner.to_string(), msg_set.to_string(),
(initial_id != null) ? initial_id.to_string() : "(null)", count, actual_count);
Gee.List<Geary.Email>? list = yield owner.remote_folder.list_email_async(msg_set,
Geary.Email.Field.NONE, cancellable);
......
......@@ -5,34 +5,6 @@
*/
private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.AbstractListEmail {
private class LocalBatchOperation : Nonblocking.BatchOperation {
public GenericFolder owner;
public Geary.EmailIdentifier id;
public Geary.Email.Field required_fields;
public LocalBatchOperation(GenericFolder owner, Geary.EmailIdentifier id,
Geary.Email.Field required_fields) {
this.owner = owner;
this.id = id;
this.required_fields = required_fields;
}
public override async Object? execute_async(Cancellable? cancellable) throws Error {
// TODO: Need a sparse ID fetch in ImapDB.Folder to scoop all these up at once
try {
return yield owner.local_folder.fetch_email_async(id, required_fields,
ImapDB.Folder.ListFlags.PARTIAL_OK, cancellable);
} catch (Error err) {
// only throw errors that are not NOT_FOUND and INCOMPLETE_MESSAGE, as these two
// are recoverable
if (!(err is Geary.EngineError.NOT_FOUND) && !(err is Geary.EngineError.INCOMPLETE_MESSAGE))
throw err;
}
return null;
}
}
private Gee.HashSet<Geary.EmailIdentifier> ids = new Gee.HashSet<Geary.EmailIdentifier>();
public ListEmailBySparseID(GenericFolder owner, Gee.Collection<Geary.EmailIdentifier> ids,
......@@ -51,31 +23,30 @@ private class Geary.ImapEngine.ListEmailBySparseID : Geary.ImapEngine.AbstractLi
return ReplayOperation.Status.CONTINUE;
}
Nonblocking.Batch batch = new Nonblocking.Batch();
// Fetch emails by ID from local store all at once
foreach (Geary.EmailIdentifier id in ids)
batch.add(new LocalBatchOperation(owner, id, required_fields));
yield batch.execute_all_async(cancellable);
batch.throw_first_exception();
Gee.List<Geary.Email>? list = yield owner.local_folder.list_email_by_sparse_id_async(ids,
required_fields, ImapDB.Folder.ListFlags.PARTIAL_OK, cancellable);
// Build list of emails fully fetched from local store and table of remaining emails by
// their lack of completeness
Gee.List<Geary.Email> fulfilled = new Gee.ArrayList<Geary.Email>();
foreach (int batch_id in batch.get_ids()) {
LocalBatchOperation local_op = (LocalBatchOperation) batch.get_operation(batch_id);
Geary.Email? email = (Geary.Email?) batch.get_result(batch_id);
if (list != null && list.size > 0) {
Gee.Map<Geary.EmailIdentifier, Geary.Email>? map = Email.emails_to_map(list);
assert(map != null);
// walk list of *requested* IDs to ensure that unknown are considering unfulfilled
foreach (Geary.EmailIdentifier id in ids) {
Geary.Email? email = map.get(id);
// if completely unknown, make sure duplicate detection fields are included; otherwise,
// if known, then they were pulled down during folder normalization and during
// vector expansion
if (email == null)
unfulfilled.set(required_fields | ImapDB.Folder.REQUIRED_FOR_DUPLICATE_DETECTION, local_op.id);