[geary/mjog/user-plugins: 22/26] Rework and clean up how plugin extensions work
- From: Michael Gratton <mjog src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [geary/mjog/user-plugins: 22/26] Rework and clean up how plugin extensions work
- Date: Tue, 17 Mar 2020 08:26:05 +0000 (UTC)
commit 154bb2d2c5e098968f961f478ed233e8b53e5924
Author: Michael Gratton <mike vee net>
Date: Wed Mar 11 11:08:49 2020 +1100
Rework and clean up how plugin extensions work
Since libpeas doesn't allow a single plugin instance to have multiple
extensions, define a single libpeas extension as a base class for all
plugins and define extensions as non-libpeas interfaces shared by a
single plugin. Manage loading/unloading these ourselves.
This also defines a new trusted extension interface for plugins that
need access to Geary's internals, new error domain for plugis, and
made the notification context a plugin interface that is implemented by
the application object.
po/POTFILES.in | 17 ++-
.../application-notification-context.vala | 164 +++------------------
.../application/application-plugin-manager.vala | 155 ++++++++++++-------
src/client/meson.build | 6 +-
.../desktop-notifications.vala | 35 +++--
.../plugin/messaging-menu/messaging-menu.vala | 10 +-
.../notification-badge/notification-badge.vala | 22 ++-
src/client/plugin/plugin-error.vala | 27 ++++
.../plugin/plugin-notification-extension.vala | 148 +++++++++++++++++++
src/client/plugin/plugin-notification.vala | 24 ---
src/client/plugin/plugin-plugin-base.vala | 36 +++++
src/client/plugin/plugin-trusted-extension.vala | 40 +++++
12 files changed, 428 insertions(+), 256 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 7f3178e5..10fdcaf5 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -84,20 +84,23 @@ 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/plugin/desktop-notifications/desktop-notifications.plugin.in
-src/client/plugin/desktop-notifications/desktop-notifications.vala
-src/client/plugin/messaging-menu/messaging-menu.plugin.in
-src/client/plugin/messaging-menu/messaging-menu.vala
-src/client/plugin/notification-badge/notification-badge.plugin.in
-src/client/plugin/notification-badge/notification-badge.vala
src/client/plugin/plugin-account.vala
src/client/plugin/plugin-application.vala
src/client/plugin/plugin-contact-store.vala
src/client/plugin/plugin-email-store.vala
src/client/plugin/plugin-email.vala
+src/client/plugin/plugin-error.vala
src/client/plugin/plugin-folder-store.vala
src/client/plugin/plugin-folder.vala
-src/client/plugin/plugin-notification.vala
+src/client/plugin/plugin-notification-etension.vala
+src/client/plugin/plugin-plugin-base.vala
+src/client/plugin/plugin-trusted-etension.vala
+src/client/plugin/desktop-notifications/desktop-notifications.plugin.in
+src/client/plugin/desktop-notifications/desktop-notifications.vala
+src/client/plugin/messaging-menu/messaging-menu.plugin.in
+src/client/plugin/messaging-menu/messaging-menu.vala
+src/client/plugin/notification-badge/notification-badge.plugin.in
+src/client/plugin/notification-badge/notification-badge.vala
src/client/sidebar/sidebar-branch.vala
src/client/sidebar/sidebar-common.vala
src/client/sidebar/sidebar-count-cell-renderer.vala
diff --git a/src/client/application/application-notification-context.vala
b/src/client/application/application-notification-context.vala
index 2152243e..3c4c018a 100644
--- a/src/client/application/application-notification-context.vala
+++ b/src/client/application/application-notification-context.vala
@@ -7,51 +7,15 @@
*/
/**
- * Provides a context for notification plugins.
- *
- * The context provides an interface for notification plugins to
- * interface with the Geary client application. Plugins that implement
- * the plugins will be passed an instance of this class as the
- * `context` property.
- *
- * Plugins should register folders they wish to monitor by calling
- * {@link start_monitoring_folder}. The context will then start
- * keeping track of email being delivered to the folder and being seen
- * in a main window updating {@link total_new_messages} and emitting
- * the {@link new_messages_arrived} and {@link new_messages_retired}
- * signals as appropriate.
- *
- * @see Plugin.NotificationPlugin
+ * Implementation of the notification extension context.
*/
-public class Application.NotificationContext : Geary.BaseObject {
+internal class Application.NotificationContext :
+ Geary.BaseObject, Plugin.NotificationContext {
private const Geary.Email.Field REQUIRED_FIELDS = FLAGS;
- private class ApplicationImpl : Geary.BaseObject, Plugin.Application {
-
-
- private Client backing;
- private FolderStoreFactory folders;
-
-
- public ApplicationImpl(Client backing,
- FolderStoreFactory folders) {
- this.backing = backing;
- this.folders = folders;
- }
-
- public override void show_folder(Plugin.Folder folder) {
- Geary.Folder? target = this.folders.get_engine_folder(folder);
- if (target != null) {
- this.backing.show_folder.begin(target);
- }
- }
-
- }
-
-
private class EmailStoreImpl : Geary.BaseObject, Plugin.EmailStore {
@@ -248,23 +212,8 @@ public class Application.NotificationContext : Geary.BaseObject {
}
}
- /**
- * Returns the plugin application object.
- *
- * No special permissions are required to use access this.
- */
- public Plugin.Application plugin_application {
- get; private set;
- }
-
- /**
- * Current total new message count for all monitored folders.
- *
- * This is the sum of the the counts returned by {@link
- * get_new_message_count} for all folders that are being monitored
- * after a call to {@link start_monitoring_folder}.
- */
- public int total_new_messages { get; private set; default = 0; }
+ public int total_new_messages { get { return this._total_new_messages; } }
+ public int _total_new_messages = 0;
private Gee.Map<Geary.Folder,MonitorInformation> folder_information =
new Gee.HashMap<Geary.Folder,MonitorInformation>();
@@ -273,84 +222,28 @@ public class Application.NotificationContext : Geary.BaseObject {
private FolderStoreFactory folders_factory;
private Plugin.FolderStore folders;
private EmailStoreImpl email;
- private PluginManager.PluginFlags flags;
-
-
- /**
- * Emitted when new messages have been downloaded.
- *
- * This will only be emitted for folders that are being monitored
- * by calling {@link start_monitoring_folder}.
- */
- public signal void new_messages_arrived(
- Plugin.Folder parent,
- int total,
- Gee.Collection<Plugin.EmailIdentifier> added
- );
-
- /**
- * Emitted when a folder has been cleared of new messages.
- *
- * This will only be emitted for folders that are being monitored
- * after a call to {@link start_monitoring_folder}.
- */
- public signal void new_messages_retired(Plugin.Folder parent, int total);
- /** Constructs a new context instance. */
internal NotificationContext(Client application,
- FolderStoreFactory folders_factory,
- PluginManager.PluginFlags flags) {
+ FolderStoreFactory folders_factory) {
this.application = application;
this.folders_factory = folders_factory;
this.folders = folders_factory.new_folder_store();
this.email = new EmailStoreImpl(application);
- this.flags = flags;
+ }
- this.plugin_application = new ApplicationImpl(
- application, folders_factory
- );
+ public async Plugin.EmailStore get_email()
+ throws Plugin.Error.PERMISSION_DENIED {
+ return this.email;
}
- /**
- * Returns a store to lookup folders for notifications.
- *
- * This method may prompt for permission before returning.
- *
- * @throws Geary.EngineError.PERMISSIONS if permission to access
- * this resource was not given
- */
public async Plugin.FolderStore get_folders()
- throws Geary.EngineError.PERMISSIONS {
+ throws Plugin.Error.PERMISSION_DENIED {
return this.folders;
}
- /**
- * Returns a store to lookup email for notifications.
- *
- * This method may prompt for permission before returning.
- *
- * @throws Geary.EngineError.PERMISSIONS if permission to access
- * this resource was not given
- */
- public async Plugin.EmailStore get_email()
- throws Geary.EngineError.PERMISSIONS {
- return this.email;
- }
-
- /**
- * Returns a store to lookup contacts for notifications.
- *
- * This method may prompt for permission before returning.
- *
- * @throws Geary.EngineError.NOT_FOUND if the given account does
- * not exist
- * @throws Geary.EngineError.PERMISSIONS if permission to access
- * this resource was not given
- */
public async Plugin.ContactStore get_contacts_for_folder(Plugin.Folder source)
- throws Geary.EngineError.NOT_FOUND,
- Geary.EngineError.PERMISSIONS {
+ throws Plugin.Error.NOT_FOUND, Plugin.Error.PERMISSION_DENIED {
Geary.Folder? folder = this.folders_factory.get_engine_folder(source);
AccountContext? context = null;
if (folder != null) {
@@ -359,30 +252,13 @@ public class Application.NotificationContext : Geary.BaseObject {
);
}
if (context == null) {
- throw new Geary.EngineError.NOT_FOUND(
+ throw new Plugin.Error.NOT_FOUND(
"No account for folder: %s", source.display_name
);
}
return new ContactStoreImpl(context.contacts);
}
- /**
- * Returns the client's application object.
- *
- * Only plugins that are trusted by the client will be provided
- * access to the application instance.
- *
- * @throws Geary.EngineError.PERMISSIONS if permission to access
- * this resource was not given
- */
- public Client get_client_application()
- throws Geary.EngineError.PERMISSIONS {
- if (!(PluginManager.PluginFlags.TRUSTED in this.flags)) {
- throw new Geary.EngineError.PERMISSIONS("Plugin is not trusted");
- }
- return this.application;
- }
-
/**
* Determines if notifications should be made for a specific folder.
*
@@ -417,14 +293,14 @@ public class Application.NotificationContext : Geary.BaseObject {
* folder by a call to {@link start_monitoring_folder}.
*/
public int get_new_message_count(Plugin.Folder target)
- throws Geary.EngineError.NOT_FOUND {
+ throws Plugin.Error.NOT_FOUND {
Geary.Folder? folder = this.folders_factory.get_engine_folder(target);
MonitorInformation? info = null;
if (folder != null) {
info = folder_information.get(folder);
}
if (info == null) {
- throw new Geary.EngineError.NOT_FOUND(
+ throw new Plugin.Error.NOT_FOUND(
"No such folder: %s", folder.path.to_string()
);
}
@@ -534,18 +410,19 @@ public class Application.NotificationContext : Geary.BaseObject {
Plugin.Folder folder =
this.folders_factory.get_plugin_folder(info.folder);
if (arrived) {
- this.total_new_messages += delta.size;
+ this._total_new_messages += delta.size;
new_messages_arrived(
folder,
info.recent_ids.size,
this.email.get_plugin_ids(delta, info.folder.account.information)
);
} else {
- this.total_new_messages -= delta.size;
+ this._total_new_messages -= delta.size;
new_messages_retired(
folder, info.recent_ids.size
);
}
+ notify_property("total-new-messages");
}
private void remove_folder(Geary.Folder target) {
@@ -555,7 +432,10 @@ public class Application.NotificationContext : Geary.BaseObject {
target.email_flags_changed.disconnect(on_email_flags_changed);
target.email_removed.disconnect(on_email_removed);
- this.total_new_messages -= info.recent_ids.size;
+ if (!info.recent_ids.is_empty) {
+ this._total_new_messages -= info.recent_ids.size;
+ notify_property("total-new-messages");
+ }
this.folder_information.unset(target);
}
diff --git a/src/client/application/application-plugin-manager.vala
b/src/client/application/application-plugin-manager.vala
index 3f3bcba7..0299727c 100644
--- a/src/client/application/application-plugin-manager.vala
+++ b/src/client/application/application-plugin-manager.vala
@@ -11,18 +11,34 @@
public class Application.PluginManager : GLib.Object {
- // Plugins that will be loaded automatically and trusted with
- // access to the application if they have been installed
- private const string[] TRUSTED_MODULES = {
+ // Plugins that will be loaded automatically when the client
+ // application stats up
+ private const string[] AUTOLOAD_MODULES = {
"desktop-notifications",
"notification-badge",
};
- /** Flags assigned to a plugin by the manager. */
- [Flags]
- public enum PluginFlags {
- /** If set, the plugin is in the set of trusted plugins. */
- TRUSTED;
+
+ private class ApplicationImpl : Geary.BaseObject, Plugin.Application {
+
+
+ private Client backing;
+ private FolderStoreFactory folders;
+
+
+ public ApplicationImpl(Client backing,
+ FolderStoreFactory folders) {
+ this.backing = backing;
+ this.folders = folders;
+ }
+
+ public override void show_folder(Plugin.Folder folder) {
+ Geary.Folder? target = this.folders.get_engine_folder(folder);
+ if (target != null) {
+ this.backing.show_folder.begin(target);
+ }
+ }
+
}
@@ -33,9 +49,10 @@ public class Application.PluginManager : GLib.Object {
private FolderStoreFactory folders_factory;
- private Peas.ExtensionSet notification_extensions;
- private Gee.Set<NotificationContext> notification_contexts =
- new Gee.HashSet<NotificationContext>();
+ private Gee.Map<Peas.PluginInfo,Plugin.PluginBase> plugin_set =
+ new Gee.HashMap<Peas.PluginInfo,Plugin.PluginBase>();
+ private Gee.Map<Peas.PluginInfo,NotificationContext> notification_contexts =
+ new Gee.HashMap<Peas.PluginInfo,NotificationContext>();
public PluginManager(Client application) throws GLib.Error {
@@ -46,40 +63,16 @@ public class Application.PluginManager : GLib.Object {
this.trusted_path = application.get_app_plugins_dir().get_path();
this.plugins.add_search_path(trusted_path, null);
- this.notification_extensions = new Peas.ExtensionSet(
- this.plugins,
- typeof(Plugin.Notification)
- );
- this.notification_extensions.extension_added.connect((info, extension) => {
- Plugin.Notification? plugin = extension as Plugin.Notification;
- if (plugin != null) {
- var context = new NotificationContext(
- this.application,
- this.folders_factory,
- to_plugin_flags(info)
- );
- this.notification_contexts.add(context);
- plugin.notifications = context;
- plugin.activate();
- }
- });
- this.notification_extensions.extension_removed.connect((info, extension) => {
- Plugin.Notification? plugin = extension as Plugin.Notification;
- if (plugin != null) {
- plugin.deactivate(this.is_shutdown);
- }
- var context = plugin.notifications;
- context.destroy();
- this.notification_contexts.remove(context);
- });
+ this.plugins.load_plugin.connect_after(on_load_plugin);
+ this.plugins.unload_plugin.connect(on_unload_plugin);
string[] optional_names = application.config.get_optional_plugins();
foreach (Peas.PluginInfo info in this.plugins.get_plugin_list()) {
string name = info.get_module_name();
try {
if (info.is_available()) {
- if (is_trusted(info)) {
- debug("Loading trusted plugin: %s", name);
+ if (is_autoload(info)) {
+ debug("Loading autoload plugin: %s", name);
this.plugins.load_plugin(info);
} else if (name in optional_names) {
debug("Loading optional plugin: %s", name);
@@ -92,15 +85,9 @@ public class Application.PluginManager : GLib.Object {
}
}
- public inline bool is_trusted(Peas.PluginInfo plugin) {
- return (
- plugin.get_module_name() in TRUSTED_MODULES &&
- plugin.get_module_dir().has_prefix(trusted_path)
- );
- }
-
- public inline PluginFlags to_plugin_flags(Peas.PluginInfo plugin) {
- return is_trusted(plugin) ? PluginFlags.TRUSTED : 0;
+ /** Returns the engine folder for the given plugin folder, if any. */
+ public Geary.Folder? get_engine_folder(Plugin.Folder plugin) {
+ return this.folders_factory.get_engine_folder(plugin);
}
public Gee.Collection<Peas.PluginInfo> get_optional_plugins() {
@@ -108,7 +95,7 @@ public class Application.PluginManager : GLib.Object {
foreach (Peas.PluginInfo plugin in this.plugins.get_plugin_list()) {
try {
plugin.is_available();
- if (!is_trusted(plugin)) {
+ if (!is_autoload(plugin)) {
plugins.add(plugin);
}
} catch (GLib.Error err) {
@@ -125,7 +112,7 @@ public class Application.PluginManager : GLib.Object {
bool loaded = false;
if (plugin.is_available() &&
!plugin.is_loaded() &&
- !is_trusted(plugin)) {
+ !is_autoload(plugin)) {
this.plugins.load_plugin(plugin);
loaded = true;
string name = plugin.get_module_name();
@@ -143,7 +130,7 @@ public class Application.PluginManager : GLib.Object {
bool unloaded = false;
if (plugin.is_available() &&
plugin.is_loaded() &&
- !is_trusted(plugin)) {
+ !is_autoload(plugin)) {
this.plugins.unload_plugin(plugin);
unloaded = true;
string name = plugin.get_module_name();
@@ -163,11 +150,75 @@ public class Application.PluginManager : GLib.Object {
internal void close() throws GLib.Error {
this.is_shutdown = true;
this.plugins.set_loaded_plugins(null);
+ this.plugins.garbage_collect();
this.folders_factory.destroy();
}
+ internal inline bool is_autoload(Peas.PluginInfo info) {
+ return info.get_module_name() in AUTOLOAD_MODULES;
+ }
+
internal Gee.Collection<NotificationContext> get_notification_contexts() {
- return this.notification_contexts.read_only_view;
+ return this.notification_contexts.values.read_only_view;
+ }
+
+ private void on_load_plugin(Peas.PluginInfo info) {
+ var plugin = this.plugins.create_extension(
+ info,
+ typeof(Plugin.PluginBase),
+ "plugin_application",
+ new ApplicationImpl(this.application, this.folders_factory)
+ ) as Plugin.PluginBase;
+ if (plugin != null) {
+ bool do_activate = true;
+ var trusted = plugin as Plugin.TrustedExtension;
+ if (trusted != null) {
+ if (info.get_module_dir().has_prefix(this.trusted_path)) {
+ trusted.client_application = this.application;
+ trusted.client_plugins = this;
+ } else {
+ do_activate = false;
+ this.plugins.unload_plugin(info);
+ }
+ }
+
+ var notification = plugin as Plugin.NotificationExtension;
+ if (notification != null) {
+ var context = new NotificationContext(
+ this.application,
+ this.folders_factory
+ );
+ this.notification_contexts.set(info, context);
+ notification.notifications = context;
+ }
+
+ if (do_activate) {
+ this.plugin_set.set(info, plugin);
+ plugin.activate();
+ }
+ } else {
+ warning(
+ "Could not construct BasePlugin from %s", info.get_module_name()
+ );
+ }
+ }
+
+ 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();
+ }
+ }
+
+ this.plugin_set.unset(info);
+ }
}
}
diff --git a/src/client/meson.build b/src/client/meson.build
index da3e81e5..d6dd9a4a 100644
--- a/src/client/meson.build
+++ b/src/client/meson.build
@@ -1,4 +1,5 @@
# Geary client
+
geary_client_vala_sources = files(
'application/application-attachment-manager.vala',
'application/application-avatar-store.vala',
@@ -97,9 +98,12 @@ geary_client_vala_sources = files(
'plugin/plugin-contact-store.vala',
'plugin/plugin-email-store.vala',
'plugin/plugin-email.vala',
+ 'plugin/plugin-error.vala',
'plugin/plugin-folder-store.vala',
'plugin/plugin-folder.vala',
- 'plugin/plugin-notification.vala',
+ 'plugin/plugin-notification-extension.vala',
+ 'plugin/plugin-plugin-base.vala',
+ 'plugin/plugin-trusted-extension.vala',
'sidebar/sidebar-branch.vala',
'sidebar/sidebar-common.vala',
diff --git a/src/client/plugin/desktop-notifications/desktop-notifications.vala
b/src/client/plugin/desktop-notifications/desktop-notifications.vala
index 95c0c9bc..24769fc8 100644
--- a/src/client/plugin/desktop-notifications/desktop-notifications.vala
+++ b/src/client/plugin/desktop-notifications/desktop-notifications.vala
@@ -10,7 +10,7 @@
public void peas_register_types(TypeModule module) {
Peas.ObjectModule obj = module as Peas.ObjectModule;
obj.register_extension_type(
- typeof(Plugin.Notification),
+ typeof(Plugin.PluginBase),
typeof(Plugin.DesktopNotifications)
);
}
@@ -18,35 +18,34 @@ public void peas_register_types(TypeModule module) {
/**
* Manages standard desktop application notifications.
*/
-public class Plugin.DesktopNotifications : Geary.BaseObject, Notification {
+public class Plugin.DesktopNotifications :
+ PluginBase, NotificationExtension, TrustedExtension {
private const Geary.SpecialFolderType[] MONITORED_TYPES = {
INBOX, NONE
};
- public global::Application.NotificationContext notifications {
- get; set;
+ public NotificationContext notifications {
+ get; set construct;
+ }
+
+ public global::Application.Client client_application {
+ get; set construct;
+ }
+
+ public global::Application.PluginManager client_plugins {
+ get; set construct;
}
private const string ARRIVED_ID = "email-arrived";
- private global::Application.Client? application = null;
private EmailStore? email = null;
private GLib.Notification? arrived_notification = null;
private GLib.Cancellable? cancellable = null;
public override void activate() {
- try {
- this.application = this.notifications.get_client_application();
- } catch (GLib.Error error) {
- warning(
- "Failed obtain application instance: %s",
- error.message
- );
- }
-
this.notifications.new_messages_arrived.connect(on_new_messages_arrived);
this.cancellable = new GLib.Cancellable();
@@ -94,7 +93,7 @@ public class Plugin.DesktopNotifications : Geary.BaseObject, Notification {
}
private void clear_arrived_notification() {
- this.application.withdraw_notification(ARRIVED_ID);
+ this.client_application.withdraw_notification(ARRIVED_ID);
this.arrived_notification = null;
}
@@ -206,8 +205,8 @@ public class Plugin.DesktopNotifications : Geary.BaseObject, Notification {
// Do not show notification actions under Unity, it's
// notifications daemon doesn't support them.
- if (this.application.config.desktop_environment == UNITY) {
- this.application.send_notification(id, notification);
+ if (this.client_application.config.desktop_environment == UNITY) {
+ this.client_application.send_notification(id, notification);
return notification;
} else {
if (action != null) {
@@ -216,7 +215,7 @@ public class Plugin.DesktopNotifications : Geary.BaseObject, Notification {
);
}
- this.application.send_notification(id, notification);
+ this.client_application.send_notification(id, notification);
return notification;
}
}
diff --git a/src/client/plugin/messaging-menu/messaging-menu.vala
b/src/client/plugin/messaging-menu/messaging-menu.vala
index a97840cd..29f65fdd 100644
--- a/src/client/plugin/messaging-menu/messaging-menu.vala
+++ b/src/client/plugin/messaging-menu/messaging-menu.vala
@@ -10,17 +10,17 @@
public void peas_register_types(TypeModule module) {
Peas.ObjectModule obj = module as Peas.ObjectModule;
obj.register_extension_type(
- typeof(Plugin.Notification),
+ typeof(Plugin.PluginBase),
typeof(Plugin.MessagingMenu)
);
}
/** Updates the Unity messaging menu when new mail arrives. */
-public class Plugin.MessagingMenu : Geary.BaseObject, Notification {
+public class Plugin.MessagingMenu : PluginBase, NotificationExtension {
- public global::Application.NotificationContext notifications {
- get; set;
+ public NotificationContext notifications {
+ get; set construct;
}
@@ -101,7 +101,7 @@ public class Plugin.MessagingMenu : Geary.BaseObject, Notification {
if (this.folders != null) {
foreach (Folder folder in this.folders.get_folders()) {
if (source_id == get_source_id(folder)) {
- this.notifications.plugin_application.show_folder(folder);
+ this.plugin_application.show_folder(folder);
break;
}
}
diff --git a/src/client/plugin/notification-badge/notification-badge.vala
b/src/client/plugin/notification-badge/notification-badge.vala
index 661ad662..58438f5c 100644
--- a/src/client/plugin/notification-badge/notification-badge.vala
+++ b/src/client/plugin/notification-badge/notification-badge.vala
@@ -10,21 +10,30 @@
public void peas_register_types(TypeModule module) {
Peas.ObjectModule obj = module as Peas.ObjectModule;
obj.register_extension_type(
- typeof(Plugin.Notification),
+ typeof(Plugin.PluginBase),
typeof(Plugin.NotificationBadge)
);
}
/** Updates Unity application badge with total new message count. */
-public class Plugin.NotificationBadge : Geary.BaseObject, Notification {
+public class Plugin.NotificationBadge :
+ PluginBase, NotificationExtension, TrustedExtension {
private const Geary.SpecialFolderType[] MONITORED_TYPES = {
INBOX, NONE
};
- public global::Application.NotificationContext notifications {
- get; set;
+ public NotificationContext notifications {
+ get; set construct;
+ }
+
+ public global::Application.Client client_application {
+ get; set construct;
+ }
+
+ public global::Application.PluginManager client_plugins {
+ get; set construct;
}
private UnityLauncherEntry? entry = null;
@@ -32,9 +41,8 @@ public class Plugin.NotificationBadge : Geary.BaseObject, Notification {
public override void activate() {
try {
- var application = this.notifications.get_client_application();
- var connection = application.get_dbus_connection();
- var path = application.get_dbus_object_path();
+ 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"
diff --git a/src/client/plugin/plugin-error.vala b/src/client/plugin/plugin-error.vala
new file mode 100644
index 00000000..67fae4b9
--- /dev/null
+++ b/src/client/plugin/plugin-error.vala
@@ -0,0 +1,27 @@
+/*
+ * Copyright © 2020 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.
+ */
+
+/**
+ * The base class for objects implementing a client plugin.
+ *
+ * To implement a new plugin, have it derive from this type and
+ * implement any additional extension interfaces (such as {@link
+ * NotificationExtension}) as required.
+ */
+
+/**
+ * Errors when plugins request resources from their contexts.
+ */
+public errordomain Plugin.Error {
+
+ /** Raised when access to a requested resource was denied. */
+ PERMISSION_DENIED,
+
+ /** Raised when a requested resource was not found. */
+ NOT_FOUND;
+
+}
diff --git a/src/client/plugin/plugin-notification-extension.vala
b/src/client/plugin/plugin-notification-extension.vala
new file mode 100644
index 00000000..230e98c7
--- /dev/null
+++ b/src/client/plugin/plugin-notification-extension.vala
@@ -0,0 +1,148 @@
+/*
+ * Copyright © 2019-2020 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.
+ */
+
+/**
+ * A plugin extension point for notifying of mail sending or arriving.
+ */
+public interface Plugin.NotificationExtension : PluginBase {
+
+ /**
+ * Context object for notifications.
+ *
+ * This will be set during (or just after) plugin construction,
+ * before {@link PluginBase.activate} is called.
+ */
+ public abstract NotificationContext notifications {
+ get; set construct;
+ }
+
+}
+
+
+// XXX this should be an inner interface of NotificationExtension, but
+// GNOME/vala#918 prevents that.
+
+/**
+ * Provides a context for notification plugins.
+ *
+ * The context provides an interface for notification plugins to
+ * interface with the Geary client application. Plugins that implement
+ * the plugins will be passed an instance of this class as the
+ * `context` property.
+ *
+ * Plugins should register folders they wish to monitor by calling
+ * {@link start_monitoring_folder}. The context will then start
+ * keeping track of email being delivered to the folder and being seen
+ * in a main window updating {@link total_new_messages} and emitting
+ * the {@link new_messages_arrived} and {@link new_messages_retired}
+ * signals as appropriate.
+ *
+ * @see Plugin.NotificationExtension.notifications
+ */
+public interface Plugin.NotificationContext : Geary.BaseObject {
+
+
+ /**
+ * Current total new message count for all monitored folders.
+ *
+ * This is the sum of the the counts returned by {@link
+ * get_new_message_count} for all folders that are being monitored
+ * after a call to {@link start_monitoring_folder}.
+ */
+ public abstract int total_new_messages { get; default = 0; }
+
+ /**
+ * Emitted when new messages have been downloaded.
+ *
+ * This will only be emitted for folders that are being monitored
+ * by calling {@link start_monitoring_folder}.
+ */
+ public signal void new_messages_arrived(
+ Plugin.Folder parent,
+ int total,
+ Gee.Collection<Plugin.EmailIdentifier> added
+ );
+
+ /**
+ * Emitted when a folder has been cleared of new messages.
+ *
+ * This will only be emitted for folders that are being monitored
+ * after a call to {@link start_monitoring_folder}.
+ */
+ public signal void new_messages_retired(Plugin.Folder parent, int total);
+
+
+ /**
+ * Returns a store to lookup email for notifications.
+ *
+ * This method may prompt for permission before returning.
+ *
+ * @throws Error.PERMISSIONS if permission to access
+ * this resource was not given
+ */
+ public abstract async Plugin.EmailStore get_email()
+ throws Error.PERMISSION_DENIED;
+
+ /**
+ * Returns a store to lookup folders for notifications.
+ *
+ * This method may prompt for permission before returning.
+ *
+ * @throws Error.PERMISSIONS if permission to access
+ * this resource was not given
+ */
+ public abstract async Plugin.FolderStore get_folders()
+ throws Error.PERMISSION_DENIED;
+
+ /**
+ * Returns a store to lookup contacts for notifications.
+ *
+ * This method may prompt for permission before returning.
+ *
+ * @throws Error.NOT_FOUND if the given account does
+ * not exist
+ * @throws Error.PERMISSIONS if permission to access
+ * this resource was not given
+ */
+ public abstract async Plugin.ContactStore get_contacts_for_folder(Plugin.Folder source)
+ throws Error.NOT_FOUND, Error.PERMISSION_DENIED;
+
+ /**
+ * Determines if notifications should be made for a specific folder.
+ *
+ * Notification plugins should call this to first before
+ * displaying a "new mail" notification for mail in a specific
+ * folder. It will return true for any monitored folder that is
+ * not currently visible in the currently focused main window, if
+ * any.
+ */
+ public abstract bool should_notify_new_messages(Plugin.Folder target);
+
+ /**
+ * Returns the new message count for a specific folder.
+ *
+ * The context must have already been requested to monitor the
+ * folder by a call to {@link start_monitoring_folder}.
+ */
+ public abstract int get_new_message_count(Plugin.Folder target)
+ throws Error.NOT_FOUND;
+
+ /**
+ * Starts monitoring a folder for new messages.
+ *
+ * Notification plugins should call this to start the context
+ * recording new messages for a specific folder.
+ */
+ public abstract void start_monitoring_folder(Plugin.Folder target);
+
+ /** Stops monitoring a folder for new messages. */
+ public abstract void stop_monitoring_folder(Plugin.Folder target);
+
+ /** Determines if a folder is curently being monitored. */
+ public abstract bool is_monitoring_folder(Plugin.Folder target);
+
+}
diff --git a/src/client/plugin/plugin-plugin-base.vala b/src/client/plugin/plugin-plugin-base.vala
new file mode 100644
index 00000000..c7f0c5b2
--- /dev/null
+++ b/src/client/plugin/plugin-plugin-base.vala
@@ -0,0 +1,36 @@
+/*
+ * Copyright © 2020 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.
+ */
+
+/**
+ * The base class for objects implementing a client plugin.
+ *
+ * To implement a new plugin, have it derive from this type and
+ * implement any additional extension interfaces (such as {@link
+ * NotificationExtension}) as required.
+ */
+public abstract class Plugin.PluginBase : Geary.BaseObject {
+
+ /**
+ * Returns an object for interacting with the client application.
+ *
+ * No special permissions are required to use access this
+ * resource.
+ *
+ * This will be set during (or just after) plugin construction,
+ * before {@link PluginBase.activate} is called.
+ */
+ public Plugin.Application plugin_application {
+ get; construct;
+ }
+
+ /** Invoked to activate the plugin, after loading. */
+ public abstract void activate();
+
+ /** Invoked to deactivate the plugin, prior to unloading */
+ public abstract void deactivate(bool is_shutdown);
+
+}
diff --git a/src/client/plugin/plugin-trusted-extension.vala b/src/client/plugin/plugin-trusted-extension.vala
new file mode 100644
index 00000000..fc0d9bd3
--- /dev/null
+++ b/src/client/plugin/plugin-trusted-extension.vala
@@ -0,0 +1,40 @@
+/*
+ * Copyright © 2020 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.
+ */
+
+/**
+ * A plugin extension point for trusted plugins.
+ *
+ * In-tree plugins may implement this interface if they require access
+ * to the client application's internal machinery.
+ *
+ * Since the client application and engine objects have no API
+ * stability guarantee, Geary will refuse to load out-of-tree plugins
+ * that implement this extension point.
+ */
+public interface Plugin.TrustedExtension : PluginBase {
+
+ /**
+ * Client application object.
+ *
+ * This will be set during (or just after) plugin construction,
+ * before {@link PluginBase.activate} is called.
+ */
+ public abstract global::Application.Client client_application {
+ get; set construct;
+ }
+
+ /**
+ * Client plugin manager object.
+ *
+ * This will be set during (or just after) plugin construction,
+ * before {@link PluginBase.activate} is called.
+ */
+ public abstract global::Application.PluginManager client_plugins {
+ get; set construct;
+ }
+
+}
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]