comparison salut.py @ 3:593345584d21 default tip

replace ad-hoc commands "subscribe" and "unsubscribe" with a single "update" command
author souliane <souliane@mailoo.org>
date Wed, 02 Sep 2015 14:02:50 +0200
parents 77b77f48c975
children
comparison
equal deleted inserted replaced
2:77b77f48c975 3:593345584d21
1 #!/usr/bin/env python2 1 #!/usr/bin/env python2
2 # -*- coding: utf-8 -*- 2 # -*- coding: utf-8 -*-
3 3
4 # SAT plugin for account creation (experimental) 4 # SAT plugin for account creation (experimental)
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Jérôme Poisson (goffi@goffi.org) 5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014, 2015 Jérôme Poisson (goffi@goffi.org)
6 # Copyright (C) 2015 Adrien Cossa (souliane@mailoo.org)
6 7
7 # This program is free software: you can redistribute it and/or modify 8 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as published by 9 # it under the terms of the GNU Affero General Public License as published by
9 # the Free Software Foundation, either version 3 of the License, or 10 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version. 11 # (at your option) any later version.
32 DATABASE = "salut.db" 33 DATABASE = "salut.db"
33 ID_CMD_LIST = disco.DiscoIdentity("automation", "command-list") 34 ID_CMD_LIST = disco.DiscoIdentity("automation", "command-list")
34 NS_COMMANDS = "http://jabber.org/protocol/commands" 35 NS_COMMANDS = "http://jabber.org/protocol/commands"
35 NS_SEARCH = 'jabber:iq:search' 36 NS_SEARCH = 'jabber:iq:search'
36 QUERY_SEARCH = "/query[@xmlns='jabber:iq:search']" 37 QUERY_SEARCH = "/query[@xmlns='jabber:iq:search']"
37 SUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='subscribe']" 38 UPDATE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='update']"
38 UNSUBSCRIBE_CMD = "/iq[@type='set']/command[@xmlns='"+NS_COMMANDS+"' and @node='unsubscribe']" 39 INSTRUCTIONS = _(u'Update your subscription to the Jabber search directory.')
39 INSTRUCTIONS = _(u'This is a minimal directory for the Libervia demo (Salut à Toi project). For testing purpose only.')
40 FORM_INSTRUCTIONS = [INSTRUCTIONS] 40 FORM_INSTRUCTIONS = [INSTRUCTIONS]
41 41 SUBSCRIBE_DONE = _("You registration to the directory has been updated.")
42 SUBSCRIBE_TXT = _(u"Please give some words about you, then submit to be registered on the directory") 42 UNSUBSCRIBE_DONE = _("You have been removed from the directory.")
43 SUBSCRIBE_DONE = _("You are now registered on the directory")
44 UNSUBSCRIBE_DONE = _("You have been removed from the directory")
45 DB_CREATE = ['PRAGMA user_version=0', 43 DB_CREATE = ['PRAGMA user_version=0',
46 'CREATE TABLE directory (jid TEXT PRIMARY KEY, description TEXT)'] 44 'CREATE TABLE directory (jid TEXT PRIMARY KEY, description TEXT)']
45
47 46
48 class SalutGateway(XMPPHandler, IQHandlerMixin): 47 class SalutGateway(XMPPHandler, IQHandlerMixin):
49 implements(iwokkel.IDisco) 48 implements(iwokkel.IDisco)
50 49
51 def __init__(self, component=False): 50 def __init__(self, component=False):
62 61
63 def connectionMade(self): 62 def connectionMade(self):
64 print "Connected!" 63 print "Connected!"
65 self.xmlstream.addObserver("/iq[@type='get']" + QUERY_SEARCH, self.handleFieldsRequest) 64 self.xmlstream.addObserver("/iq[@type='get']" + QUERY_SEARCH, self.handleFieldsRequest)
66 self.xmlstream.addObserver("/iq[@type='set']" + QUERY_SEARCH, self.handleSearchRequest) 65 self.xmlstream.addObserver("/iq[@type='set']" + QUERY_SEARCH, self.handleSearchRequest)
67 self.xmlstream.addObserver(SUBSCRIBE_CMD, self.onSubscribe) 66 self.xmlstream.addObserver(UPDATE_CMD, self.onUpdate)
68 self.xmlstream.addObserver(UNSUBSCRIBE_CMD, self.onUnsubscribe)
69 self.discoHandler.setHandlerParent(self.parent) 67 self.discoHandler.setHandlerParent(self.parent)
70 68
71 def connectionLost(self, reason): 69 def connectionLost(self, reason):
72 print "Disconnected!" 70 print "Disconnected!"
73 71
74 def handleFieldsRequest(self, request): 72 def handleFieldsRequest(self, request):
73 """Respond to a fields request with the fields' list.
74
75 @param request (domish.Element): the request to respond to
76 """
75 result = domish.Element((None, 'iq')) 77 result = domish.Element((None, 'iq'))
76 result['type'] = 'result' 78 result['type'] = 'result'
77 result['id'] = request['id'] 79 result['id'] = request['id']
78 result['to'] = request['from'] 80 result['to'] = request['from']
79 query_elt = result.addElement('query', NS_SEARCH) 81 query_elt = result.addElement('query', NS_SEARCH)
81 form = data_form.Form('form', title=_('Directory search'), 83 form = data_form.Form('form', title=_('Directory search'),
82 instructions=FORM_INSTRUCTIONS, 84 instructions=FORM_INSTRUCTIONS,
83 formNamespace=NS_SEARCH) 85 formNamespace=NS_SEARCH)
84 form.addField(data_form.Field('fixed', label=_('Enter part of description or jid to find somebody,'))) 86 form.addField(data_form.Field('fixed', label=_('Enter part of description or jid to find somebody,')))
85 form.addField(data_form.Field('fixed', label=('let empty to have a full list of people'))) 87 form.addField(data_form.Field('fixed', label=('let empty to have a full list of people')))
86 form.addField(data_form.Field('text-single', 'jid', label=_('jid'))) 88 form.addField(data_form.Field('text-single', 'jid', label=_('Jabber ID')))
87 form.addField(data_form.Field('text-single', 'description', label=_('Description'))) 89 form.addField(data_form.Field('text-single', 'description', label=_('Description')))
88 query_elt.addChild(form.toElement()) 90 query_elt.addChild(form.toElement())
89 self.xmlstream.send(result) 91 self.xmlstream.send(result)
90 92
91 def handleSearchRequest(self, request): 93 def handleSearchRequest(self, request):
92 query = ["SELECT jid, description FROM directory"] 94 """Respond to a search request with the search results.
95
96 @param request (domish.Element): the request to respond to
97 """
93 args = OrderedDict() 98 args = OrderedDict()
94 try: 99 try:
95 query_elt = request.elements(NS_SEARCH, 'query').next() 100 query_elt = request.elements(NS_SEARCH, 'query').next()
96 form_elt = query_elt.elements(data_form.NS_X_DATA, 'x').next() 101 form_elt = query_elt.elements(data_form.NS_X_DATA, 'x').next()
97 except StopIteration: 102 except StopIteration:
104 except KeyError: 109 except KeyError:
105 pass 110 pass
106 else: 111 else:
107 if value: 112 if value:
108 args[col] = value 113 args[col] = value
109 114
110 if args:
111 query.append("WHERE")
112 query.append(" AND ".join(("%s LIKE ?" % col for col in args)))
113
114 row_iter = self.db.execute(' '.join(query), tuple(['%'+arg+'%' for arg in args.values()]))
115
116 result = domish.Element((None, 'iq')) 115 result = domish.Element((None, 'iq'))
117 result['type'] = 'result' 116 result['type'] = 'result'
118 result['id'] = request['id'] 117 result['id'] = request['id']
119 result['to'] = request['from'] 118 result['to'] = request['from']
120 query_elt = result.addElement('query', NS_SEARCH) 119 query_elt = result.addElement('query', NS_SEARCH)
125 jid_field_elt['label'] = 'Jabber ID' 124 jid_field_elt['label'] = 'Jabber ID'
126 jid_field_elt['var'] = 'jid' 125 jid_field_elt['var'] = 'jid'
127 description_field_elt = reported_elt.addElement('field') 126 description_field_elt = reported_elt.addElement('field')
128 description_field_elt['label'] = 'Description' 127 description_field_elt['label'] = 'Description'
129 description_field_elt['var'] = 'description' 128 description_field_elt['var'] = 'description'
130 for row in row_iter: 129
130 for row in self._getSubscriptions(args):
131 for col, value in zip(('jid', 'description'), row): 131 for col, value in zip(('jid', 'description'), row):
132 item_elt = x_form_elt.addElement('item') 132 item_elt = x_form_elt.addElement('item')
133 field_elt = item_elt.addElement('field') 133 field_elt = item_elt.addElement('field')
134 field_elt['var'] = col 134 field_elt['var'] = col
135 value_elt = field_elt.addElement('value', content=value) 135 value_elt = field_elt.addElement('value', content=value)
136 136
137 query_elt.addChild(x_form_elt) 137 query_elt.addChild(x_form_elt)
138 self.xmlstream.send(result) 138 self.xmlstream.send(result)
139 139
140 def onSubscribe(self, request): 140 def _getSubscriptions(self, args=None):
141 """Retrieve the subscriptions to the directory.
142
143 @param args (OrderedDict): arguments to filter the request
144 @return: list[(boolean, unicode)]
145 """
146 query = ["SELECT jid, description FROM directory"]
147
148 if args:
149 query.append("WHERE")
150 query.append(" AND ".join(("%s LIKE ?" % col for col in args)))
151
152 return self.db.execute(' '.join(query), tuple(['%'+arg+'%' for arg in args.values()]))
153
154 def _getSubscription(self, jid):
155 """Retrieve the subscription of the given JID.
156
157 @param jid (jid.JID): JID to search for
158 @return: (boolean, unicode)
159 """
160 return self._getSubscriptions(OrderedDict({'jid': jid.userhost()})).fetchone()
161
162 def _setSubscription(self, from_jid, subscribed, description):
163 """Set the subscription of the given JID.
164
165 @param from_jid (jid.JID): JID to subscribe (or unsubscribe)
166 @param subscribed (boolean): set to True to subscribe and False to unsubscribe
167 @param description (unicode): description
168 """
169 if subscribed:
170 self.db.execute('REPLACE INTO directory(jid,description) VALUES (?,?)', (from_jid.userhost(), description))
171 else:
172 self.db.execute('DELETE FROM directory WHERE jid=?', (from_jid.userhost(),))
173 self.db.connection.commit()
174
175 def onUpdate(self, request):
176 """Process the request to update a subscription.
177
178 @param request (domish.Element): the update request
179 """
141 result = domish.Element((None, 'iq')) 180 result = domish.Element((None, 'iq'))
142 result['type'] = 'result' 181 result['type'] = 'result'
143 result['id'] = request['id'] 182 result['id'] = request['id']
144 result['to'] = request['from'] 183 result['to'] = request['from']
145 from_ = jid.JID(request['from']) 184 from_jid = jid.JID(request['from'])
185
146 request_cmd = request.elements(NS_COMMANDS, 'command').next() 186 request_cmd = request.elements(NS_COMMANDS, 'command').next()
147 command_elt = result.addElement('command', NS_COMMANDS) 187 command_elt = result.addElement('command', NS_COMMANDS)
188 command_elt['node'] = request_cmd['node']
189
148 try: 190 try:
149 session_id = request_cmd['sessionid'] 191 session_id = request_cmd['sessionid']
150 except KeyError: 192 except KeyError:
151 session_id = None 193 session_id = None
152 194
153 if session_id is None: 195 if session_id is None:
154 # first request, we send the form 196 # first request, we send the form
155 command_elt['status'] = 'executing' 197 command_elt['status'] = 'executing'
156 session_id = str(uuid.uuid4()) 198 session_id = str(uuid.uuid4())
157 actions_elt = command_elt.addElement('actions') 199 actions_elt = command_elt.addElement("actions")
158 actions_elt['execute'] = 'next' 200 actions_elt['execute'] = 'complete'
159 actions_elt.addElement('next') 201 actions_elt.addElement('complete')
160 form = data_form.Form('form', instructions=FORM_INSTRUCTIONS, title=_('Directory subscription')) 202
161 infos = data_form.Field('fixed', value=SUBSCRIBE_TXT) 203 subscription = self._getSubscription(from_jid)
162 desc = data_form.Field('text-single', 'description', label=_(u"Some words about you")) 204 subscribed, desc = ('true', subscription[1]) if subscription else ('false', '')
163 form.addField(infos) 205
164 form.addField(desc) 206 form = data_form.Form('form', title=_('Directory subscription'),
207 instructions=FORM_INSTRUCTIONS,
208 formNamespace=NS_SEARCH)
209 form.addField(data_form.Field('boolean', 'subscribed', label=_('Subscribed'), values=[subscribed]))
210 form.addField(data_form.Field('text-single', 'description', label=_(u"Some words about you"), values=[desc]))
165 command_elt.addChild(form.toElement()) 211 command_elt.addChild(form.toElement())
166 else: 212 else:
167 req_forms = request_cmd.elements(data_form.NS_X_DATA, 'x') 213 try:
168 try: 214 request_form = request_cmd.elements(data_form.NS_X_DATA).next()
169 req_form = req_forms.next()
170 parsed_form = data_form.Form.fromElement(req_form)
171 description = parsed_form['description']
172 except (StopIteration, KeyError): 215 except (StopIteration, KeyError):
173 raise ValueError # TODO: properly cancel the command 216 raise ValueError # TODO: properly cancel the command
174 self.db.execute('REPLACE INTO directory(jid,description) VALUES (?,?)', (from_.userhost(), description)) 217 parsed_form = data_form.Form.fromElement(request_form)
175 self.db.connection.commit() 218 subscribed = parsed_form['subscribed'] in ("true", "1")
219 description = parsed_form['description']
220 self._setSubscription(from_jid, subscribed, description)
221
176 command_elt['status'] = 'completed' 222 command_elt['status'] = 'completed'
177 note_elt = command_elt.addElement('note') 223 note_elt = command_elt.addElement('note')
178 note_elt['type'] = 'info' 224 note_elt['type'] = 'info'
179 note_elt.addContent(SUBSCRIBE_DONE) 225 if subscribed:
226 note_elt.addContent(SUBSCRIBE_DONE)
227 else:
228 note_elt.addContent(UNSUBSCRIBE_DONE)
229
180 command_elt['sessionid'] = session_id 230 command_elt['sessionid'] = session_id
181 command_elt['node'] = request_cmd['node']
182 self.xmlstream.send(result)
183
184 def onUnsubscribe(self, request):
185 result = domish.Element((None, 'iq'))
186 result['type'] = 'result'
187 result['id'] = request['id']
188 result['to'] = request['from']
189 from_ = jid.JID(request['from'])
190 request_cmd = request.elements(NS_COMMANDS, 'command').next()
191 command_elt = result.addElement('command', NS_COMMANDS)
192 self.db.execute('DELETE FROM directory WHERE jid=?', (from_.userhost(),))
193 self.db.connection.commit()
194 command_elt['status'] = 'completed'
195 note_elt = command_elt.addElement('note')
196 note_elt['type'] = 'info'
197 note_elt.addContent(UNSUBSCRIBE_DONE)
198 command_elt['node'] = request_cmd['node']
199 self.xmlstream.send(result) 231 self.xmlstream.send(result)
200 232
201 def getDiscoInfo(self, requestor, target, nodeIdentifier=''): 233 def getDiscoInfo(self, requestor, target, nodeIdentifier=''):
202 return [disco.DiscoFeature(NS_SEARCH), 234 return [disco.DiscoFeature(NS_SEARCH),
203 disco.DiscoIdentity(u"directory", u"user", u"salut"), 235 disco.DiscoIdentity(u"directory", u"user", u"salut"),
205 ID_CMD_LIST] 237 ID_CMD_LIST]
206 238
207 def getDiscoItems(self, requestor, target, nodeIdentifier=''): 239 def getDiscoItems(self, requestor, target, nodeIdentifier=''):
208 ret = [] 240 ret = []
209 if nodeIdentifier == NS_COMMANDS: 241 if nodeIdentifier == NS_COMMANDS:
210 ret.append(disco.DiscoItem(target, "subscribe", "Subscribe to the directory")) 242 ret.append(disco.DiscoItem(target, "update", "Update your subscription to the directory"))
211 ret.append(disco.DiscoItem(target, "unsubscribe", "Unsubscribe from the directory"))
212 return ret 243 return ret