Merge branch 'wip/789924-network-transition'. Fixes Bug 789924.
This commit is contained in:
commit
fddf609a97
14 changed files with 1097 additions and 1022 deletions
|
|
@ -889,15 +889,15 @@ public class GearyController : Geary.BaseObject {
|
|||
// they've hit cancel, so there's not much for us to do here.
|
||||
close_account(account);
|
||||
break;
|
||||
|
||||
case Geary.Account.Problem.EMAIL_DELIVERY_FAILURE:
|
||||
|
||||
case Geary.Account.Problem.SEND_EMAIL_DELIVERY_FAILURE:
|
||||
handle_outbox_failure(StatusBar.Message.OUTBOX_SEND_FAILURE);
|
||||
break;
|
||||
|
||||
case Geary.Account.Problem.SAVE_SENT_MAIL_FAILED:
|
||||
|
||||
case Geary.Account.Problem.SEND_EMAIL_SAVE_FAILED:
|
||||
handle_outbox_failure(StatusBar.Message.OUTBOX_SAVE_SENT_MAIL_FAILED);
|
||||
break;
|
||||
|
||||
|
||||
case Geary.Account.Problem.CONNECTION_FAILURE:
|
||||
ErrorDialog dialog = new ErrorDialog(main_window,
|
||||
_("Error connecting to the server"),
|
||||
|
|
|
|||
|
|
@ -22,16 +22,17 @@
|
|||
|
||||
public abstract class Geary.Account : BaseObject {
|
||||
public enum Problem {
|
||||
RECV_EMAIL_LOGIN_FAILED,
|
||||
SEND_EMAIL_LOGIN_FAILED,
|
||||
CONNECTION_FAILURE,
|
||||
DATABASE_FAILURE,
|
||||
HOST_UNREACHABLE,
|
||||
NETWORK_UNAVAILABLE,
|
||||
DATABASE_FAILURE,
|
||||
EMAIL_DELIVERY_FAILURE,
|
||||
SAVE_SENT_MAIL_FAILED,
|
||||
CONNECTION_FAILURE,
|
||||
RECV_EMAIL_LOGIN_FAILED,
|
||||
SEND_EMAIL_DELIVERY_FAILURE,
|
||||
SEND_EMAIL_ERROR,
|
||||
SEND_EMAIL_LOGIN_FAILED,
|
||||
SEND_EMAIL_SAVE_FAILED,
|
||||
}
|
||||
|
||||
|
||||
public Geary.AccountInformation information { get; protected set; }
|
||||
|
||||
public Geary.ProgressMonitor search_upgrade_monitor { get; protected set; }
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@ public class Geary.Endpoint : BaseObject {
|
|||
}
|
||||
|
||||
public NetworkAddress remote_address { get; private set; }
|
||||
public ConnectivityManager connectivity { get; private set; }
|
||||
public Flags flags { get; private set; }
|
||||
public uint timeout_sec { get; private set; }
|
||||
public TlsCertificateFlags tls_validation_flags { get; set; default = TlsCertificateFlags.VALIDATE_ALL; }
|
||||
|
|
@ -120,13 +121,15 @@ public class Geary.Endpoint : BaseObject {
|
|||
* @see tls_validation_warnings
|
||||
*/
|
||||
public signal void untrusted_host(SecurityType security, TlsConnection cx);
|
||||
|
||||
|
||||
|
||||
public Endpoint(string host_specifier, uint16 default_port, Flags flags, uint timeout_sec) {
|
||||
this.remote_address = new NetworkAddress(host_specifier, default_port);
|
||||
this.flags = flags;
|
||||
this.timeout_sec = timeout_sec;
|
||||
this.connectivity = new ConnectivityManager(this);
|
||||
}
|
||||
|
||||
|
||||
private SocketClient get_socket_client() {
|
||||
if (socket_client != null)
|
||||
return socket_client;
|
||||
|
|
|
|||
|
|
@ -130,7 +130,6 @@ public class Geary.Engine : BaseObject {
|
|||
AccountInformation.init();
|
||||
Logging.init();
|
||||
RFC822.init();
|
||||
ImapEngine.init();
|
||||
Imap.init();
|
||||
HTML.init();
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -8,74 +8,60 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
private const int FETCH_DATE_RECEIVED_CHUNK_COUNT = 25;
|
||||
private const int SYNC_DELAY_SEC = 10;
|
||||
private const int RETRY_SYNC_DELAY_SEC = 60;
|
||||
|
||||
public GenericAccount account { get; private set; }
|
||||
|
||||
|
||||
private weak GenericAccount account { get; private set; }
|
||||
private weak Imap.Account remote { get; private set; }
|
||||
|
||||
private Nonblocking.Mailbox<MinimalFolder> bg_queue = new Nonblocking.Mailbox<MinimalFolder>(bg_queue_comparator);
|
||||
private Gee.HashSet<MinimalFolder> made_available = new Gee.HashSet<MinimalFolder>();
|
||||
private Gee.HashSet<FolderPath> unavailable_paths = new Gee.HashSet<FolderPath>();
|
||||
private MinimalFolder? current_folder = null;
|
||||
private Cancellable? bg_cancellable = null;
|
||||
private Nonblocking.Semaphore stopped = new Nonblocking.Semaphore();
|
||||
private Gee.HashSet<FolderPath> unavailable_paths = new Gee.HashSet<FolderPath>();
|
||||
private DateTime max_epoch = new DateTime(new TimeZone.local(), 2000, 1, 1, 0, 0, 0.0);
|
||||
|
||||
public AccountSynchronizer(GenericAccount account) {
|
||||
|
||||
|
||||
public AccountSynchronizer(GenericAccount account, Imap.Account remote) {
|
||||
this.account = account;
|
||||
|
||||
this.remote = remote;
|
||||
|
||||
// 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);
|
||||
account.folders_contents_altered.connect(on_folders_contents_altered);
|
||||
account.email_sent.connect(on_email_sent);
|
||||
this.bg_queue.allow_duplicates = false;
|
||||
this.bg_queue.requeue_duplicate = false;
|
||||
|
||||
this.account.information.notify["prefetch-period-days"].connect(on_account_prefetch_changed);
|
||||
this.account.closed.connect(on_account_closed);
|
||||
this.account.folders_available_unavailable.connect(on_folders_available_unavailable);
|
||||
this.account.folders_contents_altered.connect(on_folders_contents_altered);
|
||||
this.account.email_sent.connect(on_email_sent);
|
||||
this.remote.ready.connect(on_account_ready);
|
||||
}
|
||||
|
||||
|
||||
~AccountSynchronizer() {
|
||||
account.opened.disconnect(on_account_opened);
|
||||
account.closed.disconnect(on_account_closed);
|
||||
account.folders_available_unavailable.disconnect(on_folders_available_unavailable);
|
||||
account.folders_contents_altered.disconnect(on_folders_contents_altered);
|
||||
account.email_sent.disconnect(on_email_sent);
|
||||
this.account.information.notify["prefetch-period-days"].connect(on_account_prefetch_changed);
|
||||
this.account.closed.disconnect(on_account_closed);
|
||||
this.account.folders_available_unavailable.disconnect(on_folders_available_unavailable);
|
||||
this.account.folders_contents_altered.disconnect(on_folders_contents_altered);
|
||||
this.account.email_sent.disconnect(on_email_sent);
|
||||
this.remote.ready.disconnect(on_account_ready);
|
||||
}
|
||||
|
||||
public async void stop_async() {
|
||||
bg_cancellable.cancel();
|
||||
|
||||
try {
|
||||
yield stopped.wait_async();
|
||||
} catch (Error err) {
|
||||
debug("Error waiting for AccountSynchronizer background task for %s to complete: %s",
|
||||
account.to_string(), err.message);
|
||||
|
||||
public void stop() {
|
||||
Cancellable? cancellable = this.bg_cancellable;
|
||||
if (cancellable != null) {
|
||||
cancellable.cancel();
|
||||
|
||||
this.bg_queue.clear();
|
||||
this.made_available.clear();
|
||||
this.unavailable_paths.clear();
|
||||
this.current_folder = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void on_account_opened() {
|
||||
if (stopped.is_passed())
|
||||
return;
|
||||
|
||||
account.information.notify["prefetch-period-days"].connect(on_account_prefetch_changed);
|
||||
|
||||
bg_queue.allow_duplicates = false;
|
||||
bg_queue.requeue_duplicate = false;
|
||||
bg_cancellable = new Cancellable();
|
||||
unavailable_paths.clear();
|
||||
|
||||
// immediately start processing folders as they are announced as available
|
||||
process_queue_async.begin();
|
||||
}
|
||||
|
||||
|
||||
private void on_account_closed() {
|
||||
account.information.notify["prefetch-period-days"].disconnect(on_account_prefetch_changed);
|
||||
|
||||
bg_cancellable.cancel();
|
||||
bg_queue.clear();
|
||||
unavailable_paths.clear();
|
||||
stop();
|
||||
}
|
||||
|
||||
|
||||
private void on_account_prefetch_changed() {
|
||||
try {
|
||||
// treat as an availability check (i.e. as if the account had just opened) because
|
||||
|
|
@ -89,10 +75,7 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
}
|
||||
|
||||
private void on_folders_available_unavailable(Gee.Collection<Folder>? available,
|
||||
Gee.Collection<Folder>? unavailable) {
|
||||
if (stopped.is_passed())
|
||||
return;
|
||||
|
||||
Gee.Collection<Folder>? unavailable) {
|
||||
if (available != null) {
|
||||
foreach (Folder folder in available)
|
||||
unavailable_paths.remove(folder.path);
|
||||
|
|
@ -237,20 +220,23 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
}
|
||||
|
||||
private async void process_queue_async() {
|
||||
for (;;) {
|
||||
if (this.bg_cancellable != null) {
|
||||
return;
|
||||
}
|
||||
Cancellable cancellable = this.bg_cancellable = new Cancellable();
|
||||
|
||||
debug("%s: Starting background sync", this.account.to_string());
|
||||
|
||||
while (!cancellable.is_cancelled()) {
|
||||
MinimalFolder folder;
|
||||
try {
|
||||
folder = yield bg_queue.recv_async(bg_cancellable);
|
||||
} catch (Error err) {
|
||||
if (!(err is IOError.CANCELLED))
|
||||
debug("Failed to receive next folder for background sync: %s", err.message);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
// mark as current folder to prevent requeues while processing
|
||||
current_folder = folder;
|
||||
|
||||
|
||||
// generate the current epoch for synchronization (could cache this value, obviously, but
|
||||
// doesn't seem like this biggest win in this class)
|
||||
DateTime epoch;
|
||||
|
|
@ -260,47 +246,60 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
} else {
|
||||
epoch = max_epoch;
|
||||
}
|
||||
|
||||
bool ok = yield process_folder_async(folder, made_available.remove(folder), epoch);
|
||||
|
||||
// clear current folder in every event
|
||||
current_folder = null;
|
||||
|
||||
if (!ok)
|
||||
|
||||
bool availability_check = false;
|
||||
try {
|
||||
// mark as current folder to prevent requeues while processing
|
||||
this.current_folder = folder;
|
||||
availability_check = this.made_available.remove(folder);
|
||||
yield process_folder_async(folder, availability_check, epoch, cancellable);
|
||||
} catch (Error err) {
|
||||
// retry the folder later
|
||||
delayed_send_all(
|
||||
iterate<Folder>(folder).to_array_list(),
|
||||
availability_check,
|
||||
RETRY_SYNC_DELAY_SEC
|
||||
);
|
||||
if (!(err is IOError.CANCELLED)) {
|
||||
debug("%s: Error synchronising %s: %s",
|
||||
this.account.to_string(), folder.to_string(), err.message);
|
||||
}
|
||||
break;
|
||||
} finally {
|
||||
this.current_folder = null;
|
||||
}
|
||||
}
|
||||
|
||||
// clear queue of any remaining folders so references aren't held
|
||||
bg_queue.clear();
|
||||
|
||||
// same with made_available table
|
||||
made_available.clear();
|
||||
|
||||
// flag as stopped for any waiting tasks
|
||||
stopped.blind_notify();
|
||||
|
||||
this.bg_cancellable = null;
|
||||
}
|
||||
|
||||
|
||||
// Returns false if IOError.CANCELLED received
|
||||
private async bool process_folder_async(MinimalFolder folder, bool availability_check, DateTime epoch) {
|
||||
private async void process_folder_async(MinimalFolder folder,
|
||||
bool availability_check,
|
||||
DateTime epoch,
|
||||
Cancellable cancellable)
|
||||
throws Error {
|
||||
// get oldest local email and its time, as well as number of messages in local store
|
||||
DateTime? oldest_local = null;
|
||||
Geary.EmailIdentifier? oldest_local_id = null;
|
||||
int local_count = 0;
|
||||
try {
|
||||
Gee.List<Geary.Email>? list = yield folder.local_folder.list_email_by_id_async(null, 1,
|
||||
Email.Field.PROPERTIES, ImapDB.Folder.ListFlags.NONE | ImapDB.Folder.ListFlags.OLDEST_TO_NEWEST,
|
||||
bg_cancellable);
|
||||
if (list != null && list.size > 0) {
|
||||
oldest_local = list[0].properties.date_received;
|
||||
oldest_local_id = list[0].id;
|
||||
}
|
||||
|
||||
local_count = yield folder.local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE,
|
||||
bg_cancellable);
|
||||
} catch (Error err) {
|
||||
debug("Unable to fetch oldest local email for %s: %s", folder.to_string(), err.message);
|
||||
Gee.List<Geary.Email>? list = yield folder.local_folder.list_email_by_id_async(
|
||||
null,
|
||||
1,
|
||||
Email.Field.PROPERTIES,
|
||||
ImapDB.Folder.ListFlags.NONE | ImapDB.Folder.ListFlags.OLDEST_TO_NEWEST,
|
||||
cancellable
|
||||
);
|
||||
if (list != null && list.size > 0) {
|
||||
oldest_local = list[0].properties.date_received;
|
||||
oldest_local_id = list[0].id;
|
||||
}
|
||||
|
||||
local_count = yield folder.local_folder.get_email_count_async(
|
||||
ImapDB.Folder.ListFlags.NONE,
|
||||
cancellable
|
||||
);
|
||||
|
||||
bool do_sync = true;
|
||||
if (availability_check) {
|
||||
// Compare the oldest mail in the local store and see if it is before the epoch; if so, no
|
||||
// need to synchronize simply because this Folder is available; wait for its contents to
|
||||
|
|
@ -308,10 +307,10 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
if (oldest_local != null) {
|
||||
if (oldest_local.compare(epoch) < 0) {
|
||||
// Oldest local email before epoch, don't sync from network
|
||||
return true;
|
||||
do_sync = false;
|
||||
} else if (folder.properties.email_total == local_count) {
|
||||
// Local earliest email is after epoch, but there's nothing before it
|
||||
return true;
|
||||
do_sync = false;
|
||||
} else {
|
||||
debug("Oldest local email in %s not old enough (%s vs. %s), email_total=%d vs. local_count=%d, synchronizing...",
|
||||
folder.to_string(), oldest_local.to_string(), epoch.to_string(),
|
||||
|
|
@ -320,62 +319,45 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
} else if (folder.properties.email_total == 0) {
|
||||
// no local messages, no remote messages -- this is as good as having everything up
|
||||
// to the epoch
|
||||
return true;
|
||||
do_sync = false;
|
||||
} else {
|
||||
debug("No oldest message found for %s, synchronizing...", folder.to_string());
|
||||
}
|
||||
} else {
|
||||
debug("Folder %s changed, synchronizing...", folder.to_string());
|
||||
}
|
||||
|
||||
try {
|
||||
yield folder.open_async(Folder.OpenFlags.FAST_OPEN, bg_cancellable);
|
||||
} catch (Error err) {
|
||||
// don't need to close folder; if either calls throws an error, the folder is not open
|
||||
if (err is IOError.CANCELLED)
|
||||
return false;
|
||||
|
||||
debug("Unable to open %s: %s", folder.to_string(), err.message);
|
||||
|
||||
// retry later
|
||||
delayed_send_all(iterate<Folder>(folder).to_array_list(), availability_check, RETRY_SYNC_DELAY_SEC);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool not_cancelled = true;
|
||||
try {
|
||||
yield sync_folder_async(folder, epoch, oldest_local, oldest_local_id);
|
||||
} catch (Error err) {
|
||||
if (err is IOError.CANCELLED) {
|
||||
not_cancelled = false;
|
||||
} else {
|
||||
debug("Error background syncing folder %s: %s", folder.to_string(), err.message);
|
||||
|
||||
// retry later
|
||||
delayed_send_all(iterate<Folder>(folder).to_array_list(), availability_check, RETRY_SYNC_DELAY_SEC);
|
||||
|
||||
if (do_sync) {
|
||||
bool opened = false;
|
||||
try {
|
||||
yield folder.open_async(Folder.OpenFlags.FAST_OPEN, cancellable);
|
||||
opened = true;
|
||||
yield sync_folder_async(folder, epoch, oldest_local, oldest_local_id, cancellable);
|
||||
} finally {
|
||||
if (opened) {
|
||||
try {
|
||||
// don't pass Cancellable; really need this to complete in all cases
|
||||
yield folder.close_async();
|
||||
} catch (Error err) {
|
||||
debug("%s: Error closing folder %s: %s",
|
||||
this.account.to_string(), folder.to_string(), err.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// fallthrough and close
|
||||
}
|
||||
|
||||
try {
|
||||
// don't pass Cancellable; really need this to complete in all cases
|
||||
yield folder.close_async();
|
||||
} catch (Error err) {
|
||||
debug("Error closing %s: %s", folder.to_string(), err.message);
|
||||
}
|
||||
|
||||
return not_cancelled;
|
||||
}
|
||||
|
||||
private async void sync_folder_async(MinimalFolder folder, DateTime epoch, DateTime? oldest_local,
|
||||
Geary.EmailIdentifier? oldest_local_id) throws Error {
|
||||
|
||||
private async void sync_folder_async(MinimalFolder folder,
|
||||
DateTime epoch,
|
||||
DateTime? oldest_local,
|
||||
Geary.EmailIdentifier? oldest_local_id,
|
||||
Cancellable cancellable)
|
||||
throws Error {
|
||||
debug("Background sync'ing %s", folder.to_string());
|
||||
|
||||
// wait for the folder to be fully opened to be sure we have all the most current
|
||||
// information
|
||||
yield folder.wait_for_open_async(bg_cancellable);
|
||||
yield folder.wait_for_open_async(cancellable);
|
||||
|
||||
// only perform vector expansion if oldest isn't old enough
|
||||
if (oldest_local == null || oldest_local.compare(epoch) > 0) {
|
||||
|
|
@ -386,7 +368,7 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
// look for complete synchronization of UIDs (i.e. complete vector normalization)
|
||||
// no need to keep searching once this happens
|
||||
int local_count = yield folder.local_folder.get_email_count_async(ImapDB.Folder.ListFlags.NONE,
|
||||
bg_cancellable);
|
||||
cancellable);
|
||||
if (local_count >= folder.properties.email_total) {
|
||||
debug("Total vector normalization for %s: %d/%d emails", folder.to_string(), local_count,
|
||||
folder.properties.email_total);
|
||||
|
|
@ -402,7 +384,7 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
max_epoch.to_string(), folder.to_string(), local_count, folder.properties.email_total);
|
||||
|
||||
yield folder.list_email_by_id_async(null, 1, Geary.Email.Field.NONE,
|
||||
Geary.Folder.ListFlags.OLDEST_TO_NEWEST, bg_cancellable);
|
||||
Geary.Folder.ListFlags.OLDEST_TO_NEWEST, cancellable);
|
||||
} else {
|
||||
// don't go past proscribed epoch
|
||||
if (current_epoch.compare(epoch) < 0)
|
||||
|
|
@ -411,7 +393,7 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
debug("Background sync'ing %s to %s (already got %d of %d emails)",
|
||||
folder.to_string(), current_epoch.to_string(), local_count, folder.properties.email_total);
|
||||
Geary.EmailIdentifier? earliest_span_id = yield folder.find_earliest_email_async(current_epoch,
|
||||
oldest_local_id, bg_cancellable);
|
||||
oldest_local_id, cancellable);
|
||||
if (earliest_span_id == null && current_epoch.compare(epoch) <= 0) {
|
||||
debug("Unable to locate epoch messages on remote folder %s%s, fetching one past oldest...",
|
||||
folder.to_string(),
|
||||
|
|
@ -424,7 +406,7 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
// is null, as that means the local folder is empty and so we should at least
|
||||
// pull the first one to get a marker of age
|
||||
yield folder.list_email_by_id_async(oldest_local_id, 1, Geary.Email.Field.NONE,
|
||||
Geary.Folder.ListFlags.NONE, bg_cancellable);
|
||||
Geary.Folder.ListFlags.NONE, cancellable);
|
||||
} else if (earliest_span_id != null) {
|
||||
// use earliest email from that span for the next round
|
||||
oldest_local_id = earliest_span_id;
|
||||
|
|
@ -441,7 +423,7 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
// always give email prefetcher time to finish its work
|
||||
debug("Waiting for email prefetcher to complete %s...", folder.to_string());
|
||||
try {
|
||||
yield folder.email_prefetcher.active_sem.wait_async(bg_cancellable);
|
||||
yield folder.email_prefetcher.active_sem.wait_async(cancellable);
|
||||
} catch (Error err) {
|
||||
debug("Error waiting for email prefetcher to complete %s: %s", folder.to_string(),
|
||||
err.message);
|
||||
|
|
@ -449,5 +431,9 @@ private class Geary.ImapEngine.AccountSynchronizer : Geary.BaseObject {
|
|||
|
||||
debug("Done background sync'ing %s", folder.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
private void on_account_ready() {
|
||||
this.process_queue_async.begin();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -28,25 +28,33 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
|
|||
private Gee.HashMap<FolderPath, uint> refresh_unseen_timeout_ids
|
||||
= new Gee.HashMap<FolderPath, uint>();
|
||||
private Gee.HashSet<Geary.Folder> in_refresh_unseen = new Gee.HashSet<Geary.Folder>();
|
||||
private uint refresh_folder_timeout_id = 0;
|
||||
private bool in_refresh_enumerate = false;
|
||||
private Cancellable refresh_cancellable = new Cancellable();
|
||||
private AccountSynchronizer sync;
|
||||
private bool awaiting_credentials = false;
|
||||
private Cancellable? enumerate_folder_cancellable = null;
|
||||
private TimeoutManager refresh_folder_timer;
|
||||
|
||||
private Gee.Map<Geary.SpecialFolderType, Gee.List<string>> special_search_names =
|
||||
new Gee.HashMap<Geary.SpecialFolderType, Gee.List<string>>();
|
||||
|
||||
|
||||
public GenericAccount(string name, Geary.AccountInformation information,
|
||||
Imap.Account remote, ImapDB.Account local) {
|
||||
base (name, information);
|
||||
|
||||
|
||||
this.remote = remote;
|
||||
this.remote.ready.connect(on_remote_ready);
|
||||
|
||||
this.local = local;
|
||||
|
||||
this.remote.login_failed.connect(on_login_failed);
|
||||
this.local.email_sent.connect(on_email_sent);
|
||||
this.local.contacts_loaded.connect(() => { contacts_loaded(); });
|
||||
|
||||
this.local.email_sent.connect(on_email_sent);
|
||||
|
||||
this.refresh_folder_timer = new TimeoutManager.seconds(
|
||||
REFRESH_FOLDER_LIST_SEC,
|
||||
() => { this.enumerate_folders_async.begin(); }
|
||||
);
|
||||
|
||||
search_upgrade_monitor = local.search_index_monitor;
|
||||
db_upgrade_monitor = local.upgrade_monitor;
|
||||
db_vacuum_monitor = local.vacuum_monitor;
|
||||
|
|
@ -61,6 +69,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
|
|||
search_path = new ImapDB.SearchFolderRoot();
|
||||
}
|
||||
|
||||
this.sync = new AccountSynchronizer(this, this.remote);
|
||||
|
||||
compile_special_search_names();
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +169,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
|
|||
// IMAP password before attempting a connection. This might have to be
|
||||
// reworked when we allow passwordless logins.
|
||||
if (!information.imap_credentials.is_complete())
|
||||
yield information.fetch_passwords_async(ServiceFlag.IMAP);
|
||||
|
||||
yield information.get_passwords_async(ServiceFlag.IMAP);
|
||||
|
||||
// need to back out local.open_async() if remote fails
|
||||
try {
|
||||
yield remote.open_async(cancellable);
|
||||
|
|
@ -174,22 +184,27 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
|
|||
|
||||
throw err;
|
||||
}
|
||||
|
||||
open = true;
|
||||
|
||||
notify_opened();
|
||||
|
||||
this.open = true;
|
||||
|
||||
notify_opened();
|
||||
notify_folders_available_unavailable(sort_by_path(local_only.values), null);
|
||||
|
||||
// schedule an immediate sweep of the folders; once this is finished, folders will be
|
||||
// regularly enumerated
|
||||
reschedule_folder_refresh(true);
|
||||
|
||||
this.enumerate_folders_async.begin();
|
||||
}
|
||||
|
||||
|
||||
public override async void close_async(Cancellable? cancellable = null) throws Error {
|
||||
if (!open)
|
||||
return;
|
||||
|
||||
this.sync.stop();
|
||||
|
||||
Cancellable folder_cancellable = this.enumerate_folder_cancellable;
|
||||
if (folder_cancellable != null) {
|
||||
folder_cancellable.cancel();
|
||||
}
|
||||
this.refresh_folder_timer.reset();
|
||||
|
||||
notify_folders_available_unavailable(null, sort_by_path(local_only.values));
|
||||
notify_folders_available_unavailable(null, sort_by_path(folder_map.values));
|
||||
|
||||
|
|
@ -368,72 +383,60 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
|
|||
}
|
||||
}
|
||||
|
||||
private void reschedule_folder_refresh(bool immediate) {
|
||||
if (in_refresh_enumerate)
|
||||
return;
|
||||
|
||||
cancel_folder_refresh();
|
||||
|
||||
refresh_folder_timeout_id = immediate
|
||||
? Idle.add(on_refresh_folders)
|
||||
: Timeout.add_seconds(REFRESH_FOLDER_LIST_SEC, on_refresh_folders);
|
||||
}
|
||||
|
||||
private void cancel_folder_refresh() {
|
||||
if (refresh_folder_timeout_id != 0) {
|
||||
Source.remove(refresh_folder_timeout_id);
|
||||
refresh_folder_timeout_id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private bool on_refresh_folders() {
|
||||
in_refresh_enumerate = true;
|
||||
enumerate_folders_async.begin(refresh_cancellable, on_refresh_completed);
|
||||
|
||||
refresh_folder_timeout_id = 0;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void on_refresh_completed(Object? source, AsyncResult result) {
|
||||
try {
|
||||
enumerate_folders_async.end(result);
|
||||
} catch (Error err) {
|
||||
if (!(err is IOError.CANCELLED))
|
||||
debug("Refresh of account %s folders did not complete: %s", to_string(), err.message);
|
||||
}
|
||||
|
||||
in_refresh_enumerate = false;
|
||||
reschedule_folder_refresh(false);
|
||||
}
|
||||
|
||||
private async void enumerate_folders_async(Cancellable? cancellable) throws Error {
|
||||
private async void enumerate_folders_async()
|
||||
throws Error {
|
||||
check_open();
|
||||
|
||||
// enumerate local folders first
|
||||
Gee.HashMap<FolderPath, ImapDB.Folder> local_folders = yield enumerate_local_folders_async(
|
||||
null, cancellable);
|
||||
|
||||
// convert to a list of Geary.Folder ... build_folder() also reports new folders, so this
|
||||
// gets the word out quickly (local_only folders have already been reported)
|
||||
Gee.Collection<Geary.Folder> existing_list = new Gee.ArrayList<Geary.Folder>();
|
||||
existing_list.add_all(build_folders(local_folders.values));
|
||||
existing_list.add_all(local_only.values);
|
||||
|
||||
// build a map of all existing folders
|
||||
Gee.HashMap<FolderPath, Geary.Folder> existing_folders
|
||||
= Geary.traverse<Geary.Folder>(existing_list).to_hash_map<FolderPath>(f => f.path);
|
||||
|
||||
// now that all local have been enumerated and reported (this is important to assist
|
||||
// startup of the UI), enumerate the remote folders
|
||||
bool remote_folders_suspect;
|
||||
Gee.HashMap<FolderPath, Imap.Folder>? remote_folders = yield enumerate_remote_folders_async(
|
||||
null, out remote_folders_suspect, cancellable);
|
||||
|
||||
// pair the local and remote folders and make sure everything is up-to-date
|
||||
yield update_folders_async(existing_folders, remote_folders, remote_folders_suspect, cancellable);
|
||||
|
||||
if (this.enumerate_folder_cancellable != null) {
|
||||
return;
|
||||
}
|
||||
Cancellable? cancellable = this.enumerate_folder_cancellable = new Cancellable();
|
||||
this.refresh_folder_timer.reset();
|
||||
|
||||
debug("%s: Enumerating folders", to_string());
|
||||
|
||||
bool successful = false;
|
||||
try {
|
||||
// enumerate local folders first
|
||||
Gee.HashMap<FolderPath, ImapDB.Folder> local_folders = yield enumerate_local_folders_async(
|
||||
null, cancellable
|
||||
);
|
||||
|
||||
// convert to a list of Geary.Folder ... build_folder() also reports new folders, so this
|
||||
// gets the word out quickly (local_only folders have already been reported)
|
||||
Gee.Collection<Geary.Folder> existing_list = new Gee.ArrayList<Geary.Folder>();
|
||||
existing_list.add_all(build_folders(local_folders.values));
|
||||
existing_list.add_all(local_only.values);
|
||||
|
||||
if (this.remote.is_ready && !cancellable.is_cancelled()) {
|
||||
// build a map of all existing folders
|
||||
Gee.HashMap<FolderPath, Geary.Folder> existing_folders
|
||||
= Geary.traverse<Geary.Folder>(existing_list).to_hash_map<FolderPath>(f => f.path);
|
||||
|
||||
// now that all local have been enumerated and
|
||||
// reported (this is important to assist startup of
|
||||
// the UI), enumerate the remote folders
|
||||
bool remote_folders_suspect;
|
||||
Gee.HashMap<FolderPath, Imap.Folder>? remote_folders = yield enumerate_remote_folders_async(
|
||||
null, out remote_folders_suspect, cancellable);
|
||||
|
||||
// pair the local and remote folders and make sure everything is up-to-date
|
||||
yield update_folders_async(existing_folders, remote_folders, remote_folders_suspect, cancellable);
|
||||
|
||||
successful = true;
|
||||
}
|
||||
} catch (Error err) {
|
||||
if (!(err is IOError.CANCELLED)) {
|
||||
report_problem(Geary.Account.Problem.CONNECTION_FAILURE, err);
|
||||
}
|
||||
}
|
||||
|
||||
this.enumerate_folder_cancellable = null;
|
||||
if (successful) {
|
||||
this.refresh_folder_timer.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private async Gee.HashMap<FolderPath, ImapDB.Folder> enumerate_local_folders_async(
|
||||
Geary.FolderPath? parent, Cancellable? cancellable) throws Error {
|
||||
check_open();
|
||||
|
|
@ -940,6 +943,10 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.Account {
|
|||
do_login_failed_async.begin(credentials, response, () => { awaiting_credentials = false; });
|
||||
}
|
||||
|
||||
private void on_remote_ready() {
|
||||
this.enumerate_folders_async.begin();
|
||||
}
|
||||
|
||||
private async void do_login_failed_async(Geary.Credentials? credentials, Geary.Imap.StatusResponse? response) {
|
||||
bool reask_password = true;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -4,12 +4,30 @@
|
|||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Base implementation of {@link Geary.Folder}.
|
||||
*
|
||||
* This class maintains both a local database and a remote IMAP
|
||||
* session and mediates between the two using the replay queue. Events
|
||||
* generated locally (message move, folder close, etc) are placed in
|
||||
* the local replay queue for execution, IMAP server messages (new
|
||||
* message delivered, etc) are placed in the remote replay queue. The
|
||||
* queue ensures that message state changes caused by server messages
|
||||
* are interleaved correctly with local operations.
|
||||
*
|
||||
* The remote folder connection is not always automatically
|
||||
* established, depending on flags passed to `open_async`. In any case
|
||||
* the remote connection may go away if the network changes while the
|
||||
* folder is still held open. In this case, the folder's remote
|
||||
* connection is reestablished when the a `ready` signal is received
|
||||
* from the IMAP stack, i.e. when connectivity to the server has been
|
||||
* restored.
|
||||
*/
|
||||
private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport.Copy,
|
||||
Geary.FolderSupport.Mark, Geary.FolderSupport.Move {
|
||||
|
||||
private const int FORCE_OPEN_REMOTE_TIMEOUT_SEC = 10;
|
||||
private const int DEFAULT_REESTABLISH_DELAY_MSEC = 500;
|
||||
private const int MAX_REESTABLISH_DELAY_MSEC = 60 * 1000;
|
||||
|
||||
|
||||
public override Account account { get { return _account; } }
|
||||
|
||||
public override FolderProperties properties { get { return _properties; } }
|
||||
|
|
@ -43,15 +61,15 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
private Folder.OpenFlags open_flags = OpenFlags.NONE;
|
||||
private int open_count = 0;
|
||||
private bool remote_opened = false;
|
||||
private TimeoutManager remote_open_timer;
|
||||
private Nonblocking.ReportingSemaphore<bool> remote_semaphore =
|
||||
new Nonblocking.ReportingSemaphore<bool>(false);
|
||||
private Nonblocking.Semaphore closed_semaphore = new Nonblocking.Semaphore();
|
||||
private ReplayQueue replay_queue;
|
||||
private int remote_count = -1;
|
||||
private uint open_remote_timer_id = 0;
|
||||
private int reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC;
|
||||
private Nonblocking.Mutex open_mutex = new Nonblocking.Mutex();
|
||||
private Nonblocking.Mutex close_mutex = new Nonblocking.Mutex();
|
||||
|
||||
|
||||
/**
|
||||
* Called when the folder is closing (and not reestablishing a connection) and will be flushing
|
||||
|
|
@ -77,8 +95,11 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
|
||||
public MinimalFolder(GenericAccount account, Imap.Account remote, ImapDB.Account local,
|
||||
ImapDB.Folder local_folder, SpecialFolderType special_folder_type) {
|
||||
_account = account;
|
||||
this._account = account;
|
||||
this.remote = remote;
|
||||
this.remote_open_timer = new TimeoutManager.seconds(
|
||||
FORCE_OPEN_REMOTE_TIMEOUT_SEC, () => { start_open_remote(); }
|
||||
);
|
||||
this.local = local;
|
||||
this.local_folder = local_folder;
|
||||
_special_folder_type = special_folder_type;
|
||||
|
|
@ -92,16 +113,13 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
|
||||
local_folder.email_complete.connect(on_email_complete);
|
||||
}
|
||||
|
||||
|
||||
~MinimalFolder() {
|
||||
if (open_count > 0)
|
||||
warning("Folder %s destroyed without closing", to_string());
|
||||
|
||||
cancel_remote_open_timer();
|
||||
|
||||
local_folder.email_complete.disconnect(on_email_complete);
|
||||
this.local_folder.email_complete.disconnect(on_email_complete);
|
||||
}
|
||||
|
||||
|
||||
protected virtual void notify_closing(Gee.List<ReplayOperation> final_ops) {
|
||||
closing(final_ops);
|
||||
}
|
||||
|
|
@ -515,15 +533,15 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
public override async void wait_for_open_async(Cancellable? cancellable = null) throws Error {
|
||||
if (open_count == 0)
|
||||
throw new EngineError.OPEN_REQUIRED("wait_for_open_async() can only be called after open_async()");
|
||||
|
||||
|
||||
// if remote has not yet been opened, do it now ... this bool can go true only once after
|
||||
// an open_async, it's reset at close time
|
||||
if (!remote_opened) {
|
||||
debug("wait_for_open_async %s: opening remote on demand...", to_string());
|
||||
|
||||
start_remote_open_now();
|
||||
// Someone wants this open right now, so cancel the timer and just do it already
|
||||
this.remote_open_timer.reset();
|
||||
start_open_remote();
|
||||
}
|
||||
|
||||
|
||||
if (!yield remote_semaphore.wait_for_result_async(cancellable))
|
||||
throw new EngineError.ALREADY_CLOSED("%s failed to open", to_string());
|
||||
}
|
||||
|
|
@ -536,8 +554,8 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
// add NO_DELAY flag if it forces an open
|
||||
if (!remote_opened)
|
||||
this.open_flags |= OpenFlags.NO_DELAY;
|
||||
|
||||
start_remote_open_now();
|
||||
|
||||
start_open_remote();
|
||||
}
|
||||
|
||||
debug("Not opening %s: already open", to_string());
|
||||
|
|
@ -565,49 +583,32 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
// second account Inbox they don't manipulate), no remote connection will ever be made,
|
||||
// meaning that folder normalization never happens and unsolicited notifications never
|
||||
// arrive
|
||||
if (open_flags.is_all_set(OpenFlags.NO_DELAY))
|
||||
start_remote_open_now();
|
||||
else
|
||||
start_remote_open_timer();
|
||||
|
||||
this.remote.ready.connect(on_remote_ready);
|
||||
if (open_flags.is_all_set(OpenFlags.NO_DELAY)) {
|
||||
start_open_remote();
|
||||
} else {
|
||||
this.remote_open_timer.start();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void start_remote_open_timer() {
|
||||
if (this.open_remote_timer_id != 0)
|
||||
Source.remove(this.open_remote_timer_id);
|
||||
|
||||
this.open_remote_timer_id = Timeout.add_seconds(FORCE_OPEN_REMOTE_TIMEOUT_SEC, () => {
|
||||
start_remote_open_now();
|
||||
this.open_remote_timer_id = 0;
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
private void start_remote_open_now() {
|
||||
if (remote_opened)
|
||||
return;
|
||||
|
||||
cancel_remote_open_timer();
|
||||
remote_opened = true;
|
||||
|
||||
open_remote_async.begin(null);
|
||||
}
|
||||
|
||||
private void cancel_remote_open_timer() {
|
||||
if (this.open_remote_timer_id == 0)
|
||||
return;
|
||||
|
||||
Source.remove(this.open_remote_timer_id);
|
||||
this.open_remote_timer_id = 0;
|
||||
private void start_open_remote() {
|
||||
if (!this.remote_opened &&
|
||||
!this.remote_open_timer.is_running &&
|
||||
this.remote.is_ready) {
|
||||
this.remote_open_timer.reset();
|
||||
this.remote_opened = true;
|
||||
this.open_remote_async.begin(null);
|
||||
}
|
||||
}
|
||||
|
||||
// Open the remote connection using a Mutex to prevent concurrency.
|
||||
//
|
||||
// start_remote_open_now() *should* prevent more than one open from occurring at the same time,
|
||||
// start_open_remote() *should* prevent more than one open from occurring at the same time,
|
||||
// but it's still wise to use a nonblocking primitive to prevent it if that does occur to at
|
||||
// least keep Folder state cogent.
|
||||
private async void open_remote_async(Cancellable? cancellable) {
|
||||
debug("%s: Opening remote folder", to_string());
|
||||
int token;
|
||||
try {
|
||||
token = yield open_mutex.claim_async(cancellable);
|
||||
|
|
@ -772,9 +773,6 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
|
||||
opening_monitor.notify_finish();
|
||||
|
||||
// open success, reset reestablishment delay
|
||||
reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC;
|
||||
|
||||
// at this point, remote_folder should be set; there's no notion of a local-only open (yet)
|
||||
assert(remote_folder != null);
|
||||
|
||||
|
|
@ -836,14 +834,11 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
// by going through the ReplayQueue. There are certain situations in open_remote_async() where
|
||||
// this is not possible (because the queue hasn't been started).
|
||||
//
|
||||
// NOTE: This bypasses open_count and forces the Folder closed, reestablishing a connection if
|
||||
// open_count is greater than zero
|
||||
// NOTE: This bypasses open_count and forces the Folder closed
|
||||
internal async void close_internal_async(Folder.CloseReason local_reason, Folder.CloseReason remote_reason,
|
||||
bool flush_pending, Cancellable? cancellable) {
|
||||
// make sure no open is waiting in the wings to start; close_internal_locked_async() will
|
||||
// reestablish a connection if necessary
|
||||
cancel_remote_open_timer();
|
||||
|
||||
this.remote_open_timer.reset();
|
||||
|
||||
int token;
|
||||
try {
|
||||
token = yield close_mutex.claim_async(cancellable);
|
||||
|
|
@ -880,7 +875,7 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
closing_remote_folder = clear_remote_folder();
|
||||
|
||||
// That said, only flush, close, and destroy the ReplayQueue if fully closing and not
|
||||
// preparing for a connection reestablishment
|
||||
// allowing for a connection reestablishment
|
||||
if (open_count <= 0) {
|
||||
// if closing and flushing the queue, give Revokables a chance to schedule their
|
||||
// commit operations before going down
|
||||
|
|
@ -917,15 +912,10 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
// through (unless open_count is > 0) ... do this before close_remote_folder_async() since
|
||||
// it's *possible* for it to loop back to open_async() before returning
|
||||
remote_opened = false;
|
||||
|
||||
// use background call to (a) close remote if necessary and/or (b) reestablish connection if
|
||||
// necessary ... store reestablish condition now, before scheduling close_remote_folder_async(),
|
||||
// as it touches open_count
|
||||
bool reestablish = open_count > 0;
|
||||
if (closing_remote_folder != null || reestablish) {
|
||||
|
||||
if (closing_remote_folder != null) {
|
||||
// to avoid keeping the caller waiting while the remote end closes (i.e. drops the
|
||||
// connection or performs an IMAP CLOSE operation), close it in the background and
|
||||
// reestablish connection there, if necessary
|
||||
// connection or performs an IMAP CLOSE operation), close it in the background
|
||||
//
|
||||
// TODO: Problem with this is that we cannot effectively signal or report a close error,
|
||||
// because by the time this operation completes the folder is considered closed. That
|
||||
|
|
@ -937,32 +927,33 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
// detection on the server, so this background op keeps a reference to the Folder
|
||||
close_remote_folder_async.begin(this, closing_remote_folder);
|
||||
}
|
||||
|
||||
// if reestablishing in close_remote_folder_async(), go no further
|
||||
if (reestablish)
|
||||
return;
|
||||
|
||||
// forced closed one way or another, so reset state
|
||||
open_flags = OpenFlags.NONE;
|
||||
reestablish_delay_msec = DEFAULT_REESTABLISH_DELAY_MSEC;
|
||||
|
||||
// use remote_reason even if remote_folder was null; it could be that the error occurred
|
||||
// while opening and remote_folder was yet unassigned ... also, need to call this every
|
||||
// time, even if remote was not fully opened, as some callers rely on order of signals
|
||||
notify_closed(remote_reason);
|
||||
|
||||
// see above note for why this must be called every time
|
||||
notify_closed(local_reason);
|
||||
|
||||
notify_closed(CloseReason.FOLDER_CLOSED);
|
||||
|
||||
// If not closing in the background, do it here
|
||||
if (closing_remote_folder == null)
|
||||
closed_semaphore.blind_notify();
|
||||
|
||||
debug("Folder %s closed", to_string());
|
||||
|
||||
// Only mark the folder as closed if there are no more
|
||||
// users of this instance
|
||||
if (open_count == 0) {
|
||||
// forced closed one way or another, so reset state
|
||||
open_flags = OpenFlags.NONE;
|
||||
|
||||
// use remote_reason even if remote_folder was null; it
|
||||
// could be that the error occurred while opening and
|
||||
// remote_folder was yet unassigned ... also, need to call
|
||||
// this every time, even if remote was not fully opened,
|
||||
// as some callers rely on order of signals
|
||||
notify_closed(remote_reason);
|
||||
|
||||
// see above note for why this must be called every time
|
||||
notify_closed(local_reason);
|
||||
|
||||
notify_closed(CloseReason.FOLDER_CLOSED);
|
||||
|
||||
// If not closing in the background, notify waiting callers here
|
||||
if (closing_remote_folder == null)
|
||||
closed_semaphore.blind_notify();
|
||||
|
||||
debug("Folder %s closed", to_string());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Returns the remote_folder, if it was set
|
||||
private Imap.Folder? clear_remote_folder() {
|
||||
if (remote_folder != null) {
|
||||
|
|
@ -1001,49 +992,15 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
yield remote_folder.close_async(null);
|
||||
} catch (Error err) {
|
||||
debug("Unable to close remote %s: %s", remote_folder.to_string(), err.message);
|
||||
|
||||
// fallthrough
|
||||
}
|
||||
|
||||
if (folder.open_count <= 0) {
|
||||
debug("Not reestablishing connection to %s: closed", folder.to_string());
|
||||
|
||||
// need to do it here if not done in close_internal_locked_async()
|
||||
if (remote_folder != null)
|
||||
folder.closed_semaphore.blind_notify();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// reestablish connection (which requires renormalizing the remote with the local) if
|
||||
// close was in error
|
||||
debug("Reestablishing broken connection to %s in %dms", folder.to_string(),
|
||||
folder.reestablish_delay_msec);
|
||||
|
||||
yield Scheduler.sleep_ms_async(folder.reestablish_delay_msec);
|
||||
|
||||
if (folder.open_count <= 0) {
|
||||
debug("Not reestablishing connection to %s: closed after delay", folder.to_string());
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// double timer now, reset to init value when cleanly opened
|
||||
folder.reestablish_delay_msec = (folder.reestablish_delay_msec * 2).clamp(
|
||||
DEFAULT_REESTABLISH_DELAY_MSEC, MAX_REESTABLISH_DELAY_MSEC);
|
||||
|
||||
// since open_async() increments open_count, artificially decrement here to
|
||||
// prevent driving the value up
|
||||
folder.open_count = Numeric.int_floor(folder.open_count - 1, 0);
|
||||
|
||||
debug("Reestablishing broken connection to %s", folder.to_string());
|
||||
yield folder.open_async(OpenFlags.NO_DELAY, null);
|
||||
} catch (Error err) {
|
||||
debug("Error reestablishing broken connection to %s: %s", folder.to_string(), err.message);
|
||||
|
||||
// need to do it here if not done in close_internal_locked_async()
|
||||
if (folder.open_count <= 0 && remote_folder != null) {
|
||||
folder.closed_semaphore.blind_notify();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public override async void find_boundaries_async(Gee.Collection<Geary.EmailIdentifier> ids,
|
||||
out Geary.EmailIdentifier? low, out Geary.EmailIdentifier? high,
|
||||
Cancellable? cancellable = null) throws Error {
|
||||
|
|
@ -1591,10 +1548,13 @@ private class Geary.ImapEngine.MinimalFolder : Geary.Folder, Geary.FolderSupport
|
|||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
public override string to_string() {
|
||||
return "%s (open_count=%d remote_opened=%s)".printf(base.to_string(), open_count,
|
||||
remote_opened.to_string());
|
||||
}
|
||||
}
|
||||
|
||||
private void on_remote_ready() {
|
||||
start_open_remote();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,58 +6,6 @@
|
|||
|
||||
namespace Geary.ImapEngine {
|
||||
|
||||
private int init_count = 0;
|
||||
private Gee.HashMap<GenericAccount, AccountSynchronizer>? account_synchronizers = null;
|
||||
|
||||
internal void init() {
|
||||
if (init_count++ != 0)
|
||||
return;
|
||||
|
||||
account_synchronizers = new Gee.HashMap<GenericAccount, AccountSynchronizer>();
|
||||
|
||||
// create a FullAccountSync object for each Account as it comes and goes
|
||||
Engine.instance.account_available.connect(on_account_available);
|
||||
Engine.instance.account_unavailable.connect(on_account_unavailable);
|
||||
}
|
||||
|
||||
private GenericAccount? get_imap_account(AccountInformation account_info) {
|
||||
try {
|
||||
return Engine.instance.get_account_instance(account_info) as GenericAccount;
|
||||
} catch (Error err) {
|
||||
debug("Unable to get account instance %s: %s", account_info.id, err.message);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private void on_account_available(AccountInformation account_info) {
|
||||
GenericAccount? imap_account = get_imap_account(account_info);
|
||||
if (imap_account == null)
|
||||
return;
|
||||
|
||||
assert(!account_synchronizers.has_key(imap_account));
|
||||
account_synchronizers.set(imap_account, new AccountSynchronizer(imap_account));
|
||||
}
|
||||
|
||||
private void on_account_unavailable(AccountInformation account_info) {
|
||||
GenericAccount? imap_account = get_imap_account(account_info);
|
||||
if (imap_account == null)
|
||||
return;
|
||||
|
||||
AccountSynchronizer? account_synchronizer = account_synchronizers.get(imap_account);
|
||||
assert(account_synchronizer != null);
|
||||
|
||||
account_synchronizer.stop_async.begin(on_synchronizer_stopped);
|
||||
}
|
||||
|
||||
private void on_synchronizer_stopped(Object? source, AsyncResult result) {
|
||||
AccountSynchronizer account_synchronizer = (AccountSynchronizer) source;
|
||||
account_synchronizer.stop_async.end(result);
|
||||
|
||||
bool removed = account_synchronizers.unset(account_synchronizer.account);
|
||||
assert(removed);
|
||||
}
|
||||
|
||||
/**
|
||||
* A hard failure is defined as one due to hardware or connectivity issues, where a soft failure
|
||||
* is due to software reasons, like credential failure or protocol violation.
|
||||
|
|
@ -66,11 +14,11 @@ private static bool is_hard_failure(Error err) {
|
|||
// CANCELLED is not a hard error
|
||||
if (err is IOError.CANCELLED)
|
||||
return false;
|
||||
|
||||
|
||||
// Treat other errors -- most likely IOErrors -- as hard failures
|
||||
if (!(err is ImapError) && !(err is EngineError))
|
||||
return true;
|
||||
|
||||
|
||||
return err is ImapError.NOT_CONNECTED
|
||||
|| err is ImapError.TIMED_OUT
|
||||
|| err is ImapError.SERVER_ERROR
|
||||
|
|
@ -78,4 +26,3 @@ private static bool is_hard_failure(Error err) {
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,9 +22,16 @@
|
|||
*/
|
||||
private class Geary.Imap.Account : BaseObject {
|
||||
|
||||
|
||||
/** Determines if the IMAP account has been opened. */
|
||||
public bool is_open { get; private set; default = false; }
|
||||
|
||||
/**
|
||||
* Determines if the IMAP account has a working connection.
|
||||
*
|
||||
* See {@link ClientSessionManager.is_open} for more details.
|
||||
*/
|
||||
public bool is_ready { get { return this.session_mgr.is_ready; } }
|
||||
|
||||
private string name;
|
||||
private AccountInformation account_information;
|
||||
private ClientSessionManager session_mgr;
|
||||
|
|
@ -36,16 +43,22 @@ private class Geary.Imap.Account : BaseObject {
|
|||
private Gee.List<StatusData>? status_collector = null;
|
||||
private Gee.List<ServerData>? server_data_collector = null;
|
||||
|
||||
/**
|
||||
* Fired after opening when the account has a working connection.
|
||||
*
|
||||
* This may be fired multiple times, see @{link
|
||||
* ClientSessionManager.ready} for details.
|
||||
*/
|
||||
public signal void ready();
|
||||
|
||||
public signal void login_failed(Geary.Credentials? cred, StatusResponse? response);
|
||||
|
||||
|
||||
public Account(Geary.AccountInformation account_information) {
|
||||
name = "IMAP Account for %s".printf(account_information.imap_credentials.to_string());
|
||||
this.account_information = account_information;
|
||||
this.session_mgr = new ClientSessionManager(account_information);
|
||||
|
||||
session_mgr.login_failed.connect(on_login_failed);
|
||||
this.session_mgr.ready.connect(on_session_ready);
|
||||
this.session_mgr.login_failed.connect(on_login_failed);
|
||||
}
|
||||
|
||||
private void check_open() throws Error {
|
||||
|
|
@ -591,6 +604,10 @@ private class Geary.Imap.Account : BaseObject {
|
|||
drop_session_async.begin(null);
|
||||
}
|
||||
|
||||
private void on_session_ready() {
|
||||
ready();
|
||||
}
|
||||
|
||||
public string to_string() {
|
||||
return name;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -488,10 +488,11 @@ public class Geary.Imap.ClientConnection : BaseObject {
|
|||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
// Close the Serializer/Deserializer, as need to use the TLS streams
|
||||
debug("[%s] Closing serializer to switch to TLS", to_string());
|
||||
yield close_channels_async(cancellable);
|
||||
|
||||
|
||||
// wrap connection with TLS connection
|
||||
TlsClientConnection tls_cx = yield endpoint.starttls_handshake_async(cx, cancellable);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
/* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
/*
|
||||
* Copyright 2017 Michael Gratton <mike@vee.net>
|
||||
* Copyright 2016 Software Freedom Conservancy Inc.
|
||||
*
|
||||
* This software is licensed under the GNU Lesser General Public License
|
||||
* (version 2.1 or later). See the COPYING file in this distribution.
|
||||
|
|
@ -6,11 +8,23 @@
|
|||
|
||||
public class Geary.Imap.ClientSessionManager : BaseObject {
|
||||
private const int DEFAULT_MIN_POOL_SIZE = 1;
|
||||
private const int AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC = 1;
|
||||
private const int AUTHORIZED_SESSION_ERROR_MAX_RETRY_TIMEOUT_SEC = 10;
|
||||
|
||||
private const int POOL_START_TIMEOUT_SEC = 4;
|
||||
private const int POOL_RETRY_MIN_TIMEOUT_SEC = 1;
|
||||
private const int POOL_RETRY_MAX_TIMEOUT_SEC = 10;
|
||||
private const int POOL_STOP_TIMEOUT_SEC = 1;
|
||||
|
||||
/** Determines if the manager has been opened. */
|
||||
public bool is_open { get; private set; default = false; }
|
||||
|
||||
|
||||
/**
|
||||
* Determines if the manager has a working connection.
|
||||
*
|
||||
* This will be true once at least one connection has been
|
||||
* established, and after the server has become reachable again
|
||||
* after being unreachable.
|
||||
*/
|
||||
public bool is_ready { get; private set; default = false; }
|
||||
|
||||
/**
|
||||
* Set to zero or negative value if keepalives should be disabled when a connection has not
|
||||
* selected a mailbox. (This is not recommended.)
|
||||
|
|
@ -44,28 +58,30 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
* happen as connections are needed or closed.
|
||||
*/
|
||||
public int min_pool_size { get; set; default = DEFAULT_MIN_POOL_SIZE; }
|
||||
|
||||
/**
|
||||
* Indicates if the {@link Endpoint} the {@link ClientSessionManager} connects to is reachable,
|
||||
* according to NetworkMonitor.
|
||||
*
|
||||
* By default, this is false, pessimistic that the network is reachable. It is updated even if the
|
||||
* {@link ClientSessionManager} is not open, maintained for the lifetime of the object.
|
||||
*/
|
||||
public bool is_endpoint_reachable { get; private set; default = false; }
|
||||
|
||||
private AccountInformation account_information;
|
||||
private Endpoint endpoint;
|
||||
private ConnectivityManager connectivity;
|
||||
private Gee.HashSet<ClientSession> sessions = new Gee.HashSet<ClientSession>();
|
||||
private int pending_sessions = 0;
|
||||
private Nonblocking.Mutex sessions_mutex = new Nonblocking.Mutex();
|
||||
private Gee.HashSet<ClientSession> reserved_sessions = new Gee.HashSet<ClientSession>();
|
||||
private bool authentication_failed = false;
|
||||
private bool untrusted_host = false;
|
||||
private uint authorized_session_error_retry_timeout_id = 0;
|
||||
private int authorized_session_retry_sec = AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC;
|
||||
|
||||
private TimeoutManager pool_start;
|
||||
private TimeoutManager pool_retry;
|
||||
private TimeoutManager pool_stop;
|
||||
|
||||
/**
|
||||
* Fired after when the manager has a working connection.
|
||||
*
|
||||
* This will be fired both after opening if online and once at
|
||||
* least one connection has been established, and after the server
|
||||
* has become reachable again after being unreachable.
|
||||
*/
|
||||
public signal void ready();
|
||||
|
||||
/** Fired when an authentication error occurs opening a session. */
|
||||
public signal void login_failed(StatusResponse? response);
|
||||
|
||||
public ClientSessionManager(AccountInformation account_information) {
|
||||
|
|
@ -79,41 +95,62 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
this.endpoint.notify[Endpoint.PROP_TRUST_UNTRUSTED_HOST].connect(on_imap_trust_untrusted_host);
|
||||
this.endpoint.untrusted_host.connect(on_imap_untrusted_host);
|
||||
|
||||
this.connectivity = new ConnectivityManager(this.endpoint);
|
||||
this.connectivity.notify["is-reachable"].connect(on_connectivity_change);
|
||||
this.connectivity.check_reachable.begin();
|
||||
this.pool_start = new TimeoutManager.seconds(
|
||||
POOL_START_TIMEOUT_SEC,
|
||||
() => { this.adjust_session_pool.begin(); }
|
||||
);
|
||||
|
||||
this.pool_retry = new TimeoutManager.seconds(
|
||||
POOL_RETRY_MIN_TIMEOUT_SEC,
|
||||
() => { this.adjust_session_pool.begin(); }
|
||||
);
|
||||
|
||||
this.pool_stop = new TimeoutManager.seconds(
|
||||
POOL_STOP_TIMEOUT_SEC,
|
||||
() => { this.force_disconnect_all.begin(); }
|
||||
);
|
||||
}
|
||||
|
||||
~ClientSessionManager() {
|
||||
if (is_open)
|
||||
warning("Destroying opened ClientSessionManager");
|
||||
|
||||
account_information.notify["imap-credentials"].disconnect(on_imap_credentials_notified);
|
||||
endpoint.untrusted_host.disconnect(on_imap_untrusted_host);
|
||||
endpoint.notify[Endpoint.PROP_TRUST_UNTRUSTED_HOST].disconnect(on_imap_trust_untrusted_host);
|
||||
this.connectivity.cancel_check();
|
||||
this.connectivity = null;
|
||||
this.account_information.notify["imap-credentials"].disconnect(on_imap_credentials_notified);
|
||||
this.endpoint.untrusted_host.disconnect(on_imap_untrusted_host);
|
||||
this.endpoint.notify[Endpoint.PROP_TRUST_UNTRUSTED_HOST].disconnect(on_imap_trust_untrusted_host);
|
||||
}
|
||||
|
||||
public async void open_async(Cancellable? cancellable) throws Error {
|
||||
if (is_open)
|
||||
throw new EngineError.ALREADY_OPEN("ClientSessionManager already open");
|
||||
|
||||
|
||||
is_open = true;
|
||||
|
||||
adjust_session_pool.begin();
|
||||
|
||||
this.endpoint.connectivity.notify["is-reachable"].connect(on_connectivity_change);
|
||||
if (this.endpoint.connectivity.is_reachable) {
|
||||
this.adjust_session_pool.begin();
|
||||
} else {
|
||||
this.endpoint.connectivity.check_reachable.begin();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public async void close_async(Cancellable? cancellable) throws Error {
|
||||
if (!is_open)
|
||||
return;
|
||||
|
||||
is_open = false;
|
||||
|
||||
|
||||
this.is_open = false;
|
||||
this.is_ready = false;
|
||||
|
||||
this.pool_start.reset();
|
||||
this.pool_retry.reset();
|
||||
this.pool_stop.reset();
|
||||
|
||||
this.endpoint.connectivity.notify["is-reachable"].disconnect(on_connectivity_change);
|
||||
|
||||
// to avoid locking down the sessions table while scheduling disconnects, make a copy
|
||||
// and work off of that
|
||||
ClientSession[]? sessions_copy = sessions.to_array();
|
||||
|
||||
|
||||
// disconnect all existing sessions at once; don't wait for each, since as they disconnect
|
||||
// they'll remove themselves from the sessions list and cause this foreach to explode
|
||||
foreach (ClientSession session in sessions_copy)
|
||||
|
|
@ -159,62 +196,51 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
token = yield sessions_mutex.claim_async();
|
||||
} catch (Error claim_err) {
|
||||
debug("Unable to claim session table mutex for adjusting pool: %s", claim_err.message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
while ((sessions.size + pending_sessions) < min_pool_size
|
||||
&& !authentication_failed
|
||||
&& is_open
|
||||
&& !untrusted_host
|
||||
&& is_endpoint_reachable) {
|
||||
&& this.is_open
|
||||
&& !this.authentication_failed
|
||||
&& !this.untrusted_host
|
||||
&& this.endpoint.connectivity.is_reachable) {
|
||||
pending_sessions++;
|
||||
create_new_authorized_session.begin(null, on_created_new_authorized_session);
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
sessions_mutex.release(ref token);
|
||||
} catch (Error release_err) {
|
||||
debug("Unable to release session table mutex after adjusting pool: %s", release_err.message);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void on_created_new_authorized_session(Object? source, AsyncResult result) {
|
||||
pending_sessions--;
|
||||
|
||||
this.pending_sessions--;
|
||||
|
||||
try {
|
||||
create_new_authorized_session.end(result);
|
||||
this.create_new_authorized_session.end(result);
|
||||
this.pool_retry.reset();
|
||||
} catch (Error err) {
|
||||
debug("Unable to create authorized session to %s: %s", endpoint.to_string(), err.message);
|
||||
|
||||
|
||||
// try again after a slight delay and bump up delay
|
||||
if (authorized_session_error_retry_timeout_id != 0)
|
||||
Source.remove(authorized_session_error_retry_timeout_id);
|
||||
|
||||
authorized_session_error_retry_timeout_id = Timeout.add_seconds(
|
||||
authorized_session_retry_sec, on_authorized_session_error_retry_timeout);
|
||||
|
||||
authorized_session_retry_sec = (authorized_session_retry_sec * 2).clamp(
|
||||
AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC, AUTHORIZED_SESSION_ERROR_MAX_RETRY_TIMEOUT_SEC);
|
||||
this.pool_retry.start();
|
||||
this.pool_retry.interval = (this.pool_retry.interval * 2).clamp(
|
||||
POOL_RETRY_MIN_TIMEOUT_SEC,
|
||||
POOL_RETRY_MAX_TIMEOUT_SEC
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private bool on_authorized_session_error_retry_timeout() {
|
||||
authorized_session_error_retry_timeout_id = 0;
|
||||
|
||||
adjust_session_pool.begin();
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private async ClientSession create_new_authorized_session(Cancellable? cancellable) throws Error {
|
||||
if (authentication_failed)
|
||||
throw new ImapError.UNAUTHENTICATED("Invalid ClientSessionManager credentials");
|
||||
|
||||
|
||||
if (untrusted_host)
|
||||
throw new ImapError.UNAUTHENTICATED("Untrusted host %s", endpoint.to_string());
|
||||
|
||||
if (!is_endpoint_reachable)
|
||||
throw new ImapError.UNAVAILABLE("Untrusted host %s", endpoint.to_string());
|
||||
|
||||
if (!this.endpoint.connectivity.is_reachable)
|
||||
throw new ImapError.UNAVAILABLE("Host at %s is unreachable", endpoint.to_string());
|
||||
|
||||
ClientSession new_session = new ClientSession(endpoint);
|
||||
|
|
@ -264,10 +290,15 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
||||
if (!this.is_ready) {
|
||||
this.is_ready = true;
|
||||
ready();
|
||||
}
|
||||
|
||||
// reset delay
|
||||
authorized_session_retry_sec = AUTHORIZED_SESSION_ERROR_MIN_RETRY_TIMEOUT_SEC;
|
||||
|
||||
this.pool_retry.interval = POOL_RETRY_MIN_TIMEOUT_SEC;
|
||||
|
||||
// do this after logging in
|
||||
new_session.enable_keepalives(selected_keepalive_sec, unselected_keepalive_sec,
|
||||
selected_with_idle_keepalive_sec);
|
||||
|
|
@ -345,11 +376,11 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
case ClientSession.ProtocolState.CONNECTING:
|
||||
case ClientSession.ProtocolState.AUTHORIZING:
|
||||
case ClientSession.ProtocolState.UNAUTHORIZED:
|
||||
yield force_disconnect_async(session, true);
|
||||
yield force_disconnect(session, true);
|
||||
break;
|
||||
|
||||
case ClientSession.ProtocolState.UNCONNECTED:
|
||||
yield force_disconnect_async(session, false);
|
||||
yield force_disconnect(session, false);
|
||||
break;
|
||||
|
||||
case ClientSession.ProtocolState.SELECTED:
|
||||
|
|
@ -368,7 +399,7 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
if (session.get_protocol_state(out mailbox) == ClientSession.ProtocolState.AUTHORIZED)
|
||||
unreserve = true;
|
||||
else
|
||||
yield force_disconnect_async(session, true);
|
||||
yield force_disconnect(session, true);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
@ -380,7 +411,7 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
|
||||
// if not open, disconnect, which will remove from the reserved pool anyway
|
||||
if (!is_open) {
|
||||
yield force_disconnect_async(session, true);
|
||||
yield force_disconnect(session, true);
|
||||
} else {
|
||||
debug("[%s] Unreserving session %s", to_string(), session.to_string());
|
||||
|
||||
|
|
@ -398,24 +429,52 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
// It's possible this will be called more than once on the same session, especially in the case of a
|
||||
// remote close on reserved ClientSession, so this code is forgiving.
|
||||
private async void force_disconnect_async(ClientSession session, bool do_disconnect) {
|
||||
debug("[%s] Dropping session %s (disconnecting=%s)", to_string(),
|
||||
session.to_string(), do_disconnect.to_string());
|
||||
|
||||
|
||||
private async void force_disconnect_all() {
|
||||
debug("[%s] Dropping and disconnecting %d sessions",
|
||||
to_string(), this.sessions.size);
|
||||
|
||||
int token;
|
||||
try {
|
||||
token = yield sessions_mutex.claim_async();
|
||||
} catch (Error err) {
|
||||
debug("Unable to acquire sessions mutex: %s", err.message);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
foreach (ClientSession session in this.sessions) {
|
||||
try {
|
||||
// Don't explicitly remove from the sessions list
|
||||
// since the disconnect handler will do that for us
|
||||
yield session.disconnect_async();
|
||||
} catch (Error err) {
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
sessions_mutex.release(ref token);
|
||||
} catch (Error err) {
|
||||
debug("Unable to release sessions mutex: %s", err.message);
|
||||
}
|
||||
}
|
||||
|
||||
// It's possible this will be called more than once on the same
|
||||
// session, especially in the case of a remote close on reserved
|
||||
// ClientSession, so this code is forgiving.
|
||||
private async void force_disconnect(ClientSession session, bool do_disconnect) {
|
||||
debug("[%s] Dropping session %s (disconnecting=%s)", to_string(),
|
||||
session.to_string(), do_disconnect.to_string());
|
||||
|
||||
int token;
|
||||
try {
|
||||
token = yield sessions_mutex.claim_async();
|
||||
} catch (Error err) {
|
||||
debug("Unable to acquire sessions mutex: %s", err.message);
|
||||
return;
|
||||
}
|
||||
|
||||
locked_remove_session(session);
|
||||
|
||||
if (do_disconnect) {
|
||||
try {
|
||||
yield session.disconnect_async();
|
||||
|
|
@ -423,28 +482,27 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
// ignored
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
sessions_mutex.release(ref token);
|
||||
} catch (Error err) {
|
||||
debug("Unable to release sessions mutex: %s", err.message);
|
||||
}
|
||||
|
||||
|
||||
adjust_session_pool.begin();
|
||||
}
|
||||
|
||||
|
||||
private void on_disconnected(ClientSession session, ClientSession.DisconnectReason reason) {
|
||||
force_disconnect_async.begin(session, false);
|
||||
force_disconnect.begin(session, false);
|
||||
}
|
||||
|
||||
|
||||
private void on_login_failed(ClientSession session, StatusResponse? response) {
|
||||
authentication_failed = true;
|
||||
|
||||
this.is_ready = false;
|
||||
this.authentication_failed = true;
|
||||
login_failed(response);
|
||||
|
||||
session.disconnect_async.begin();
|
||||
}
|
||||
|
||||
|
||||
// Only call with sessions mutex locked
|
||||
private void locked_add_session(ClientSession session) {
|
||||
sessions.add(session);
|
||||
|
|
@ -499,13 +557,17 @@ public class Geary.Imap.ClientSessionManager : BaseObject {
|
|||
}
|
||||
|
||||
private void on_connectivity_change() {
|
||||
this.is_endpoint_reachable = this.connectivity.is_reachable;
|
||||
debug("Host %s became %s",
|
||||
this.endpoint.to_string(),
|
||||
this.is_endpoint_reachable ? "reachable" : "unreachable");
|
||||
if (this.is_endpoint_reachable) {
|
||||
this.adjust_session_pool.begin();
|
||||
}
|
||||
bool is_reachable = this.endpoint.connectivity.is_reachable;
|
||||
if (is_reachable) {
|
||||
this.pool_start.start();
|
||||
this.pool_stop.reset();
|
||||
} else {
|
||||
// Get a ready signal again once we are back online
|
||||
this.is_ready = false;
|
||||
this.pool_start.reset();
|
||||
this.pool_stop.start();
|
||||
}
|
||||
this.pool_retry.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -248,7 +248,6 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
cancellable.cancel();
|
||||
|
||||
// wait for outstanding I/O to exit
|
||||
debug("[%s] Waiting for deserializer to close...", to_string());
|
||||
yield closed_semaphore.wait_async();
|
||||
debug("[%s] Deserializer closed", to_string());
|
||||
}
|
||||
|
|
@ -818,14 +817,14 @@ public class Geary.Imap.Deserializer : BaseObject {
|
|||
|
||||
private uint on_error(uint state, uint event, void *user, Object? object, Error? err) {
|
||||
assert(err != null);
|
||||
|
||||
debug("[%s] input error: %s", to_string(), err.message);
|
||||
|
||||
|
||||
// only Cancellable allowed is internal used to notify when closed; all other errors should
|
||||
// be reported
|
||||
if (!(err is IOError.CANCELLED))
|
||||
if (!(err is IOError.CANCELLED)) {
|
||||
debug("[%s] input error: %s", to_string(), err.message);
|
||||
receive_failure(err);
|
||||
|
||||
}
|
||||
|
||||
// always signal as closed and notify
|
||||
closed_semaphore.blind_notify();
|
||||
eos();
|
||||
|
|
|
|||
|
|
@ -23,7 +23,8 @@ public class Geary.ConnectivityManager : BaseObject {
|
|||
/** Determines if the managed endpoint is currently reachable. */
|
||||
public bool is_reachable { get; private set; default = false; }
|
||||
|
||||
private Endpoint endpoint;
|
||||
// Weak to avoid a circular ref with the endpoint
|
||||
private weak Endpoint endpoint;
|
||||
|
||||
private NetworkMonitor monitor;
|
||||
|
||||
|
|
@ -45,11 +46,11 @@ public class Geary.ConnectivityManager : BaseObject {
|
|||
}
|
||||
|
||||
/**
|
||||
* Starts checking if the manager's endpoint is reachable.
|
||||
*
|
||||
* This will cancel any existing check, and start a new one
|
||||
* running, updating the `is_reachable` property on completion.
|
||||
*/
|
||||
* Starts checking if the manager's endpoint is reachable.
|
||||
*
|
||||
* This will cancel any existing check, and start a new one
|
||||
* running, updating the `is_reachable` property on completion.
|
||||
*/
|
||||
public async void check_reachable() {
|
||||
// We use a cancellable here as a guard instead of a boolean
|
||||
// "is_checking" var since when a series of checks are
|
||||
|
|
@ -79,7 +80,7 @@ public class Geary.ConnectivityManager : BaseObject {
|
|||
// just assume the service is reachable is for now. :(
|
||||
is_reachable = true;
|
||||
debug("Assuming %s is reachable, despite network unavailability",
|
||||
endpoint);
|
||||
endpoint);
|
||||
} else if (!(err is IOError.CANCELLED)) {
|
||||
// Service is unreachable
|
||||
debug("Error checking %s reachable, treating as unreachable: %s",
|
||||
|
|
@ -95,8 +96,8 @@ public class Geary.ConnectivityManager : BaseObject {
|
|||
}
|
||||
|
||||
/**
|
||||
* Cancels any running reachability check, if any.
|
||||
*/
|
||||
* Cancels any running reachability check, if any.
|
||||
*/
|
||||
public void cancel_check() {
|
||||
if (this.existing_check != null) {
|
||||
this.existing_check.cancel();
|
||||
|
|
@ -108,7 +109,7 @@ public class Geary.ConnectivityManager : BaseObject {
|
|||
// Always check if reachable because IMAP server could be on
|
||||
// localhost. (This is a Linux program, after all...)
|
||||
debug("Network changed: %s",
|
||||
some_available ? "some available" : "none available");
|
||||
some_available ? "some available" : "none available");
|
||||
if (some_available) {
|
||||
// Some hosts may have dropped out despite network being
|
||||
// still xavailable, so need to check again
|
||||
|
|
@ -125,6 +126,8 @@ public class Geary.ConnectivityManager : BaseObject {
|
|||
// change. 0.36 fixes that, so pull this out when we can
|
||||
// depend on that as a minimum.
|
||||
if (this.is_reachable != reachable) {
|
||||
debug("Host %s became %s",
|
||||
this.endpoint.to_string(), reachable ? "reachable" : "unreachable");
|
||||
this.is_reachable = reachable;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue