[geary/mjog/folder-load-sync-race: 1/6] Geary.ImapEngine.AccountSychronizer: Clean up implementation
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/mjog/folder-load-sync-race: 1/6] Geary.ImapEngine.AccountSychronizer: Clean up implementation
- Date: Mon, 8 Feb 2021 14:07:11 +0000 (UTC)
commit 2ec23bac76cf1619df78cdeb9c22332a57730e4f
Author: Michael Gratton <mike vee net>
Date: Tue Feb 9 00:44:14 2021 +1100
Geary.ImapEngine.AccountSychronizer: Clean up implementation
This does a few things:
* Adds a high-level API for queuing different kinds of sync's instead
of listening to account signals
* Splits out background GC work into a third type of concrete sync
op so it can be executed when offline
* Ensures refresh and full sync ops are only executed when online, so
they don't needlessly jam up the account processor's queue
* Clean up sync op class hierarchy a bit to accommodate new ops
.../imap-engine-account-synchronizer.vala | 309 +++++++++++++--------
1 file changed, 198 insertions(+), 111 deletions(-)
---
diff --git a/src/engine/imap-engine/imap-engine-account-synchronizer.vala
b/src/engine/imap-engine/imap-engine-account-synchronizer.vala
index d1b86112a..5f7e6f50c 100644
--- a/src/engine/imap-engine/imap-engine-account-synchronizer.vala
+++ b/src/engine/imap-engine/imap-engine-account-synchronizer.vala
@@ -1,15 +1,24 @@
/*
- * Copyright 2016 Software Freedom Conservancy Inc.
- * Copyright 2017 Michael Gratton <mike vee net>
+ * Copyright © 2016 Software Freedom Conservancy Inc.
+ * Copyright © 2017-2021 Michael Gratton <mike vee net>
*
* This software is licensed under the GNU Lesser General Public License
- * (version 2.1 or later). See the COPYING file in this distribution.
+ * (version 2.1 or later). See the COPYING file in this distribution.
*/
private class Geary.ImapEngine.AccountSynchronizer :
Geary.BaseObject, Logging.Source {
+ private enum Reason {
+
+ REFRESH_CONTENTS,
+ FULL_SYNC,
+ TRUNCATE_TO_EPOCH;
+
+ }
+
+
private weak GenericAccount account { get; private set; }
private TimeoutManager prefetch_timer;
@@ -25,9 +34,7 @@ private class Geary.ImapEngine.AccountSynchronizer :
);
this.account.information.notify["prefetch-period-days"].connect(on_account_prefetch_changed);
- this.account.old_messages_background_cleanup_request.connect(old_messages_background_cleanup);
- this.account.folders_available_unavailable.connect(on_folders_updated);
- this.account.folders_contents_altered.connect(on_folders_contents_altered);
+ this.account.folders_available_unavailable.connect(on_folders_discovered);
}
/** {@inheritDoc} */
@@ -45,10 +52,34 @@ private class Geary.ImapEngine.AccountSynchronizer :
);
}
+ internal void folders_discovered(Gee.Collection<Folder> available) {
+ if (this.account.imap.current_status == CONNECTED) {
+ send_all(available, FULL_SYNC);
+ }
+ }
+
+ internal void folders_contents_altered(Gee.Collection<Folder> altered) {
+ if (this.account.imap.current_status == CONNECTED) {
+ send_all(altered, REFRESH_CONTENTS);
+ }
+ }
+
+ internal void cleanup_storage() {
+ IdleGarbageCollection op = new IdleGarbageCollection(this.account);
+
+ send_all(this.account.list_folders(), TRUNCATE_TO_EPOCH, op);
+
+ // Add GC operation after message removal during background cleanup
+ try {
+ this.account.queue_operation(op);
+ } catch (Error err) {
+ warning("Failed to queue sync operation: %s", err.message);
+ }
+ }
+
private void send_all(Gee.Collection<Folder> folders,
- bool became_available,
- bool for_storage_clean=false,
- IdleGarbageCollection? post_idle_detach_op=null) {
+ Reason reason,
+ IdleGarbageCollection? post_idle_detach_op = null) {
foreach (Folder folder in folders) {
// Only sync folders that:
@@ -65,23 +96,37 @@ private class Geary.ImapEngine.AccountSynchronizer :
!folder.properties.is_local_only &&
!folder.properties.is_virtual) {
- AccountOperation op;
- if (became_available || for_storage_clean) {
- CheckFolderSync check_op = new CheckFolderSync(
+ AccountOperation? op = null;
+ switch (reason) {
+ case REFRESH_CONTENTS:
+ op = new RefreshFolderSync(
+ this.account,
+ imap_folder,
+ this.max_epoch
+ );
+ break;
+
+ case FULL_SYNC:
+ op = new FullFolderSync(
+ this.account,
+ imap_folder,
+ this.max_epoch
+ );
+ break;
+
+ case TRUNCATE_TO_EPOCH:
+ op = new TruncateToEpochFolderSync(
this.account,
imap_folder,
this.max_epoch,
- for_storage_clean,
post_idle_detach_op
);
- op = check_op;
- } else {
- op = new RefreshFolderSync(this.account, imap_folder);
+ break;
}
try {
this.account.queue_operation(op);
- } catch (Error err) {
+ } catch (GLib.Error err) {
warning("Failed to queue sync operation: %s", err.message);
}
}
@@ -89,27 +134,9 @@ private class Geary.ImapEngine.AccountSynchronizer :
}
private void do_prefetch_changed() {
- // treat as an availability check (i.e. as if the account had
- // just opened) because just because this value has changed
- // doesn't mean the contents in the folders have changed
- if (this.account.is_open()) {
- send_all(this.account.list_folders(), true);
- }
- }
-
- private void old_messages_background_cleanup(GLib.Cancellable? cancellable) {
-
- if (this.account.is_open()) {
- IdleGarbageCollection op = new IdleGarbageCollection(account);
-
- send_all(this.account.list_folders(), false, true, op);
-
- // Add GC operation after message removal during background cleanup
- try {
- this.account.queue_operation(op);
- } catch (Error err) {
- warning("Failed to queue sync operation: %s", err.message);
- }
+ if (this.account.is_open() &&
+ this.account.imap.current_status == CONNECTED) {
+ send_all(this.account.list_folders(), FULL_SYNC);
}
}
@@ -117,39 +144,39 @@ private class Geary.ImapEngine.AccountSynchronizer :
this.prefetch_timer.start();
}
- private void on_folders_updated(Gee.Collection<Folder>? available,
- Gee.Collection<Folder>? unavailable) {
+ private void on_folders_discovered(Gee.Collection<Folder>? available,
+ Gee.Collection<Folder>? unavailable) {
if (available != null) {
- send_all(available, true);
+ folders_discovered(available);
}
}
- private void on_folders_contents_altered(Gee.Collection<Folder> altered) {
- send_all(altered, false);
- }
-
}
+
/**
- * Synchronises a folder after its contents have changed.
- *
- * This synchronisation process simply opens the remote folder, waits
- * for it to finish opening for normalisation and pre-fetching to
- * complete, then closes it again.
+ * Base class for folder synchronisation account operations.
*/
-private class Geary.ImapEngine.RefreshFolderSync : FolderOperation {
+private abstract class Geary.ImapEngine.FolderSync : FolderOperation {
- GLib.Cancellable? closed_cancellable = null;
+ protected GLib.DateTime sync_max_epoch { get; private set; }
+ private Folder.OpenFlags open_flags;
+ private GLib.Cancellable? closed_cancellable = null;
- internal RefreshFolderSync(GenericAccount account,
- MinimalFolder folder) {
+
+ internal FolderSync(GenericAccount account,
+ MinimalFolder folder,
+ GLib.DateTime sync_max_epoch,
+ Folder.OpenFlags open_flags) {
base(account, folder);
+ this.sync_max_epoch = sync_max_epoch;
+ this.open_flags = open_flags;
this.folder.closed.connect(on_folder_close);
}
- ~RefreshFolderSync() {
+ ~FolderSync() {
weak Geary.Folder? folder = this.folder;
if (folder != null) {
this.folder.closed.disconnect(on_folder_close);
@@ -165,10 +192,23 @@ private class Geary.ImapEngine.RefreshFolderSync : FolderOperation {
bool was_opened = false;
MinimalFolder minimal = (MinimalFolder) this.folder;
try {
- yield minimal.open_async(Folder.OpenFlags.NO_DELAY, cancellable);
+ yield minimal.open_async(this.open_flags, cancellable);
was_opened = true;
+
debug("Synchronising");
- yield sync_folder(cancellable);
+ // Determine the earliest date we should be synchronising
+ // back to
+ DateTime actual_max_epoch;
+ if (this.account.information.prefetch_period_days >= 0) {
+ actual_max_epoch = new DateTime.now_local();
+ actual_max_epoch = actual_max_epoch.add_days(
+ 0 - account.information.prefetch_period_days
+ );
+ } else {
+ actual_max_epoch = this.sync_max_epoch;
+ }
+
+ yield sync_folder(actual_max_epoch, cancellable);
} catch (GLib.IOError.CANCELLED err) {
// All good
} catch (EngineError.ALREADY_CLOSED err) {
@@ -219,10 +259,9 @@ private class Geary.ImapEngine.RefreshFolderSync : FolderOperation {
}
}
- protected virtual async void sync_folder(GLib.Cancellable cancellable)
- throws GLib.Error {
- yield this.folder.synchronise_remote(cancellable);
- }
+ protected abstract async void sync_folder(GLib.DateTime max_epoch,
+ GLib.Cancellable cancellable)
+ throws GLib.Error;
private void on_folder_close() {
if (this.closed_cancellable != null) {
@@ -232,69 +271,73 @@ private class Geary.ImapEngine.RefreshFolderSync : FolderOperation {
}
+
/**
- * Synchronises a folder after first checking if it needs to be sync'ed.
+ * Refreshes a folder's contents after they have changed.
+ *
+ * This synchronisation process simply opens the remote folder, waits
+ * for it to finish opening for normalisation and pre-fetching to
+ * complete, then closes it again.
+ */
+private class Geary.ImapEngine.RefreshFolderSync : FolderSync {
+
+
+ internal RefreshFolderSync(GenericAccount account,
+ MinimalFolder folder,
+ GLib.DateTime sync_max_epoch) {
+ base(account, folder, sync_max_epoch, NO_DELAY);
+ }
+
+ protected override async void sync_folder(GLib.DateTime max_epoch,
+ GLib.Cancellable cancellable)
+ throws GLib.Error {
+ yield this.folder.synchronise_remote(cancellable);
+ }
+
+}
+
+/**
+ * Refreshes folder contents and ensures its vector extends to the epoch.
*
* This synchronisation process performs the same work as its base
* class, but also ensures enough mail has been fetched to satisfy the
* account's prefetch period, by checking the earliest mail in the
* folder and if later than the maximum prefetch epoch, expands the
* folder's vector until it does.
+ *
+ * It also truncates email that extends past the epoch, ensuring no
+ * more email is stored than is desired.
*/
-private class Geary.ImapEngine.CheckFolderSync : RefreshFolderSync {
+private class Geary.ImapEngine.FullFolderSync : RefreshFolderSync {
- private DateTime sync_max_epoch;
- private bool for_storage_clean;
- private IdleGarbageCollection? post_idle_detach_op;
- internal CheckFolderSync(GenericAccount account,
- MinimalFolder folder,
- DateTime sync_max_epoch,
- bool for_storage_clean,
- IdleGarbageCollection? post_idle_detach_op) {
- base(account, folder);
- this.sync_max_epoch = sync_max_epoch;
- this.for_storage_clean = for_storage_clean;
- this.post_idle_detach_op = post_idle_detach_op;
+ internal FullFolderSync(GenericAccount account,
+ MinimalFolder folder,
+ DateTime sync_max_epoch) {
+ base(account, folder, sync_max_epoch);
}
- protected override async void sync_folder(Cancellable cancellable)
- throws Error {
- // Determine the earliest date we should be synchronising back to
- DateTime prefetch_max_epoch;
- if (this.account.information.prefetch_period_days >= 0) {
- prefetch_max_epoch = new DateTime.now_local();
- prefetch_max_epoch = prefetch_max_epoch.add_days(
- 0 - account.information.prefetch_period_days
- );
- } else {
- prefetch_max_epoch = this.sync_max_epoch;
- }
-
+ protected override async void sync_folder(GLib.DateTime max_epoch,
+ GLib.Cancellable cancellable)
+ throws GLib.Error {
ImapDB.Folder local_folder = ((MinimalFolder) this.folder).local_folder;
// Detach older emails outside the prefetch window
if (this.account.information.prefetch_period_days >= 0) {
Gee.Collection<Geary.EmailIdentifier>? detached_ids =
- yield local_folder.detach_emails_before_timestamp(prefetch_max_epoch,
+ yield local_folder.detach_emails_before_timestamp(max_epoch,
cancellable);
if (detached_ids != null) {
+ this.account.email_locally_removed(this.folder, detached_ids);
this.folder.email_locally_removed(detached_ids);
- if (post_idle_detach_op != null) {
- post_idle_detach_op.messages_detached();
- }
- if (!for_storage_clean) {
- GenericAccount imap_account = (GenericAccount) account;
- ForegroundGarbageCollection op =
- new ForegroundGarbageCollection(imap_account);
- try {
- imap_account.queue_operation(op);
- } catch (Error err) {
- warning("Failed to queue sync operation: %s", err.message);
- }
- }
+ // Ensure a foreground GC is queued so any email now
+ // folderless will be reaped
+ var imap_account = (GenericAccount) this.account;
+ imap_account.queue_operation(
+ new ForegroundGarbageCollection(imap_account)
+ );
}
}
@@ -320,45 +363,45 @@ private class Geary.ImapEngine.CheckFolderSync : RefreshFolderSync {
}
DateTime? next_epoch = oldest_date;
- while (next_epoch.compare(prefetch_max_epoch) > 0) {
+ while (next_epoch.compare(max_epoch) > 0) {
int local_count = yield local_folder.get_email_count_async(
ImapDB.Folder.ListFlags.NONE, cancellable
);
next_epoch = next_epoch.add_months(-3);
- if (next_epoch.compare(prefetch_max_epoch) < 0) {
- next_epoch = prefetch_max_epoch;
+ if (next_epoch.compare(max_epoch) < 0) {
+ next_epoch = max_epoch;
}
debug("Fetching to: %s", next_epoch.to_string());
if (local_count < this.folder.properties.email_total &&
- next_epoch.compare(prefetch_max_epoch) >= 0) {
+ next_epoch.compare(max_epoch) >= 0) {
if (next_epoch.compare(this.sync_max_epoch) > 0) {
current_oldest = yield expand_vector(
next_epoch, current_oldest, cancellable
);
if (current_oldest == null &&
- next_epoch.equal(prefetch_max_epoch)) {
+ next_epoch.equal(max_epoch)) {
yield expand_to_previous(
current_oldest, cancellable
);
// Exit next time around
- next_epoch = prefetch_max_epoch.add_days(-1);
+ next_epoch = max_epoch.add_days(-1);
}
} else {
yield expand_complete_vector(cancellable);
// Exit next time around
- next_epoch = prefetch_max_epoch.add_days(-1);
+ next_epoch = max_epoch.add_days(-1);
}
} else {
// Exit next time around
- next_epoch = prefetch_max_epoch.add_days(-1);
+ next_epoch = max_epoch.add_days(-1);
}
// Wait for basic syncing (i.e. the prefetcher) to
// complete as well.
- yield base.sync_folder(cancellable);
+ yield base.sync_folder(max_epoch, cancellable);
}
}
@@ -426,6 +469,49 @@ private class Geary.ImapEngine.CheckFolderSync : RefreshFolderSync {
}
+
+/**
+ * Removes any email locally from folder that extends past the epoch.
+ *
+ * This synchronisation operation ensures that the folder's vector
+ * does not extend pass the epoch, ensuring no more email is stored
+ * than is desired.
+ */
+private class Geary.ImapEngine.TruncateToEpochFolderSync : FolderSync {
+
+
+ private IdleGarbageCollection? post_idle_detach_op;
+
+
+ internal TruncateToEpochFolderSync(GenericAccount account,
+ MinimalFolder folder,
+ DateTime sync_max_epoch,
+ IdleGarbageCollection? post_idle_detach_op) {
+ base(account, folder, sync_max_epoch, NONE);
+ this.post_idle_detach_op = post_idle_detach_op;
+ }
+
+ protected override async void sync_folder(GLib.DateTime max_epoch,
+ GLib.Cancellable cancellable)
+ throws GLib.Error {
+ ImapDB.Folder local_folder = ((MinimalFolder) this.folder).local_folder;
+
+ // Detach older emails outside the prefetch window
+ if (this.account.information.prefetch_period_days >= 0) {
+ Gee.Collection<Geary.EmailIdentifier>? detached_ids =
+ yield local_folder.detach_emails_before_timestamp(max_epoch,
+ cancellable);
+ if (detached_ids != null) {
+ this.account.email_locally_removed(this.folder, detached_ids);
+ this.folder.email_locally_removed(detached_ids);
+ this.post_idle_detach_op.messages_detached();
+ }
+ }
+ }
+
+}
+
+
/**
* Kicks off garbage collection after old messages have been removed.
*
@@ -494,4 +580,5 @@ private class Geary.ImapEngine.IdleGarbageCollection: AccountOperation {
// Reap is forced if messages were detached
this.options |= FORCE_REAP;
}
+
}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]