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.
This commit is contained in:
Michael Gratton 2020-03-11 11:08:49 +11:00 committed by Michael James Gratton
parent 835eb04daa
commit 154bb2d2c5
12 changed files with 430 additions and 258 deletions

View file

@ -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/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-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/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-folder-store.vala
src/client/plugin/plugin-folder.vala
src/client/plugin/plugin-notification.vala
src/client/sidebar/sidebar-branch.vala
src/client/sidebar/sidebar-common.vala
src/client/sidebar/sidebar-count-cell-renderer.vala

View file

@ -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
);
}
/**
* 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 {
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 {
throws Plugin.Error.PERMISSION_DENIED {
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.FolderStore get_folders()
throws Plugin.Error.PERMISSION_DENIED {
return this.folders;
}
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);
}

View file

@ -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);
}
}
}

View file

@ -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',

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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"

View file

@ -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;
}

View file

@ -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);
}

View file

@ -1,24 +0,0 @@
/*
* 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.
*/
/**
* A plugin extension point for notifying of mail sending or arriving.
*/
public interface Plugin.Notification : Geary.BaseObject {
/** Context object for notifications. */
public abstract global::Application.NotificationContext notifications {
get; set;
}
/* 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);
}

View file

@ -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);
}

View file

@ -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;
}
}