From c4020014dacfc85405b8cd96bf08c22cff5650b9 Mon Sep 17 00:00:00 2001 From: Charles Lindsay Date: Mon, 3 Mar 2014 15:05:41 -0800 Subject: [PATCH] Use STATUS command to keep unread counts accurate We know we've got some accounting problems in our unread counts. This patches over the most egregious errors by fetching the real count from the server more frequently. It also more frequently triggers the full email synchronization process. Closes: bgo #714865 --- .../imap-engine-generic-account.vala | 77 ++++++++++++++++++- .../imap-engine-minimal-folder.vala | 2 +- src/engine/imap/api/imap-account.vala | 28 +++++-- 3 files changed, 99 insertions(+), 8 deletions(-) diff --git a/src/engine/imap-engine/imap-engine-generic-account.vala b/src/engine/imap-engine/imap-engine-generic-account.vala index cd3b24cb..29f7478f 100644 --- a/src/engine/imap-engine/imap-engine-generic-account.vala +++ b/src/engine/imap-engine/imap-engine-generic-account.vala @@ -5,7 +5,8 @@ */ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { - private const int REFRESH_FOLDER_LIST_SEC = 4 * 60; + private const int REFRESH_FOLDER_LIST_SEC = 2 * 60; + private const int REFRESH_UNSEEN_SEC = 1; private static Geary.FolderPath? outbox_path = null; private static Geary.FolderPath? search_path = null; @@ -16,6 +17,9 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { private Gee.HashMap folder_map = new Gee.HashMap< FolderPath, MinimalFolder>(); private Gee.HashMap local_only = new Gee.HashMap(); + private Gee.HashMap refresh_unseen_timeout_ids + = new Gee.HashMap(); + private Gee.HashSet in_refresh_unseen = new Gee.HashSet(); private uint refresh_folder_timeout_id = 0; private bool in_refresh_enumerate = false; private Cancellable refresh_cancellable = new Cancellable(); @@ -68,6 +72,27 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { } } + protected override void notify_email_appended(Geary.Folder folder, Gee.Collection ids) { + base.notify_email_appended(folder, ids); + reschedule_unseen_update(folder); + } + + protected override void notify_email_inserted(Geary.Folder folder, Gee.Collection ids) { + base.notify_email_inserted(folder, ids); + reschedule_unseen_update(folder); + } + + protected override void notify_email_removed(Geary.Folder folder, Gee.Collection ids) { + base.notify_email_removed(folder, ids); + reschedule_unseen_update(folder); + } + + protected override void notify_email_flags_changed(Geary.Folder folder, + Gee.Map flag_map) { + base.notify_email_flags_changed(folder, flag_map); + reschedule_unseen_update(folder); + } + private void check_open() throws EngineError { if (!open) throw new EngineError.OPEN_REQUIRED("Account %s not opened", to_string()); @@ -261,6 +286,54 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { return all_folders; } + private void reschedule_unseen_update(Geary.Folder folder) { + if (!folder_map.has_key(folder.path)) + return; + + if (refresh_unseen_timeout_ids.get(folder.path) != 0) + Source.remove(refresh_unseen_timeout_ids.get(folder.path)); + + refresh_unseen_timeout_ids.set(folder.path, + Timeout.add_seconds(REFRESH_UNSEEN_SEC, () => on_refresh_unseen(folder))); + } + + private bool on_refresh_unseen(Geary.Folder folder) { + // If we're in the process already, reschedule for later. + if (in_refresh_unseen.contains(folder)) + return true; + + refresh_unseen_async.begin(folder, null, on_refresh_unseen_completed); + + refresh_unseen_timeout_ids.unset(folder.path); + return false; + } + + private void on_refresh_unseen_completed(Object? source, AsyncResult result) { + try { + refresh_unseen_async.end(result); + } catch (Error e) { + debug("Error refreshing unseen counts: %s", e.message); + } + } + + private async void refresh_unseen_async(Geary.Folder folder, Cancellable? cancellable) throws Error { + in_refresh_unseen.add(folder); + + debug("Refreshing unseen counts for %s", folder.to_string()); + + bool folder_created; + Imap.Folder remote_folder = yield remote.fetch_folder_async(folder.path, + out folder_created, cancellable); + + if (!folder_created) { + int unseen_count = yield remote.fetch_unseen_count_async(folder.path, cancellable); + remote_folder.properties.set_status_unseen(unseen_count); + yield local.update_folder_status_async(remote_folder, false, cancellable); + } + + in_refresh_unseen.remove(folder); + } + private void reschedule_folder_refresh(bool immediate) { if (in_refresh_enumerate) return; @@ -420,7 +493,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount { continue; Imap.Folder remote_folder = (Imap.Folder) yield remote.fetch_folder_async(folder, - cancellable); + null, cancellable); yield local.clone_folder_async(remote_folder, cancellable); } diff --git a/src/engine/imap-engine/imap-engine-minimal-folder.vala b/src/engine/imap-engine/imap-engine-minimal-folder.vala index e23528e2..45c1aa10 100644 --- a/src/engine/imap-engine/imap-engine-minimal-folder.vala +++ b/src/engine/imap-engine/imap-engine-minimal-folder.vala @@ -520,7 +520,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.AbstractFolder, Geary.Folde try { debug("Fetching information for remote folder %s", to_string()); opening_folder = yield remote.fetch_folder_async(local_folder.get_path(), - cancellable); + null, cancellable); debug("Opening remote folder %s", opening_folder.to_string()); yield opening_folder.open_async(cancellable); diff --git a/src/engine/imap/api/imap-account.vala b/src/engine/imap/api/imap-account.vala index d3a90928..c04d314c 100644 --- a/src/engine/imap/api/imap-account.vala +++ b/src/engine/imap/api/imap-account.vala @@ -164,13 +164,17 @@ private class Geary.Imap.Account : BaseObject { } } - public async Imap.Folder fetch_folder_async(FolderPath path, Cancellable? cancellable) - throws Error { + public async Imap.Folder fetch_folder_async(FolderPath path, out bool created, + Cancellable? cancellable) throws Error { check_open(); + created = false; + if (folders.has_key(path)) return folders.get(path); + created = true; + // if not in map, use list_children_async to add it (if it exists) if (!path_to_mailbox.has_key(path)) { debug("Listing children to find %s", path.to_string()); @@ -183,7 +187,7 @@ private class Geary.Imap.Account : BaseObject { Imap.Folder folder; if (!mailbox_info.attrs.is_no_select) { - StatusData status = yield fetch_status_async(path, cancellable); + StatusData status = yield fetch_status_async(path, StatusDataType.all(), cancellable); folder = new Imap.Folder(session_mgr, status, mailbox_info); } else { @@ -195,13 +199,27 @@ private class Geary.Imap.Account : BaseObject { return folder; } - private async StatusData fetch_status_async(FolderPath path, Cancellable? cancellable) + public async int fetch_unseen_count_async(FolderPath path, Cancellable? cancellable) throws Error { check_open(); + MailboxInformation? mailbox_info = path_to_mailbox.get(path); + if (mailbox_info == null) + throw_not_found(path); + if (mailbox_info.attrs.is_no_select) + throw new EngineError.UNSUPPORTED("Can't fetch unseen count for unselectable folder %s", path); + + StatusData data = yield fetch_status_async(path, { StatusDataType.UNSEEN }, cancellable); + return data.unseen; + } + + private async StatusData fetch_status_async(FolderPath path, StatusDataType[] status_types, + Cancellable? cancellable) throws Error { + check_open(); + Gee.List status_results = new Gee.ArrayList(); StatusResponse response = yield send_command_async( - new StatusCommand(new MailboxSpecifier.from_folder_path(path, null), StatusDataType.all()), + new StatusCommand(new MailboxSpecifier.from_folder_path(path, null), status_types), null, status_results, cancellable); throw_fetch_error(response, path, status_results.size);