[geary/mjog/plugin-support: 9/13] Convert Notification.Desktop to a plugin



commit 486c2fc1378964c82959ff67482461f314c12a15
Author: Michael Gratton <mike vee net>
Date:   Fri Sep 27 02:51:05 2019 +1000

    Convert Notification.Desktop to a plugin
    
    Rename to DesktopNotifications and use Plugins.Notification as the base
    class. Add a plugin config file. Add build config to build the plugin.
    Update Plugins.Notification class to suit the requirements of this
    plugin better.

 po/POTFILES.in                                     |   8 +-
 src/client/application/application-controller.vala |  15 +-
 .../application/application-plugin-manager.vala    |  25 ++-
 src/client/application/geary-application.vala      |  31 ++-
 src/client/meson.build                             |   3 +-
 src/client/notification/notification-desktop.vala  | 221 ---------------------
 .../desktop-notifications.plugin.in                |   5 +
 .../desktop-notifications.vala                     | 213 ++++++++++++++++++++
 .../plugin/desktop-notifications/meson.build       |  24 +++
 src/client/plugin/meson.build                      |  25 +++
 src/client/plugin/plugin-notification.vala         |   9 +-
 src/meson.build                                    |   1 +
 12 files changed, 331 insertions(+), 249 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 4cb50a03..6452104b 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -77,12 +77,8 @@ src/client/folder-list/folder-list-inboxes-branch.vala
 src/client/folder-list/folder-list-search-branch.vala
 src/client/folder-list/folder-list-special-grouping.vala
 src/client/folder-list/folder-list-tree.vala
-src/client/notification/in-app-notification.vala
-src/client/notification/libmessagingmenu.vala
-src/client/notification/new-messages-indicator.vala
-src/client/notification/notification-desktop.vala
-src/client/notification/null-indicator.vala
-src/client/notification/unity-launcher.vala
+src/client/plugin/desktop-notifications/desktop-notifications.plugin.in
+src/client/plugin/desktop-notifications/desktop-notifications.vala
 src/client/plugin/plugin-notification.vala
 src/client/sidebar/sidebar-branch.vala
 src/client/sidebar/sidebar-common.vala
diff --git a/src/client/application/application-controller.vala 
b/src/client/application/application-controller.vala
index 2a489cb6..03d23bf9 100644
--- a/src/client/application/application-controller.vala
+++ b/src/client/application/application-controller.vala
@@ -127,9 +127,6 @@ public class Application.Controller : Geary.BaseObject {
         get; private set;
     }
 
