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:
parent
835eb04daa
commit
154bb2d2c5
12 changed files with 430 additions and 258 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
27
src/client/plugin/plugin-error.vala
Normal file
27
src/client/plugin/plugin-error.vala
Normal 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;
|
||||
|
||||
}
|
||||
148
src/client/plugin/plugin-notification-extension.vala
Normal file
148
src/client/plugin/plugin-notification-extension.vala
Normal 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);
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
||||
}
|
||||
36
src/client/plugin/plugin-plugin-base.vala
Normal file
36
src/client/plugin/plugin-plugin-base.vala
Normal 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);
|
||||
|
||||
}
|
||||
40
src/client/plugin/plugin-trusted-extension.vala
Normal file
40
src/client/plugin/plugin-trusted-extension.vala
Normal 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;
|
||||
}
|
||||
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue