Fixes message list not updated when folder is selected: #3741

Engine now uses a master Folder object that synchronizes between the network and the local store.  Also, the Geary.Folder interface was getting ugly to code when the implementation was more than trivial, so moved to standard getters for it.
This commit is contained in:
Jim Nelson 2011-06-13 15:16:57 -07:00
parent e22fa2b510
commit ac41e9269a
15 changed files with 273 additions and 55 deletions

View file

@ -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 \

View file

@ -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
);
}

View file

@ -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);
}

View file

@ -21,19 +21,31 @@ public class Geary.Engine : Object, Geary.Account {
public async Gee.Collection<Geary.Folder> list_async(string? parent_folder,
Cancellable? cancellable = null) throws Error {
Gee.Collection<Geary.Folder> list = yield local.list_async(parent_folder, cancellable);
Gee.Collection<Geary.Folder> local_list = yield local.list_async(parent_folder, cancellable);
background_update_folders.begin(parent_folder, list);
Gee.Collection<Geary.Folder> engine_list = new Gee.ArrayList<Geary.Folder>();
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<string> get_folder_names(Gee.Collection<Geary.Folder> folders) {
Gee.Set<string> names = new Gee.HashSet<string>();
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<string> names) {
Gee.List<Geary.Folder> excluded = new Gee.ArrayList<Geary.Folder>();
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<Geary.Folder> local_folders) {
Gee.Collection<Geary.Folder> engine_folders) {
Gee.Collection<Geary.Folder> 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<string> local_names = get_folder_names(local_folders);
Gee.Set<string> local_names = get_folder_names(engine_folders);
Gee.Set<string> net_names = get_folder_names(net_folders);
debug("%d local names, %d net names", local_names.size, net_names.size);
Gee.List<Geary.Folder>? to_add = get_excluded_folders(net_folders, local_names);
Gee.List<Geary.Folder>? to_remove = get_excluded_folders(local_folders, net_names);
Gee.List<Geary.Folder>? 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<Geary.Folder> engine_added = null;
if (to_add != null) {
engine_added = new Gee.ArrayList<Geary.Folder>();
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 {

View file

@ -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<Geary.EmailHeader>? 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() {
}
}

View file

@ -16,6 +16,9 @@ public interface Geary.Account : Object {
public abstract async Gee.Collection<Geary.Folder> 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;

View file

@ -6,6 +6,7 @@
public errordomain Geary.EngineError {
OPEN_REQUIRED,
ALREADY_OPEN
ALREADY_OPEN,
NOT_FOUND
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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
}

View file

@ -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 {

View file

@ -17,17 +17,21 @@ public class Geary.Imap.MailboxInformation {
}
public class Geary.Imap.ListResults : Geary.Imap.CommandResults {
private Gee.List<MailboxInformation> list;
private Gee.Map<string, MailboxInformation> map;
public ListResults(StatusResponse status_response, Gee.Map<string, MailboxInformation> map) {
public ListResults(StatusResponse status_response, Gee.Map<string, MailboxInformation> map,
Gee.List<MailboxInformation> list) {
base (status_response);
this.map = map;
this.list = list;
}
public static ListResults decode(CommandResponse response) {
assert(response.is_sealed());
Gee.List<MailboxInformation> list = new Gee.ArrayList<MailboxInformation>();
Gee.Map<string, MailboxInformation> map = new Gee.HashMap<string, MailboxInformation>();
foreach (ServerData data in response.server_data) {
try {
@ -43,7 +47,7 @@ public class Geary.Imap.ListResults : Geary.Imap.CommandResults {
continue;
}
Gee.Collection<MailboxAttribute> list = new Gee.ArrayList<MailboxAttribute>();
Gee.Collection<MailboxAttribute> attrlist = new Gee.ArrayList<MailboxAttribute>();
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<string> get_names() {
return map.keys;
}
public Gee.Collection<MailboxInformation> get_all() {
return map.values;
public Gee.List<MailboxInformation> get_all() {
return list;
}
public MailboxInformation? get_info(string name) {

View file

@ -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(

View file

@ -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<Geary.Folder> folders,
Cancellable? cancellable = null) throws Error {
Gee.List<FolderRow> rows = new Gee.ArrayList<FolderRow>();
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);
}

View file

@ -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 {