diff --git a/src/client/geary-application.vala b/src/client/geary-application.vala index 47fd46d5..2c564db6 100644 --- a/src/client/geary-application.vala +++ b/src/client/geary-application.vala @@ -17,17 +17,11 @@ public class GearyApplication : YorbaApplication { public const string WEBSITE = "http://www.yorba.org"; public static string WEBSITE_LABEL = _("Visit the Yorba web site"); - // Named actions. - public const string ACTION_DONATE = "GearyDonate"; - public const string ACTION_ABOUT = "GearyAbout"; - public const string ACTION_QUIT = "GearyQuit"; - public const string ACTION_NEW_MESSAGE = "GearyNewMessage"; - public const string ACTION_DEBUG_PRINT = "GearyDebugPrint"; - public const string PREFIX = _PREFIX; public const string[] AUTHORS = { "Jim Nelson ", + "Eric Gregory ", null }; @@ -68,7 +62,7 @@ along with Geary; if not, write to the Free Software Foundation, Inc., private static GearyApplication _instance = null; - private MainWindow? main_window = null; + private GearyController? controller = null; private Geary.EngineAccount? account = null; private File exec_dir; @@ -81,18 +75,14 @@ along with Geary; if not, write to the Free Software Foundation, Inc., public override void activate() { // If Geary is already running, show the main window and return. - if (main_window != null) { - main_window.present(); + if (controller != null && controller.main_window != null) { + controller.main_window.present(); return; } exec_dir = (File.new_for_path(Environment.find_program_in_path(args[0]))).get_parent(); // Start Geary. - actions.add_actions(create_actions(), this); - ui_manager.insert_action_group(actions, 0); - load_ui_file("accelerators.ui"); - config = new Configuration(GearyApplication.instance.get_install_dir() != null, GearyApplication.instance.get_exec_dir().get_child("build/src/client").get_path()); @@ -131,15 +121,15 @@ along with Geary; if not, write to the Free Software Foundation, Inc., error("Unable to open mail database for %s: %s", cred.user, err.message); } - main_window = new MainWindow(); - main_window.show_all(); - main_window.start(account); + controller = new GearyController(); + controller.start(account); + return; } public override void exiting(bool panicked) { - if (main_window != null) - main_window.destroy(); + if (controller.main_window != null) + controller.main_window.destroy(); } public File get_user_data_directory() { @@ -194,103 +184,8 @@ along with Geary; if not, write to the Free Software Foundation, Inc., } } - private Gtk.ActionEntry[] create_actions() { - Gtk.ActionEntry[] entries = new Gtk.ActionEntry[0]; - - Gtk.ActionEntry donate = { ACTION_DONATE, null, TRANSLATABLE, null, null, on_donate }; - donate.label = _("_Donate"); - entries += donate; - - Gtk.ActionEntry about = { ACTION_ABOUT, Gtk.Stock.ABOUT, TRANSLATABLE, null, null, on_about }; - about.label = _("_About"); - entries += about; - - Gtk.ActionEntry quit = { ACTION_QUIT, Gtk.Stock.QUIT, TRANSLATABLE, "Q", null, on_quit }; - quit.label = _("_Quit"); - entries += quit; - - Gtk.ActionEntry new_message = { ACTION_NEW_MESSAGE, Gtk.Stock.NEW, TRANSLATABLE, "N", - null, on_new_message }; - new_message.label = _("_New Message"); - entries += new_message; - - Gtk.ActionEntry secret_debug = { ACTION_DEBUG_PRINT, null, null, "P", - null, on_debug_print }; - entries += secret_debug; - - return entries; - } - - public void on_quit() { - GearyApplication.instance.exit(); - } - - public void on_about() { - Gtk.show_about_dialog(main_window, - "program-name", GearyApplication.NAME, - "comments", GearyApplication.DESCRIPTION, - "authors", GearyApplication.AUTHORS, - "copyright", GearyApplication.COPYRIGHT, - "license", GearyApplication.LICENSE, - "version", GearyApplication.VERSION, - "website", GearyApplication.WEBSITE, - "website-label", GearyApplication.WEBSITE_LABEL - ); - } - - public void on_donate() { - try { - Gtk.show_uri(main_window.get_screen(), "http://yorba.org/donate/", Gdk.CURRENT_TIME); - } catch (Error err) { - debug("Unable to open URL. %s", err.message); - } - } - - private void on_new_message() { - ComposerWindow w = new ComposerWindow(); - w.set_position(Gtk.WindowPosition.CENTER); - w.send.connect(on_send); - w.show_all(); - } - - private void on_send(ComposerWindow cw) { - string username; - try { - // TODO: Multiple accounts. - username = Geary.Engine.get_usernames(GearyApplication.instance.get_user_data_directory()) - .get(0); - } catch (Error e) { - error("Unable to get username. Error: %s", e.message); - } - - Geary.ComposedEmail email = new Geary.ComposedEmail(new DateTime.now_local(), - new Geary.RFC822.MailboxAddresses.from_rfc822_string(username)); - - if (!Geary.String.is_empty(cw.to)) - email.to = new Geary.RFC822.MailboxAddresses.from_rfc822_string(cw.to); - - if (!Geary.String.is_empty(cw.cc)) - email.cc = new Geary.RFC822.MailboxAddresses.from_rfc822_string(cw.cc); - - if (!Geary.String.is_empty(cw.bcc)) - email.bcc = new Geary.RFC822.MailboxAddresses.from_rfc822_string(cw.bcc); - - if (!Geary.String.is_empty(cw.subject)) - email.subject = new Geary.RFC822.Subject(cw.subject); - - email.body = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(cw.message)); - - account.send_email_async.begin(email); - - cw.destroy(); - } - - private void on_debug_print() { - main_window.debug_print_selected(); - } - public Gtk.Window get_main_window() { - return main_window; + return controller.main_window; } } diff --git a/src/client/geary-controller.vala b/src/client/geary-controller.vala new file mode 100644 index 00000000..e5e5b48b --- /dev/null +++ b/src/client/geary-controller.vala @@ -0,0 +1,517 @@ +/* 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. + */ + +// Primary controller object for Geary. +public class GearyController { + + private class FetchPreviewOperation : Geary.NonblockingBatchOperation { + public MainWindow owner; + public Geary.Folder folder; + public Geary.EmailIdentifier email_id; + public Geary.Conversation conversation; + + public FetchPreviewOperation(MainWindow owner, Geary.Folder folder, + Geary.EmailIdentifier email_id, Geary.Conversation conversation) { + this.owner = owner; + this.folder = folder; + this.email_id = email_id; + this.conversation = conversation; + } + + public override async Object? execute_async(Cancellable? cancellable) throws Error { + Geary.Email? preview = yield folder.fetch_email_async(email_id, + MessageListStore.WITH_PREVIEW_FIELDS, cancellable); + if (preview != null) + owner.message_list_store.set_preview_for_conversation(conversation, preview); + + return null; + } + } + + 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 FetchSpecialFolderOperation : Geary.NonblockingBatchOperation { + public Geary.Account account; + public Geary.SpecialFolder special_folder; + + public FetchSpecialFolderOperation(Geary.Account account, Geary.SpecialFolder special_folder) { + this.account = account; + this.special_folder = special_folder; + } + + public override async Object? execute_async(Cancellable? cancellable) throws Error { + return yield account.fetch_folder_async(special_folder.path); + } + } + + // Named actions. + public const string ACTION_DONATE = "GearyDonate"; + public const string ACTION_ABOUT = "GearyAbout"; + public const string ACTION_QUIT = "GearyQuit"; + public const string ACTION_NEW_MESSAGE = "GearyNewMessage"; + public const string ACTION_DEBUG_PRINT = "GearyDebugPrint"; + + private const int FETCH_EMAIL_CHUNK_COUNT = 50; + + public MainWindow main_window { get; private set; } + public bool enable_load_more { get; set; default = true; } + + private Cancellable cancellable_folder = new Cancellable(); + private Cancellable cancellable_message = new Cancellable(); + private Geary.EngineAccount? account = null; + private Geary.Folder? current_folder = null; + private Geary.Conversations? current_conversations = null; + private bool second_list_pass_required = false; + + public GearyController() { + // Setup actions. + GearyApplication.instance.actions.add_actions(create_actions(), this); + GearyApplication.instance.ui_manager.insert_action_group( + GearyApplication.instance.actions, 0); + GearyApplication.instance.load_ui_file("accelerators.ui"); + + // Create the main window (must be done after creating actions.) + main_window = new MainWindow(); + + main_window.message_list_view.conversation_selected.connect(on_conversation_selected); + main_window.message_list_view.load_more.connect(on_load_more); + main_window.folder_list_view.folder_selected.connect(on_folder_selected); + } + + ~GearyController() { + if (account != null) + account.folders_added_removed.disconnect(on_folders_added_removed); + } + + private Gtk.ActionEntry[] create_actions() { + Gtk.ActionEntry[] entries = new Gtk.ActionEntry[0]; + + Gtk.ActionEntry donate = { ACTION_DONATE, null, TRANSLATABLE, null, null, on_donate }; + donate.label = _("_Donate"); + entries += donate; + + Gtk.ActionEntry about = { ACTION_ABOUT, Gtk.Stock.ABOUT, TRANSLATABLE, null, null, on_about }; + about.label = _("_About"); + entries += about; + + Gtk.ActionEntry quit = { ACTION_QUIT, Gtk.Stock.QUIT, TRANSLATABLE, "Q", null, on_quit }; + quit.label = _("_Quit"); + entries += quit; + + Gtk.ActionEntry new_message = { ACTION_NEW_MESSAGE, Gtk.Stock.NEW, TRANSLATABLE, "N", + null, on_new_message }; + new_message.label = _("_New Message"); + entries += new_message; + + Gtk.ActionEntry secret_debug = { ACTION_DEBUG_PRINT, null, null, "P", + null, debug_print_selected }; + entries += secret_debug; + + return entries; + } + + public void start(Geary.EngineAccount account) { + this.account = account; + account.folders_added_removed.connect(on_folders_added_removed); + + main_window.folder_list_store.set_user_folders_root_name(account.get_user_folders_label()); + + main_window.show_all(); + do_start.begin(); + } + + private async void do_start() { + try { + // add all the special folders, which are assumed to always exist + Geary.SpecialFolderMap? special_folders = account.get_special_folder_map(); + if (special_folders != null) { + Geary.NonblockingBatch batch = new Geary.NonblockingBatch(); + foreach (Geary.SpecialFolder special_folder in special_folders.get_all()) + batch.add(new FetchSpecialFolderOperation(account, special_folder)); + + debug("Listing special folders"); + yield batch.execute_all_async(); + debug("Completed list of special folders"); + + foreach (int id in batch.get_ids()) { + FetchSpecialFolderOperation op = (FetchSpecialFolderOperation) + batch.get_operation(id); + try { + Geary.Folder folder = (Geary.Folder) batch.get_result(id); + main_window.folder_list_store.add_special_folder(op.special_folder, folder); + } catch (Error inner_error) { + message("Unable to fetch special folder %s: %s", + op.special_folder.path.to_string(), inner_error.message); + } + } + + // If inbox is specified, select that + Geary.SpecialFolder? inbox = special_folders.get_folder(Geary.SpecialFolderType.INBOX); + if (inbox != null) + main_window.folder_list_view.select_path(inbox.path); + } + + // pull down the root-level user folders + Gee.Collection 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"); + main_window.message_list_store.clear(); + + return; + } + + debug("Folder %s selected", folder.to_string()); + + do_select_folder.begin(folder, on_select_folder_completed); + } + + private async void do_select_folder(Geary.Folder folder) throws Error { + cancel_folder(); + main_window.message_list_store.clear(); + + if (current_folder != null) { + yield current_folder.close_async(); + } + + current_folder = folder; + + yield current_folder.open_async(true, cancellable_folder); + + current_conversations = new Geary.Conversations(current_folder, + MessageListStore.REQUIRED_FIELDS); + + current_conversations.monitor_new_messages(cancellable_folder); + + current_conversations.scan_started.connect(on_scan_started); + current_conversations.scan_error.connect(on_scan_error); + current_conversations.scan_completed.connect(on_scan_completed); + current_conversations.conversations_added.connect(on_conversations_added); + current_conversations.conversation_appended.connect(on_conversation_appended); + current_conversations.conversation_trimmed.connect(on_conversation_trimmed); + current_conversations.conversation_removed.connect(on_conversation_removed); + current_conversations.updated_placeholders.connect(on_updated_placeholders); + + // Do a quick-list of the messages (which should return what's in the local store) if + // supported by the Folder, followed by a complete list if needed + second_list_pass_required = + current_folder.get_supported_list_flags().is_all_set(Geary.Folder.ListFlags.FAST); + + // Load all conversations from the DB. + current_conversations.lazy_load(-1, -1, Geary.Folder.ListFlags.FAST, cancellable_folder); + } + + public void on_scan_started(Geary.EmailIdentifier? id, int low, int count) { + debug("on scan started. id = %s low = %d count = %d", id != null ? id.to_string() : "(null)", + low, count); + main_window.message_list_view.enable_load_more = false; + } + + public void on_scan_error(Error err) { + debug("Scan error: %s", err.message); + } + + public void on_scan_completed() { + debug("on scan completed"); + + do_fetch_previews.begin(cancellable_folder); + main_window.message_list_view.enable_load_more = true; + } + + public void on_conversations_added(Gee.Collection conversations) { + debug("on conversation added"); + foreach (Geary.Conversation c in conversations) { + if (!main_window.message_list_store.has_conversation(c)) + main_window.message_list_store.append_conversation(c); + } + } + + public void on_conversation_appended(Geary.Conversation conversation, + Gee.Collection email) { + main_window.message_list_store.update_conversation(conversation); + } + + public void on_conversation_trimmed(Geary.Conversation conversation, Geary.Email email) { + main_window.message_list_store.update_conversation(conversation); + } + + public void on_conversation_removed(Geary.Conversation conversation) { + main_window.message_list_store.remove_conversation(conversation); + } + + public void on_updated_placeholders(Geary.Conversation conversation, + Gee.Collection email) { + main_window.message_list_store.update_conversation(conversation); + } + + private void on_load_more() { + debug("on_load_more"); + main_window.message_list_view.enable_load_more = false; + + Geary.EmailIdentifier? low_id = main_window.message_list_store.get_email_id_lowest(); + if (low_id == null) + return; + + current_conversations.load_by_id_async.begin(low_id, - FETCH_EMAIL_CHUNK_COUNT, + Geary.Folder.ListFlags.EXCLUDING_ID, cancellable_folder, on_load_more_completed); + } + + private void on_load_more_completed(Object? source, AsyncResult result) { + debug("on load more completed"); + try { + current_conversations.load_by_id_async.end(result); + } catch (Error err) { + debug("Error, unable to load conversations: %s", err.message); + } + } + + private async void do_fetch_previews(Cancellable? cancellable) throws Error { + Geary.NonblockingBatch batch = new Geary.NonblockingBatch(); + + int count = main_window.message_list_store.get_count(); + for (int ctr = 0; ctr < count; ctr++) { + Geary.Conversation? conversation; + Geary.Email? email = main_window.message_list_store.get_newest_message_at_index( + ctr, out conversation); + + if (email != null) + batch.add(new FetchPreviewOperation(main_window, current_folder, email.id, + conversation)); + } + + debug("Fetching %d previews", count); + yield batch.execute_all_async(cancellable); + debug("Completed fetching %d previews", count); + + batch.throw_first_exception(); + + // with all the previews fetched, now go back and do a full list (if required) + if (second_list_pass_required) { + second_list_pass_required = false; + debug("Doing second list pass now"); + current_conversations.lazy_load(-1, FETCH_EMAIL_CHUNK_COUNT, Geary.Folder.ListFlags.NONE, + cancellable); + } + } + + private void on_select_folder_completed(Object? source, AsyncResult result) { + try { + do_select_folder.end(result); + } catch (Error err) { + debug("Unable to select folder: %s", err.message); + } + } + + private void on_conversation_selected(Geary.Conversation? conversation) { + cancel_message(); + main_window.message_viewer.clear(); + + if (conversation != null) + do_select_message.begin(conversation, cancellable_message, on_select_message_completed); + } + + private async void do_select_message(Geary.Conversation conversation, Cancellable? + cancellable = null) throws Error { + if (current_folder == null) { + debug("Conversation selected with no folder selected"); + + return; + } + + foreach (Geary.Email email in conversation.get_pool_sorted(compare_email)) { + Geary.Email full_email = yield current_folder.fetch_email_async(email.id, + MessageViewer.REQUIRED_FIELDS, cancellable); + + if (cancellable.is_cancelled()) + break; + + main_window.message_viewer.add_message(full_email); + } + } + + private void on_select_message_completed(Object? source, AsyncResult result) { + try { + do_select_message.end(result); + } catch (Error err) { + if (!(err is IOError.CANCELLED)) + debug("Unable to select message: %s", err.message); + } + } + + private void on_folders_added_removed(Gee.Collection? added, + Gee.Collection? removed) { + if (added != null && added.size > 0) { + Gee.Set? ignored_paths = account.get_ignored_paths(); + + Gee.ArrayList skipped = new Gee.ArrayList(); + foreach (Geary.Folder folder in added) { + if (ignored_paths != null && ignored_paths.contains(folder.get_path())) + skipped.add(folder); + else + main_window.folder_list_store.add_user_folder(folder); + } + + Gee.Collection remaining = added; + if (skipped.size > 0) { + remaining = new Gee.ArrayList(); + remaining.add_all(added); + remaining.remove_all(skipped); + } + + search_folders_for_children.begin(remaining); + } + } + + private async void search_folders_for_children(Gee.Collection folders) { + Geary.NonblockingBatch batch = new Geary.NonblockingBatch(); + foreach (Geary.Folder folder in folders) + batch.add(new ListFoldersOperation(account, folder.get_path())); + + debug("Listing folder children"); + try { + yield batch.execute_all_async(); + } catch (Error err) { + debug("Unable to execute batch: %s", err.message); + + return; + } + debug("Completed listing folder children"); + + Gee.ArrayList accumulator = new Gee.ArrayList(); + foreach (int id in batch.get_ids()) { + ListFoldersOperation op = (ListFoldersOperation) batch.get_operation(id); + try { + Gee.Collection children = (Gee.Collection) + 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); + } + + public void debug_print_selected() { + if (main_window.message_viewer.messages.size == 0) { + debug("Nothing to print"); + return; + } + + debug("---------------------------"); + foreach (Geary.Email e in main_window.message_viewer.messages) { + debug("Message: %s", e.id.to_string()); + if (e.header != null) + debug("\n%s", e.header.buffer.to_utf8()); + else + debug("No message data."); + + debug("---------------------------"); + } + } + + private void cancel_folder() { + Cancellable old_cancellable = cancellable_folder; + cancellable_folder = new Cancellable(); + cancel_message(); + + old_cancellable.cancel(); + } + + private void cancel_message() { + Cancellable old_cancellable = cancellable_message; + cancellable_message = new Cancellable(); + + old_cancellable.cancel(); + } + + public void on_quit() { + GearyApplication.instance.exit(); + } + + public void on_about() { + Gtk.show_about_dialog(main_window, + "program-name", GearyApplication.NAME, + "comments", GearyApplication.DESCRIPTION, + "authors", GearyApplication.AUTHORS, + "copyright", GearyApplication.COPYRIGHT, + "license", GearyApplication.LICENSE, + "version", GearyApplication.VERSION, + "website", GearyApplication.WEBSITE, + "website-label", GearyApplication.WEBSITE_LABEL + ); + } + + public void on_donate() { + try { + Gtk.show_uri(main_window.get_screen(), "http://yorba.org/donate/", Gdk.CURRENT_TIME); + } catch (Error err) { + debug("Unable to open URL. %s", err.message); + } + } + + private void on_new_message() { + ComposerWindow w = new ComposerWindow(); + w.set_position(Gtk.WindowPosition.CENTER); + w.send.connect(on_send); + w.show_all(); + } + + private void on_send(ComposerWindow cw) { + string username; + try { + // TODO: Multiple accounts. + username = Geary.Engine.get_usernames(GearyApplication.instance.get_user_data_directory()) + .get(0); + } catch (Error e) { + error("Unable to get username. Error: %s", e.message); + } + + Geary.ComposedEmail email = new Geary.ComposedEmail(new DateTime.now_local(), + new Geary.RFC822.MailboxAddresses.from_rfc822_string(username)); + + if (!Geary.String.is_empty(cw.to)) + email.to = new Geary.RFC822.MailboxAddresses.from_rfc822_string(cw.to); + + if (!Geary.String.is_empty(cw.cc)) + email.cc = new Geary.RFC822.MailboxAddresses.from_rfc822_string(cw.cc); + + if (!Geary.String.is_empty(cw.bcc)) + email.bcc = new Geary.RFC822.MailboxAddresses.from_rfc822_string(cw.bcc); + + if (!Geary.String.is_empty(cw.subject)) + email.subject = new Geary.RFC822.Subject(cw.subject); + + email.body = new Geary.RFC822.Text(new Geary.Memory.StringBuffer(cw.message)); + + account.send_email_async.begin(email); + + cw.destroy(); + } +} + diff --git a/src/client/ui/main-toolbar.vala b/src/client/ui/main-toolbar.vala index f5b63ba3..c407195e 100644 --- a/src/client/ui/main-toolbar.vala +++ b/src/client/ui/main-toolbar.vala @@ -19,7 +19,7 @@ public class MainToolbar : Gtk.VBox { Gtk.ToolButton new_message = builder.get_object("new_button") as Gtk.ToolButton; new_message.set_related_action(GearyApplication.instance.actions.get_action( - GearyApplication.ACTION_NEW_MESSAGE)); + GearyController.ACTION_NEW_MESSAGE)); menu_button = builder.get_object("menu_button") as Gtk.ToolButton; menu_button.clicked.connect(on_show_menu); diff --git a/src/client/ui/main-window.vala b/src/client/ui/main-window.vala index c5f614fc..1abaff16 100644 --- a/src/client/ui/main-window.vala +++ b/src/client/ui/main-window.vala @@ -6,149 +6,31 @@ public class MainWindow : Gtk.Window { private const int MESSAGE_LIST_WIDTH = 250; - private const int FETCH_EMAIL_CHUNK_COUNT = 50; - private class FetchPreviewOperation : Geary.NonblockingBatchOperation { - public MainWindow owner; - public Geary.Folder folder; - public Geary.EmailIdentifier email_id; - public Geary.Conversation conversation; - - public FetchPreviewOperation(MainWindow owner, Geary.Folder folder, - Geary.EmailIdentifier email_id, Geary.Conversation conversation) { - this.owner = owner; - this.folder = folder; - this.email_id = email_id; - this.conversation = conversation; - } - - public override async Object? execute_async(Cancellable? cancellable) throws Error { - Geary.Email? preview = yield folder.fetch_email_async(email_id, - MessageListStore.WITH_PREVIEW_FIELDS, cancellable); - if (preview != null) - owner.message_list_store.set_preview_for_conversation(conversation, preview); - - return null; - } - } + public FolderListStore folder_list_store { get; private set; default = new FolderListStore(); } + public MessageListStore message_list_store { get; private set; default = new MessageListStore(); } + public MainToolbar main_toolbar { get; private set; } + public MessageListView message_list_view { get; private set; } + public FolderListView folder_list_view { get; private set; } + public MessageViewer message_viewer { get; private set; default = new MessageViewer(); } - 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 FetchSpecialFolderOperation : Geary.NonblockingBatchOperation { - public Geary.Account account; - public Geary.SpecialFolder special_folder; - - public FetchSpecialFolderOperation(Geary.Account account, Geary.SpecialFolder special_folder) { - this.account = account; - this.special_folder = special_folder; - } - - public override async Object? execute_async(Cancellable? cancellable) throws Error { - return yield account.fetch_folder_async(special_folder.path); - } - } - - private MainToolbar main_toolbar; - private MessageListStore message_list_store = new MessageListStore(); - private MessageListView message_list_view; - private FolderListStore folder_list_store = new FolderListStore(); - private FolderListView folder_list_view; - private MessageViewer message_viewer = new MessageViewer(); - private Geary.EngineAccount? account = null; - private Geary.Folder? current_folder = null; - private Geary.Conversations? current_conversations = null; - private bool second_list_pass_required = false; private int window_width; private int window_height; private bool window_maximized; private Gtk.HPaned folder_paned = new Gtk.HPaned(); private Gtk.HPaned messages_paned = new Gtk.HPaned(); - private Cancellable cancellable_folder = new Cancellable(); - private Cancellable cancellable_message = new Cancellable(); public MainWindow() { title = GearyApplication.NAME; message_list_view = new MessageListView(message_list_store); - message_list_view.conversation_selected.connect(on_conversation_selected); - message_list_view.load_more.connect(on_load_more); - folder_list_view = new FolderListView(folder_list_store); - folder_list_view.folder_selected.connect(on_folder_selected); add_accel_group(GearyApplication.instance.ui_manager.get_accel_group()); create_layout(); } - ~MainWindow() { - if (account != null) - account.folders_added_removed.disconnect(on_folders_added_removed); - } - - public void start(Geary.EngineAccount account) { - this.account = account; - account.folders_added_removed.connect(on_folders_added_removed); - - folder_list_store.set_user_folders_root_name(account.get_user_folders_label()); - - do_start.begin(); - } - - private async void do_start() { - try { - // add all the special folders, which are assumed to always exist - Geary.SpecialFolderMap? special_folders = account.get_special_folder_map(); - if (special_folders != null) { - Geary.NonblockingBatch batch = new Geary.NonblockingBatch(); - foreach (Geary.SpecialFolder special_folder in special_folders.get_all()) - batch.add(new FetchSpecialFolderOperation(account, special_folder)); - - debug("Listing special folders"); - yield batch.execute_all_async(); - debug("Completed list of special folders"); - - foreach (int id in batch.get_ids()) { - FetchSpecialFolderOperation op = (FetchSpecialFolderOperation) batch.get_operation(id); - try { - Geary.Folder folder = (Geary.Folder) batch.get_result(id); - folder_list_store.add_special_folder(op.special_folder, folder); - } catch (Error inner_error) { - message("Unable to fetch special folder %s: %s", op.special_folder.path.to_string(), - inner_error.message); - } - - } - - // If inbox is specified, select that - Geary.SpecialFolder? inbox = special_folders.get_folder(Geary.SpecialFolderType.INBOX); - if (inbox != null) - folder_list_view.select_path(inbox.path); - } - - // pull down the root-level user folders - Gee.Collection 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); - } - } - public override void show_all() { set_default_size(GearyApplication.instance.config.window_width, GearyApplication.instance.config.window_height); @@ -222,274 +104,5 @@ public class MainWindow : Gtk.Window { add(main_layout); } - - private void on_folder_selected(Geary.Folder? folder) { - if (folder == null) { - debug("no folder selected"); - message_list_store.clear(); - - return; - } - - debug("Folder %s selected", folder.to_string()); - - do_select_folder.begin(folder, on_select_folder_completed); - } - - private async void do_select_folder(Geary.Folder folder) throws Error { - cancel_folder(); - message_list_store.clear(); - - if (current_folder != null) { - yield current_folder.close_async(); - } - - current_folder = folder; - - yield current_folder.open_async(true, cancellable_folder); - - current_conversations = new Geary.Conversations(current_folder, - MessageListStore.REQUIRED_FIELDS); - - current_conversations.monitor_new_messages(cancellable_folder); - - current_conversations.scan_started.connect(on_scan_started); - current_conversations.scan_error.connect(on_scan_error); - current_conversations.scan_completed.connect(on_scan_completed); - current_conversations.conversations_added.connect(on_conversations_added); - current_conversations.conversation_appended.connect(on_conversation_appended); - current_conversations.conversation_trimmed.connect(on_conversation_trimmed); - current_conversations.conversation_removed.connect(on_conversation_removed); - current_conversations.updated_placeholders.connect(on_updated_placeholders); - - // Do a quick-list of the messages (which should return what's in the local store) if - // supported by the Folder, followed by a complete list if needed - second_list_pass_required = - current_folder.get_supported_list_flags().is_all_set(Geary.Folder.ListFlags.FAST); - - // Load all conversations from the DB. - current_conversations.lazy_load(-1, -1, Geary.Folder.ListFlags.FAST, cancellable_folder); - } - - public void on_scan_started(Geary.EmailIdentifier? id, int low, int count) { - debug("on scan started. id = %s low = %d count = %d", id != null ? id.to_string() : "(null)", - low, count); - message_list_view.enable_load_more = false; - } - - public void on_scan_error(Error err) { - debug("Scan error: %s", err.message); - } - - public void on_scan_completed() { - debug("on scan completed"); - - do_fetch_previews.begin(cancellable_folder); - message_list_view.enable_load_more = true; - } - - public void on_conversations_added(Gee.Collection conversations) { - debug("on conversation added"); - foreach (Geary.Conversation c in conversations) { - if (!message_list_store.has_conversation(c)) - message_list_store.append_conversation(c); - } - } - - public void on_conversation_appended(Geary.Conversation conversation, - Gee.Collection email) { - message_list_store.update_conversation(conversation); - } - - public void on_conversation_trimmed(Geary.Conversation conversation, Geary.Email email) { - message_list_store.update_conversation(conversation); - } - - public void on_conversation_removed(Geary.Conversation conversation) { - message_list_store.remove_conversation(conversation); - } - - public void on_updated_placeholders(Geary.Conversation conversation, - Gee.Collection email) { - message_list_store.update_conversation(conversation); - } - - private void on_load_more() { - debug("on_load_more"); - message_list_view.enable_load_more = false; - - Geary.EmailIdentifier? low_id = message_list_store.get_email_id_lowest(); - if (low_id == null) - return; - - current_conversations.load_by_id_async.begin(low_id, - FETCH_EMAIL_CHUNK_COUNT, - Geary.Folder.ListFlags.EXCLUDING_ID, cancellable_folder, on_load_more_completed); - } - - private void on_load_more_completed(Object? source, AsyncResult result) { - debug("on load more completed"); - try { - current_conversations.load_by_id_async.end(result); - } catch (Error err) { - debug("Error, unable to load conversations: %s", err.message); - } - } - - private async void do_fetch_previews(Cancellable? cancellable) throws Error { - Geary.NonblockingBatch batch = new Geary.NonblockingBatch(); - - int count = message_list_store.get_count(); - for (int ctr = 0; ctr < count; ctr++) { - Geary.Conversation? conversation; - Geary.Email? email = message_list_store.get_newest_message_at_index(ctr, out conversation); - if (email != null) - batch.add(new FetchPreviewOperation(this, current_folder, email.id, conversation)); - } - - debug("Fetching %d previews", count); - yield batch.execute_all_async(cancellable); - debug("Completed fetching %d previews", count); - - batch.throw_first_exception(); - - // with all the previews fetched, now go back and do a full list (if required) - if (second_list_pass_required) { - second_list_pass_required = false; - debug("Doing second list pass now"); - current_conversations.lazy_load(-1, FETCH_EMAIL_CHUNK_COUNT, Geary.Folder.ListFlags.NONE, - cancellable); - } - } - - private void on_select_folder_completed(Object? source, AsyncResult result) { - try { - do_select_folder.end(result); - } catch (Error err) { - debug("Unable to select folder: %s", err.message); - } - } - - private void on_conversation_selected(Geary.Conversation? conversation) { - cancel_message(); - message_viewer.clear(); - - if (conversation != null) - do_select_message.begin(conversation, cancellable_message, on_select_message_completed); - } - - private async void do_select_message(Geary.Conversation conversation, Cancellable? - cancellable = null) throws Error { - if (current_folder == null) { - debug("Conversation selected with no folder selected"); - - return; - } - - foreach (Geary.Email email in conversation.get_pool_sorted(compare_email)) { - Geary.Email full_email = yield current_folder.fetch_email_async(email.id, - MessageViewer.REQUIRED_FIELDS, cancellable); - - if (cancellable.is_cancelled()) - break; - - message_viewer.add_message(full_email); - } - } - - private void on_select_message_completed(Object? source, AsyncResult result) { - try { - do_select_message.end(result); - } catch (Error err) { - if (!(err is IOError.CANCELLED)) - debug("Unable to select message: %s", err.message); - } - } - - private void on_folders_added_removed(Gee.Collection? added, - Gee.Collection? removed) { - if (added != null && added.size > 0) { - Gee.Set? ignored_paths = account.get_ignored_paths(); - - Gee.ArrayList skipped = new Gee.ArrayList(); - foreach (Geary.Folder folder in added) { - if (ignored_paths != null && ignored_paths.contains(folder.get_path())) - skipped.add(folder); - else - folder_list_store.add_user_folder(folder); - } - - Gee.Collection remaining = added; - if (skipped.size > 0) { - remaining = new Gee.ArrayList(); - remaining.add_all(added); - remaining.remove_all(skipped); - } - - search_folders_for_children.begin(remaining); - } - } - - private async void search_folders_for_children(Gee.Collection folders) { - Geary.NonblockingBatch batch = new Geary.NonblockingBatch(); - foreach (Geary.Folder folder in folders) - batch.add(new ListFoldersOperation(account, folder.get_path())); - - debug("Listing folder children"); - try { - yield batch.execute_all_async(); - } catch (Error err) { - debug("Unable to execute batch: %s", err.message); - - return; - } - debug("Completed listing folder children"); - - Gee.ArrayList accumulator = new Gee.ArrayList(); - foreach (int id in batch.get_ids()) { - ListFoldersOperation op = (ListFoldersOperation) batch.get_operation(id); - try { - Gee.Collection children = (Gee.Collection) 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); - } - - public void debug_print_selected() { - if (message_viewer.messages.size == 0) { - debug("Nothing to print"); - return; - } - - debug("---------------------------"); - foreach (Geary.Email e in message_viewer.messages) { - debug("Message: %s", e.id.to_string()); - if (e.header != null) - debug("\n%s", e.header.buffer.to_utf8()); - else - debug("No message data."); - - debug("---------------------------"); - } - } - - private void cancel_folder() { - Cancellable old_cancellable = cancellable_folder; - cancellable_folder = new Cancellable(); - cancel_message(); - - old_cancellable.cancel(); - } - - private void cancel_message() { - Cancellable old_cancellable = cancellable_message; - cancellable_message = new Cancellable(); - - old_cancellable.cancel(); - } } diff --git a/src/client/wscript_build b/src/client/wscript_build index 8cb182f5..7d41d440 100644 --- a/src/client/wscript_build +++ b/src/client/wscript_build @@ -6,6 +6,7 @@ client_src = [ 'geary-application.vala', 'geary-config.vala', +'geary-controller.vala', 'main.vala', 'ui/background-box.vala',