Add folder avail. signals to Account; closes #6208

Squashed commit of the following:

commit 3b133030800699c2106bfb30a104cd58710cf245
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 17 12:06:11 2013 -0800

    Clean up state on close; ref #6208

commit a4637a49277740b64ac198fa057eb52556c80efa
Author: Charles Lindsay <chaz@yorba.org>
Date:   Thu Jan 17 11:58:22 2013 -0800

    Simplify build_folder; ref #6208

commit e9afa248ce525f0496220cfbb0d8529e38cbceae
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 16 18:10:00 2013 -0800

    Cleanup; ref #6208

commit 95ee3a2c6fcfd4b9b7e10aae1100fe7a8697c25a
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 16 18:02:02 2013 -0800

    Aggregate building folders; ref #6208

commit 28ee5df718e08c0121b26b1934b619a409fb451a
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 16 16:49:17 2013 -0800

    Make local-only folders available; ref #6208

    This also slightly cleans up the special-casing of local-only folders.

commit 19c48fe657fca989fe0b519d39833dd8e0d95cfe
Author: Charles Lindsay <chaz@yorba.org>
Date:   Wed Jan 16 15:44:55 2013 -0800

    Use new interface; ref #6208

commit f64f82f9362a0081a3e4f7a21dc401f3d4ca320f
Author: Charles Lindsay <chaz@yorba.org>
Date:   Mon Jan 14 17:11:04 2013 -0800

    Try new interface in Account; ref #6208
This commit is contained in:
Charles Lindsay 2013-01-17 12:50:30 -08:00
parent 1ab3b22d02
commit 13b804eca0
4 changed files with 113 additions and 107 deletions

View file