-    /** Desktop notifications for the application. */
-    public Notification.Desktop notifications { get; private set; }
-
     /** Avatar store for the application. */
     public Application.AvatarStore avatars {
         get; private set; default = new Application.AvatarStore();
@@ -272,9 +269,7 @@ public class Application.Controller : Geary.BaseObject {
 
         }
 
-        this.plugin_manager = new PluginManager(
-            application.get_app_plugins_dir()
-        );
+        this.plugin_manager = new PluginManager(application);
         this.plugin_manager.notifications = new NotificationContext(
             this.avatars,
             this.get_contact_store_for_account,
@@ -321,12 +316,6 @@ public class Application.Controller : Geary.BaseObject {
 
         this.unity_launcher = new UnityLauncher(this.plugin_manager.notifications);
 
-        this.notifications = new Notification.Desktop(
-            this.plugin_manager.notifications,
-            this.application,
-            cancellable
-        );
-
         this.main_window.conversation_list_view.grab_focus();
 
         // initialize revokable
@@ -553,7 +542,7 @@ public class Application.Controller : Geary.BaseObject {
         Geary.ServiceProblemReport? service_report =
             report as Geary.ServiceProblemReport;
         if (service_report != null && service_report.service.protocol == SMTP) {
-            this.notifications.set_error_notification(
+            this.application.send_error_notification(
                 /// Notification title.
                 _("A problem occurred sending email for %s").printf(
                     service_report.account.display_name
diff --git a/src/client/application/application-plugin-manager.vala 
b/src/client/application/application-plugin-manager.vala
index 85421982..89fa686b 100644
--- a/src/client/application/application-plugin-manager.vala
+++ b/src/client/application/application-plugin-manager.vala
@@ -13,32 +13,47 @@ public class Application.PluginManager : GLib.Object {
 
     public NotificationContext notifications { get; set; }
 
+    private GearyApplication application;
     private Peas.Engine engine;
     private Peas.ExtensionSet? notification_extensions = null;
+    private bool is_shutdown = false;
 
 
-    public PluginManager(GLib.File app_plugin_dir) {
+    public PluginManager(GearyApplication application) {
+        this.application = application;
         this.engine = Peas.Engine.get_default();
-        this.engine.add_search_path(app_plugin_dir.get_path(), null);
+        this.engine.add_search_path(
+            application.get_app_plugins_dir().get_path(), null
+        );
     }
 
     public void load() {
         this.notification_extensions = new Peas.ExtensionSet(
             this.engine,
             typeof(Plugin.Notification),
+            "application", this.application,
             "context", this.notifications
         );
         this.notification_extensions.extension_added.connect((info, extension) => {
                 (extension as Plugin.Notification).activate();
             });
         this.notification_extensions.extension_removed.connect((info, extension) => {
-                (extension as Plugin.Notification).deactivate();
+                (extension as Plugin.Notification).deactivate(this.is_shutdown);
             });
 
         // Load built-in plugins by default
         foreach (Peas.PluginInfo info in this.engine.get_plugin_list()) {
-            if (info.is_builtin()) {
-                this.engine.load_plugin(info);
+            try {
+                info.is_available();
+                if (info.is_builtin()) {
+                    debug("Loading built-in plugin: %s", info.get_name());
+                    this.engine.load_plugin(info);
+                } else {
+                    debug("Not loading plugin: %s", info.get_name());
+                }
+            } catch (GLib.Error err) {
+                warning("Plugin %s not available: %s",
+                        info.get_name(), err.message);
             }
         }
     }
diff --git a/src/client/application/geary-application.vala b/src/client/application/geary-application.vala
index f09da89d..5746ad0c 100644
--- a/src/client/application/geary-application.vala
+++ b/src/client/application/geary-application.vala
@@ -161,6 +161,9 @@ public class GearyApplication : Gtk.Application {
     private const int64 USEC_PER_SEC = 1000000;
     private const int64 FORCE_SHUTDOWN_USEC = 5 * USEC_PER_SEC;
 
+    private const string ERROR_NOTIFICATION_ID = "error";
+
+
 
     /** Object returned by {@link get_runtime_information}. */
     public struct RuntimeDetail {
@@ -265,6 +268,7 @@ public class GearyApplication : Gtk.Application {
     private GLib.Cancellable controller_cancellable = new GLib.Cancellable();
     private Components.Inspector? inspector = null;
     private Geary.Nonblocking.Mutex controller_mutex = new Geary.Nonblocking.Mutex();
+    private GLib.Notification? error_notification = null;
 
 
     /**
@@ -625,7 +629,8 @@ public class GearyApplication : Gtk.Application {
     public GLib.File get_app_plugins_dir() {
         return (is_installed)
             ? GLib.File.new_for_path(_PLUGINS_DIR)
-            : GLib.File.new_for_path(BUILD_ROOT_DIR).get_child("src");
+            : GLib.File.new_for_path(BUILD_ROOT_DIR)
+                  .get_child("src").get_child("client").get_child("plugin");
     }
 
     /** Displays a URI on the current active window, if any. */
@@ -701,6 +706,30 @@ public class GearyApplication : Gtk.Application {
         Posix.exit(1);
     }
 
+    /**
+     * Displays an error notification.
+     *
+     * Use _very_ sparingly.
+     */
+    internal void send_error_notification(string summary, string body) {
+        if (this.error_notification != null) {
+            clear_error_notification();
+        }
+
+        GLib.Notification error = new GLib.Notification(summary);
+        error.set_body(body);
+        error.set_icon(
+            new GLib.ThemedIcon("%s-symbolic".printf(GearyApplication.APP_ID))
+        );
+        send_notification(ERROR_NOTIFICATION_ID, error);
+        this.error_notification = error;
+    }
+
+    internal void clear_error_notification() {
+        this.error_notification = null;
+        withdraw_notification(ERROR_NOTIFICATION_ID);
+    }
+
     // Presents a main window. If the controller is not open, opens it
     // first.
     private async void present() {
diff --git a/src/client/meson.build b/src/client/meson.build
index 6e0e3d7f..015d28eb 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -84,7 +84,6 @@ geary_client_vala_sources = files(
   'folder-list/folder-list-search-branch.vala',
   'folder-list/folder-list-special-grouping.vala',
 
-  'notification/notification-desktop.vala',
   'notification/in-app-notification.vala',
   'notification/libmessagingmenu.vala',
   'notification/new-messages-indicator.vala',
@@ -175,3 +174,5 @@ geary_client_dep = declare_dependency(
   link_with: geary_client_lib,
   include_directories: include_directories('.'),
 )
+
+subdir('plugin')
diff --git a/src/client/plugin/desktop-notifications/desktop-notifications.plugin.in 
b/src/client/plugin/desktop-notifications/desktop-notifications.plugin.in
new file mode 100644
index 00000000..837778b9
--- /dev/null
+++ b/src/client/plugin/desktop-notifications/desktop-notifications.plugin.in
@@ -0,0 +1,5 @@
+[Plugin]
+Module=libdesktop-notifications.so
+Name=Desktop Notifications
+Description=Displays desktop notifications when new email is delivered
+Builtin=true
diff --git a/src/client/plugin/desktop-notifications/desktop-notifications.vala 
b/src/client/plugin/desktop-notifications/desktop-notifications.vala
new file mode 100644
index 00000000..506fa3c3
--- /dev/null
+++ b/src/client/plugin/desktop-notifications/desktop-notifications.vala
@@ -0,0 +1,213 @@
+/*
+ * Copyright 2016 Software Freedom Conservancy Inc.
+ * Copyright 2019 Michael Gratton <mike vee net>.
+ *
+ * This software is licensed under the GNU Lesser General Public License
+ * (version 2.1 or later). See the COPYING file in this distribution.
+ */
+
+[ModuleInit]
+public void peas_register_types(TypeModule module) {
+    Peas.ObjectModule obj = module as Peas.ObjectModule;
+    obj.register_extension_type(
+        typeof(Plugin.Notification),
+        typeof(Plugin.DesktopNotifications)
+    );
+}
+
+/**
+ * Manages standard desktop application notifications.
+ */
+public class Plugin.DesktopNotifications : Notification {
+
+
+    public const Geary.Email.Field REQUIRED_FIELDS =
+        Geary.Email.Field.ORIGINATORS | Geary.Email.Field.SUBJECT;
+
+    public override GearyApplication application {
+        get; construct set;
+    }
+
+    public override Application.NotificationContext context {
+        get; construct set;
+    }
+
+    private const string ARRIVED_ID = "email-arrived";
+
+    private GLib.Notification? arrived_notification = null;
+    private GLib.Cancellable? cancellable = null;
+
+
+    public override void activate() {
+        this.context.add_required_fields(REQUIRED_FIELDS);
+        this.context.new_messages_arrived.connect(on_new_messages_arrived);
+        this.cancellable = new GLib.Cancellable();
+    }
+
+    public override void deactivate(bool is_shutdown) {
+        this.cancellable.cancel();
+        this.context.new_messages_arrived.disconnect(on_new_messages_arrived);
+        this.context.remove_required_fields(REQUIRED_FIELDS);
+
+        // Keep existing notifications if shutting down since they are
+        // persistent, but revoke if the plugin is being disabled.
+        if (!is_shutdown) {
+            clear_arrived_notification();
+        }
+    }
+
+    private void clear_arrived_notification() {
+        this.application.withdraw_notification(ARRIVED_ID);
+        this.arrived_notification = null;
+    }
+
+    private void notify_new_mail(Geary.Folder folder, int added) {
+        string body = ngettext(
+            /// Notification body text for new email when no other
+            /// new messages are already awaiting.
+            "%d new message", "%d new messages", added
+        ).printf(added);
+
+        int total = 0;
+        try {
+            total = this.context.get_new_message_count(folder);
+        } catch (Geary.EngineError err) {
+            // All good
+        }
+
+        if (total > added) {
+            body = ngettext(
+                /// Notification body text for new email when
+                /// other new messages have already been notified
+                /// about
+                "%s, %d new message total", "%s, %d new messages total",
+                total
+            ).printf(body, total);
+        }
+
+        issue_arrived_notification(
+            folder.account.information.display_name, body, folder, null
+        );
+    }
+
+    private async void notify_one_message(Geary.Folder folder,
+                                          Geary.Email email,
+                                          GLib.Cancellable? cancellable)
+        throws GLib.Error {
+        Geary.RFC822.MailboxAddress? originator =
+            Util.Email.get_primary_originator(email);
+        if (originator != null) {
+            Application.ContactStore contacts =
+                this.context.get_contact_store(folder.account);
+            Application.Contact contact = yield contacts.load(
+                originator, cancellable
+            );
+
+            int count = 1;
+            try {
+                count = this.context.get_new_message_count(folder);
+            } catch (Geary.EngineError.NOT_FOUND err) {
+                // All good
+            }
+
+            string body = "";
+            if (count <= 1) {
+                body = Util.Email.strip_subject_prefixes(email);
+            } else {
+                body = ngettext(
+                    "%s\n(%d other new message for %s)",
+                    "%s\n(%d other new messages for %s)", count - 1).printf(
+                        Util.Email.strip_subject_prefixes(email),
+                        count - 1,
+                        folder.account.information.display_name
+                    );
+            }
+
+            issue_arrived_notification(
+                contact.is_trusted
+                ? contact.display_name : originator.to_short_display(),
+                body,
+                folder,
+                email.id
+            );
+        } else {
+            notify_new_mail(folder, 1);
+        }
+    }
+
+    private void issue_arrived_notification(string summary,
+                                            string body,
+                                            Geary.Folder folder,
+                                            Geary.EmailIdentifier? id) {
+        // only one outstanding notification at a time
+        clear_arrived_notification();
+
+        string? action = null;
+        GLib.Variant[] target_param = new GLib.Variant[] {
+            folder.account.information.id,
+            new GLib.Variant.variant(folder.path.to_variant())
+        };
+
+        if (id == null) {
+            action = GearyApplication.ACTION_SHOW_FOLDER;
+        } else {
+            action = GearyApplication.ACTION_SHOW_EMAIL;
+            target_param += new GLib.Variant.variant(id.to_variant());
+        }
+
+        this.arrived_notification = issue_notification(
+            ARRIVED_ID,
+            summary,
+            body,
+            "app." + action,
+            new GLib.Variant.tuple(target_param)
+        );
+    }
+
+    private GLib.Notification issue_notification(string id,
+                                                 string summary,
+                                                 string body,
+                                                 string? action,
+                                                 GLib.Variant? action_target) {
+        GLib.Notification notification = new GLib.Notification(summary);
+        notification.set_body(body);
+        notification.set_icon(
+            new GLib.ThemedIcon("%s-symbolic".printf(GearyApplication.APP_ID))
+        );
+
+        /* We do not show notification action under Unity */
+
+        if (this.application.config.desktop_environment == Configuration.DesktopEnvironment.UNITY) {
+            this.application.send_notification(id, notification);
+            return notification;
+        } else {
+            if (action != null) {
+                notification.set_default_action_and_target_value(
+                    action, action_target
+                );
+            }
+
+            this.application.send_notification(id, notification);
+            return notification;
+        }
+    }
+
+    private void on_new_messages_arrived(Geary.Folder folder,
+                                         int total,
+                                         int added) {
+        if (this.context.should_notify_new_messages(folder)) {
+            if (added == 1 &&
+                this.context.last_new_message_folder != null &&
+                this.context.last_new_message != null) {
+                this.notify_one_message.begin(
+                    this.context.last_new_message_folder,
+                    this.context.last_new_message,
+                    this.cancellable
+                );
+            } else if (added > 0) {
+                notify_new_mail(folder, added);
+            }
+        }
+    }
+
+}
diff --git a/src/client/plugin/desktop-notifications/meson.build 
b/src/client/plugin/desktop-notifications/meson.build
new file mode 100644
index 00000000..06b9b8e8
--- /dev/null
+++ b/src/client/plugin/desktop-notifications/meson.build
@@ -0,0 +1,24 @@
+
+plugin_name = 'desktop-notifications'
+
+plugin_src = join_paths(plugin_name + '.vala')
+plugin_data = join_paths(plugin_name + '.plugin')
+plugin_dest = join_paths(plugins_dir, plugin_name)
+
+shared_module(
+  plugin_name,
+  sources: plugin_src,
+  dependencies: plugin_dependencies,
+  c_args: plugin_cflags,
+  install: true,
+  install_dir: plugin_dest
+)
+
+i18n.merge_file(
+  input: plugin_data + '.in',
+  output: plugin_data,
+  type: 'desktop',
+  po_dir: po_dir,
+  install: true,
+  install_dir: plugin_dest
+)
diff --git a/src/client/plugin/meson.build b/src/client/plugin/meson.build
new file mode 100644
index 00000000..4826545b
--- /dev/null
+++ b/src/client/plugin/meson.build
@@ -0,0 +1,25 @@
+#
+# Builds individual plugins. The client's plugin classes themselves
+# are built at the next directory back up the tree.
+#
+
+plugin_dependencies = [
+  folks,
+  gdk,
+  geary_client_dep,
+  geary_engine_dep,
+  gee,
+  gmime,
+  goa,
+  gtk,
+  javascriptcoregtk,
+  libhandy,
+  libmath,
+  libpeas,
+  libsoup,
+  webkit2gtk,
+]
+
+plugin_cflags = geary_c_options
+
+subdir('desktop-notifications')
diff --git a/src/client/plugin/plugin-notification.vala b/src/client/plugin/plugin-notification.vala
index 92e825fc..48ab11fa 100644
--- a/src/client/plugin/plugin-notification.vala
+++ b/src/client/plugin/plugin-notification.vala
@@ -10,7 +10,12 @@
  */
 public abstract class Plugin.Notification : Geary.BaseObject {
 
-    /** Context object for notification information. */
+    /** The application instance containing the plugin. */
+    public abstract GearyApplication application {
+        get; construct set;
+    }
+
+    /** Context object for notifications. */
     public abstract Application.NotificationContext context {
         get; construct set;
     }
@@ -19,6 +24,6 @@ public abstract class Plugin.Notification : Geary.BaseObject {
     public abstract void activate();
 
     /* Invoked to deactivate the plugin, prior to unloading */
-    public abstract void deactivate();
+    public abstract void deactivate(bool is_shutdown);
 
 }
diff --git a/src/meson.build b/src/meson.build
index 050e2368..4f8c4fa3 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -72,6 +72,7 @@ geary_bin_dependencies = [
   javascriptcoregtk,
   libhandy,
   libmath,
+  libpeas,
   libsoup,
   webkit2gtk,
 ]


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