| ... |
... |
@@ -14,32 +14,54 @@ |
|
14
|
14
|
|
|
15
|
15
|
# XEP-0048: Bookmarks
|
|
16
|
16
|
|
|
|
17
|
+from typing import Any
|
|
|
18
|
+from typing import List
|
|
|
19
|
+from typing import Optional
|
|
|
20
|
+
|
|
17
|
21
|
import logging
|
|
18
|
22
|
import copy
|
|
19
|
23
|
from collections import OrderedDict
|
|
20
|
24
|
|
|
21
|
25
|
import nbxmpp
|
|
|
26
|
+from gi.repository import GLib
|
|
22
|
27
|
|
|
23
|
28
|
from gajim.common import app
|
|
24
|
29
|
from gajim.common import helpers
|
|
25
|
30
|
from gajim.common.const import BookmarkStorageType
|
|
26
|
|
-from gajim.common.nec import NetworkIncomingEvent
|
|
|
31
|
+from gajim.common.const import PEPEventType
|
|
|
32
|
+from gajim.common.nec import NetworkEvent
|
|
|
33
|
+from gajim.common.exceptions import StanzaMalformed
|
|
|
34
|
+from gajim.common.modules.pep import AbstractPEPModule
|
|
|
35
|
+from gajim.common.modules.pep import AbstractPEPData
|
|
27
|
36
|
from gajim.common.modules.util import from_xs_boolean
|
|
28
|
37
|
from gajim.common.modules.util import to_xs_boolean
|
|
29
|
38
|
|
|
|
39
|
+
|
|
30
|
40
|
log = logging.getLogger('gajim.c.m.bookmarks')
|
|
31
|
41
|
|
|
32
|
42
|
NS_GAJIM_BM = 'xmpp:gajim.org/bookmarks'
|
|
33
|
43
|
|
|
34
|
44
|
|
|
35
|
|
-class Bookmarks:
|
|
|
45
|
+class BookmarksData(AbstractPEPData):
|
|
|
46
|
+
|
|
|
47
|
+ type_ = PEPEventType.BOOKMARKS
|
|
|
48
|
+
|
|
|
49
|
+
|
|
|
50
|
+class Bookmarks(AbstractPEPModule):
|
|
|
51
|
+
|
|
|
52
|
+ name = 'storage'
|
|
|
53
|
+ namespace = 'storage:bookmarks'
|
|
|
54
|
+ pep_class = BookmarksData
|
|
|
55
|
+ store_publish = False
|
|
|
56
|
+ _log = log
|
|
|
57
|
+
|
|
36
|
58
|
def __init__(self, con):
|
|
37
|
|
- self._con = con
|
|
38
|
|
- self._account = con.name
|
|
|
59
|
+ AbstractPEPModule.__init__(self, con)
|
|
|
60
|
+
|
|
39
|
61
|
self.bookmarks = {}
|
|
40
|
62
|
self.conversion = False
|
|
41
|
|
-
|
|
42
|
|
- self.handlers = []
|
|
|
63
|
+ self._join_timeouts = []
|
|
|
64
|
+ self._request_in_progress = False
|
|
43
|
65
|
|
|
44
|
66
|
def pass_disco(self, from_, _identities, features, _data, _node):
|
|
45
|
67
|
if nbxmpp.NS_BOOKMARK_CONVERSION not in features:
|
| ... |
... |
@@ -47,6 +69,52 @@ class Bookmarks: |
|
47
|
69
|
self.conversion = True
|
|
48
|
70
|
log.info('Discovered Bookmarks Conversion: %s', from_)
|
|
49
|
71
|
|
|
|
72
|
+ def _extract_info(self, item):
|
|
|
73
|
+ storage = item.getTag('storage', namespace=self.namespace)
|
|
|
74
|
+ if storage is None:
|
|
|
75
|
+ raise StanzaMalformed('No storage node')
|
|
|
76
|
+ return storage
|
|
|
77
|
+
|
|
|
78
|
+ def _notification_received(self, jid: nbxmpp.JID, user_pep: Any) -> None:
|
|
|
79
|
+ if self._request_in_progress:
|
|
|
80
|
+ log.info('Ignore update, pubsub request in progress')
|
|
|
81
|
+ return
|
|
|
82
|
+
|
|
|
83
|
+ if not self._pubsub_support() or not self.conversion:
|
|
|
84
|
+ return
|
|
|
85
|
+
|
|
|
86
|
+ old_bookmarks = self._convert_to_set(self.bookmarks)
|
|
|
87
|
+ self.bookmarks = self._parse_bookmarks(user_pep.data)
|
|
|
88
|
+ self._act_on_changed_bookmarks(old_bookmarks)
|
|
|
89
|
+ app.nec.push_incoming_event(
|
|
|
90
|
+ NetworkEvent('bookmarks-received', account=self._account))
|
|
|
91
|
+
|
|
|
92
|
+ def _act_on_changed_bookmarks(self, old_bookmarks):
|
|
|
93
|
+ new_bookmarks = self._convert_to_set(self.bookmarks)
|
|
|
94
|
+ changed = new_bookmarks - old_bookmarks
|
|
|
95
|
+ if not changed:
|
|
|
96
|
+ return
|
|
|
97
|
+
|
|
|
98
|
+ join = [jid for jid, autojoin in changed if autojoin]
|
|
|
99
|
+ for jid in join:
|
|
|
100
|
+ log.info('Schedule autojoin in 10s for: %s', jid)
|
|
|
101
|
+ # If another client creates a MUC, the MUC is locked until the
|
|
|
102
|
+ # configuration is finished. Give the user some time to finish
|
|
|
103
|
+ # the configuration.
|
|
|
104
|
+ timeout_id = GLib.timeout_add_seconds(
|
|
|
105
|
+ 10, self._join_with_timeout, join)
|
|
|
106
|
+ self._join_timeouts.append(timeout_id)
|
|
|
107
|
+
|
|
|
108
|
+ # TODO: leave mucs
|
|
|
109
|
+ # leave = [jid for jid, autojoin in changed if not autojoin]
|
|
|
110
|
+
|
|
|
111
|
+ @staticmethod
|
|
|
112
|
+ def _convert_to_set(bookmarks):
|
|
|
113
|
+ set_ = set()
|
|
|
114
|
+ for jid in bookmarks:
|
|
|
115
|
+ set_.add((jid, bookmarks[jid]['autojoin']))
|
|
|
116
|
+ return set_
|
|
|
117
|
+
|
|
50
|
118
|
def get_sorted_bookmarks(self, short_name=False):
|
|
51
|
119
|
# This returns a sorted by name copy of the bookmarks
|
|
52
|
120
|
sorted_bookmarks = {}
|
| ... |
... |
@@ -72,65 +140,43 @@ class Bookmarks: |
|
72
|
140
|
return (self._con.get_module('PEP').supported and
|
|
73
|
141
|
self._con.get_module('PubSub').publish_options)
|
|
74
|
142
|
|
|
75
|
|
- def get_bookmarks(self, storage_type=None):
|
|
|
143
|
+ def get_bookmarks(self):
|
|
76
|
144
|
if not app.account_is_connected(self._account):
|
|
77
|
145
|
return
|
|
78
|
146
|
|
|
79
|
|
- if storage_type in (None, BookmarkStorageType.PUBSUB):
|
|
80
|
|
- if self._pubsub_support():
|
|
81
|
|
- self._request_pubsub_bookmarks()
|
|
82
|
|
- else:
|
|
83
|
|
- # Fallback, request private storage
|
|
84
|
|
- self._request_private_bookmarks()
|
|
|
147
|
+ if self._pubsub_support() and self.conversion:
|
|
|
148
|
+ self._request_pubsub_bookmarks()
|
|
85
|
149
|
else:
|
|
86
|
|
- log.info('Request Bookmarks (PrivateStorage)')
|
|
87
|
150
|
self._request_private_bookmarks()
|
|
88
|
151
|
|
|
89
|
152
|
def _request_pubsub_bookmarks(self) -> None:
|
|
90
|
153
|
log.info('Request Bookmarks (PubSub)')
|
|
|
154
|
+ self._request_in_progress = True
|
|
91
|
155
|
self._con.get_module('PubSub').send_pb_retrieve(
|
|
92
|
|
- '', 'storage:bookmarks',
|
|
93
|
|
- cb=self._pubsub_bookmarks_received)
|
|
94
|
|
-
|
|
95
|
|
- def _pubsub_bookmarks_received(self, _con, stanza):
|
|
96
|
|
- if not nbxmpp.isResultNode(stanza):
|
|
97
|
|
- log.info('No pubsub bookmarks: %s', stanza.getError())
|
|
98
|
|
- # Fallback, request private storage, only if server
|
|
99
|
|
- # doesnt have bookmark conversion
|
|
100
|
|
- if not self.conversion:
|
|
101
|
|
- self._request_private_bookmarks()
|
|
102
|
|
- return
|
|
103
|
|
-
|
|
104
|
|
- log.info('Received Bookmarks (PubSub)')
|
|
105
|
|
- self._parse_bookmarks(stanza)
|
|
106
|
|
- if not self.conversion:
|
|
107
|
|
- # If server does not have bookmark conversion, request private
|
|
108
|
|
- # storage and try to merge the bookmarks
|
|
109
|
|
- self._request_private_bookmarks()
|
|
|
156
|
+ '', 'storage:bookmarks', cb=self._bookmarks_received)
|
|
110
|
157
|
|
|
111
|
158
|
def _request_private_bookmarks(self) -> None:
|
|
112
|
|
- if not app.account_is_connected(self._account):
|
|
113
|
|
- return
|
|
114
|
|
-
|
|
|
159
|
+ self._request_in_progress = True
|
|
115
|
160
|
iq = nbxmpp.Iq(typ='get')
|
|
116
|
161
|
query = iq.addChild(name='query', namespace=nbxmpp.NS_PRIVATE)
|
|
117
|
162
|
query.addChild(name='storage', namespace='storage:bookmarks')
|
|
118
|
163
|
log.info('Request Bookmarks (PrivateStorage)')
|
|
119
|
164
|
self._con.connection.SendAndCallForResponse(
|
|
120
|
|
- iq, self._private_bookmarks_received)
|
|
|
165
|
+ iq, self._bookmarks_received, {})
|
|
121
|
166
|
|
|
122
|
|
- def _private_bookmarks_received(self, stanza: nbxmpp.Iq) -> None:
|
|
|
167
|
+ def _bookmarks_received(self, _con, stanza):
|
|
|
168
|
+ self._request_in_progress = False
|
|
123
|
169
|
if not nbxmpp.isResultNode(stanza):
|
|
124
|
|
- log.info('No private bookmarks: %s', stanza.getError())
|
|
|
170
|
+ log.info('No bookmarks found: %s', stanza.getError())
|
|
125
|
171
|
else:
|
|
126
|
|
- log.info('Received Bookmarks (PrivateStorage)')
|
|
127
|
|
- merged = self._parse_bookmarks(stanza, check_merge=True)
|
|
128
|
|
- if merged and self._pubsub_support():
|
|
129
|
|
- log.info('Merge PrivateStorage with PubSub')
|
|
130
|
|
- self.store_bookmarks(BookmarkStorageType.PUBSUB)
|
|
131
|
|
- self.auto_join_bookmarks()
|
|
132
|
|
- app.nec.push_incoming_event(BookmarksReceivedEvent(
|
|
133
|
|
- None, account=self._account))
|
|
|
172
|
+ log.info('Received Bookmarks')
|
|
|
173
|
+ storage = self._get_storage_node(stanza)
|
|
|
174
|
+ if storage is not None:
|
|
|
175
|
+ self.bookmarks = self._parse_bookmarks(storage)
|
|
|
176
|
+ self.auto_join_bookmarks()
|
|
|
177
|
+
|
|
|
178
|
+ app.nec.push_incoming_event(
|
|
|
179
|
+ NetworkEvent('bookmarks-received', account=self._account))
|
|
134
|
180
|
|
|
135
|
181
|
@staticmethod
|
|
136
|
182
|
def _get_storage_node(stanza):
|
| ... |
... |
@@ -163,13 +209,9 @@ class Bookmarks: |
|
163
|
209
|
return
|
|
164
|
210
|
return storage
|
|
165
|
211
|
|
|
166
|
|
- def _parse_bookmarks(self, stanza: nbxmpp.Iq,
|
|
167
|
|
- check_merge: bool = False) -> bool:
|
|
168
|
|
- merged = False
|
|
169
|
|
- storage = self._get_storage_node(stanza)
|
|
170
|
|
- if storage is None:
|
|
171
|
|
- return False
|
|
172
|
|
-
|
|
|
212
|
+ @staticmethod
|
|
|
213
|
+ def _parse_bookmarks(storage: nbxmpp.Node) -> bool:
|
|
|
214
|
+ bookmarks = {}
|
|
173
|
215
|
confs = storage.getTags('conference')
|
|
174
|
216
|
for conf in confs:
|
|
175
|
217
|
autojoin_val = conf.getAttr('autojoin')
|
| ... |
... |
@@ -177,18 +219,14 @@ class Bookmarks: |
|
177
|
219
|
autojoin_val = False
|
|
178
|
220
|
|
|
179
|
221
|
minimize_val = conf.getTag('minimize', namespace=NS_GAJIM_BM)
|
|
180
|
|
- if not minimize_val: # not there, try old Gajim behaviour
|
|
181
|
|
- minimize_val = conf.getAttr('minimize')
|
|
182
|
|
- if not minimize_val: # not there (it's optional)
|
|
183
|
|
- minimize_val = False
|
|
|
222
|
+ if not minimize_val:
|
|
|
223
|
+ minimize_val = False
|
|
184
|
224
|
else:
|
|
185
|
225
|
minimize_val = minimize_val.getData()
|
|
186
|
226
|
|
|
187
|
227
|
print_status = conf.getTag('print_status', namespace=NS_GAJIM_BM)
|
|
188
|
228
|
if not print_status: # not there, try old Gajim behaviour
|
|
189
|
|
- print_status = conf.getTagData('print_status')
|
|
190
|
|
- if not print_status: # not there, try old Gajim behaviour
|
|
191
|
|
- print_status = conf.getTagData('show_status')
|
|
|
229
|
+ print_status = None
|
|
192
|
230
|
else:
|
|
193
|
231
|
print_status = print_status.getData()
|
|
194
|
232
|
|
| ... |
... |
@@ -213,20 +251,16 @@ class Bookmarks: |
|
213
|
251
|
log.warning(error)
|
|
214
|
252
|
continue
|
|
215
|
253
|
|
|
216
|
|
- if check_merge:
|
|
217
|
|
- if jid in self.bookmarks:
|
|
218
|
|
- continue
|
|
219
|
|
- merged = True
|
|
220
|
|
-
|
|
221
|
254
|
log.debug('Found Bookmark: %s', jid)
|
|
222
|
|
- self.bookmarks[jid] = bookmark
|
|
|
255
|
+ bookmarks[jid] = bookmark
|
|
223
|
256
|
|
|
224
|
|
- return merged
|
|
|
257
|
+ return bookmarks
|
|
225
|
258
|
|
|
226
|
|
- def _build_storage_node(self) -> nbxmpp.Node:
|
|
|
259
|
+ @staticmethod
|
|
|
260
|
+ def _build_storage_node(bookmarks):
|
|
227
|
261
|
storage_node = nbxmpp.Node(
|
|
228
|
262
|
tag='storage', attrs={'xmlns': 'storage:bookmarks'})
|
|
229
|
|
- for jid, bm in self.bookmarks.items():
|
|
|
263
|
+ for jid, bm in bookmarks.items():
|
|
230
|
264
|
conf_node = storage_node.addChild(name="conference")
|
|
231
|
265
|
conf_node.setAttr('jid', jid)
|
|
232
|
266
|
conf_node.setAttr('autojoin', to_xs_boolean(bm['autojoin']))
|
| ... |
... |
@@ -246,6 +280,9 @@ class Bookmarks: |
|
246
|
280
|
namespace=NS_GAJIM_BM).setData(bm['print_status'])
|
|
247
|
281
|
return storage_node
|
|
248
|
282
|
|
|
|
283
|
+ def _build_node(self, _data):
|
|
|
284
|
+ pass
|
|
|
285
|
+
|
|
249
|
286
|
@staticmethod
|
|
250
|
287
|
def get_bookmark_publish_options() -> nbxmpp.Node:
|
|
251
|
288
|
options = nbxmpp.Node(nbxmpp.NS_DATA + ' x',
|
| ... |
... |
@@ -257,23 +294,14 @@ class Bookmarks: |
|
257
|
294
|
field.setTagData('value', 'whitelist')
|
|
258
|
295
|
return options
|
|
259
|
296
|
|
|
260
|
|
- def store_bookmarks(self, storage_type=None):
|
|
|
297
|
+ def store_bookmarks(self):
|
|
261
|
298
|
if not app.account_is_connected(self._account):
|
|
262
|
299
|
return
|
|
263
|
300
|
|
|
264
|
|
- storage_node = self._build_storage_node()
|
|
265
|
|
-
|
|
266
|
|
- if storage_type is None:
|
|
267
|
|
- if self._pubsub_support():
|
|
268
|
|
- self._pubsub_store(storage_node)
|
|
269
|
|
- if self.conversion:
|
|
270
|
|
- # Only push to either pubsub or private storage
|
|
271
|
|
- return
|
|
272
|
|
- self._private_store(storage_node)
|
|
273
|
|
- elif storage_type == BookmarkStorageType.PUBSUB:
|
|
274
|
|
- if self._pubsub_support():
|
|
275
|
|
- self._pubsub_store(storage_node)
|
|
276
|
|
- elif storage_type == BookmarkStorageType.PRIVATE:
|
|
|
301
|
+ storage_node = self._build_storage_node(self.bookmarks)
|
|
|
302
|
+ if self._pubsub_support() and self.conversion:
|
|
|
303
|
+ self._pubsub_store(storage_node)
|
|
|
304
|
+ else:
|
|
277
|
305
|
self._private_store(storage_node)
|
|
278
|
306
|
|
|
279
|
307
|
def _pubsub_store(self, storage_node: nbxmpp.Node) -> None:
|
| ... |
... |
@@ -301,18 +329,27 @@ class Bookmarks: |
|
301
|
329
|
log.error('Error: %s', stanza.getError())
|
|
302
|
330
|
return
|
|
303
|
331
|
|
|
304
|
|
- def auto_join_bookmarks(self) -> None:
|
|
|
332
|
+ def _join_with_timeout(self, bookmarks: Optional[List[str]] = None) -> None:
|
|
|
333
|
+ self._join_timeouts.pop(0)
|
|
|
334
|
+ self.auto_join_bookmarks(bookmarks)
|
|
|
335
|
+
|
|
|
336
|
+ def auto_join_bookmarks(self, bookmarks: Optional[List[str]] = None) -> None:
|
|
305
|
337
|
if app.is_invisible(self._account):
|
|
306
|
338
|
return
|
|
307
|
|
- for jid, bm in self.bookmarks.items():
|
|
308
|
|
- if bm['autojoin']:
|
|
|
339
|
+ if bookmarks is None:
|
|
|
340
|
+ bookmarks = self.bookmarks.keys()
|
|
|
341
|
+
|
|
|
342
|
+ for jid in bookmarks:
|
|
|
343
|
+ bookmark = self.bookmarks[jid]
|
|
|
344
|
+ if bookmark['autojoin']:
|
|
309
|
345
|
# Only join non-opened groupchats. Opened one are already
|
|
310
|
346
|
# auto-joined on re-connection
|
|
311
|
347
|
if jid not in app.gc_connected[self._account]:
|
|
312
|
348
|
# we are not already connected
|
|
|
349
|
+ log.info('Autojoin Bookmark: %s', jid)
|
|
313
|
350
|
app.interface.join_gc_room(
|
|
314
|
|
- self._account, jid, bm['nick'],
|
|
315
|
|
- bm['password'], minimize=bm['minimize'])
|
|
|
351
|
+ self._account, jid, bookmark['nick'],
|
|
|
352
|
+ bookmark['password'], minimize=bookmark['minimize'])
|
|
316
|
353
|
|
|
317
|
354
|
def add_bookmark(self, name, jid, autojoin,
|
|
318
|
355
|
minimize, password, nick):
|
| ... |
... |
@@ -325,8 +362,8 @@ class Bookmarks: |
|
325
|
362
|
'print_status': None}
|
|
326
|
363
|
|
|
327
|
364
|
self.store_bookmarks()
|
|
328
|
|
- app.nec.push_incoming_event(BookmarksReceivedEvent(
|
|
329
|
|
- None, account=self._account))
|
|
|
365
|
+ app.nec.push_incoming_event(
|
|
|
366
|
+ NetworkEvent('bookmarks-received', account=self._account))
|
|
330
|
367
|
|
|
331
|
368
|
def get_name_from_bookmark(self, jid: str) -> str:
|
|
332
|
369
|
fallback = jid.split('@')[0]
|
| ... |
... |
@@ -341,9 +378,12 @@ class Bookmarks: |
|
341
|
378
|
self._con.get_module('PubSub').send_pb_purge('', 'storage:bookmarks')
|
|
342
|
379
|
self._con.get_module('PubSub').send_pb_delete('', 'storage:bookmarks')
|
|
343
|
380
|
|
|
|
381
|
+ def _remove_timeouts(self):
|
|
|
382
|
+ for _id in self._join_timeouts:
|
|
|
383
|
+ GLib.source_remove(_id)
|
|
344
|
384
|
|
|
345
|
|
-class BookmarksReceivedEvent(NetworkIncomingEvent):
|
|
346
|
|
- name = 'bookmarks-received'
|
|
|
385
|
+ def cleanup(self):
|
|
|
386
|
+ self._remove_timeouts()
|
|
347
|
387
|
|
|
348
|
388
|
|
|
349
|
389
|
def get_instance(*args, **kwargs):
|