comparison src/twisted/plugins/pubsub.py @ 369:dabee42494ac

config file + cleaning: - SàT Pubsub can now be configured using the same config file as SàT itself (i.e. sat.conf or .sat.conf), in the same locations (/etc, local dir, xdg dir). Its options must be in the "pubsub" section - options on command line override config options - removed tap and http files which are not used anymore - changed directory structure to put source in src, to be coherent with SàT and Libervia - changed options name, db* become db_*, secret become xmpp_pwd - an exception is raised if jid or xmpp_pwd is are not configured
author Goffi <goffi@goffi.org>
date Fri, 02 Mar 2018 12:59:38 +0100
parents twisted/plugins/sat_pubsub.py@618a92080812
children 9a787881b824
comparison
equal deleted inserted replaced
368:618a92080812 369:dabee42494ac
1 #!/usr/bin/python
2 #-*- coding: utf-8 -*-
3
4 # Copyright (c) 2012-2018 Jérôme Poisson
5 # Copyright (c) 2003-2011 Ralph Meijer
6
7
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU Affero General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
12
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU Affero General Public License for more details.
17
18 # You should have received a copy of the GNU Affero General Public License
19 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 # --
21
22 # This program is based on Idavoll (http://idavoll.ik.nu/),
23 # originaly written by Ralph Meijer (http://ralphm.net/blog/)
24 # It is sublicensed under AGPL v3 (or any later version) as allowed by the original
25 # license.
26
27 # --
28
29 # Here is a copy of the original license:
30
31 # Copyright (c) 2003-2011 Ralph Meijer
32
33 # Permission is hereby granted, free of charge, to any person obtaining
34 # a copy of this software and associated documentation files (the
35 # "Software"), to deal in the Software without restriction, including
36 # without limitation the rights to use, copy, modify, merge, publish,
37 # distribute, sublicense, and/or sell copies of the Software, and to
38 # permit persons to whom the Software is furnished to do so, subject to
39 # the following conditions:
40
41 # The above copyright notice and this permission notice shall be
42 # included in all copies or substantial portions of the Software.
43
44 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
45 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
46 # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
47 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
48 # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
49 # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
50 # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
51
52
53 import sat_pubsub
54 from twisted.application.service import IServiceMaker
55 from twisted.application import service
56 from twisted.python import usage
57 from twisted.words.protocols.jabber.jid import JID
58 from twisted.plugin import IPlugin
59
60 from wokkel.component import Component
61 from wokkel.disco import DiscoHandler
62 from wokkel.generic import FallbackHandler, VersionHandler
63 from wokkel.iwokkel import IPubSubResource
64 from wokkel import data_form
65 from wokkel import pubsub
66 from wokkel import rsm
67 from wokkel import mam
68 from zope.interface import implements
69
70 from sat_pubsub import const
71 from sat_pubsub import mam as pubsub_mam
72 from sat_pubsub.backend import BackendService
73 from sat_pubsub.schema import SchemaHandler
74 from sat_pubsub.privilege import PrivilegesHandler
75 from sat_pubsub.delegation import DelegationsHandler
76 from os.path import expanduser, realpath
77 import ConfigParser
78
79
80 OPT_PARAMETERS_BOTH = [
81 ['jid', None, None, 'JID this component will be available at'],
82 ['xmpp_pwd', None, None, 'XMPP server component password'],
83 ['rhost', None, '127.0.0.1', 'XMPP server host'],
84 ['rport', None, '5347', 'XMPP server port'],
85 ['backend', None, 'pgsql', 'Choice of storage backend'],
86 ['db_user', None, None, 'Database user (pgsql backend)'],
87 ['db_name', None, 'pubsub', 'Database name (pgsql backend)'],
88 ['db_pass', None, None, 'Database password (pgsql backend)'],
89 ['db_host', None, None, 'Database host (pgsql backend)'],
90 ['db_port', None, None, 'Database port (pgsql backend)'],
91 ]
92 # here for future use
93 OPT_PARAMETERS_CFG = []
94
95 CONFIG_FILENAME = u'sat'
96 # List of the configuration filenames sorted by ascending priority
97 CONFIG_FILES = [realpath(expanduser(path) + CONFIG_FILENAME + '.conf') for path in (
98 '/etc/', '/etc/{}/'.format(CONFIG_FILENAME),
99 '~/', '~/.',
100 '.config/', '.config/.',
101 '', '.')]
102 CONFIG_SECTION = 'pubsub'
103
104
105 class Options(usage.Options):
106 optParameters = OPT_PARAMETERS_BOTH
107
108 optFlags = [
109 ('verbose', 'v', 'Show traffic'),
110 ('hide-nodes', None, 'Hide all nodes for disco')
111 ]
112
113 def __init__(self):
114 """Read SàT Pubsub configuration file in order to overwrite the hard-coded default values.
115
116 Priority for the usage of the values is (from lowest to highest):
117 - hard-coded default values
118 - values from SàT configuration files
119 - values passed on the command line
120 """
121 # If we do it the reading later: after the command line options have been parsed, there's no good way to know
122 # if the options values are the hard-coded ones or if they have been passed on the command line.
123
124 # FIXME: must be refactored + code can be factorised with backend
125 config_parser = ConfigParser.SafeConfigParser()
126 config_parser.read(CONFIG_FILES)
127 for param in self.optParameters + OPT_PARAMETERS_CFG:
128 name = param[0]
129 try:
130 value = config_parser.get(CONFIG_SECTION, name)
131 if isinstance(value, unicode):
132 value = value.encode('utf-8')
133 try:
134 param[2] = param[4](value)
135 except IndexError: # the coerce method is optional
136 param[2] = value
137 except (ConfigParser.NoSectionError, ConfigParser.NoOptionError):
138 pass
139 usage.Options.__init__(self)
140 for opt_data in OPT_PARAMETERS_CFG:
141 self[opt_data[0]] = opt_data[2]
142
143 def postOptions(self):
144 if self['backend'] not in ['pgsql', 'memory']:
145 raise usage.UsageError, "Unknown backend!"
146 if self['backend'] == 'memory':
147 raise NotImplementedError('memory backend is not available at the moment')
148
149 self['jid'] = JID(self['jid']) if self['jid'] else None
150
151
152 class SatPubsubMaker(object):
153 implements(IServiceMaker, IPlugin)
154 tapname = "sat-pubsub"
155 description = u"Salut à Toi Publish-Subscribe Service Component".encode('utf-8')
156 options = Options
157
158 def makeService(self, config):
159 if not config['jid'] or not config['xmpp_pwd']:
160 raise usage.UsageError("You must specify jid and xmpp_pwd")
161 s = service.MultiService()
162
163 # Create backend service with storage
164
165 if config['backend'] == 'pgsql':
166 from twisted.enterprise import adbapi
167 from sat_pubsub.pgsql_storage import Storage
168 from psycopg2.extras import NamedTupleConnection
169 keys_map = {
170 'db_user': 'user',
171 'db_pass': 'password',
172 'db_name': 'database',
173 'db_host': 'host',
174 'db_port': 'port',
175 }
176 kwargs = {}
177 for config_k, k in keys_map.iteritems():
178 v = config.get(config_k)
179 if v is None:
180 continue
181 kwargs[k] = v
182 dbpool = adbapi.ConnectionPool('psycopg2',
183 cp_reconnect=True,
184 client_encoding='utf-8',
185 connection_factory=NamedTupleConnection,
186 **kwargs
187 )
188 st = Storage(dbpool)
189 elif config['backend'] == 'memory':
190 from sat_pubsub.memory_storage import Storage
191 st = Storage()
192
193 bs = BackendService(st)
194 bs.setName('backend')
195 bs.setServiceParent(s)
196
197 # Set up XMPP server-side component with publish-subscribe capabilities
198
199 cs = Component(config["rhost"], int(config["rport"]),
200 config["jid"].full(), config["xmpp_pwd"])
201 cs.setName('component')
202 cs.setServiceParent(s)
203
204 cs.factory.maxDelay = 900
205
206 if config["verbose"]:
207 cs.logTraffic = True
208
209 FallbackHandler().setHandlerParent(cs)
210 VersionHandler(u'SàT Pubsub', sat_pubsub.__version__).setHandlerParent(cs)
211 DiscoHandler().setHandlerParent(cs)
212
213 ph = PrivilegesHandler(config['jid'])
214 ph.setHandlerParent(cs)
215 bs.privilege = ph
216
217 resource = IPubSubResource(bs)
218 resource.hideNodes = config["hide-nodes"]
219 resource.serviceJID = config["jid"]
220
221 ps = (rsm if const.FLAG_ENABLE_RSM else pubsub).PubSubService(resource)
222 ps.setHandlerParent(cs)
223 resource.pubsubService = ps
224
225 if const.FLAG_ENABLE_MAM:
226 mam_resource = pubsub_mam.MAMResource(bs)
227 mam_s = mam.MAMService(mam_resource)
228 mam_s.addFilter(data_form.Field(var=const.MAM_FILTER_CATEGORY))
229 mam_s.setHandlerParent(cs)
230
231 sh = SchemaHandler()
232 sh.setHandlerParent(cs)
233
234 # XXX: delegation must be instancied at the end,
235 # because it does some MonkeyPatching on handlers
236 dh = DelegationsHandler()
237 dh.setHandlerParent(cs)
238 bs.delegation = dh
239
240 return s
241
242 serviceMaker = SatPubsubMaker()