286 lines
8.3 KiB
Python
286 lines
8.3 KiB
Python
|
# This file is part of Gajim.
|
||
|
#
|
||
|
# Gajim 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; version 3 only.
|
||
|
#
|
||
|
# Gajim 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 Gajim. If not, see <http://www.gnu.org/licenses/>.
|
||
|
|
||
|
'''
|
||
|
Base class for implementing plugin.
|
||
|
|
||
|
:author: Mateusz Biliński <mateusz@bilinski.it>
|
||
|
:since: 1st June 2008
|
||
|
:copyright: Copyright (2008) Mateusz Biliński <mateusz@bilinski.it>
|
||
|
:license: GPL
|
||
|
'''
|
||
|
|
||
|
from typing import List # pylint: disable=W0611
|
||
|
from typing import Tuple # pylint: disable=W0611
|
||
|
from typing import Dict # pylint: disable=W0611
|
||
|
from typing import Any # pylint: disable=W0611
|
||
|
|
||
|
import os
|
||
|
import locale
|
||
|
import logging
|
||
|
import pickle
|
||
|
|
||
|
from gajim.common import configpaths
|
||
|
from gajim.common.types import PluginExtensionPoints # pylint: disable=W0611
|
||
|
from gajim.common.types import EventHandlersDict # pylint: disable=W0611
|
||
|
from gajim.common.types import PluginEvents # pylint: disable=W0611
|
||
|
|
||
|
from gajim.plugins.helpers import log
|
||
|
from gajim.plugins.gui import GajimPluginConfigDialog
|
||
|
|
||
|
|
||
|
log = logging.getLogger('gajim.p.plugin')
|
||
|
|
||
|
|
||
|
class GajimPlugin:
|
||
|
'''
|
||
|
Base class for implementing Gajim plugins.
|
||
|
'''
|
||
|
name = ''
|
||
|
'''
|
||
|
Name of plugin.
|
||
|
|
||
|
Will be shown in plugins management GUI.
|
||
|
|
||
|
:type: str
|
||
|
'''
|
||
|
short_name = ''
|
||
|
'''
|
||
|
Short name of plugin.
|
||
|
|
||
|
Used for quick identification of plugin.
|
||
|
|
||
|
:type: str
|
||
|
|
||
|
:todo: decide whether we really need this one, because class name (with
|
||
|
module name) can act as such short name
|
||
|
'''
|
||
|
encryption_name = ''
|
||
|
'''
|
||
|
Name of the encryption scheme.
|
||
|
|
||
|
The name that Gajim displays in the encryption menu.
|
||
|
Leave empty if the plugin is not an encryption plugin.
|
||
|
|
||
|
:type: str
|
||
|
|
||
|
'''
|
||
|
version = ''
|
||
|
'''
|
||
|
Version of plugin.
|
||
|
|
||
|
:type: str
|
||
|
|
||
|
:todo: decide how to compare version between each other (which one
|
||
|
is higher). Also rethink: do we really need to compare versions
|
||
|
of plugins between each other? This would be only useful if we detect
|
||
|
same plugin class but with different version and we want only the newest
|
||
|
one to be active - is such policy good?
|
||
|
'''
|
||
|
description = ''
|
||
|
'''
|
||
|
Plugin description.
|
||
|
|
||
|
:type: str
|
||
|
|
||
|
:todo: should be allow rich text here (like HTML or reStructuredText)?
|
||
|
'''
|
||
|
authors = [] # type: List[str]
|
||
|
'''
|
||
|
Plugin authors.
|
||
|
|
||
|
:type: [] of str
|
||
|
|
||
|
:todo: should we decide on any particular format of author strings?
|
||
|
Especially: should we force format of giving author's e-mail?
|
||
|
'''
|
||
|
homepage = ''
|
||
|
'''
|
||
|
URL to plug-in's homepage.
|
||
|
|
||
|
:type: str
|
||
|
|
||
|
:todo: should we check whether provided string is valid URI? (Maybe
|
||
|
using 'property')
|
||
|
'''
|
||
|
gui_extension_points = {} # type: PluginExtensionPoints
|
||
|
'''
|
||
|
Extension points that plugin wants to connect with and handlers to be used.
|
||
|
|
||
|
Keys of this string should be strings with name of GUI extension point
|
||
|
to handles. Values should be 2-element tuples with references to handling
|
||
|
functions. First function will be used to connect plugin with extpoint,
|
||
|
the second one to successfully disconnect from it. Connecting takes places
|
||
|
when plugin is activated and extpoint already exists, or when plugin is
|
||
|
already activated but extpoint is being created (eg. chat window opens).
|
||
|
Disconnecting takes place when plugin is deactivated and extpoint exists
|
||
|
or when extpoint is destroyed and plugin is activate (eg. chat window
|
||
|
closed).
|
||
|
'''
|
||
|
config_default_values = {} # type: Dict[str, Tuple[Any, str]]
|
||
|
'''
|
||
|
Default values for keys that should be stored in plug-in config.
|
||
|
|
||
|
This dict is used when when someone calls for config option but it has not
|
||
|
been set yet.
|
||
|
|
||
|
Values are tuples: (default_value, option_description). The first one can
|
||
|
be anything (this is the advantage of using shelve/pickle instead of
|
||
|
custom-made config I/O handling); the second one should be str (gettext
|
||
|
can be used if need and/or translation is planned).
|
||
|
|
||
|
:type: {} of 2-element tuples
|
||
|
'''
|
||
|
events_handlers = {} # type: EventHandlersDict
|
||
|
'''
|
||
|
Dictionary with events handlers.
|
||
|
|
||
|
Keys are event names. Values should be 2-element tuples with handler
|
||
|
priority as first element and reference to handler function as second
|
||
|
element. Priority is integer. See `ged` module for predefined priorities
|
||
|
like `ged.PRECORE`, `ged.CORE` or `ged.POSTCORE`.
|
||
|
|
||
|
:type: {} with 2-element tuples
|
||
|
'''
|
||
|
events = [] # type: PluginEvents
|
||
|
'''
|
||
|
New network event classes to be registered in Network Events Controller.
|
||
|
|
||
|
:type: [] of `nec.NetworkIncomingEvent` or `nec.NetworkOutgoingEvent`
|
||
|
subclasses.
|
||
|
'''
|
||
|
|
||
|
def __init__(self) -> None:
|
||
|
self.config = GajimPluginConfig(self)
|
||
|
'''
|
||
|
Plug-in configuration dictionary.
|
||
|
|
||
|
Automatically saved and loaded and plug-in (un)load.
|
||
|
|
||
|
:type: `plugins.plugin.GajimPluginConfig`
|
||
|
'''
|
||
|
self.activatable = True
|
||
|
self.available_text = ''
|
||
|
self.load_config()
|
||
|
self.config_dialog = GajimPluginConfigDialog(self)
|
||
|
self.init()
|
||
|
|
||
|
def save_config(self) -> None:
|
||
|
self.config.save()
|
||
|
|
||
|
def load_config(self) -> None:
|
||
|
self.config.load()
|
||
|
|
||
|
def __eq__(self, plugin):
|
||
|
if self.short_name == plugin.short_name:
|
||
|
return True
|
||
|
|
||
|
return False
|
||
|
|
||
|
def __ne__(self, plugin):
|
||
|
if self.short_name != plugin.short_name:
|
||
|
return True
|
||
|
|
||
|
return False
|
||
|
|
||
|
def local_file_path(self, file_name):
|
||
|
return os.path.join(self.__path__, file_name)
|
||
|
|
||
|
def init(self):
|
||
|
pass
|
||
|
|
||
|
def activate(self):
|
||
|
pass
|
||
|
|
||
|
def deactivate(self):
|
||
|
pass
|
||
|
|
||
|
|
||
|
class GajimPluginConfig():
|
||
|
def __init__(self, plugin):
|
||
|
self.plugin = plugin
|
||
|
self.FILE_PATH = (configpaths.get('PLUGINS_CONFIG_DIR') /
|
||
|
self.plugin.short_name)
|
||
|
self.data = {}
|
||
|
|
||
|
def __getitem__(self, key):
|
||
|
if not key in self.data:
|
||
|
self.data[key] = self.plugin.config_default_values[key][0]
|
||
|
self.save()
|
||
|
|
||
|
return self.data[key]
|
||
|
|
||
|
def __setitem__(self, key, value):
|
||
|
self.data[key] = value
|
||
|
self.save()
|
||
|
|
||
|
def __delitem__(self, key):
|
||
|
del self.data[key]
|
||
|
self.save()
|
||
|
|
||
|
def __contains__(self, key):
|
||
|
return key in self.data
|
||
|
|
||
|
def __iter__(self):
|
||
|
for k in self.data.keys():
|
||
|
yield k
|
||
|
|
||
|
def keys(self):
|
||
|
return self.data.keys()
|
||
|
|
||
|
def items(self):
|
||
|
return self.data.items()
|
||
|
|
||
|
def save(self):
|
||
|
with open(self.FILE_PATH, 'wb') as fd:
|
||
|
pickle.dump(self.data, fd)
|
||
|
|
||
|
def load(self):
|
||
|
if not self.FILE_PATH.is_file():
|
||
|
self.data = {}
|
||
|
self.save()
|
||
|
return
|
||
|
with open(self.FILE_PATH, 'rb') as fd:
|
||
|
try:
|
||
|
self.data = pickle.load(fd)
|
||
|
except Exception:
|
||
|
try:
|
||
|
import shelve
|
||
|
s = shelve.open(self.FILE_PATH)
|
||
|
for (k, v) in s.items():
|
||
|
self.data[k] = v
|
||
|
if not isinstance(self.data, dict):
|
||
|
raise GajimPluginException
|
||
|
s.close()
|
||
|
self.save()
|
||
|
except Exception:
|
||
|
enc = locale.getpreferredencoding()
|
||
|
filename = self.FILE_PATH.decode(enc) + '.bak'
|
||
|
log.warning(
|
||
|
'%s plugin config file not readable. Saving it as '
|
||
|
'%s and creating a new one',
|
||
|
self.plugin.short_name, filename)
|
||
|
if os.path.exists(self.FILE_PATH + '.bak'):
|
||
|
os.remove(self.FILE_PATH + '.bak')
|
||
|
os.rename(self.FILE_PATH, self.FILE_PATH + '.bak')
|
||
|
self.data = {}
|
||
|
self.save()
|
||
|
|
||
|
|
||
|
class GajimPluginException(Exception):
|
||
|
pass
|
||
|
|
||
|
class GajimPluginInitError(GajimPluginException):
|
||
|
pass
|