@ -6,34 +6,6 @@
// Primary controller object for Geary.
public class GearyController {
private class ListFoldersOperation : Geary.NonblockingBatchOperation {
public Geary.Account account;
public Geary.FolderPath path;
public ListFoldersOperation(Geary.Account account, Geary.FolderPath path) {
this.account = account;
this.path = path;
}
public override async Object? execute_async(Cancellable? cancellable) throws Error {
return yield account.list_folders_async(path, cancellable);
}
}
private class FetchFolderOperation : Geary.NonblockingBatchOperation {
public Geary.Account account;
public Geary.FolderPath folder_path;
public FetchFolderOperation(Geary.Account account, Geary.FolderPath folder_path) {
this.account = account;
this.folder_path = folder_path;
}
public override async Object? execute_async(Cancellable? cancellable) throws Error {
return yield account.fetch_folder_async(folder_path);
}
}
// Named actions.
public const string ACTION_HELP = "GearyHelp";
public const string ACTION_ABOUT = "GearyAbout";
@ -274,7 +246,7 @@ public class GearyController {
cancel_inbox();
cancel_message();
account.folders_added_removed.disconnect(on_folders_added_removed);
account.folders_available_unavailable.disconnect(on_folders_available_unavailable);
main_window.title = GearyApplication.NAME;
main_window.conversation_list_store.account_owner_email = null;
@ -312,6 +284,8 @@ public class GearyController {
// Connect the new account, if any.
if (account != null) {
account.folders_available_unavailable.connect(on_folders_available_unavailable);
try {
yield account.open_async(cancellable);
} catch (Error open_err) {
@ -323,7 +297,6 @@ public class GearyController {
GearyApplication.instance.panic();
}
account.folders_added_removed.connect(on_folders_added_removed);
account.email_sent.connect(on_sent);
if (account.settings.service_provider == Geary.ServiceProvider.YAHOO)
@ -331,7 +304,6 @@ public class GearyController {
main_window.conversation_list_store.account_owner_email = account.settings.email.address;
main_window.folder_list.set_user_folders_root_name(_("Labels"));
load_folders.begin(cancellable_folder);
}
}
@ -361,19 +333,6 @@ public class GearyController {
}
}
private async void load_folders(Cancellable? cancellable) {
try {
// pull down the root-level user folders and recursively add to sidebar
Gee.Collection<Geary.Folder> folders = yield account.list_folders_async(null);
if (folders != null)
on_folders_added_removed(folders, null);
else
debug("no folders");
} catch (Error err) {
message("%s", err.message);
}
}
private void on_folder_selected(Geary.Folder? folder) {
if (folder == null) {
debug("no folder selected");
@ -624,11 +583,11 @@ public class GearyController {
main_window.folder_list.add_folder(folder);
}
private void on_folders_added_removed(Gee.Collection<Geary.Folder>? added,
Gee.Collection<Geary.Folder>? removed) {
private void on_folders_available_unavailable(Gee.Collection<Geary.Folder>? available,
Gee.Collection<Geary.Folder>? unavailable) {
if (added != null && added.size > 0) {
foreach (Geary.Folder folder in added) {
if (available != null && available.size > 0) {
foreach (Geary.Folder folder in available) {
main_window.folder_list.add_folder(folder);
main_window.main_toolbar.copy_folder_menu.add_folder(folder);
main_window.main_toolbar.move_folder_menu.add_folder(folder);
@ -660,49 +619,9 @@ public class GearyController {
folder.special_folder_type_changed.connect(on_special_folder_type_changed);
}
search_folders_for_children.begin(added);
}
}
private async void search_folders_for_children(Gee.Collection<Geary.Folder> folders) {
set_busy(true);
Geary.NonblockingBatch batch = new Geary.NonblockingBatch();
foreach (Geary.Folder folder in folders) {
// Search for children unless Folder is absolutely certain it doesn't have any
if (folder.has_children().is_possible())
batch.add(new ListFoldersOperation(account, folder.get_path()));
}
debug("Listing %d folder children", batch.size);
try {
yield batch.execute_all_async();
} catch (Error err) {
debug("Unable to execute batch: %s", err.message);
set_busy(false);
return;
}
debug("Completed listing folder children");
Gee.ArrayList<Geary.Folder> accumulator = new Gee.ArrayList<Geary.Folder>();
foreach (int id in batch.get_ids()) {
ListFoldersOperation op = (ListFoldersOperation) batch.get_operation(id);
try {
Gee.Collection<Geary.Folder> children = (Gee.Collection<Geary.Folder>)
batch.get_result(id);
accumulator.add_all(children);
} catch (Error err2) {
debug("Unable to list children of %s: %s", op.path.to_string(), err2.message);
}
}
if (accumulator.size > 0)
on_folders_added_removed(accumulator, null);
set_busy(false);
}
private void cancel_folder() {
Cancellable old_cancellable = cancellable_folder;
cancellable_folder = new Cancellable();

View file

@ -14,6 +14,11 @@ public abstract class Geary.AbstractAccount : Object, Geary.Account {
this.settings = settings;
}
protected virtual void notify_folders_available_unavailable(Gee.Collection<Geary.Folder>? available,
Gee.Collection<Geary.Folder>? unavailable) {
folders_available_unavailable(available, unavailable);
}
protected virtual void notify_folders_added_removed(Gee.Collection<Geary.Folder>? added,
Gee.Collection<Geary.Folder>? removed) {
folders_added_removed(added, removed);

View file

@ -24,6 +24,18 @@ public interface Geary.Account : Object {
public signal void report_problem(Geary.Account.Problem problem, Geary.AccountSettings settings,
Error? err);
/**
* Fired when folders become available or unavailable in the account.
* Folders become available when the account is first opened or when
* they're created later; they become unavailable when the account is
* closed or they're deleted later.
*/
public signal void folders_available_unavailable(Gee.Collection<Geary.Folder>? available,
Gee.Collection<Geary.Folder>? unavailable);
/**
* Fired when folders are created or deleted.
*/
public signal void folders_added_removed(Gee.Collection<Geary.Folder>? added,
Gee.Collection<Geary.Folder>? removed);
@ -48,6 +60,12 @@ public interface Geary.Account : Object {
protected abstract void notify_report_problem(Geary.Account.Problem problem,
Geary.AccountSettings? settings, Error? err);
/**
* Signal notification method for subclasses to use.
*/
public abstract void notify_folders_available_unavailable(Gee.Collection<Geary.Folder>? available,
Gee.Collection<Geary.Folder>? unavailable);
/**
* Signal notification method for subclasses to use.
*/

View file

@ -15,7 +15,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
FolderPath, Imap.FolderProperties>(Hashable.hash_func, Equalable.equal_func);
private Gee.HashMap<FolderPath, GenericFolder> existing_folders = new Gee.HashMap<
FolderPath, GenericFolder>(Hashable.hash_func, Equalable.equal_func);
private Gee.HashSet<FolderPath> local_only = new Gee.HashSet<FolderPath>(
private Gee.HashMap<FolderPath, Folder> local_only = new Gee.HashMap<FolderPath, Folder>(
Hashable.hash_func, Equalable.equal_func);
public GenericAccount(string name, Geary.AccountSettings settings, Imap.Account remote,
@ -35,7 +35,6 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
if (outbox_path == null) {
outbox_path = new SmtpOutboxFolderRoot();
local_only.add(outbox_path);
}
}
@ -48,6 +47,26 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
throw new EngineError.OPEN_REQUIRED("Account %s not opened", to_string());
}
protected override void notify_folders_available_unavailable(Gee.Collection<Geary.Folder>? available,
Gee.Collection<Geary.Folder>? unavailable) {
base.notify_folders_available_unavailable(available, unavailable);
if (available != null) {
foreach(Folder f in available) {
if (f.has_children().is_possible())
enumerate_folders_async.begin(f.get_path(), null, on_enumerate_folders_async_complete);
}
}
}
private void on_enumerate_folders_async_complete(Object? object, AsyncResult result) {
try {
enumerate_folders_async.end(result);
} catch (Error e) {
debug("Error enumerating subfolders: %s", e.message);
}
}
public override async void open_async(Cancellable? cancellable = null) throws Error {
if (open)
throw new EngineError.ALREADY_OPEN("Account %s already opened", to_string());
@ -56,6 +75,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
// outbox is now available
local.outbox.report_problem.connect(notify_report_problem);
local_only.set(outbox_path, local.outbox);
// need to back out local.open_async() if remote fails
try {
@ -74,11 +94,17 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
open = true;
notify_opened();
notify_folders_available_unavailable(local_only.values, null);
yield enumerate_folders_async(null, cancellable);
}
public override async void close_async(Cancellable? cancellable = null) throws Error {
if (!open)
return;
notify_folders_available_unavailable(null, local_only.values);
notify_folders_available_unavailable(null, existing_folders.values);
local.outbox.report_problem.disconnect(notify_report_problem);
@ -96,6 +122,10 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
} catch (Error rclose_err) {
remote_err = rclose_err;
}
properties_map.clear();
existing_folders.clear();
local_only.clear();
if (local_err != null)
throw local_err;
@ -113,17 +143,48 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
ImapDB.Account local_account, ImapDB.Folder local_folder);
private GenericFolder build_folder(ImapDB.Folder local_folder) {
GenericFolder? folder = existing_folders.get(local_folder.get_path());
if (folder != null)
return folder;
folder = new_folder(local_folder.get_path(), remote, local, local_folder);
existing_folders.set(folder.get_path(), folder);
return folder;
return Geary.Collection.get_first(build_folders(new Geary.Singleton<ImapDB.Folder>(local_folder)));
}
private Gee.Collection<GenericFolder> build_folders(Gee.Collection<ImapDB.Folder> local_folders) {
Gee.ArrayList<ImapDB.Folder> folders_to_build = new Gee.ArrayList<ImapDB.Folder>();
Gee.ArrayList<GenericFolder> built_folders = new Gee.ArrayList<GenericFolder>();
Gee.ArrayList<GenericFolder> return_folders = new Gee.ArrayList<GenericFolder>();
foreach(ImapDB.Folder local_folder in local_folders) {
if (existing_folders.has_key(local_folder.get_path()))
return_folders.add(existing_folders.get(local_folder.get_path()));
else
folders_to_build.add(local_folder);
}
foreach(ImapDB.Folder folder_to_build in folders_to_build) {
GenericFolder folder = new_folder(folder_to_build.get_path(), remote, local, folder_to_build);
existing_folders.set(folder.get_path(), folder);
built_folders.add(folder);
return_folders.add(folder);
}
if (built_folders.size > 0)
notify_folders_available_unavailable(built_folders, null);
return return_folders;
}
public override async Gee.Collection<Geary.Folder> list_folders_async(Geary.FolderPath? parent,
Cancellable? cancellable = null) throws Error {
Gee.ArrayList<Geary.Folder> matches = new Gee.ArrayList<Geary.Folder>();
foreach(FolderPath path in existing_folders.keys) {
FolderPath? path_parent = path.get_parent();
if ((parent == null && path_parent == null) ||
(parent != null && path_parent != null && path_parent.equals(parent))) {
matches.add(existing_folders.get(path));
}
}
return matches;
}
private async Gee.Collection<Geary.Folder> enumerate_folders_async(Geary.FolderPath? parent,
Cancellable? cancellable = null) throws Error {
check_open();
@ -138,13 +199,12 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
Gee.Collection<Geary.Folder> engine_list = new Gee.ArrayList<Geary.Folder>();
if (local_list != null && local_list.size > 0) {
foreach (ImapDB.Folder local_folder in local_list)
engine_list.add(build_folder(local_folder));
engine_list.add_all(build_folders(local_list));
}
// Add Outbox to root
// Add local folders (assume that local-only folders always go in root)
if (parent == null)
engine_list.add(local.outbox);
engine_list.add_all(local_only.values);
background_update_folders.begin(parent, engine_list, cancellable);
@ -170,8 +230,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
Cancellable? cancellable = null) throws Error {
check_open();
if (path.equals(local.outbox.get_path()))
return local.outbox;
if (local_only.has_key(path))
return local_only.get(path);
try {
return build_folder((ImapDB.Folder) yield local.fetch_folder_async(path, cancellable));
@ -255,7 +315,7 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
// it
Gee.List<Geary.Folder>? to_remove = new Gee.ArrayList<Geary.Imap.Folder>();
foreach (Geary.Folder folder in engine_folders) {
if (!remote_paths.contains(folder.get_path()) && !local_only.contains(folder.get_path()))
if (!remote_paths.contains(folder.get_path()) && !local_only.keys.contains(folder.get_path()))
to_remove.add(folder);
}
@ -281,10 +341,12 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
Gee.Collection<Geary.Folder> engine_added = null;
if (to_add != null) {
engine_added = new Gee.ArrayList<Geary.Folder>();
Gee.ArrayList<ImapDB.Folder> folders_to_build = new Gee.ArrayList<ImapDB.Folder>();
foreach (Geary.Imap.Folder remote_folder in to_add) {
try {
engine_added.add(build_folder((ImapDB.Folder) yield local.fetch_folder_async(
remote_folder.get_path(), cancellable)));
folders_to_build.add((ImapDB.Folder) yield local.fetch_folder_async(
remote_folder.get_path(), cancellable));
} catch (Error convert_err) {
// This isn't fatal, but irksome ... in the future, when local folders are
// removed, it's possible for one to disappear between cloning it and fetching
@ -292,6 +354,8 @@ private abstract class Geary.ImapEngine.GenericAccount : Geary.AbstractAccount {
debug("Unable to fetch local folder after cloning: %s", convert_err.message);
}
}
engine_added.add_all(build_folders(folders_to_build));
}
// TODO: Remove local folders no longer available remotely.