mirror of
https://github.com/pulb/mailnag.git
synced 2026-01-13 17:32:03 +01:00
278 lines
6.9 KiB
Python
278 lines
6.9 KiB
Python
# Copyright 2013 - 2019 Patrick Ulbrich <zulu99@gmx.net>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify
|
|
# it under the terms of the GNU General Public License as published by
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
# (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License
|
|
# along with this program; if not, write to the Free Software
|
|
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
# MA 02110-1301, USA.
|
|
#
|
|
|
|
import os
|
|
import imp
|
|
import inspect
|
|
import logging
|
|
from enum import Enum
|
|
|
|
from Mailnag.common.config import cfg_folder
|
|
from Mailnag.common.dist_cfg import LIB_DIR
|
|
|
|
PLUGIN_LIB_PATH = os.path.join(LIB_DIR, 'plugins')
|
|
PLUGIN_USER_PATH = os.path.join(cfg_folder, 'plugins')
|
|
PLUGIN_PATHS = [ PLUGIN_LIB_PATH, PLUGIN_USER_PATH ]
|
|
|
|
#
|
|
# All known hook types.
|
|
#
|
|
class HookTypes(Enum):
|
|
# func signature:
|
|
# IN: List of loaded accounts
|
|
# OUT: None
|
|
ACCOUNTS_LOADED = 'accounts-loaded'
|
|
# func signature:
|
|
# IN: None
|
|
# OUT: None
|
|
MAIL_CHECK = 'mail-check'
|
|
# func signature:
|
|
# IN: new mails, all mails
|
|
# OUT: None
|
|
MAILS_ADDED = 'mails-added'
|
|
# func signature:
|
|
# IN: remaining mails
|
|
# OUT: None
|
|
MAILS_REMOVED = 'mails-removed'
|
|
# func signature:
|
|
# IN: all mails
|
|
# OUT: filtered mails
|
|
FILTER_MAILS = 'filter-mails'
|
|
|
|
|
|
#
|
|
# Registry class for plugin hooks.
|
|
#
|
|
# Registered hook functions must not block the mailnag daemon.
|
|
# Hook functions with an execution time > 1s should be
|
|
# implemented non-blocking (i. e. asynchronously).
|
|
class HookRegistry:
|
|
def __init__(self):
|
|
self._hooks = {
|
|
HookTypes.ACCOUNTS_LOADED : [],
|
|
HookTypes.MAIL_CHECK : [],
|
|
HookTypes.MAILS_ADDED : [],
|
|
HookTypes.MAILS_REMOVED : [],
|
|
HookTypes.FILTER_MAILS : []
|
|
}
|
|
|
|
# Priority should be an integer value fom 0 (very high) to 100 (very low)
|
|
# Plugin hooks will be called in order from high to low priority.
|
|
def register_hook_func(self, hooktype, func, priority = 100):
|
|
self._hooks[hooktype].append( (priority, func) )
|
|
|
|
|
|
def unregister_hook_func(self, hooktype, func):
|
|
pairs = self._hooks[hooktype]
|
|
pair = next(pa for pa in pairs if (pa[1] == func))
|
|
pairs.remove(pair)
|
|
|
|
|
|
def get_hook_funcs(self, hooktype):
|
|
pairs_by_prio = sorted(self._hooks[hooktype], key = lambda p: p[0])
|
|
funcs = [ f for p, f in pairs_by_prio ]
|
|
return funcs
|
|
|
|
|
|
# Abstract base class for a MailnagController instance
|
|
# passed to plugins.
|
|
class MailnagController:
|
|
# Returns a HookRegistry object.
|
|
def get_hooks(self): pass
|
|
# Shuts down the Mailnag process.
|
|
# May throw an InvalidOperationException.
|
|
def shutdown(self): pass
|
|
# Enforces a manual mail check.
|
|
# May throw an InvalidOperationException.
|
|
def check_for_mails(self): pass
|
|
# Marks the mail with specified mail_id as read.
|
|
# May throw an InvalidOperationException.
|
|
def mark_mail_as_read(self, mail_id): pass
|
|
|
|
|
|
#
|
|
# Mailnag Plugin base class
|
|
#
|
|
class Plugin:
|
|
def __init__(self):
|
|
# Plugins shouldn't do anything in the constructor.
|
|
# They are expected to start living if they are actually
|
|
# enabled (i.e. in the enable() method).
|
|
# Plugin data isn't enabled yet and call to methods like
|
|
# get_mailnag_controller() or get_config().
|
|
pass
|
|
|
|
#
|
|
# Abstract methods,
|
|
# to be overriden by derived plugin types.
|
|
#
|
|
def enable(self):
|
|
# Plugins are expected to
|
|
# register all hooks here.
|
|
raise NotImplementedError
|
|
|
|
|
|
def disable(self):
|
|
# Plugins are expected to
|
|
# unregister all hooks here,
|
|
# free all allocated resources,
|
|
# and terminate threads (if any).
|
|
raise NotImplementedError
|
|
|
|
|
|
def get_manifest(self):
|
|
# Plugins are expected to
|
|
# return a tuple of the following form:
|
|
# (name, description, version, author, mandatory).
|
|
raise NotImplementedError
|
|
|
|
|
|
def get_default_config(self):
|
|
# Plugins are expected to return a
|
|
# dictionary with default values.
|
|
raise NotImplementedError
|
|
|
|
|
|
def has_config_ui(self):
|
|
# Plugins are expected to return True if
|
|
# they provide a configuration widget,
|
|
# otherwise they must return False.
|
|
raise NotImplementedError
|
|
|
|
|
|
def get_config_ui(self):
|
|
# Plugins are expected to
|
|
# return a GTK widget here.
|
|
# Return None if the plugin
|
|
# does not need a config widget.
|
|
raise NotImplementedError
|
|
|
|
|
|
def load_ui_from_config(self, config_ui):
|
|
# Plugins are expected to
|
|
# load their config values (get_config())
|
|
# in the widget returned by get_config_ui().
|
|
raise NotImplementedError
|
|
|
|
|
|
def save_ui_to_config(self, config_ui):
|
|
# Plugins are expected to
|
|
# save the config values of the widget
|
|
# returned by get_config_ui() to their config
|
|
# (get_config()).
|
|
raise NotImplementedError
|
|
|
|
|
|
#
|
|
# Public methods
|
|
#
|
|
def init(self, modname, cfg, mailnag_controller):
|
|
config = {}
|
|
|
|
# try to load plugin config
|
|
if cfg.has_section(modname):
|
|
for name, value in cfg.items(modname):
|
|
config[name] = value
|
|
|
|
# sync with default config
|
|
default_config = self.get_default_config()
|
|
for k, v in default_config.items():
|
|
if k not in config:
|
|
config[k] = v
|
|
|
|
self._modname = modname
|
|
self._config = config
|
|
self._mailnag_controller = mailnag_controller
|
|
|
|
|
|
def get_name(self):
|
|
name = self.get_manifest()[0]
|
|
return name
|
|
|
|
|
|
def get_modname(self):
|
|
return self._modname
|
|
|
|
|
|
def get_config(self):
|
|
return self._config
|
|
|
|
|
|
#
|
|
# Protected methods
|
|
#
|
|
def get_mailnag_controller(self):
|
|
return self._mailnag_controller
|
|
|
|
|
|
#
|
|
# Static methods
|
|
#
|
|
|
|
# Note : Plugin instances do not own
|
|
# a reference to MailnagController object
|
|
# when instantiated in *config mode*.
|
|
@staticmethod
|
|
def load_plugins(cfg, mailnag_controller = None, filter_names = None):
|
|
plugins = []
|
|
plugin_types = Plugin._load_plugin_types()
|
|
|
|
for modname, t in plugin_types:
|
|
try:
|
|
if (filter_names == None) or (modname in filter_names):
|
|
p = t()
|
|
p.init(modname, cfg, mailnag_controller)
|
|
plugins.append(p)
|
|
except:
|
|
logging.exception("Failed to instantiate plugin '%s'" % modname)
|
|
|
|
return plugins
|
|
|
|
|
|
@staticmethod
|
|
def _load_plugin_types():
|
|
plugin_types = []
|
|
|
|
for path in PLUGIN_PATHS:
|
|
if not os.path.exists(path):
|
|
continue
|
|
|
|
for f in os.listdir(path):
|
|
mod = None
|
|
modname, ext = os.path.splitext(f)
|
|
|
|
try:
|
|
if ext.lower() == '.py':
|
|
if not os.path.exists(os.path.join(path, modname + '.pyc')):
|
|
mod = imp.load_source(modname, os.path.join(path, f))
|
|
elif ext.lower() == '.pyc':
|
|
mod = imp.load_compiled(modname, os.path.join(path, f))
|
|
|
|
if mod != None:
|
|
for t in dir(mod):
|
|
t = getattr(mod, t)
|
|
if inspect.isclass(t) and \
|
|
(inspect.getmodule(t) == mod) and \
|
|
issubclass(t, Plugin):
|
|
plugin_types.append((modname, t))
|
|
|
|
except:
|
|
logging.exception("Error while opening plugin file '%s'" % f)
|
|
|
|
return plugin_types
|