From 7f293f9a6f006cd688559c873c6fcc2d03cb71d0 Mon Sep 17 00:00:00 2001 From: Jim Nelson Date: Mon, 4 Jul 2011 13:54:00 -0700 Subject: [PATCH] No longer crashes when running for the first time: #3798 Problem was that the client fetches folder objects for the special folders (Inbox, Drafts, etc.) prior to fetching the full folder list and the Engine's fetch_folder_async() call wasn't prepared to handle fetches for folders not located in the local database. --- src/engine/api/geary-abstract-account.vala | 3 ++ src/engine/api/geary-account.vala | 8 ++++ .../api/geary-generic-imap-account.vala | 40 +++++++++++++++++-- src/engine/imap/api/imap-account.vala | 27 ++++++++++--- .../imap-client-session-manager.vala | 9 +++++ src/engine/sqlite/api/sqlite-account.vala | 14 +++++++ 6 files changed, 92 insertions(+), 9 deletions(-) diff --git a/src/engine/api/geary-abstract-account.vala b/src/engine/api/geary-abstract-account.vala index 120bcdce..23e5a386 100644 --- a/src/engine/api/geary-abstract-account.vala +++ b/src/engine/api/geary-abstract-account.vala @@ -21,6 +21,9 @@ public abstract class Geary.AbstractAccount : Object, Geary.Account { public abstract async Gee.Collection list_folders_async(Geary.FolderPath? parent, Cancellable? cancellable = null) throws Error; + public abstract async bool folder_exists_async(Geary.FolderPath path, Cancellable? cancellable = null) + throws Error; + public abstract async Geary.Folder fetch_folder_async(Geary.FolderPath path, Cancellable? cancellable = null) throws Error; diff --git a/src/engine/api/geary-account.vala b/src/engine/api/geary-account.vala index 89f30e5f..7d2bf46c 100644 --- a/src/engine/api/geary-account.vala +++ b/src/engine/api/geary-account.vala @@ -32,6 +32,14 @@ public interface Geary.Account : Object { public abstract async Gee.Collection list_folders_async(Geary.FolderPath? parent, Cancellable? cancellable = null) throws Error; + /** + * Returns true if the folder exists. + * + * This method never throws EngineError.NOT_FOUND. + */ + public abstract async bool folder_exists_async(Geary.FolderPath path, Cancellable? cancellable = null) + throws Error; + /** * Fetches a Folder object corresponding to the supplied path. If the backing medium does * not have a record of a folder at the path, EngineError.NOT_FOUND will be thrown. diff --git a/src/engine/api/geary-generic-imap-account.vala b/src/engine/api/geary-generic-imap-account.vala index f977f8d7..c44a55d3 100644 --- a/src/engine/api/geary-generic-imap-account.vala +++ b/src/engine/api/geary-generic-imap-account.vala @@ -58,12 +58,46 @@ private class Geary.GenericImapAccount : Geary.EngineAccount { return engine_list; } + public override async bool folder_exists_async(Geary.FolderPath path, + Cancellable? cancellable = null) throws Error { + if (yield local.folder_exists_async(path, cancellable)) + return true; + + return yield remote.folder_exists_async(path, cancellable); + } + public override async Geary.Folder fetch_folder_async(Geary.FolderPath path, Cancellable? cancellable = null) throws Error { - LocalFolder local_folder = (LocalFolder) yield local.fetch_folder_async(path, cancellable); - Geary.Folder engine_folder = new EngineFolder(remote, local, local_folder); + LocalFolder? local_folder = null; + try { + local_folder = (LocalFolder) yield local.fetch_folder_async(path, cancellable); + + return new EngineFolder(remote, local, local_folder); + } catch (EngineError err) { + // don't thrown NOT_FOUND's, that means we need to fall through and clone from the + // server + if (!(err is EngineError.NOT_FOUND)) + throw err; + } - return engine_folder; + // clone the entire path + int length = path.get_path_length(); + for (int ctr = 0; ctr < length; ctr++) { + Geary.FolderPath folder = path.get_folder_at(ctr); + + if (yield local.folder_exists_async(folder)) + continue; + + RemoteFolder remote_folder = (RemoteFolder) yield remote.fetch_folder_async(folder, + cancellable); + + yield local.clone_folder_async(remote_folder, cancellable); + } + + // Fetch the local account's version of the folder for the EngineFolder + local_folder = (LocalFolder) yield local.fetch_folder_async(path, cancellable); + + return new EngineFolder(remote, local, local_folder); } private Gee.Set get_folder_names(Gee.Collection folders) { diff --git a/src/engine/imap/api/imap-account.vala b/src/engine/imap/api/imap-account.vala index 36d835af..fbcf9ad0 100644 --- a/src/engine/imap/api/imap-account.vala +++ b/src/engine/imap/api/imap-account.vala @@ -72,6 +72,15 @@ public class Geary.Imap.Account : Geary.AbstractAccount, Geary.RemoteAccount { return folders; } + public override async bool folder_exists_async(Geary.FolderPath path, Cancellable? cancellable = null) + throws Error { + Geary.FolderPath? processed = process_path(path, null, path.get_root().default_separator); + if (processed == null) + throw new ImapError.INVALID_PATH("Invalid path %s", path.to_string()); + + return yield session_mgr.folder_exists_async(processed.get_fullpath(), cancellable); + } + public override async Geary.Folder fetch_folder_async(Geary.FolderPath path, Cancellable? cancellable = null) throws Error { Geary.FolderPath? processed = process_path(path, null, path.get_root().default_separator); @@ -103,25 +112,31 @@ public class Geary.Imap.Account : Geary.AbstractAccount, Geary.RemoteAccount { // application. private static Geary.FolderPath? process_path(Geary.FolderPath? parent, string? basename, string? delim) throws ImapError { + bool empty_basename = String.is_empty(basename); + // 1. Both null, done - if (parent == null && basename == null) + if (parent == null && empty_basename) return null; // 2. Parent null but basename not, create FolderRoot for Inbox - if (parent == null && basename != null && basename.up() == INBOX_NAME) + if (parent == null && !empty_basename && basename.up() == INBOX_NAME) return new Geary.FolderRoot(INBOX_NAME, delim, false); // 3. Parent and basename supplied, verify parent is not Inbox, as IMAP does not allow it // to have children - if (parent != null && basename != null && parent.get_root().basename.up() == INBOX_NAME) + if (parent != null && !empty_basename && parent.get_root().basename.up() == INBOX_NAME) throw new ImapError.INVALID_PATH("Inbox may not have children"); - // 4. Default behavior: create child of basename or basename as root, otherwise return parent + // 4. Parent supplied but basename is not; if parent points to Inbox, normalize it + if (parent != null && empty_basename && parent.basename.up() == INBOX_NAME) + return new Geary.FolderRoot(INBOX_NAME, delim, false); + + // 5. Default behavior: create child of basename or basename as root, otherwise return parent // unmodified - if (parent != null && basename != null) + if (parent != null && !empty_basename) return parent.get_child(basename); - if (basename != null) + if (!empty_basename) return new Geary.FolderRoot(basename, delim, Folder.CASE_SENSITIVE); return parent; diff --git a/src/engine/imap/transport/imap-client-session-manager.vala b/src/engine/imap/transport/imap-client-session-manager.vala index 081b55d9..dff03ab4 100644 --- a/src/engine/imap/transport/imap-client-session-manager.vala +++ b/src/engine/imap/transport/imap-client-session-manager.vala @@ -76,6 +76,15 @@ public class Geary.Imap.ClientSessionManager { return results.get_all(); } + public async bool folder_exists_async(string path, Cancellable? cancellable = null) throws Error { + ClientSession session = yield get_authorized_session(cancellable); + + ListResults results = ListResults.decode(yield session.send_command_async( + new ListCommand(session.generate_tag(), path), cancellable)); + + return (results.status_response.status == Status.OK) && (results.get_count() == 1); + } + public async Geary.Imap.MailboxInformation? fetch_async(string path, Cancellable? cancellable = null) throws Error { ClientSession session = yield get_authorized_session(cancellable); diff --git a/src/engine/sqlite/api/sqlite-account.vala b/src/engine/sqlite/api/sqlite-account.vala index 79f60999..0406b92a 100644 --- a/src/engine/sqlite/api/sqlite-account.vala +++ b/src/engine/sqlite/api/sqlite-account.vala @@ -85,6 +85,20 @@ public class Geary.Sqlite.Account : Geary.AbstractAccount, Geary.LocalAccount { return folders; } + public override async bool folder_exists_async(Geary.FolderPath path, + Cancellable? cancellable = null) throws Error { + try { + int64 id = yield fetch_id_async(path, cancellable); + + return (id != Row.INVALID_ID); + } catch (EngineError err) { + if (err is EngineError.NOT_FOUND) + return false; + else + throw err; + } + } + public override async Geary.Folder fetch_folder_async(Geary.FolderPath path, Cancellable? cancellable = null) throws Error { FolderRow? row = yield folder_table.fetch_descend_async(path.as_list(), cancellable);