Mercurial > libervia-pubsub
diff twisted/plugins/pubsub.py @ 405:c56a728412f1
file organisation + setup refactoring:
- `/src` has been renamed to `/sat_pubsub`, this is the recommended naming convention
- revamped `setup.py` on the basis of SàT's `setup.py`
- added a `VERSION` which is the unique place where version number will now be set
- use same trick as in SàT to specify dev version (`D` at the end)
- use setuptools_scm to retrieve Mercurial hash when in dev version
author | Goffi <goffi@goffi.org> |
---|---|
date | Fri, 16 Aug 2019 12:00:02 +0200 |
parents | src/twisted/plugins/pubsub.py@aa3a464df605 |
children | a58610ab2983 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/twisted/plugins/pubsub.py Fri Aug 16 12:00:02 2019 +0200 @@ -0,0 +1,276 @@ +#!/usr/bin/python +#-*- coding: utf-8 -*- + +# Copyright (c) 2012-2019 Jérôme Poisson +# Copyright (c) 2003-2011 Ralph Meijer + + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU Affero General Public License as published by +# the Free Software Foundation, either version 3 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 Affero General Public License for more details. + +# You should have received a copy of the GNU Affero General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. +# -- + +# This program is based on Idavoll (http://idavoll.ik.nu/), +# originaly written by Ralph Meijer (http://ralphm.net/blog/) +# It is sublicensed under AGPL v3 (or any later version) as allowed by the original +# license. + +# -- + +# Here is a copy of the original license: + +# Copyright (c) 2003-2011 Ralph Meijer + +# Permission is hereby granted, free of charge, to any person obtaining +# a copy of this software and associated documentation files (the +# "Software"), to deal in the Software without restriction, including +# without limitation the rights to use, copy, modify, merge, publish, +# distribute, sublicense, and/or sell copies of the Software, and to +# permit persons to whom the Software is furnished to do so, subject to +# the following conditions: + +# The above copyright notice and this permission notice shall be +# included in all copies or substantial portions of the Software. + +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + +import csv +import sat_pubsub +import sys +from twisted.application.service import IServiceMaker +from twisted.application import service +from twisted.python import usage, log +from twisted.words.protocols.jabber.jid import JID +from twisted.plugin import IPlugin + +from wokkel.component import Component +from wokkel.disco import DiscoHandler +from wokkel.generic import FallbackHandler, VersionHandler +from wokkel.iwokkel import IPubSubResource +from wokkel import data_form +from wokkel import pubsub +from wokkel import rsm +from wokkel import mam +from zope.interface import implements + +from sat_pubsub import const +from sat_pubsub import mam as pubsub_mam +from sat_pubsub import pubsub_admin +from sat_pubsub.backend import BackendService, ExtraDiscoHandler +from sat_pubsub.schema import SchemaHandler +from sat_pubsub.privilege import PrivilegesHandler +from sat_pubsub.delegation import DelegationsHandler +from os.path import expanduser, realpath +import ConfigParser + + +def coerceListType(value): + return csv.reader( + [value], delimiter=",", quotechar='"', skipinitialspace=True + ).next() + + +def coerceJidListType(value): + values = [JID(v) for v in coerceListType(value)] + if any((j.resource for j in values)): + raise ValueError(u"you must use bare jids") + return values + + + +OPT_PARAMETERS_BOTH = [ + ['jid', None, None, 'JID this component will be available at'], + ['xmpp_pwd', None, None, 'XMPP server component password'], + ['rhost', None, '127.0.0.1', 'XMPP server host'], + ['rport', None, '5347', 'XMPP server port'], + ['backend', None, 'pgsql', 'Choice of storage backend'], + ['db_user', None, None, 'Database user (pgsql backend)'], + ['db_name', None, 'pubsub', 'Database name (pgsql backend)'], + ['db_pass', None, None, 'Database password (pgsql backend)'], + ['db_host', None, None, 'Database host (pgsql backend)'], + ['db_port', None, None, 'Database port (pgsql backend)'], + ] +# here for future use +OPT_PARAMETERS_CFG = [ + ["admins_jids_list", None, [], "List of administrators' bare jids", + coerceJidListType] + ] + +CONFIG_FILENAME = u'sat' +# List of the configuration filenames sorted by ascending priority +CONFIG_FILES = [realpath(expanduser(path) + CONFIG_FILENAME + '.conf') for path in ( + '/etc/', '/etc/{}/'.format(CONFIG_FILENAME), + '~/', '~/.', + '.config/', '.config/.', + '', '.')] +CONFIG_SECTION = 'pubsub' + + +class Options(usage.Options): + optParameters = OPT_PARAMETERS_BOTH + + optFlags = [ + ('verbose', 'v', 'Show traffic'), + ('hide-nodes', None, 'Hide all nodes for disco') + ] + + def __init__(self): + """Read SàT Pubsub configuration file in order to overwrite the hard-coded default values. + + Priority for the usage of the values is (from lowest to highest): + - hard-coded default values + - values from SàT configuration files + - values passed on the command line + """ + # If we do it the reading later: after the command line options have been parsed, there's no good way to know + # if the options values are the hard-coded ones or if they have been passed on the command line. + + # FIXME: must be refactored + code can be factorised with backend + config_parser = ConfigParser.SafeConfigParser() + config_parser.read(CONFIG_FILES) + for param in self.optParameters + OPT_PARAMETERS_CFG: + name = param[0] + try: + value = config_parser.get(CONFIG_SECTION, name) + if isinstance(value, unicode): + value = value.encode('utf-8') + try: + param[2] = param[4](value) + except IndexError: # the coerce method is optional + param[2] = value + except Exception as e: + log.err(u'Invalid value for setting "{name}": {msg}'.format( + name=name, msg=e)) + sys.exit(1) + except (ConfigParser.NoSectionError, ConfigParser.NoOptionError): + pass + usage.Options.__init__(self) + for opt_data in OPT_PARAMETERS_CFG: + self[opt_data[0]] = opt_data[2] + + def postOptions(self): + if self['backend'] not in ['pgsql', 'memory']: + raise usage.UsageError, "Unknown backend!" + if self['backend'] == 'memory': + raise NotImplementedError('memory backend is not available at the moment') + + self['jid'] = JID(self['jid']) if self['jid'] else None + + +class SatPubsubMaker(object): + implements(IServiceMaker, IPlugin) + tapname = "sat-pubsub" + description = u"Salut à Toi Publish-Subscribe Service Component".encode('utf-8') + options = Options + + def makeService(self, config): + if not config['jid'] or not config['xmpp_pwd']: + raise usage.UsageError("You must specify jid and xmpp_pwd") + s = service.MultiService() + + # Create backend service with storage + + if config['backend'] == 'pgsql': + from twisted.enterprise import adbapi + from sat_pubsub.pgsql_storage import Storage + from psycopg2.extras import NamedTupleConnection + keys_map = { + 'db_user': 'user', + 'db_pass': 'password', + 'db_name': 'database', + 'db_host': 'host', + 'db_port': 'port', + } + kwargs = {} + for config_k, k in keys_map.iteritems(): + v = config.get(config_k) + if v is None: + continue + kwargs[k] = v + dbpool = adbapi.ConnectionPool('psycopg2', + cp_reconnect=True, + client_encoding='utf-8', + connection_factory=NamedTupleConnection, + **kwargs + ) + st = Storage(dbpool) + elif config['backend'] == 'memory': + from sat_pubsub.memory_storage import Storage + st = Storage() + + bs = BackendService(st, config) + bs.setName('backend') + bs.setServiceParent(s) + + # Set up XMPP server-side component with publish-subscribe capabilities + + cs = Component(config["rhost"], int(config["rport"]), + config["jid"].full(), config["xmpp_pwd"]) + cs.setName('component') + cs.setServiceParent(s) + + cs.factory.maxDelay = 900 + + if config["verbose"]: + cs.logTraffic = True + + FallbackHandler().setHandlerParent(cs) + VersionHandler(u'SàT Pubsub', sat_pubsub.__version__).setHandlerParent(cs) + DiscoHandler().setHandlerParent(cs) + + ph = PrivilegesHandler(config['jid']) + ph.setHandlerParent(cs) + bs.privilege = ph + + resource = IPubSubResource(bs) + resource.hideNodes = config["hide-nodes"] + resource.serviceJID = config["jid"] + + ps = (rsm if const.FLAG_ENABLE_RSM else pubsub).PubSubService(resource) + ps.setHandlerParent(cs) + resource.pubsubService = ps + + if const.FLAG_ENABLE_MAM: + mam_resource = pubsub_mam.MAMResource(bs) + mam_s = mam.MAMService(mam_resource) + mam_s.addFilter(data_form.Field(var=const.MAM_FILTER_CATEGORY)) + mam_s.setHandlerParent(cs) + + pa = pubsub_admin.PubsubAdminHandler(bs) + pa.setHandlerParent(cs) + + sh = SchemaHandler() + sh.setHandlerParent(cs) + + # wokkel.pubsub doesn't handle non pubsub# disco + # and we need to announce other feature, so this is a workaround + # to add them + # FIXME: propose a patch upstream to fix this situation + ed = ExtraDiscoHandler() + ed.setHandlerParent(cs) + + # XXX: delegation must be instancied at the end, + # because it does some MonkeyPatching on handlers + dh = DelegationsHandler() + dh.setHandlerParent(cs) + bs.delegation = dh + + return s + +serviceMaker = SatPubsubMaker()