comparison src/plugins/plugin_xep_0048.py @ 982:0e80ee1fe9af

plugin XEP-0048: bookmarks (first draft)
author Goffi <goffi@goffi.org>
date Mon, 07 Apr 2014 16:22:35 +0200
parents
children c34e0b2bbf08
comparison
equal deleted inserted replaced
981:58a57ce5932a 982:0e80ee1fe9af
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # SAT plugin for Bookmarks (xep-0048)
5 # Copyright (C) 2009, 2010, 2011, 2012, 2013, 2014 Jérôme Poisson (goffi@goffi.org)
6
7 # 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 # the Free Software Foundation, either version 3 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
16
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20 from sat.core.i18n import _, D_
21 from sat.core import exceptions
22 from sat.memory.persistent import PersistentBinaryDict
23 from sat.tools import xml_tools
24 from logging import debug, info, warning, error
25 from twisted.words.xish import domish
26 from twisted.words.protocols.jabber import jid
27
28 from twisted.internet import defer
29
30 NS_BOOKMARKS = 'storage:bookmarks'
31
32 PLUGIN_INFO = {
33 "name": "Bookmarks",
34 "import_name": "XEP-0048",
35 "type": "XEP",
36 "protocols": ["XEP-0048"],
37 "dependencies": ["XEP-0045"],
38 "recommendations": ["XEP-0049"],
39 "main": "XEP_0048",
40 "handler": "no",
41 "description": _("""Implementation of bookmarks""")
42 }
43
44
45 class XEP_0048(object):
46 MUC_TYPE = 'muc'
47 URL_TYPE = 'url'
48 MUC_KEY = 'jid'
49 URL_KEY = 'url'
50 MUC_ATTRS = ('autojoin', 'name')
51 URL_ATTRS = ('name',)
52
53 def __init__(self, host):
54 info(_("Bookmarks plugin initialization"))
55 self.host = host
56 # self.__menu_id = host.registerCallback(self._bookmarksMenu, with_data=True)
57 self.__bm_save_id = host.registerCallback(self._bookmarksSaveCb, with_data=True)
58 host.importMenu((D_("Communication"), D_("bookmarks")), self._bookmarksMenu, security_limit=0, help_string=D_("Use and manage bookmarks"))
59 try:
60 self.private_plg = self.host.plugins["XEP-0049"]
61 except KeyError:
62 self.private_plg = None
63
64 @defer.inlineCallbacks
65 def profileConnected(self, profile):
66 client = self.host.getClient(profile)
67 local = client.bookmarks_local = PersistentBinaryDict(NS_BOOKMARKS, profile)
68 yield local.load()
69 if not local:
70 local[XEP_0048.MUC_TYPE] = dict()
71 local[XEP_0048.URL_TYPE] = dict()
72 private = yield self._getServerBookmarks('private', profile)
73 pubsub = client.bookmarks_pubsub = None
74
75 for bookmarks in (local, private, pubsub):
76 if bookmarks is not None:
77 for (room_jid, data) in bookmarks[XEP_0048.MUC_TYPE].items():
78 if data.get('autojoin', 'false') == 'true':
79 nick = data.get('nick', client.jid.user)
80 self.host.plugins['XEP-0045'].join(room_jid, nick, {}, profile_key=client.profile)
81
82 @defer.inlineCallbacks
83 def _getServerBookmarks(self, storage_type, profile):
84 """Get distants bookmarks
85
86 update also the client.bookmarks_[type] key, with None if service is not available
87 @param storage_type: storage type, can be:
88 - 'private': XEP-0049 storage
89 - 'pubsub': XEP-0223 storage
90 @param profile: %(doc_profile)s
91 @return: data dictionary, or None if feature is not available
92
93 @raise: exception.FeatureNotFound
94 """
95 client = self.host.getClient(profile)
96 if storage_type == 'private':
97 try:
98 bookmarks_private_xml = yield self.private_plg.privateXMLGet('storage', NS_BOOKMARKS, profile)
99 data = client.bookmarks_private = self._bookmarkElt2Dict(bookmarks_private_xml)
100 except (exceptions.FeatureNotFound, AttributeError):
101 info(_("Private XML storage not available"))
102 data = client.bookmarks_private = None
103 elif storage_type == 'pubsub':
104 raise NotImplementedError
105 else:
106 raise ValueError("storage_type must be 'private' or 'pubsub'")
107 defer.returnValue(data)
108
109 @defer.inlineCallbacks
110 def _setServerBookmarks(self, storage_type, bookmarks_elt, profile):
111 """Save bookmarks on server
112
113 @param storage_type: storage type, can be:
114 - 'private': XEP-0049 storage
115 - 'pubsub': XEP-0223 storage
116 @param bookmarks_elt (domish.Element): bookmarks XML
117 @param profile: %(doc_profile)s
118 """
119 if storage_type == 'private':
120 yield self.private_plg.privateXMLStore(bookmarks_elt, profile)
121 elif storage_type == 'pubsub':
122 raise NotImplementedError
123 else:
124 raise ValueError("storage_type must be 'private' or 'pubsub'")
125
126 def _bookmarkElt2Dict(self, storage_elt):
127 """Parse bookmarks to get dictionary
128 @param storage_elt (domish.Element): bookmarks storage
129 @return (dict): bookmark data (key: bookmark type, value: list) where key can be:
130 - XEP_0048.MUC_TYPE
131 - XEP_0048.URL_TYPE
132 - value (dict): data as for addBookmark
133 """
134 conf_data = {}
135 url_data = {}
136
137 conference_elts = storage_elt.elements(NS_BOOKMARKS, 'conference')
138 for conference_elt in conference_elts:
139 try:
140 room_jid = jid.JID(conference_elt[XEP_0048.MUC_KEY])
141 except KeyError:
142 warning ("invalid bookmark found, igoring it:\n%s" % conference_elt.toXml())
143 continue
144
145 data = conf_data[room_jid] = {}
146
147 for attr in XEP_0048.MUC_ATTRS:
148 if conference_elt.hasAttribute(attr):
149 data[attr] = conference_elt[attr]
150 try:
151 data['nick'] = unicode(conference_elt.elements(NS_BOOKMARKS, 'nick').next())
152 except StopIteration:
153 pass
154 # TODO: manage password (need to be secured, see XEP-0049 §4)
155
156 url_elts = storage_elt.elements(NS_BOOKMARKS, 'url')
157 for url_elt in url_elts:
158 try:
159 url = url_elt[XEP_0048.URL_KEY]
160 except KeyError:
161 warning ("invalid bookmark found, igoring it:\n%s" % url_elt.toXml())
162 continue
163 data = url_data[url] = {}
164 for attr in XEP_0048.URL_ATTRS:
165 if url_elt.hasAttribute(attr):
166 data[attr] = url_elt[attr]
167
168 return {XEP_0048.MUC_TYPE: conf_data, XEP_0048.URL_TYPE: url_data}
169
170 def _dict2BookmarkElt(self, type_, data):
171 """Construct a bookmark element from a data dict
172 @param data (dict): bookmark data (key: bookmark type, value: list) where key can be:
173 - XEP_0048.MUC_TYPE
174 - XEP_0048.URL_TYPE
175 - value (dict): data as for addBookmark
176 @return (domish.Element): bookmark element
177 """
178 rooms_data = data.get(XEP_0048.MUC_TYPE, {})
179 urls_data = data.get(XEP_0048.URL_TYPE, {})
180 storage_elt = domish.Element((NS_BOOKMARKS, 'storage'))
181 for room_jid in rooms_data:
182 conference_elt = storage_elt.addElement('conference')
183 conference_elt[XEP_0048.MUC_KEY] = room_jid.full()
184 for attr in XEP_0048.MUC_ATTRS:
185 try:
186 conference_elt[attr] = rooms_data[room_jid][attr]
187 except KeyError:
188 pass
189 try:
190 conference_elt.addElement('nick', content=rooms_data[room_jid]['nick'])
191 except KeyError:
192 pass
193
194 for url in urls_data:
195 url_elt = storage_elt.addElement('url')
196 url_elt[XEP_0048.URL_KEY] = url
197 for attr in XEP_0048.URL_ATTRS:
198 try:
199 url_elt[attr] = url[attr]
200 except KeyError:
201 pass
202
203 return storage_elt
204
205 def _bookmarksMenu(self, data, profile):
206 """ XMLUI activated by menu: return Gateways UI
207 @param profile: %(doc_profile)s
208
209 """
210 client = self.host.getClient(profile)
211 xmlui = xml_tools.XMLUI(title=_('Bookmarks manager'))
212 xmlui.addText(_("add a bookmark"))
213 xmlui.changeContainer("pairs")
214 xmlui.addLabel(_('Name'))
215 xmlui.addString('name')
216 xmlui.addLabel(_('jid'))
217 xmlui.addString('jid')
218 xmlui.addLabel(_('Nickname'))
219 xmlui.addString('nick', client.jid.user)
220 xmlui.addLabel(_('Autojoin'))
221 xmlui.addBool('autojoin')
222 xmlui.changeContainer("vertical")
223 xmlui.addButton(self.__bm_save_id, _("Save"), ('name', 'jid', 'nick', 'autojoin'))
224 return {'xmlui': xmlui.toXml()}
225
226 def _bookmarksSaveCb(self, data, profile):
227 bm_data = xml_tools.XMLUIResult2DataFormResult(data)
228 try:
229 location = jid.JID(bm_data.pop('jid'))
230 except KeyError:
231 raise exceptions.InternalError("Can't find mandatory key")
232 d = self.addBookmark(XEP_0048.MUC_TYPE, location, bm_data, profile_key=profile)
233 d.addCallback(lambda dummy: {})
234 return d
235
236 @defer.inlineCallbacks
237 def addBookmark(self, type_, location, data, storage_type="auto", profile_key="@NONE@"):
238 """ Store a new bookmark
239 @param type_: bookmark type, one of:
240 - XEP_0048.MUC_TYPE: Multi-User chat room
241 - XEP_0048.URL_TYPE: web page URL
242 @param location: dependeding on type_, can be a MUC room jid or an url
243 @param data (dict): depending on type_, can contains the following keys:
244 - name: human readable name of the bookmark
245 - nick: user preferred room nick (default to user part of profile's jid)
246 - autojoin: "true" if room must be automatically joined on connection
247 - password: unused yet TODO
248 @param storage_type: where the bookmark will be stored, can be:
249 - "auto": find best available option: pubsub, private, local in that order
250 - "pubsub": PubSub private storage (XEP-0223)
251 - "private": Private XML storage (XEP-0049)
252 - "local": Store in SàT database
253 @param profile_key: %(doc_profile_key)s
254
255 """
256 assert storage_type in ('auto', 'pubsub', 'private', 'local')
257 client = self.host.getClient(profile_key)
258 if storage_type == 'auto':
259 if client.bookmarks_pubsub is not None:
260 storage_type = 'pubsub'
261 elif client.bookmarks_private is not None:
262 storage_type = 'private'
263 else:
264 storage_type = 'local'
265 warning(_("Bookmarks will be local only"))
266 info(_('Type selected for "auto" storage: %s') % storage_type)
267
268 if storage_type == 'local':
269 client.bookmarks_local[type_][location] = data
270 yield client.bookmarks_local.force(type_)
271 else:
272 bookmarks = yield self._getServerBookmarks(storage_type, client.profile)
273 bookmarks[type_][location] = data
274 bookmark_elt = self._dict2BookmarkElt(type_, bookmarks)
275 yield self._setServerBookmarks(storage_type, bookmark_elt, client.profile)
276
277
278
279
280