Folders to load on new accounts: Closes #7238
Numerous changes to make account synchronization and email prefetcher more efficient and not hold the database lock for so long.
This commit is contained in:
parent
547114f186
commit
d472b9e4c9
15 changed files with 259 additions and 165 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -221,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 {
|
||||
|
|
@ -248,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);
|
||||
|
||||
// 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);
|
||||
else if (!email.fields.fulfills(required_fields))
|
||||
unfulfilled.set(required_fields.clear(email.fields), local_op.id);
|
||||
else
|
||||
fulfilled.add(email);
|
||||
// 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, id);
|
||||
else if (!email.fields.fulfills(required_fields))
|
||||
unfulfilled.set(required_fields.clear(email.fields), id);
|
||||
else
|
||||
fulfilled.add(email);
|
||||
}
|
||||
}
|
||||
|
||||
if (fulfilled.size > 0) {
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ private class Geary.ImapEngine.YahooAccount : Geary.ImapEngine.GenericAccount {
|
|||
if (special_map == null) {
|
||||
special_map = new Gee.HashMap<Geary.FolderPath, Geary.SpecialFolderType>();
|
||||
|
||||
special_map.set(new Geary.FolderRoot(Imap.Account.INBOX_NAME, null, false),
|
||||
Geary.SpecialFolderType.INBOX);
|
||||
special_map.set(Imap.MailboxSpecifier.inbox.to_folder_path(), Geary.SpecialFolderType.INBOX);
|
||||
special_map.set(new Geary.FolderRoot("Sent", null, false), Geary.SpecialFolderType.SENT);
|
||||
special_map.set(new Geary.FolderRoot("Draft", null, false), Geary.SpecialFolderType.DRAFTS);
|
||||
special_map.set(new Geary.FolderRoot("Bulk Mail", null, false), Geary.SpecialFolderType.SPAM);
|
||||
|
|
|
|||
|
|
@ -18,10 +18,6 @@
|
|||
*/
|
||||
|
||||
private class Geary.Imap.Account : BaseObject {
|
||||
// all references to Inbox are converted to this string, purely for sanity sake when dealing
|
||||
// with Inbox's case issues
|
||||
public const string INBOX_NAME = "INBOX";
|
||||
|
||||
public bool is_open { get; private set; default = false; }
|
||||
|
||||
private string name;
|
||||
|
|
@ -400,12 +396,12 @@ private class Geary.Imap.Account : BaseObject {
|
|||
return null;
|
||||
|
||||
FolderRoot root = path.get_root();
|
||||
if (root.basename.up() != INBOX_NAME)
|
||||
if (root.basename.up() != Imap.MailboxSpecifier.NORMALIZED_INBOX_NAME)
|
||||
return path;
|
||||
|
||||
// create new FolderPath with normalized INBOX at its root
|
||||
FolderPath new_path = new Geary.FolderRoot(INBOX_NAME, root.default_separator,
|
||||
root.case_sensitive);
|
||||
FolderPath new_path = new Geary.FolderRoot(Imap.MailboxSpecifier.NORMALIZED_INBOX_NAME,
|
||||
root.default_separator, root.case_sensitive);
|
||||
|
||||
// copy in children starting at 1 (zero is INBOX)
|
||||
Gee.List<string> basenames = path.as_list();
|
||||
|
|
|
|||
|
|
@ -94,16 +94,20 @@ public class Geary.Imap.FolderProperties : Geary.FolderProperties {
|
|||
}
|
||||
|
||||
/**
|
||||
* Use with FolderProperties of the *same folder* seen at different times (i.e. after SELECTing
|
||||
* versus data stored locally). Only compares fields that suggest the contents of the folder
|
||||
* have changed.
|
||||
* Use with {@link FolderProperties} of the *same folder* seen at different times (i.e. after
|
||||
* SELECTing versus data stored locally). Only compares fields that suggest the contents of
|
||||
* the folder have changed.
|
||||
*
|
||||
* Note that this is *not* concerned with message flags changing.
|
||||
* Note that have_contents_changed does *not* discern if message flags have changed.
|
||||
*/
|
||||
public Trillian have_contents_changed(Geary.Imap.FolderProperties other) {
|
||||
public bool have_contents_changed(Geary.Imap.FolderProperties other, string name) {
|
||||
// UIDNEXT changes indicate messages have been added, but not if they've been removed
|
||||
if (uid_next != null && other.uid_next != null && !uid_next.equal_to(other.uid_next))
|
||||
return Trillian.TRUE;
|
||||
if (uid_next != null && other.uid_next != null && !uid_next.equal_to(other.uid_next)) {
|
||||
debug("%s FolderProperties changed: UIDNEXT=%s other.UIDNEXT=%s", name,
|
||||
uid_next.to_string(), other.uid_next.to_string());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// Gmail includes Chat messages in STATUS results but not in SELECT/EXAMINE
|
||||
// results, so message count comparison has to be from the same origin ... use SELECT/EXAMINE
|
||||
|
|
@ -111,16 +115,27 @@ public class Geary.Imap.FolderProperties : Geary.FolderProperties {
|
|||
//
|
||||
// TODO: If this continues to work, it might be worthwhile to change the result of this
|
||||
// method to boolean
|
||||
if (select_examine_messages >= 0 && other.select_examine_messages >= 0
|
||||
&& select_examine_messages != other.select_examine_messages) {
|
||||
return Trillian.TRUE;
|
||||
if (select_examine_messages >= 0 && other.select_examine_messages >= 0) {
|
||||
int diff = select_examine_messages - other.select_examine_messages;
|
||||
if (diff != 0) {
|
||||
debug("%s FolderProperties changed: SELECT/EXAMINE=%d other.SELECT/EXAMINE=%d diff=%d",
|
||||
name, select_examine_messages, other.select_examine_messages, diff);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (status_messages >= 0 && other.status_messages >= 0 && status_messages != other.status_messages) {
|
||||
return Trillian.TRUE;
|
||||
if (status_messages >= 0 && other.status_messages >= 0) {
|
||||
int diff = status_messages - other.status_messages;
|
||||
if (diff != 0) {
|
||||
debug("%s FolderProperties changed: STATUS=%d other.STATUS=%d diff=%d", name,
|
||||
status_messages, other.status_messages, diff);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return Trillian.FALSE;
|
||||
return false;
|
||||
}
|
||||
|
||||
private void init_flags() {
|
||||
|
|
|
|||
|
|
@ -247,7 +247,7 @@ public class Geary.Imap.MessageSet : BaseObject {
|
|||
}
|
||||
|
||||
public string to_string() {
|
||||
return value;
|
||||
return "%s:%s".printf(is_uid ? "UID" : "pos", value);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@
|
|||
*/
|
||||
|
||||
public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpecifier>, Gee.Comparable<MailboxSpecifier> {
|
||||
// all references to Inbox are converted to this string, purely for sanity sake when dealing
|
||||
// with Inbox's case issues
|
||||
public const string NORMALIZED_INBOX_NAME = "INBOX";
|
||||
|
||||
/**
|
||||
* An instance of an Inbox MailboxSpecifier.
|
||||
*
|
||||
|
|
@ -22,7 +26,7 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
|
|||
private static MailboxSpecifier? _inbox = null;
|
||||
public static MailboxSpecifier inbox {
|
||||
get {
|
||||
return (_inbox != null) ? _inbox : _inbox = new MailboxSpecifier("Inbox");
|
||||
return (_inbox != null) ? _inbox : _inbox = new MailboxSpecifier(NORMALIZED_INBOX_NAME);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -48,6 +52,10 @@ public class Geary.Imap.MailboxSpecifier : BaseObject, Gee.Hashable<MailboxSpeci
|
|||
init(param.decode());
|
||||
}
|
||||
|
||||
public static bool folder_path_is_inbox(FolderPath path) {
|
||||
return path.is_root() && path.basename.up() == NORMALIZED_INBOX_NAME;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a generic {@link FolderPath} into an IMAP mailbox specifier.
|
||||
*
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue