Philipp Hörist pushed to branch master at gajim / gajim

Commits:

3 changed files:

Changes:

  • gajim/common/app.py
    ... ... @@ -159,7 +159,7 @@ gajim_common_features = [nbxmpp.NS_BYTESTREAM, nbxmpp.NS_SI, nbxmpp.NS_FILE,
    159 159
         nbxmpp.NS_ROSTERX, nbxmpp.NS_SECLABEL, nbxmpp.NS_HASHES_2,
    
    160 160
         nbxmpp.NS_HASHES_MD5, nbxmpp.NS_HASHES_SHA1, nbxmpp.NS_HASHES_SHA256,
    
    161 161
         nbxmpp.NS_HASHES_SHA512, nbxmpp.NS_CONFERENCE, nbxmpp.NS_CORRECT,
    
    162
    -    nbxmpp.NS_EME, 'urn:xmpp:avatar:metadata+notify']
    
    162
    +    nbxmpp.NS_EME, 'urn:xmpp:avatar:metadata+notify', 'storage:bookmarks+notify']
    
    163 163
     
    
    164 164
     # Optional features gajim supports per account
    
    165 165
     gajim_optional_features = {}  # type: Dict[str, List[str]]
    

  • gajim/common/const.py
    ... ... @@ -172,6 +172,7 @@ class PEPEventType(IntEnum):
    172 172
         NICKNAME = 5
    
    173 173
         AVATAR = 6
    
    174 174
         ATOM = 7
    
    175
    +    BOOKMARKS = 8
    
    175 176
     
    
    176 177
     
    
    177 178
     @unique
    

  • gajim/common/modules/bookmarks.py
    ... ... @@ -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):