[geary/mjog/user-plugins: 23/26] Make plugin activation and deactivation async and throw errors



commit df7889d7326dfc0e04f1e3225a1d0d323ef0fb27
Author: Michael Gratton <mike vee net>
Date:   Wed Mar 11 12:09:43 2020 +1100

    Make plugin activation and deactivation async and throw errors
    
    This makes plugin implementation substatially less verbose.

 .../application/application-plugin-manager.vala    | 110 ++++++++++++++++++---
 .../desktop-notifications.vala                     |  50 +++-------
 .../plugin/messaging-menu/messaging-menu.vala      |  38 +++----
 .../notification-badge/notification-badge.vala     |  70 +++++--------
 src/client/plugin/plugin-plugin-base.vala          |  19 +++-
 5 files changed, 165 insertions(+), 122 deletions(-)
---
diff --git a/src/client/application/application-plugin-manager.vala 
b/src/client/application/application-plugin-manager.vala
index 0299727c..bc2e809f 100644
--- a/src/client/application/application-plugin-manager.vala
+++ b/src/client/application/application-plugin-manager.vala
@@ -19,6 +19,29 @@ public class Application.PluginManager : GLib.Object {
     };
 
 
+    private class PluginContext {
+
+
+        public Peas.PluginInfo info { get; private set; }
+        public Plugin.PluginBase plugin { get; private set; }
+
+
+        public PluginContext(Peas.PluginInfo info, Plugin.PluginBase plugin) {
+            this.info = info;
+            this.plugin = plugin;
+        }
+
+        public async void activate() throws GLib.Error {
+            yield this.plugin.activate();
+        }
+
+        public async void deactivate(bool is_shutdown) throws GLib.Error {
+            yield this.plugin.deactivate(is_shutdown);
+        }
+
+    }
+
+
     private class ApplicationImpl : Geary.BaseObject, Plugin.Application {
 
 
@@ -42,6 +65,21 @@ public class Application.PluginManager : GLib.Object {
     }
 
 
+    /** Emitted when a plugin is successfully loaded and activated. */
+    public signal void plugin_activated(Peas.PluginInfo info);
+
+    /** Emitted when a plugin raised an error loading or activating. */
+    public signal void plugin_error(Peas.PluginInfo info, GLib.Error error);
+
+    /**
+     * Emitted when a plugin was unloaded.
+     *
+     * If the given error is not null, it was raised on deactivate.
+     */
+    public signal void plugin_deactivated(Peas.PluginInfo info,
+                                          GLib.Error? error);
+
+
     private Client application;
     private Peas.Engine plugins;
     private bool is_shutdown = false;
@@ -49,8 +87,8 @@ public class Application.PluginManager : GLib.Object {
 
     private FolderStoreFactory folders_factory;
 
-    private Gee.Map<Peas.PluginInfo,Plugin.PluginBase> plugin_set =
-        new Gee.HashMap<Peas.PluginInfo,Plugin.PluginBase>();
+    private Gee.Map<Peas.PluginInfo,PluginContext> plugin_set =
+        new Gee.HashMap<Peas.PluginInfo,PluginContext>();
     private Gee.Map<Peas.PluginInfo,NotificationContext> notification_contexts =
         new Gee.HashMap<Peas.PluginInfo,NotificationContext>();
 
@@ -193,8 +231,10 @@ public class Application.PluginManager : GLib.Object {
             }
 
             if (do_activate) {
-                this.plugin_set.set(info, plugin);
-                plugin.activate();
+                var plugin_context = new PluginContext(info, plugin);
+                plugin_context.activate.begin((obj, res) => {
+                        on_plugin_activated(plugin_context, res);
+                    });
             }
         } else {
             warning(
@@ -204,21 +244,59 @@ public class Application.PluginManager : GLib.Object {
     }
 
     private void on_unload_plugin(Peas.PluginInfo info) {
-        var plugin = this.plugin_set.get(info);
-        if (plugin != null) {
-            plugin.deactivate(this.is_shutdown);
-
-            var notification = plugin as Plugin.NotificationExtension;
-            if (notification != null) {
-                var context = this.notification_contexts.get(info);
-                if (context != null) {
-                    this.notification_contexts.unset(info);
-                    context.destroy();
+        var plugin_context = this.plugin_set.get(info);
+        if (plugin_context != null) {
+            plugin_context.deactivate.begin(
+                this.is_shutdown,
+                (obj, res) => {
+                    on_plugin_deactivated(plugin_context, res);
                 }
-            }
+            );
+        }
+    }
+
+    private void on_plugin_activated(PluginContext context,
+                                     GLib.AsyncResult result) {
+        try {
+            context.activate.end(result);
+            this.plugin_set.set(context.info, context);
+            plugin_activated(context.info);
+        } catch (GLib.Error err) {
+            plugin_error(context.info, err);
+            warning(
+                "Activating plugin %s threw error, unloading: %s",
+                context.info.get_module_name(),
+                err.message
+            );
+            this.plugins.unload_plugin(context.info);
+        }
+    }
 
-            this.plugin_set.unset(info);
+    private void on_plugin_deactivated(PluginContext context,
+                                       GLib.AsyncResult result) {
+        GLib.Error? error = null;
+        try {
+            context.deactivate.end(result);
+        } catch (GLib.Error err) {
+            warning(
+                "Deactivating plugin %s threw error: %s",
+                context.info.get_module_name(),
+                err.message
+            );
+            error = err;
         }
+
+        var notification = context.plugin as Plugin.NotificationExtension;
+        if (notification != null) {
+            var notifications = this.notification_contexts.get(context.info);
+            if (notifications != null) {
+                this.notification_contexts.unset(context.info);
+                notifications.destroy();
+            }
+        }
+
+        plugin_deactivated(context.info, error);
+        this.plugin_set.unset(context.info);
     }
 
 }
diff --git a/src/client/plugin/desktop-notifications/desktop-notifications.vala 
b/src/client/plugin/desktop-notifications/desktop-notifications.vala
index 24769fc8..4f533795 100644
--- a/src/client/plugin/desktop-notifications/desktop-notifications.vala
+++ b/src/client/plugin/desktop-notifications/desktop-notifications.vala
@@ -45,14 +45,26 @@ public class Plugin.DesktopNotifications :
     private GLib.Cancellable? cancellable = null;
 
 
-    public override void activate() {
-        this.notifications.new_messages_arrived.connect(on_new_messages_arrived);
+    public override async void activate() throws GLib.Error {
         this.cancellable = new GLib.Cancellable();
+        this.email = yield this.notifications.get_email();
+
+        this.notifications.new_messages_arrived.connect(on_new_messages_arrived);
 
-        this.connect_signals.begin();
+        FolderStore folders = yield this.notifications.get_folders();
+        folders.folders_available.connect(
+            (folders) => check_folders(folders)
+        );
+        folders.folders_unavailable.connect(
+                (folders) => check_folders(folders)
+        );
+        folders.folders_type_changed.connect(
+            (folders) => check_folders(folders)
+        );
+        check_folders(folders.get_folders());
     }
 
-    public override void deactivate(bool is_shutdown) {
+    public override async void deactivate(bool is_shutdown) throws GLib.Error {
         this.cancellable.cancel();
 
         // Keep existing notifications if shutting down since they are
@@ -62,36 +74,6 @@ public class Plugin.DesktopNotifications :
         }
     }
 
-    private async void connect_signals() {
-        try {
-            this.email = yield this.notifications.get_email();
-        } catch (GLib.Error error) {
-            warning(
-                "Unable to get folders for plugin: %s",
-                error.message
-            );
-        }
-
-        try {
-            FolderStore folders = yield this.notifications.get_folders();
-            folders.folders_available.connect(
-                (folders) => check_folders(folders)
-            );
-            folders.folders_unavailable.connect(
-                (folders) => check_folders(folders)
-            );
-            folders.folders_type_changed.connect(
-                (folders) => check_folders(folders)
-            );
-            check_folders(folders.get_folders());
-        } catch (GLib.Error error) {
-            warning(
-                "Unable to get folders for plugin: %s",
-                error.message
-            );
-        }
-    }
-
     private void clear_arrived_notification() {
         this.client_application.withdraw_notification(ARRIVED_ID);
         this.arrived_notification = null;
diff --git a/src/client/plugin/messaging-menu/messaging-menu.vala 
b/src/client/plugin/messaging-menu/messaging-menu.vala
index 29f65fdd..7ce36ee8 100644
--- a/src/client/plugin/messaging-menu/messaging-menu.vala
+++ b/src/client/plugin/messaging-menu/messaging-menu.vala
@@ -28,7 +28,7 @@ public class Plugin.MessagingMenu : PluginBase, NotificationExtension {
     private FolderStore? folders = null;
 
 
-    public override void activate() {
+    public override async void activate() throws GLib.Error {
         this.app = new global::MessagingMenu.App(
             "%s.desktop".printf(global::Application.Client.APP_ID)
         );
@@ -37,36 +37,26 @@ public class Plugin.MessagingMenu : PluginBase, NotificationExtension {
 
         this.notifications.new_messages_arrived.connect(on_new_messages_changed);
         this.notifications.new_messages_retired.connect(on_new_messages_changed);
-        this.connect_folders.begin();
+
+        this.folders = yield this.notifications.get_folders();
+        folders.folders_available.connect(
+            (folders) => check_folders(folders)
+        );
+        folders.folders_unavailable.connect(
+            (folders) => check_folders(folders)
+        );
+        folders.folders_type_changed.connect(
+            (folders) => check_folders(folders)
+        );
+        check_folders(folders.get_folders());
     }
 
-    public override void deactivate(bool is_shutdown) {
+    public override async void deactivate(bool is_shutdown) throws GLib.Error {
         this.app.activate_source.disconnect(on_activate_source);
         this.app.unregister();
         this.app = null;
     }
 
-    private async void connect_folders() {
-        try {
-            this.folders = yield this.notifications.get_folders();
-            folders.folders_available.connect(
-                (folders) => check_folders(folders)
-            );
-            folders.folders_unavailable.connect(
-                (folders) => check_folders(folders)
-            );
-            folders.folders_type_changed.connect(
-                (folders) => check_folders(folders)
-            );
-            check_folders(folders.get_folders());
-        } catch (GLib.Error error) {
-            warning(
-                "Unable to get folders for plugin: %s",
-                error.message
-            );
-        }
-    }
-
     private void show_new_messages_count(Folder folder, int count) {
         if (this.notifications.should_notify_new_messages(folder)) {
             string source_id = get_source_id(folder);
diff --git a/src/client/plugin/notification-badge/notification-badge.vala 
b/src/client/plugin/notification-badge/notification-badge.vala
index 58438f5c..304249a1 100644
--- a/src/client/plugin/notification-badge/notification-badge.vala
+++ b/src/client/plugin/notification-badge/notification-badge.vala
@@ -39,61 +39,43 @@ public class Plugin.NotificationBadge :
     private UnityLauncherEntry? entry = null;
 
 
-    public override void activate() {
-        try {
-            var connection = this.client_application.get_dbus_connection();
-            var path = this.client_application.get_dbus_object_path();
-            if (connection == null || path == null) {
-                throw new GLib.IOError.NOT_CONNECTED(
-                    "Application does not have a DBus connection or path"
-                );
-            }
-            this.entry = new UnityLauncherEntry(
-                connection,
-                path + "/plugin/notificationbadge",
-                global::Application.Client.APP_ID + ".desktop"
-            );
-        } catch (GLib.Error error) {
-            warning(
-                "Failed to register Unity Launcher Entry: %s",
-                error.message
+    public override async void activate() throws GLib.Error {
+        var connection = this.client_application.get_dbus_connection();
+        var path = this.client_application.get_dbus_object_path();
+        if (connection == null || path == null) {
+            throw new GLib.IOError.NOT_CONNECTED(
+                "Application does not have a DBus connection or path"
             );
         }
+        this.entry = new UnityLauncherEntry(
+            connection,
+            path + "/plugin/notificationbadge",
+            global::Application.Client.APP_ID + ".desktop"
+        );
+
+        FolderStore folders = yield this.notifications.get_folders();
+        folders.folders_available.connect(
+            (folders) => check_folders(folders)
+        );
+        folders.folders_unavailable.connect(
+            (folders) => check_folders(folders)
+        );
+        folders.folders_type_changed.connect(
+            (folders) => check_folders(folders)
+        );
+        check_folders(folders.get_folders());
 
-        connect_folders.begin();
+        this.notifications.notify["total-new-messages"].connect(on_total_changed);
+        update_count();
     }
 
-    public override void deactivate(bool is_shutdown) {
+    public override async void deactivate(bool is_shutdown) throws GLib.Error {
         this.notifications.notify["total-new-messages"].disconnect(
             on_total_changed
         );
         this.entry = null;
     }
 
-    public async void connect_folders() {
-        try {
-            FolderStore folders = yield this.notifications.get_folders();
-            folders.folders_available.connect(
-                (folders) => check_folders(folders)
-            );
-            folders.folders_unavailable.connect(
-                (folders) => check_folders(folders)
-            );
-            folders.folders_type_changed.connect(
-                (folders) => check_folders(folders)
-            );
-            check_folders(folders.get_folders());
-        } catch (GLib.Error error) {
-            warning(
-                "Unable to get folders for plugin: %s",
-                error.message
-            );
-        }
-
-        this.notifications.notify["total-new-messages"].connect(on_total_changed);
-        update_count();
-    }
-
     private void check_folders(Gee.Collection<Folder> folders) {
         foreach (Folder folder in folders) {
             if (folder.folder_type in MONITORED_TYPES) {
diff --git a/src/client/plugin/plugin-plugin-base.vala b/src/client/plugin/plugin-plugin-base.vala
index c7f0c5b2..0be37b0e 100644
--- a/src/client/plugin/plugin-plugin-base.vala
+++ b/src/client/plugin/plugin-plugin-base.vala
@@ -27,10 +27,21 @@ public abstract class Plugin.PluginBase : Geary.BaseObject {
         get; construct;
     }
 
-    /** Invoked to activate the plugin, after loading. */
-    public abstract void activate();
+    /**
+     * Invoked to activate the plugin, after loading.
+     *
+     * If this method raises an error, it will be unloaded without
+     * deactivating.
+     */
+    public abstract async void activate() throws GLib.Error;
 
-    /** Invoked to deactivate the plugin, prior to unloading */
-    public abstract void deactivate(bool is_shutdown);
+    /**
+     * Invoked to deactivate the plugin, prior to unloading.
+     *
+     * If `is_shutdown` is true, the plugin is being unloaded because
+     * the client application is quitting. Otherwise, the plugin is
+     * being unloaded at end-user request.
+     */
+    public abstract async void deactivate(bool is_shutdown) throws GLib.Error;
 
 }


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]