diff --git a/Makefile b/Makefile index 211ad516..c6cdb1f8 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ APPS := geary console watchmbox ENGINE_SRC := \ src/engine/Engine.vala \ + src/engine/EngineFolder.vala \ src/engine/api/Account.vala \ src/engine/api/Email.vala \ src/engine/api/Folder.vala \ diff --git a/src/client/ui/FolderListStore.vala b/src/client/ui/FolderListStore.vala index 070b5f53..8febb083 100644 --- a/src/client/ui/FolderListStore.vala +++ b/src/client/ui/FolderListStore.vala @@ -47,7 +47,7 @@ public class FolderListStore : Gtk.TreeStore { append(out iter, null); set(iter, - Column.NAME, folder.name, + Column.NAME, folder.get_name(), Column.FOLDER_OBJECT, folder ); } diff --git a/src/client/ui/MainWindow.vala b/src/client/ui/MainWindow.vala index 5ea68388..43b51ee8 100644 --- a/src/client/ui/MainWindow.vala +++ b/src/client/ui/MainWindow.vala @@ -188,7 +188,7 @@ public class MainWindow : Gtk.Window { return; } - debug("Folder %s selected", folder.name); + debug("Folder %s selected", folder.get_name()); do_select_folder.begin(folder, on_select_folder_completed); } diff --git a/src/engine/Engine.vala b/src/engine/Engine.vala index 0c01376b..372def40 100644 --- a/src/engine/Engine.vala +++ b/src/engine/Engine.vala @@ -21,19 +21,31 @@ public class Geary.Engine : Object, Geary.Account { public async Gee.Collection list_async(string? parent_folder, Cancellable? cancellable = null) throws Error { - Gee.Collection list = yield local.list_async(parent_folder, cancellable); + Gee.Collection local_list = yield local.list_async(parent_folder, cancellable); - background_update_folders.begin(parent_folder, list); + Gee.Collection engine_list = new Gee.ArrayList(); + foreach (Geary.Folder local_folder in local_list) + engine_list.add(new EngineFolder(net, local, local_folder)); - debug("Reporting %d folders", list.size); + background_update_folders.begin(parent_folder, engine_list); - return list; + debug("Reporting %d folders", engine_list.size); + + return engine_list; + } + + public async Geary.Folder fetch_async(string? parent_folder, string folder_name, + Cancellable? cancellable = null) throws Error { + Geary.Folder local_folder = yield local.fetch_async(parent_folder, folder_name, cancellable); + Geary.Folder engine_folder = new EngineFolder(net, local, local_folder); + + return engine_folder; } private Gee.Set get_folder_names(Gee.Collection folders) { Gee.Set names = new Gee.HashSet(); foreach (Geary.Folder folder in folders) - names.add(folder.name); + names.add(folder.get_name()); return names; } @@ -42,7 +54,7 @@ public class Geary.Engine : Object, Geary.Account { Gee.Set names) { Gee.List excluded = new Gee.ArrayList(); foreach (Geary.Folder folder in folders) { - if (!names.contains(folder.name)) + if (!names.contains(folder.get_name())) excluded.add(folder); } @@ -50,7 +62,7 @@ public class Geary.Engine : Object, Geary.Account { } private async void background_update_folders(string? parent_folder, - Gee.Collection local_folders) { + Gee.Collection engine_folders) { Gee.Collection net_folders; try { net_folders = yield net.list_async(parent_folder); @@ -58,13 +70,13 @@ public class Geary.Engine : Object, Geary.Account { error("Unable to retrieve folder list from server: %s", neterror.message); } - Gee.Set local_names = get_folder_names(local_folders); + Gee.Set local_names = get_folder_names(engine_folders); Gee.Set net_names = get_folder_names(net_folders); debug("%d local names, %d net names", local_names.size, net_names.size); Gee.List? to_add = get_excluded_folders(net_folders, local_names); - Gee.List? to_remove = get_excluded_folders(local_folders, net_names); + Gee.List? to_remove = get_excluded_folders(engine_folders, net_names); debug("Adding %d, removing %d to/from local store", to_add.size, to_remove.size); @@ -74,15 +86,28 @@ public class Geary.Engine : Object, Geary.Account { if (to_remove.size == 0) to_remove = null; - if (to_add != null || to_remove != null) - notify_folders_added_removed(to_add, null); - try { if (to_add != null) yield local.create_many_async(to_add); } catch (Error err) { error("Unable to add/remove folders: %s", err.message); } + + Gee.Collection engine_added = null; + if (to_add != null) { + engine_added = new Gee.ArrayList(); + foreach (Geary.Folder net_folder in to_add) { + try { + engine_added.add(new EngineFolder(net, local, + yield local.fetch_async(parent_folder, net_folder.get_name()))); + } catch (Error convert_err) { + error("Unable to fetch local folder: %s", convert_err.message); + } + } + } + + if (engine_added != null) + notify_folders_added_removed(engine_added, null); } public async void create_async(Geary.Folder folder, Cancellable? cancellable = null) throws Error { diff --git a/src/engine/EngineFolder.vala b/src/engine/EngineFolder.vala new file mode 100644 index 00000000..f8da8b3f --- /dev/null +++ b/src/engine/EngineFolder.vala @@ -0,0 +1,89 @@ +/* Copyright 2011 Yorba Foundation + * + * This software is licensed under the GNU Lesser General Public License + * (version 2.1 or later). See the COPYING file in this distribution. + */ + +private class Geary.EngineFolder : Object, Geary.Folder { + private NetworkAccount net; + private LocalAccount local; + private Geary.Folder local_folder; + private Geary.Folder net_folder; + + public EngineFolder(NetworkAccount net, LocalAccount local, Geary.Folder local_folder) { + this.net = net; + this.local = local; + this.local_folder = local_folder; + + local_folder.updated.connect(on_local_updated); + } + + ~EngineFolder() { + local_folder.updated.disconnect(on_local_updated); + } + + public string get_name() { + return local_folder.get_name(); + } + + public Trillian is_readonly() { + return local_folder.is_readonly(); + } + + public Trillian does_support_children() { + return local_folder.does_support_children(); + } + + public Trillian has_children() { + return local_folder.has_children(); + } + + public Trillian is_openable() { + return local_folder.is_openable(); + } + + public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error { + if (net_folder == null) { + net_folder = yield net.fetch_async(null, local_folder.get_name(), cancellable); + net_folder.updated.connect(on_net_updated); + } + + yield net_folder.open_async(readonly, cancellable); + } + + public async void close_async(Cancellable? cancellable = null) throws Error { + if (net_folder != null) { + net_folder.updated.disconnect(on_net_updated); + yield net_folder.close_async(cancellable); + } + + net_folder = null; + } + + public int get_message_count() throws Error { + return 0; + } + + public async Gee.List? read_async(int low, int count, + Cancellable? cancellable = null) throws Error { + if (net_folder == null) + throw new EngineError.OPEN_REQUIRED("Folder %s not opened", get_name()); + + return yield net_folder.read_async(low, count, cancellable); + } + + public async Geary.Email fetch_async(Geary.EmailHeader header, + Cancellable? cancellable = null) throws Error { + if (net_folder == null) + throw new EngineError.OPEN_REQUIRED("Folder %s not opened", get_name()); + + return yield net_folder.fetch_async(header, cancellable); + } + + private void on_local_updated() { + } + + private void on_net_updated() { + } +} + diff --git a/src/engine/api/Account.vala b/src/engine/api/Account.vala index f6f9a22d..f720817b 100644 --- a/src/engine/api/Account.vala +++ b/src/engine/api/Account.vala @@ -16,6 +16,9 @@ public interface Geary.Account : Object { public abstract async Gee.Collection list_async(string? parent_folder, Cancellable? cancellable = null) throws Error; + public abstract async Geary.Folder fetch_async(string? parent_folder, string folder_name, + Cancellable? cancellable = null) throws Error; + public abstract async void create_async(Geary.Folder folder, Cancellable? cancellable = null) throws Error; diff --git a/src/engine/api/EngineError.vala b/src/engine/api/EngineError.vala index 573d725f..ec6c83db 100644 --- a/src/engine/api/EngineError.vala +++ b/src/engine/api/EngineError.vala @@ -6,6 +6,7 @@ public errordomain Geary.EngineError { OPEN_REQUIRED, - ALREADY_OPEN + ALREADY_OPEN, + NOT_FOUND } diff --git a/src/engine/api/Folder.vala b/src/engine/api/Folder.vala index 1255146e..6ba16887 100644 --- a/src/engine/api/Folder.vala +++ b/src/engine/api/Folder.vala @@ -11,18 +11,23 @@ public interface Geary.Folder : Object { FOLDER_CLOSED } - public abstract string name { get; protected set; } - public abstract Trillian is_readonly { get; protected set; } - public abstract Trillian supports_children { get; protected set; } - public abstract Trillian has_children { get; protected set; } - public abstract Trillian is_openable { get; protected set; } - public signal void opened(); public signal void closed(CloseReason reason); public signal void updated(); + public abstract string get_name(); + + // This is only for when a context has been selected + public abstract Trillian is_readonly(); + + public abstract Trillian does_support_children(); + + public abstract Trillian has_children(); + + public abstract Trillian is_openable(); + public abstract async void open_async(bool readonly, Cancellable? cancellable = null) throws Error; public abstract async void close_async(Cancellable? cancellable = null) throws Error; diff --git a/src/engine/imap/ClientSessionManager.vala b/src/engine/imap/ClientSessionManager.vala index 90e38670..89017529 100644 --- a/src/engine/imap/ClientSessionManager.vala +++ b/src/engine/imap/ClientSessionManager.vala @@ -43,6 +43,20 @@ public class Geary.Imap.ClientSessionManager { return results.get_all(); } + public async Geary.Imap.MailboxInformation? fetch_async(string? parent_name, string folder_name, + Cancellable? cancellable = null) throws Error { + // build a proper IMAP specifier + string specifier = parent_name ?? "/"; + specifier += (specifier.has_suffix("/")) ? folder_name : "/%s".printf(folder_name); + + ClientSession session = yield get_authorized_session(cancellable); + + ListResults results = ListResults.decode(yield session.send_command_async( + new ListCommand(session.generate_tag(), specifier), cancellable)); + + return (results.get_count() > 0) ? results.get_all()[0] : null; + } + public async Mailbox select_mailbox(string path, Cancellable? cancellable = null) throws Error { return yield select_examine_mailbox(path, true, cancellable); } diff --git a/src/engine/imap/api/Account.vala b/src/engine/imap/api/Account.vala index 9f99fb3e..8e0ea796 100644 --- a/src/engine/imap/api/Account.vala +++ b/src/engine/imap/api/Account.vala @@ -26,6 +26,16 @@ public class Geary.Imap.Account : Object, Geary.Account, Geary.NetworkAccount { return folders; } + public async Geary.Folder fetch_async(string? parent_folder, string folder_name, + Cancellable? cancellable = null) throws Error { + MailboxInformation? mbox = yield session_mgr.fetch_async(parent_folder, folder_name, + cancellable); + if (mbox == null) + throw new EngineError.NOT_FOUND("Folder %s not found on server", folder_name); + + return new Geary.Imap.Folder(session_mgr, mbox); + } + public async void create_async(Geary.Folder folder, Cancellable? cancellable = null) throws Error { // TODO } diff --git a/src/engine/imap/api/Folder.vala b/src/engine/imap/api/Folder.vala index d12e70d8..27290a6b 100644 --- a/src/engine/imap/api/Folder.vala +++ b/src/engine/imap/api/Folder.vala @@ -5,15 +5,13 @@ */ public class Geary.Imap.Folder : Object, Geary.Folder { - public string name { get; protected set; } - // This is only for when a context has been selected - public Trillian is_readonly { get; protected set; } - public Trillian supports_children { get; protected set; } - public Trillian has_children { get; protected set; } - public Trillian is_openable { get; protected set; } - private ClientSessionManager session_mgr; private MailboxInformation info; + private string name; + private Trillian readonly; + private Trillian supports_children; + private Trillian children; + private Trillian openable; private Mailbox? mailbox = null; internal Folder(ClientSessionManager session_mgr, MailboxInformation info) { @@ -21,12 +19,32 @@ public class Geary.Imap.Folder : Object, Geary.Folder { this.info = info; name = info.name; - is_readonly = Trillian.UNKNOWN; + readonly = Trillian.UNKNOWN; supports_children = Trillian.from_boolean(!info.attrs.contains(MailboxAttribute.NO_INFERIORS)); // \HasNoChildren is an optional attribute and lack of presence doesn't indiciate anything - has_children = info.attrs.contains(MailboxAttribute.HAS_NO_CHILDREN) ? Trillian.TRUE + children = info.attrs.contains(MailboxAttribute.HAS_NO_CHILDREN) ? Trillian.TRUE : Trillian.UNKNOWN; - is_openable = Trillian.from_boolean(!info.attrs.contains(MailboxAttribute.NO_SELECT)); + openable = Trillian.from_boolean(!info.attrs.contains(MailboxAttribute.NO_SELECT)); + } + + public string get_name() { + return name; + } + + public Trillian is_readonly() { + return readonly; + } + + public Trillian does_support_children() { + return supports_children; + } + + public Trillian has_children() { + return children; + } + + public Trillian is_openable() { + return openable; } public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error { @@ -36,12 +54,12 @@ public class Geary.Imap.Folder : Object, Geary.Folder { mailbox = yield session_mgr.select_examine_mailbox(name, !readonly, cancellable); // hook up signals - this.is_readonly = Trillian.from_boolean(readonly); + this.readonly = Trillian.from_boolean(readonly); } public async void close_async(Cancellable? cancellable = null) throws Error { mailbox = null; - is_readonly = Trillian.UNKNOWN; + readonly = Trillian.UNKNOWN; } public int get_message_count() throws Error { diff --git a/src/engine/imap/decoders/ListResults.vala b/src/engine/imap/decoders/ListResults.vala index c2d20b7c..5f8aa93c 100644 --- a/src/engine/imap/decoders/ListResults.vala +++ b/src/engine/imap/decoders/ListResults.vala @@ -17,17 +17,21 @@ public class Geary.Imap.MailboxInformation { } public class Geary.Imap.ListResults : Geary.Imap.CommandResults { + private Gee.List list; private Gee.Map map; - public ListResults(StatusResponse status_response, Gee.Map map) { + public ListResults(StatusResponse status_response, Gee.Map map, + Gee.List list) { base (status_response); this.map = map; + this.list = list; } public static ListResults decode(CommandResponse response) { assert(response.is_sealed()); + Gee.List list = new Gee.ArrayList(); Gee.Map map = new Gee.HashMap(); foreach (ServerData data in response.server_data) { try { @@ -43,7 +47,7 @@ public class Geary.Imap.ListResults : Geary.Imap.CommandResults { continue; } - Gee.Collection list = new Gee.ArrayList(); + Gee.Collection attrlist = new Gee.ArrayList(); foreach (Parameter attr in attrs.get_all()) { StringParameter? stringp = attr as StringParameter; if (stringp == null) { @@ -53,25 +57,32 @@ public class Geary.Imap.ListResults : Geary.Imap.CommandResults { continue; } - list.add(new MailboxAttribute(stringp.value)); + attrlist.add(new MailboxAttribute(stringp.value)); } - map.set(mailbox.value, - new MailboxInformation(mailbox.value, delim.value, new MailboxAttributes(list))); + MailboxInformation info = new MailboxInformation(mailbox.value, delim.value, + new MailboxAttributes(attrlist)); + + map.set(mailbox.value, info); + list.add(info); } catch (ImapError ierr) { debug("Unable to decode \"%s\": %s", data.to_string(), ierr.message); } } - return new ListResults(response.status_response, map); + return new ListResults(response.status_response, map, list); + } + + public int get_count() { + return list.size; } public Gee.Collection get_names() { return map.keys; } - public Gee.Collection get_all() { - return map.values; + public Gee.List get_all() { + return list; } public MailboxInformation? get_info(string name) { diff --git a/src/engine/sqlite/FolderTable.vala b/src/engine/sqlite/FolderTable.vala index b8320d0e..03722a3e 100644 --- a/src/engine/sqlite/FolderTable.vala +++ b/src/engine/sqlite/FolderTable.vala @@ -57,6 +57,17 @@ public class Geary.Sqlite.FolderTable : Geary.Sqlite.Table { return rows; } + public async FolderRow? fetch_async(int64 parent_id, string name, Cancellable? cancellable = null) + throws Error { + SQLHeavy.Query query = db.prepare("SELECT * FROM FolderTable WHERE parent_id=? AND name=?"); + query.bind_int64(0, parent_id); + query.bind_string(1, name); + + SQLHeavy.QueryResult result = yield query.execute_async(cancellable); + + return (!result.finished) ? new FolderRow.from_query_result(result) : null; + } + private SQLHeavy.Query create_query(SQLHeavy.Queryable? queryable = null) throws SQLHeavy.Error { SQLHeavy.Queryable q = queryable ?? db; SQLHeavy.Query query = q.prepare( diff --git a/src/engine/sqlite/api/Account.vala b/src/engine/sqlite/api/Account.vala index d8dfdb95..dc707813 100644 --- a/src/engine/sqlite/api/Account.vala +++ b/src/engine/sqlite/api/Account.vala @@ -29,17 +29,28 @@ public class Geary.Sqlite.Account : Object, Geary.Account, Geary.LocalAccount { return folders; } + public async Geary.Folder fetch_async(string? parent_folder, string folder_name, + Cancellable? cancellable = null) throws Error { + FolderRow? row = yield folder_table.fetch_async(Row.INVALID_ID, folder_name, cancellable); + if (row == null) + throw new EngineError.NOT_FOUND("%s not found in local database", folder_name); + + return new Geary.Sqlite.Folder(row); + } + public async void create_async(Geary.Folder folder, Cancellable? cancellable = null) throws Error { yield folder_table.create_async( - new FolderRow(folder.name, folder.supports_children, folder.is_openable), + new FolderRow(folder.get_name(), folder.does_support_children(), folder.is_openable()), cancellable); } public async void create_many_async(Gee.Collection folders, Cancellable? cancellable = null) throws Error { Gee.List rows = new Gee.ArrayList(); - foreach (Geary.Folder folder in folders) - rows.add(new FolderRow(folder.name, folder.supports_children, folder.is_openable)); + foreach (Geary.Folder folder in folders) { + rows.add(new FolderRow(folder.get_name(), folder.does_support_children(), + folder.is_openable())); + } yield folder_table.create_many_async(rows, cancellable); } diff --git a/src/engine/sqlite/api/Folder.vala b/src/engine/sqlite/api/Folder.vala index 38bd4413..acce3b11 100644 --- a/src/engine/sqlite/api/Folder.vala +++ b/src/engine/sqlite/api/Folder.vala @@ -6,29 +6,48 @@ public class Geary.Sqlite.Folder : Object, Geary.Folder { private FolderRow row; - - public string name { get; protected set; } - public Trillian is_readonly { get; protected set; } - public Trillian supports_children { get; protected set; } - public Trillian has_children { get; protected set; } - public Trillian is_openable { get; protected set; } + private string name; + private Trillian readonly; + private Trillian supports_children; + private Trillian children; + private Trillian openable; internal Folder(FolderRow row) throws Error { this.row = row; name = row.name; - is_readonly = Trillian.UNKNOWN; + readonly = Trillian.UNKNOWN; supports_children = row.supports_children; - has_children = Trillian.UNKNOWN; - is_openable = row.is_openable; + children = Trillian.UNKNOWN; + openable = row.is_openable; + } + + public string get_name() { + return name; + } + + public Trillian is_readonly() { + return readonly; + } + + public Trillian does_support_children() { + return supports_children; + } + + public Trillian has_children() { + return children; + } + + public Trillian is_openable() { + return openable; } public async void open_async(bool readonly, Cancellable? cancellable = null) throws Error { - is_readonly = Trillian.TRUE; + this.readonly = Trillian.TRUE; } public async void close_async(Cancellable? cancellable = null) throws Error { - is_readonly = Trillian.UNKNOWN; + this.readonly = Trillian.UNKNOWN; } public int get_message_count() throws Error {