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

Commits:

25 changed files:

Changes:

  • gajim/app_actions.py
    ... ... @@ -27,8 +27,9 @@ from gajim import accounts_window
    27 27
     import gajim.plugins.gui
    
    28 28
     from gajim import history_window
    
    29 29
     from gajim import disco
    
    30
    -from gajim.history_sync import HistorySyncAssistant
    
    30
    +from gajim.gtk.history_sync import HistorySyncAssistant
    
    31 31
     from gajim.server_info import ServerInfoDialog
    
    32
    +from gajim.gtk.mam_preferences import MamPreferences
    
    32 33
     
    
    33 34
     
    
    34 35
     # General Actions
    
    ... ... @@ -181,14 +182,13 @@ def on_import_contacts(action, param):
    181 182
     # Advanced Actions
    
    182 183
     
    
    183 184
     
    
    184
    -def on_archiving_preferences(action, param):
    
    185
    +def on_mam_preferences(action, param):
    
    185 186
         account = param.get_string()
    
    186
    -    if 'archiving_preferences' in interface.instances[account]:
    
    187
    -        interface.instances[account]['archiving_preferences'].window.\
    
    188
    -            present()
    
    187
    +    window = app.get_app_window(MamPreferences, account)
    
    188
    +    if window is None:
    
    189
    +        MamPreferences(account)
    
    189 190
         else:
    
    190
    -        interface.instances[account]['archiving_preferences'] = \
    
    191
    -            dialogs.Archiving313PreferencesWindow(account)
    
    191
    +        window.present()
    
    192 192
     
    
    193 193
     
    
    194 194
     def on_history_sync(action, param):
    

  • gajim/application.py
    ... ... @@ -356,7 +356,7 @@ class GajimApplication(Gtk.Application):
    356 356
                 ('-profile', app_actions.on_profile, 'feature', 's'),
    
    357 357
                 ('-xml-console', app_actions.on_xml_console, 'always', 's'),
    
    358 358
                 ('-server-info', app_actions.on_server_info, 'online', 's'),
    
    359
    -            ('-archive', app_actions.on_archiving_preferences, 'feature', 's'),
    
    359
    +            ('-archive', app_actions.on_mam_preferences, 'feature', 's'),
    
    360 360
                 ('-sync-history', app_actions.on_history_sync, 'online', 's'),
    
    361 361
                 ('-privacylists', app_actions.on_privacy_lists, 'feature', 's'),
    
    362 362
                 ('-send-server-message',
    

  • gajim/chat_control.py
    ... ... @@ -809,8 +809,13 @@ class ChatControl(ChatControlBase):
    809 809
         def _nec_mam_decrypted_message_received(self, obj):
    
    810 810
             if obj.conn.name != self.account:
    
    811 811
                 return
    
    812
    -        if obj.with_ != self.contact.jid:
    
    813
    -            return
    
    812
    +
    
    813
    +        if obj.muc_pm:
    
    814
    +            if not obj.with_ == self.contact.get_full_jid():
    
    815
    +                return
    
    816
    +        else:
    
    817
    +            if not obj.with_.bareMatch(self.contact.jid):
    
    818
    +                return
    
    814 819
     
    
    815 820
             kind = '' # incoming
    
    816 821
             if obj.kind == KindConstant.CHAT_MSG_SENT:
    

  • gajim/common/app.py
    ... ... @@ -595,11 +595,17 @@ def prefers_app_menu():
    595 595
             return False
    
    596 596
         return app.prefers_app_menu()
    
    597 597
     
    
    598
    -def get_app_window(cls):
    
    598
    +def get_app_window(cls, account=None):
    
    599 599
         for win in app.get_windows():
    
    600 600
             if isinstance(cls, str):
    
    601 601
                 if type(win).__name__ == cls:
    
    602
    +                if account is not None:
    
    603
    +                    if account != win.account:
    
    604
    +                        continue
    
    602 605
                     return win
    
    603 606
             elif isinstance(win, cls):
    
    607
    +            if account is not None:
    
    608
    +                if account != win.account:
    
    609
    +                    continue
    
    604 610
                 return win
    
    605 611
         return None

  • gajim/common/config.py
    ... ... @@ -305,7 +305,6 @@ class Config:
    305 305
                 'use_keyring': [opt_bool, True, _('If true, Gajim will use the Systems Keyring to store account passwords.')],
    
    306 306
                 'pgp_encoding': [ opt_str, '', _('Sets the encoding used by python-gnupg'), True],
    
    307 307
                 'remote_commands': [opt_bool, False, _('If true, Gajim will execute XEP-0146 Commands.')],
    
    308
    -            'mam_blacklist': [opt_str, '', _('All non-compliant MAM Groupchats')],
    
    309 308
         }, {})
    
    310 309
     
    
    311 310
         __options_per_key = {
    

  • gajim/common/connection.py
    ... ... @@ -121,9 +121,6 @@ class CommonConnection:
    121 121
             self.privacy_rules_supported = False
    
    122 122
             self.vcard_supported = False
    
    123 123
             self.private_storage_supported = False
    
    124
    -        self.archiving_namespace = None
    
    125
    -        self.archiving_supported = False
    
    126
    -        self.archiving_313_supported = False
    
    127 124
             self.roster_supported = True
    
    128 125
             self.blocking_supported = False
    
    129 126
             self.addressing_supported = False
    
    ... ... @@ -1611,12 +1608,11 @@ class Connection(CommonConnection, ConnectionHandlers):
    1611 1608
     
    
    1612 1609
                 if obj.fjid == our_jid:
    
    1613 1610
                     if nbxmpp.NS_MAM_2 in obj.features:
    
    1614
    -                    self.archiving_namespace = nbxmpp.NS_MAM_2
    
    1611
    +                    self.get_module('MAM').archiving_namespace = nbxmpp.NS_MAM_2
    
    1615 1612
                     elif nbxmpp.NS_MAM_1 in obj.features:
    
    1616
    -                    self.archiving_namespace = nbxmpp.NS_MAM_1
    
    1617
    -                if self.archiving_namespace:
    
    1618
    -                    self.archiving_supported = True
    
    1619
    -                    self.archiving_313_supported = True
    
    1613
    +                    self.get_module('MAM').archiving_namespace = nbxmpp.NS_MAM_1
    
    1614
    +                if self.get_module('MAM').archiving_namespace:
    
    1615
    +                    self.get_module('MAM').available = True
    
    1620 1616
                         get_action(self.name + '-archive').set_enabled(True)
    
    1621 1617
                     for identity in obj.identities:
    
    1622 1618
                         if identity['category'] == 'pubsub':
    

  • gajim/common/connection_handlers.py
    ... ... @@ -45,8 +45,8 @@ from gajim.common.caps_cache import muc_caps_cache
    45 45
     from gajim.common.protocol.caps import ConnectionCaps
    
    46 46
     from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
    
    47 47
     from gajim.common.protocol.bytestream import ConnectionIBBytestream
    
    48
    -from gajim.common.message_archiving import ConnectionArchive313
    
    49 48
     from gajim.common.connection_handlers_events import *
    
    49
    +from gajim.common.modules.misc import parse_eme
    
    50 50
     
    
    51 51
     from gajim.common import ged
    
    52 52
     from gajim.common.nec import NetworkEvent
    
    ... ... @@ -295,7 +295,9 @@ class ConnectionHandlersBase:
    295 295
             # XEPs that are based on Message
    
    296 296
             self._message_namespaces = set([nbxmpp.NS_HTTP_AUTH,
    
    297 297
                                             nbxmpp.NS_PUBSUB_EVENT,
    
    298
    -                                        nbxmpp.NS_ROSTERX])
    
    298
    +                                        nbxmpp.NS_ROSTERX,
    
    299
    +                                        nbxmpp.NS_MAM_1,
    
    300
    +                                        nbxmpp.NS_MAM_2])
    
    299 301
     
    
    300 302
             app.ged.register_event_handler('iq-error-received', ged.CORE,
    
    301 303
                 self._nec_iq_error_received)
    
    ... ... @@ -303,10 +305,6 @@ class ConnectionHandlersBase:
    303 305
                 self._nec_presence_received)
    
    304 306
             app.ged.register_event_handler('message-received', ged.CORE,
    
    305 307
                 self._nec_message_received)
    
    306
    -        app.ged.register_event_handler('mam-message-received', ged.CORE,
    
    307
    -            self._nec_message_received)
    
    308
    -        app.ged.register_event_handler('mam-gc-message-received', ged.CORE,
    
    309
    -            self._nec_message_received)
    
    310 308
             app.ged.register_event_handler('decrypted-message-received', ged.CORE,
    
    311 309
                 self._nec_decrypted_message_received)
    
    312 310
             app.ged.register_event_handler('gc-message-received', ged.CORE,
    
    ... ... @@ -319,10 +317,6 @@ class ConnectionHandlersBase:
    319 317
                 self._nec_presence_received)
    
    320 318
             app.ged.remove_event_handler('message-received', ged.CORE,
    
    321 319
                 self._nec_message_received)
    
    322
    -        app.ged.remove_event_handler('mam-message-received', ged.CORE,
    
    323
    -            self._nec_message_received)
    
    324
    -        app.ged.remove_event_handler('mam-gc-message-received', ged.CORE,
    
    325
    -            self._nec_message_received)
    
    326 320
             app.ged.remove_event_handler('decrypted-message-received', ged.CORE,
    
    327 321
                 self._nec_decrypted_message_received)
    
    328 322
             app.ged.remove_event_handler('gc-message-received', ged.CORE,
    
    ... ... @@ -460,37 +454,15 @@ class ConnectionHandlersBase:
    460 454
             app.plugin_manager.extension_point(
    
    461 455
                 'decrypt', self, obj, self._on_message_received)
    
    462 456
             if not obj.encrypted:
    
    463
    -            # XEP-0380
    
    464
    -            enc_tag = obj.stanza.getTag('encryption', namespace=nbxmpp.NS_EME)
    
    465
    -            if enc_tag:
    
    466
    -                ns = enc_tag.getAttr('namespace')
    
    467
    -                if ns:
    
    468
    -                    if ns == 'urn:xmpp:otr:0':
    
    469
    -                        obj.msgtxt = _('This message was encrypted with OTR '
    
    470
    -                        'and could not be decrypted.')
    
    471
    -                    elif ns == 'jabber:x:encrypted':
    
    472
    -                        obj.msgtxt = _('This message was encrypted with Legacy '
    
    473
    -                        'OpenPGP and could not be decrypted. You can install '
    
    474
    -                        'the PGP plugin to handle those messages.')
    
    475
    -                    elif ns == 'urn:xmpp:openpgp:0':
    
    476
    -                        obj.msgtxt = _('This message was encrypted with '
    
    477
    -                        'OpenPGP for XMPP and could not be decrypted.')
    
    478
    -                    else:
    
    479
    -                        enc_name = enc_tag.getAttr('name')
    
    480
    -                        if not enc_name:
    
    481
    -                            enc_name = ns
    
    482
    -                        obj.msgtxt = _('This message was encrypted with %s '
    
    483
    -                        'and could not be decrypted.') % enc_name
    
    457
    +            eme = parse_eme(obj.stanza)
    
    458
    +            if eme is not None:
    
    459
    +                obj.msgtxt = eme
    
    484 460
                 self._on_message_received(obj)
    
    485 461
     
    
    486 462
         def _on_message_received(self, obj):
    
    487
    -        if isinstance(obj, MessageReceivedEvent):
    
    488
    -            app.nec.push_incoming_event(
    
    489
    -                DecryptedMessageReceivedEvent(
    
    490
    -                    None, conn=self, msg_obj=obj, stanza_id=obj.unique_id))
    
    491
    -        else:
    
    492
    -            app.nec.push_incoming_event(
    
    493
    -                MamDecryptedMessageReceivedEvent(None, **vars(obj)))
    
    463
    +        app.nec.push_incoming_event(
    
    464
    +            DecryptedMessageReceivedEvent(
    
    465
    +                None, conn=self, msg_obj=obj, stanza_id=obj.unique_id))
    
    494 466
     
    
    495 467
         def _nec_decrypted_message_received(self, obj):
    
    496 468
             if obj.conn.name != self.name:
    
    ... ... @@ -564,7 +536,7 @@ class ConnectionHandlersBase:
    564 536
         def _check_for_mam_compliance(self, room_jid, stanza_id):
    
    565 537
             namespace = muc_caps_cache.get_mam_namespace(room_jid)
    
    566 538
             if stanza_id is None and namespace == nbxmpp.NS_MAM_2:
    
    567
    -            helpers.add_to_mam_blacklist(room_jid)
    
    539
    +            log.warning('%s announces mam:2 without stanza-id')
    
    568 540
     
    
    569 541
         def _nec_gc_message_received(self, obj):
    
    570 542
             if obj.conn.name != self.name:
    
    ... ... @@ -743,11 +715,10 @@ class ConnectionHandlersBase:
    743 715
     
    
    744 716
             return sess
    
    745 717
     
    
    746
    -class ConnectionHandlers(ConnectionArchive313,
    
    747
    -ConnectionSocks5Bytestream, ConnectionDisco, ConnectionCaps,
    
    748
    -ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
    
    718
    +class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
    
    719
    +                         ConnectionCaps, ConnectionHandlersBase,
    
    720
    +                         ConnectionJingle, ConnectionIBBytestream):
    
    749 721
         def __init__(self):
    
    750
    -        ConnectionArchive313.__init__(self)
    
    751 722
             ConnectionSocks5Bytestream.__init__(self)
    
    752 723
             ConnectionIBBytestream.__init__(self)
    
    753 724
     
    
    ... ... @@ -772,9 +743,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
    772 743
     
    
    773 744
             app.nec.register_incoming_event(StreamConflictReceivedEvent)
    
    774 745
             app.nec.register_incoming_event(MessageReceivedEvent)
    
    775
    -        app.nec.register_incoming_event(ArchivingErrorReceivedEvent)
    
    776
    -        app.nec.register_incoming_event(
    
    777
    -            Archiving313PreferencesChangedReceivedEvent)
    
    778 746
             app.nec.register_incoming_event(NotificationEvent)
    
    779 747
     
    
    780 748
             app.ged.register_event_handler('roster-set-received',
    
    ... ... @@ -799,7 +767,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
    799 767
         def cleanup(self):
    
    800 768
             ConnectionHandlersBase.cleanup(self)
    
    801 769
             ConnectionCaps.cleanup(self)
    
    802
    -        ConnectionArchive313.cleanup(self)
    
    803 770
             app.ged.remove_event_handler('roster-set-received',
    
    804 771
                 ged.CORE, self._nec_roster_set_received)
    
    805 772
             app.ged.remove_event_handler('roster-received', ged.CORE,
    
    ... ... @@ -1343,8 +1310,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
    1343 1310
             con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
    
    1344 1311
                 nbxmpp.NS_DISCO_ITEMS)
    
    1345 1312
     
    
    1346
    -        con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_1)
    
    1347
    -        con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_2)
    
    1348 1313
             con.RegisterHandler('iq', self._JingleCB, 'result')
    
    1349 1314
             con.RegisterHandler('iq', self._JingleCB, 'error')
    
    1350 1315
             con.RegisterHandler('iq', self._JingleCB, 'set', nbxmpp.NS_JINGLE)
    

  • gajim/common/connection_handlers_events.py
    ... ... @@ -77,7 +77,10 @@ class HelperEvent:
    77 77
                 del self.conn.groupchat_jids[self.id_]
    
    78 78
             else:
    
    79 79
                 self.fjid = helpers.get_full_jid_from_iq(self.stanza)
    
    80
    -        self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
    
    80
    +        if self.fjid is None:
    
    81
    +            self.jid = None
    
    82
    +        else:
    
    83
    +            self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
    
    81 84
     
    
    82 85
         def get_id(self):
    
    83 86
             self.id_ = self.stanza.getID()
    
    ... ... @@ -630,240 +633,6 @@ class BeforeChangeShowEvent(nec.NetworkIncomingEvent):
    630 633
         name = 'before-change-show'
    
    631 634
         base_network_events = []
    
    632 635
     
    
    633
    -class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    
    634
    -    name = 'mam-message-received'
    
    635
    -    base_network_events = ['raw-mam-message-received']
    
    636
    -
    
    637
    -    def __init__(self, name, base_event):
    
    638
    -        '''
    
    639
    -        Pre-Generated attributes on self:
    
    640
    -
    
    641
    -        :conn:          Connection instance
    
    642
    -        :stanza:        Complete stanza Node
    
    643
    -        :forwarded:     Forwarded Node
    
    644
    -        :result:        Result Node
    
    645
    -        '''
    
    646
    -        self._set_base_event_vars_as_attributes(base_event)
    
    647
    -        self.additional_data = {}
    
    648
    -        self.encrypted = False
    
    649
    -        self.groupchat = False
    
    650
    -        self.nick = None
    
    651
    -        self.self_message = None
    
    652
    -        self.muc_pm = None
    
    653
    -
    
    654
    -    def generate(self):
    
    655
    -        account = self.conn.name
    
    656
    -        archive_jid = self.stanza.getFrom()
    
    657
    -        own_jid = self.conn.get_own_jid()
    
    658
    -        if archive_jid and not archive_jid.bareMatch(own_jid):
    
    659
    -            # MAM Message not from our Archive
    
    660
    -            return False
    
    661
    -
    
    662
    -        self.msg_ = self.forwarded.getTag('message', protocol=True)
    
    663
    -
    
    664
    -        if self.msg_.getType() == 'groupchat':
    
    665
    -            return False
    
    666
    -
    
    667
    -        # use stanza-id as unique-id
    
    668
    -        self.unique_id, origin_id = self.get_unique_id()
    
    669
    -        self.message_id = self.msg_.getID()
    
    670
    -
    
    671
    -        # Check for duplicates
    
    672
    -        if app.logger.find_stanza_id(account,
    
    673
    -                                     own_jid.getStripped(),
    
    674
    -                                     self.unique_id, origin_id):
    
    675
    -            return
    
    676
    -
    
    677
    -        self.msgtxt = self.msg_.getTagData('body')
    
    678
    -
    
    679
    -        frm = self.msg_.getFrom()
    
    680
    -        # Some servers dont set the 'to' attribute when
    
    681
    -        # we send a message to ourself
    
    682
    -        to = self.msg_.getTo()
    
    683
    -        if to is None:
    
    684
    -            to = own_jid
    
    685
    -
    
    686
    -        if frm.bareMatch(own_jid):
    
    687
    -            self.with_ = to
    
    688
    -            self.kind = KindConstant.CHAT_MSG_SENT
    
    689
    -        else:
    
    690
    -            self.with_ = frm
    
    691
    -            self.kind = KindConstant.CHAT_MSG_RECV
    
    692
    -
    
    693
    -        delay = self.forwarded.getTagAttr(
    
    694
    -            'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
    
    695
    -        if delay is None:
    
    696
    -            log.error('Received MAM message without timestamp')
    
    697
    -            log.error(self.stanza)
    
    698
    -            return
    
    699
    -
    
    700
    -        self.timestamp = helpers.parse_datetime(
    
    701
    -            delay, check_utc=True, epoch=True)
    
    702
    -        if self.timestamp is None:
    
    703
    -            log.error('Received MAM message with invalid timestamp: %s', delay)
    
    704
    -            log.error(self.stanza)
    
    705
    -            return
    
    706
    -
    
    707
    -        # Save timestamp added by the user
    
    708
    -        user_delay = self.msg_.getTagAttr(
    
    709
    -            'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
    
    710
    -        if user_delay is not None:
    
    711
    -            self.user_timestamp = helpers.parse_datetime(
    
    712
    -                user_delay, check_utc=True, epoch=True)
    
    713
    -            if self.user_timestamp is None:
    
    714
    -                log.warning('Received MAM message with '
    
    715
    -                            'invalid user timestamp: %s', user_delay)
    
    716
    -                log.warning(self.stanza)
    
    717
    -
    
    718
    -        log.debug('Received mam-message: unique id: %s', self.unique_id)
    
    719
    -        return True
    
    720
    -
    
    721
    -    def get_unique_id(self):
    
    722
    -        stanza_id = self.get_stanza_id(self.result, query=True)
    
    723
    -
    
    724
    -        if self._is_self_message(self.msg_) or self._is_muc_pm(self.msg_):
    
    725
    -            origin_id = self.msg_.getOriginID()
    
    726
    -            return stanza_id, origin_id
    
    727
    -
    
    728
    -        if self.conn.get_own_jid().bareMatch(self.msg_.getFrom()):
    
    729
    -            # message we sent
    
    730
    -            origin_id = self.msg_.getOriginID()
    
    731
    -            return stanza_id, origin_id
    
    732
    -
    
    733
    -        # A message we received
    
    734
    -        return stanza_id, None
    
    735
    -
    
    736
    -class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    
    737
    -    name = 'mam-gc-message-received'
    
    738
    -    base_network_events = ['raw-mam-message-received']
    
    739
    -
    
    740
    -    def __init__(self, name, base_event):
    
    741
    -        '''
    
    742
    -        Pre-Generated attributes on self:
    
    743
    -
    
    744
    -        :conn:          Connection instance
    
    745
    -        :stanza:        Complete stanza Node
    
    746
    -        :forwarded:     Forwarded Node
    
    747
    -        :result:        Result Node
    
    748
    -        :muc_pm:        True, if this is a MUC PM
    
    749
    -                        propagated to MamDecryptedMessageReceivedEvent
    
    750
    -        '''
    
    751
    -        self._set_base_event_vars_as_attributes(base_event)
    
    752
    -        self.additional_data = {}
    
    753
    -        self.encrypted = False
    
    754
    -        self.groupchat = True
    
    755
    -        self.kind = KindConstant.GC_MSG
    
    756
    -
    
    757
    -    def generate(self):
    
    758
    -        account = self.conn.name
    
    759
    -        self.msg_ = self.forwarded.getTag('message', protocol=True)
    
    760
    -
    
    761
    -        if self.msg_.getType() != 'groupchat':
    
    762
    -            return False
    
    763
    -
    
    764
    -        try:
    
    765
    -            self.room_jid = self.stanza.getFrom().getStripped()
    
    766
    -        except AttributeError:
    
    767
    -            log.warning('Received GC MAM message '
    
    768
    -                        'without from attribute\n%s', self.stanza)
    
    769
    -            return False
    
    770
    -
    
    771
    -        self.unique_id = self.get_stanza_id(self.result, query=True)
    
    772
    -        self.message_id = self.msg_.getID()
    
    773
    -
    
    774
    -        # Check for duplicates
    
    775
    -        if app.logger.find_stanza_id(account,
    
    776
    -                                     self.room_jid,
    
    777
    -                                     self.unique_id,
    
    778
    -                                     groupchat=True):
    
    779
    -            return
    
    780
    -
    
    781
    -        self.msgtxt = self.msg_.getTagData('body')
    
    782
    -        self.with_ = self.msg_.getFrom().getStripped()
    
    783
    -        self.nick = self.msg_.getFrom().getResource()
    
    784
    -
    
    785
    -        # Get the real jid if we have it
    
    786
    -        self.real_jid = None
    
    787
    -        muc_user = self.msg_.getTag('x', namespace=nbxmpp.NS_MUC_USER)
    
    788
    -        if muc_user is not None:
    
    789
    -            self.real_jid = muc_user.getTagAttr('item', 'jid')
    
    790
    -
    
    791
    -        delay = self.forwarded.getTagAttr(
    
    792
    -            'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
    
    793
    -        if delay is None:
    
    794
    -            log.error('Received MAM message without timestamp')
    
    795
    -            log.error(self.stanza)
    
    796
    -            return
    
    797
    -
    
    798
    -        self.timestamp = helpers.parse_datetime(
    
    799
    -            delay, check_utc=True, epoch=True)
    
    800
    -        if self.timestamp is None:
    
    801
    -            log.error('Received MAM message with invalid timestamp: %s', delay)
    
    802
    -            log.error(self.stanza)
    
    803
    -            return
    
    804
    -
    
    805
    -        # Save timestamp added by the user
    
    806
    -        user_delay = self.msg_.getTagAttr(
    
    807
    -            'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
    
    808
    -        if user_delay is not None:
    
    809
    -            self.user_timestamp = helpers.parse_datetime(
    
    810
    -                user_delay, check_utc=True, epoch=True)
    
    811
    -            if self.user_timestamp is None:
    
    812
    -                log.warning('Received MAM message with '
    
    813
    -                            'invalid user timestamp: %s', user_delay)
    
    814
    -                log.warning(self.stanza)
    
    815
    -
    
    816
    -        log.debug('Received mam-gc-message: unique id: %s', self.unique_id)
    
    817
    -        return True
    
    818
    -
    
    819
    -class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    
    820
    -    name = 'mam-decrypted-message-received'
    
    821
    -    base_network_events = []
    
    822
    -
    
    823
    -    def generate(self):
    
    824
    -        self.correct_id = None
    
    825
    -
    
    826
    -        if not self.msgtxt:
    
    827
    -            # For example Chatstates, Receipts, Chatmarkers
    
    828
    -            log.debug('Received MAM message without text')
    
    829
    -            return
    
    830
    -
    
    831
    -        replace = self.msg_.getTag('replace', namespace=nbxmpp.NS_CORRECT)
    
    832
    -        if replace is not None:
    
    833
    -            self.correct_id = replace.getAttr('id')
    
    834
    -
    
    835
    -        self.get_oob_data(self.msg_)
    
    836
    -
    
    837
    -        if self.groupchat:
    
    838
    -            return True
    
    839
    -
    
    840
    -        if not self.muc_pm:
    
    841
    -            # muc_pm = False, means only there was no muc#user namespace
    
    842
    -            # This could still be a muc pm, we check the database if we
    
    843
    -            # know this jid. If not we disco it.
    
    844
    -            self.muc_pm = app.logger.jid_is_room_jid(self.with_.getStripped())
    
    845
    -            if self.muc_pm is None:
    
    846
    -                # Check if this event is triggered after a disco, so we dont
    
    847
    -                # run into an endless loop
    
    848
    -                if hasattr(self, 'disco'):
    
    849
    -                    log.error('JID not known even after sucessful disco')
    
    850
    -                    log.error(self.with_.getStripped())
    
    851
    -                    return
    
    852
    -                # we don't know this JID, we need to disco it.
    
    853
    -                server = self.with_.getDomain()
    
    854
    -                if server not in self.conn.mam_awaiting_disco_result:
    
    855
    -                    self.conn.mam_awaiting_disco_result[server] = [self]
    
    856
    -                    self.conn.discoverInfo(server)
    
    857
    -                else:
    
    858
    -                    self.conn.mam_awaiting_disco_result[server].append(self)
    
    859
    -                return
    
    860
    -
    
    861
    -        if self.muc_pm:
    
    862
    -            self.with_ = str(self.with_)
    
    863
    -        else:
    
    864
    -            self.with_ = self.with_.getStripped()
    
    865
    -        return True
    
    866
    -
    
    867 636
     class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    
    868 637
         name = 'message-received'
    
    869 638
         base_network_events = ['raw-message-received']
    
    ... ... @@ -968,30 +737,6 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    968 737
                         return
    
    969 738
                     self.forwarded = True
    
    970 739
     
    
    971
    -        result = self.stanza.getTag('result', protocol=True)
    
    972
    -        if result and result.getNamespace() in (nbxmpp.NS_MAM_1,
    
    973
    -                                                nbxmpp.NS_MAM_2):
    
    974
    -
    
    975
    -            if result.getAttr('queryid') not in self.conn.mam_query_ids:
    
    976
    -                log.warning('Invalid MAM Message: unknown query id')
    
    977
    -                log.debug(self.stanza)
    
    978
    -                return
    
    979
    -
    
    980
    -            forwarded = result.getTag('forwarded',
    
    981
    -                                      namespace=nbxmpp.NS_FORWARD,
    
    982
    -                                      protocol=True)
    
    983
    -            if not forwarded:
    
    984
    -                log.warning('Invalid MAM Message: no forwarded child')
    
    985
    -                return
    
    986
    -
    
    987
    -            app.nec.push_incoming_event(
    
    988
    -                NetworkEvent('raw-mam-message-received',
    
    989
    -                             conn=self.conn,
    
    990
    -                             stanza=self.stanza,
    
    991
    -                             forwarded=forwarded,
    
    992
    -                             result=result))
    
    993
    -            return
    
    994
    -
    
    995 740
             # Mediated invitation?
    
    996 741
             muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
    
    997 742
             if muc_user:
    
    ... ... @@ -1085,7 +830,7 @@ class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
    1085 830
                 return
    
    1086 831
     
    
    1087 832
             # Messages we receive live
    
    1088
    -        if self.conn.archiving_namespace != nbxmpp.NS_MAM_2:
    
    833
    +        if self.conn.get_module('MAM').archiving_namespace != nbxmpp.NS_MAM_2:
    
    1089 834
                 # Only mam:2 ensures valid stanza-id
    
    1090 835
                 return
    
    1091 836
     
    
    ... ... @@ -1498,77 +1243,6 @@ class JingleErrorReceivedEvent(nec.NetworkIncomingEvent):
    1498 1243
             self.sid = self.jingle_session.sid
    
    1499 1244
             return True
    
    1500 1245
     
    
    1501
    -class ArchivingReceivedEvent(nec.NetworkIncomingEvent):
    
    1502
    -    name = 'archiving-received'
    
    1503
    -    base_network_events = []
    
    1504
    -
    
    1505
    -    def generate(self):
    
    1506
    -        self.type_ = self.stanza.getType()
    
    1507
    -        if self.type_ not in ('result', 'set', 'error'):
    
    1508
    -            return
    
    1509
    -        return True
    
    1510
    -
    
    1511
    -class ArchivingErrorReceivedEvent(nec.NetworkIncomingEvent):
    
    1512
    -    name = 'archiving-error-received'
    
    1513
    -    base_network_events = ['archiving-received']
    
    1514
    -
    
    1515
    -    def generate(self):
    
    1516
    -        self.conn = self.base_event.conn
    
    1517
    -        self.stanza = self.base_event.stanza
    
    1518
    -        self.type_ = self.base_event.type_
    
    1519
    -
    
    1520
    -        if self.type_ == 'error':
    
    1521
    -            self.error_msg = self.stanza.getErrorMsg()
    
    1522
    -            return True
    
    1523
    -
    
    1524
    -class ArchivingCountReceived(nec.NetworkIncomingEvent):
    
    1525
    -    name = 'archiving-count-received'
    
    1526
    -    base_network_events = []
    
    1527
    -
    
    1528
    -    def generate(self):
    
    1529
    -        return True
    
    1530
    -
    
    1531
    -class ArchivingIntervalFinished(nec.NetworkIncomingEvent):
    
    1532
    -    name = 'archiving-interval-finished'
    
    1533
    -    base_network_events = []
    
    1534
    -
    
    1535
    -    def generate(self):
    
    1536
    -        return True
    
    1537
    -
    
    1538
    -class ArchivingQueryID(nec.NetworkIncomingEvent):
    
    1539
    -    name = 'archiving-query-id'
    
    1540
    -    base_network_events = []
    
    1541
    -
    
    1542
    -    def generate(self):
    
    1543
    -        return True
    
    1544
    -
    
    1545
    -class Archiving313PreferencesChangedReceivedEvent(nec.NetworkIncomingEvent):
    
    1546
    -    name = 'archiving-313-preferences-changed-received'
    
    1547
    -    base_network_events = ['archiving-received']
    
    1548
    -
    
    1549
    -    def generate(self):
    
    1550
    -        self.conn = self.base_event.conn
    
    1551
    -        self.stanza = self.base_event.stanza
    
    1552
    -        self.type_ = self.base_event.type_
    
    1553
    -        self.items = []
    
    1554
    -        self.default = None
    
    1555
    -        self.id = self.stanza.getID()
    
    1556
    -        self.answer = None
    
    1557
    -        prefs = self.stanza.getTag('prefs')
    
    1558
    -
    
    1559
    -        if self.type_ != 'result' or not prefs:
    
    1560
    -            return
    
    1561
    -
    
    1562
    -        self.default = prefs.getAttr('default')
    
    1563
    -
    
    1564
    -        for item in prefs.getTag('always').getTags('jid'):
    
    1565
    -            self.items.append((item.getData(), 'Always'))
    
    1566
    -
    
    1567
    -        for item in prefs.getTag('never').getTags('jid'):
    
    1568
    -            self.items.append((item.getData(), 'Never'))
    
    1569
    -
    
    1570
    -        return True
    
    1571
    -
    
    1572 1246
     class AccountCreatedEvent(nec.NetworkIncomingEvent):
    
    1573 1247
         name = 'account-created'
    
    1574 1248
         base_network_events = []
    

  • gajim/common/helpers.py
    ... ... @@ -43,7 +43,7 @@ import shlex
    43 43
     from gajim.common import caps_cache
    
    44 44
     import socket
    
    45 45
     import time
    
    46
    -from datetime import datetime, timedelta, timezone, tzinfo
    
    46
    +from datetime import datetime, timedelta
    
    47 47
     from distutils.version import LooseVersion as V
    
    48 48
     
    
    49 49
     from encodings.punycode import punycode_encode
    
    ... ... @@ -89,77 +89,6 @@ log = logging.getLogger('gajim.c.helpers')
    89 89
     
    
    90 90
     special_groups = (_('Transports'), _('Not in Roster'), _('Observers'), _('Groupchats'))
    
    91 91
     
    
    92
    -# Patterns for DateTime parsing XEP-0082
    
    93
    -PATTERN_DATETIME = re.compile(
    
    94
    -        r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
    
    95
    -        r'T'
    
    96
    -        r'([0-9]{2}:[0-9]{2}:[0-9]{2})'
    
    97
    -        r'(\.[0-9]{0,6})?'
    
    98
    -        r'(?:[0-9]+)?'
    
    99
    -        r'(?:(Z)|(?:([-+][0-9]{2}):([0-9]{2})))$'
    
    100
    -        )
    
    101
    -
    
    102
    -PATTERN_DELAY = re.compile(
    
    103
    -        r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
    
    104
    -        r'T'
    
    105
    -        r'([0-9]{2}:[0-9]{2}:[0-9]{2})'
    
    106
    -        r'(\.[0-9]{0,6})?'
    
    107
    -        r'(?:[0-9]+)?'
    
    108
    -        r'(?:(Z)|(?:([-+][0]{2}):([0]{2})))$'
    
    109
    -        )
    
    110
    -
    
    111
    -ZERO = timedelta(0)
    
    112
    -HOUR = timedelta(hours=1)
    
    113
    -SECOND = timedelta(seconds=1)
    
    114
    -
    
    115
    -STDOFFSET = timedelta(seconds=-time.timezone)
    
    116
    -if time.daylight:
    
    117
    -    DSTOFFSET = timedelta(seconds=-time.altzone)
    
    118
    -else:
    
    119
    -    DSTOFFSET = STDOFFSET
    
    120
    -
    
    121
    -DSTDIFF = DSTOFFSET - STDOFFSET
    
    122
    -
    
    123
    -
    
    124
    -class LocalTimezone(tzinfo):
    
    125
    -    '''
    
    126
    -    A class capturing the platform's idea of local time.
    
    127
    -    May result in wrong values on historical times in
    
    128
    -    timezones where UTC offset and/or the DST rules had
    
    129
    -    changed in the past.
    
    130
    -    '''
    
    131
    -    def fromutc(self, dt):
    
    132
    -        assert dt.tzinfo is self
    
    133
    -        stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND
    
    134
    -        args = time.localtime(stamp)[:6]
    
    135
    -        dst_diff = DSTDIFF // SECOND
    
    136
    -        # Detect fold
    
    137
    -        fold = (args == time.localtime(stamp - dst_diff))
    
    138
    -        return datetime(*args, microsecond=dt.microsecond,
    
    139
    -                        tzinfo=self, fold=fold)
    
    140
    -
    
    141
    -    def utcoffset(self, dt):
    
    142
    -        if self._isdst(dt):
    
    143
    -            return DSTOFFSET
    
    144
    -        else:
    
    145
    -            return STDOFFSET
    
    146
    -
    
    147
    -    def dst(self, dt):
    
    148
    -        if self._isdst(dt):
    
    149
    -            return DSTDIFF
    
    150
    -        else:
    
    151
    -            return ZERO
    
    152
    -
    
    153
    -    def tzname(self, dt):
    
    154
    -        return 'local'
    
    155
    -
    
    156
    -    def _isdst(self, dt):
    
    157
    -        tt = (dt.year, dt.month, dt.day,
    
    158
    -              dt.hour, dt.minute, dt.second,
    
    159
    -              dt.weekday(), 0, 0)
    
    160
    -        stamp = time.mktime(tt)
    
    161
    -        tt = time.localtime(stamp)
    
    162
    -        return tt.tm_isdst > 0
    
    163 92
     
    
    164 93
     class InvalidFormat(Exception):
    
    165 94
         pass
    
    ... ... @@ -673,56 +602,6 @@ def datetime_tuple(timestamp):
    673 602
             tim = tim.timetuple()
    
    674 603
         return tim
    
    675 604
     
    
    676
    -def parse_datetime(timestring, check_utc=False, convert='utc', epoch=False):
    
    677
    -    '''
    
    678
    -    Parse a XEP-0082 DateTime Profile String
    
    679
    -    https://xmpp.org/extensions/xep-0082.html
    
    680
    -
    
    681
    -    :param timestring: a XEP-0082 DateTime profile formated string
    
    682
    -
    
    683
    -    :param check_utc:  if True, returns None if timestring is not
    
    684
    -                       a timestring expressing UTC
    
    685
    -
    
    686
    -    :param convert:    convert the given timestring to utc or local time
    
    687
    -
    
    688
    -    :param epoch:      if True, returns the time in epoch
    
    689
    -
    
    690
    -    Examples:
    
    691
    -    '2017-11-05T01:41:20Z'
    
    692
    -    '2017-11-05T01:41:20.123Z'
    
    693
    -    '2017-11-05T01:41:20.123+05:00'
    
    694
    -
    
    695
    -    return a datetime or epoch
    
    696
    -    '''
    
    697
    -    if convert not in (None, 'utc', 'local'):
    
    698
    -        raise TypeError('"%s" is not a valid value for convert')
    
    699
    -    if check_utc:
    
    700
    -        match = PATTERN_DELAY.match(timestring)
    
    701
    -    else:
    
    702
    -        match = PATTERN_DATETIME.match(timestring)
    
    703
    -
    
    704
    -    if match:
    
    705
    -        timestring = ''.join(match.groups(''))
    
    706
    -        strformat = '%Y-%m-%d%H:%M:%S%z'
    
    707
    -        if match.group(3):
    
    708
    -            # Fractional second addendum to Time
    
    709
    -            strformat = '%Y-%m-%d%H:%M:%S.%f%z'
    
    710
    -        if match.group(4):
    
    711
    -            # UTC string denoted by addition of the character 'Z'
    
    712
    -            timestring = timestring[:-1] + '+0000'
    
    713
    -        try:
    
    714
    -            date_time = datetime.strptime(timestring, strformat)
    
    715
    -        except ValueError:
    
    716
    -            pass
    
    717
    -        else:
    
    718
    -            if not check_utc and convert == 'utc':
    
    719
    -                date_time = date_time.astimezone(timezone.utc)
    
    720
    -            if convert == 'local':
    
    721
    -                date_time = date_time.astimezone(LocalTimezone())
    
    722
    -            if epoch:
    
    723
    -                return date_time.timestamp()
    
    724
    -            return date_time
    
    725
    -    return None
    
    726 605
     
    
    727 606
     from gajim.common import app
    
    728 607
     if app.is_installed('PYCURL'):
    
    ... ... @@ -1003,6 +882,9 @@ def get_full_jid_from_iq(iq_obj):
    1003 882
         """
    
    1004 883
         Return the full jid (with resource) from an iq
    
    1005 884
         """
    
    885
    +    jid = iq_obj.getFrom()
    
    886
    +    if jid is None:
    
    887
    +        return None
    
    1006 888
         return parse_jid(str(iq_obj.getFrom()))
    
    1007 889
     
    
    1008 890
     def get_jid_from_iq(iq_obj):
    
    ... ... @@ -1626,21 +1508,3 @@ def get_emoticon_theme_path(theme):
    1626 1508
         emoticons_user_path = os.path.join(configpaths.get('MY_EMOTS'), theme)
    
    1627 1509
         if os.path.exists(emoticons_user_path):
    
    1628 1510
             return emoticons_user_path
    1629
    -
    
    1630
    -def add_to_mam_blacklist(jid):
    
    1631
    -    config_value = app.config.get('mam_blacklist')
    
    1632
    -    if not config_value:
    
    1633
    -        config_value = [jid]
    
    1634
    -    else:
    
    1635
    -        if jid in config_value:
    
    1636
    -            return
    
    1637
    -        config_value = config_value.split(',')
    
    1638
    -        config_value.append(jid)
    
    1639
    -    log.warning('Found not-compliant MUC. %s added to MAM Blacklist', jid)
    
    1640
    -    app.config.set('mam_blacklist', ','.join(config_value))
    
    1641
    -
    
    1642
    -def get_mam_blacklist():
    
    1643
    -    config_value = app.config.get('mam_blacklist')
    
    1644
    -    if not config_value:
    
    1645
    -        return []
    
    1646
    -    return config_value.split(',')

  • gajim/common/logger.py
    ... ... @@ -374,14 +374,10 @@ class Logger:
    374 374
             """
    
    375 375
             Return True if it's a room jid, False if it's not, None if we don't know
    
    376 376
             """
    
    377
    -        row = self._con.execute(
    
    378
    -            'SELECT type FROM jids WHERE jid=?', (jid,)).fetchone()
    
    379
    -        if row is None:
    
    380
    -            return None
    
    381
    -        else:
    
    382
    -            if row.type == JIDConstant.ROOM_TYPE:
    
    383
    -                return True
    
    384
    -            return False
    
    377
    +        jid_ = self._jid_ids.get(jid)
    
    378
    +        if jid_ is None:
    
    379
    +            return
    
    380
    +        return jid_.type == JIDConstant.ROOM_TYPE
    
    385 381
     
    
    386 382
         @staticmethod
    
    387 383
         def _get_family_jids(account, jid):
    

  • gajim/common/message_archiving.py deleted
    1
    -# -*- coding:utf-8 -*-
    
    2
    -## src/common/message_archiving.py
    
    3
    -##
    
    4
    -## Copyright (C) 2009 Anaël Verrier <elghinn AT free.fr>
    
    5
    -##
    
    6
    -## This file is part of Gajim.
    
    7
    -##
    
    8
    -## Gajim is free software; you can redistribute it and/or modify
    
    9
    -## it under the terms of the GNU General Public License as published
    
    10
    -## by the Free Software Foundation; version 3 only.
    
    11
    -##
    
    12
    -## Gajim 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 General Public License for more details.
    
    16
    -##
    
    17
    -## You should have received a copy of the GNU General Public License
    
    18
    -## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    19
    -##
    
    20
    -
    
    21
    -import logging
    
    22
    -from datetime import datetime, timedelta
    
    23
    -
    
    24
    -import nbxmpp
    
    25
    -
    
    26
    -from gajim.common import app
    
    27
    -from gajim.common import ged
    
    28
    -from gajim.common import helpers
    
    29
    -from gajim.common.const import ArchiveState, JIDConstant
    
    30
    -from gajim.common.caps_cache import muc_caps_cache
    
    31
    -import gajim.common.connection_handlers_events as ev
    
    32
    -
    
    33
    -log = logging.getLogger('gajim.c.message_archiving')
    
    34
    -
    
    35
    -
    
    36
    -class ConnectionArchive313:
    
    37
    -    def __init__(self):
    
    38
    -        self.archiving_313_supported = False
    
    39
    -        self.mam_awaiting_disco_result = {}
    
    40
    -        self.iq_answer = []
    
    41
    -        self.mam_query_ids = []
    
    42
    -        app.nec.register_incoming_event(ev.MamMessageReceivedEvent)
    
    43
    -        app.nec.register_incoming_event(ev.MamGcMessageReceivedEvent)
    
    44
    -        app.ged.register_event_handler('agent-info-error-received', ged.CORE,
    
    45
    -            self._nec_agent_info_error)
    
    46
    -        app.ged.register_event_handler('agent-info-received', ged.CORE,
    
    47
    -            self._nec_agent_info)
    
    48
    -        app.ged.register_event_handler('mam-decrypted-message-received',
    
    49
    -            ged.CORE, self._nec_mam_decrypted_message_received)
    
    50
    -        app.ged.register_event_handler(
    
    51
    -            'archiving-313-preferences-changed-received', ged.CORE,
    
    52
    -            self._nec_archiving_313_preferences_changed_received)
    
    53
    -
    
    54
    -    def cleanup(self):
    
    55
    -        app.ged.remove_event_handler('agent-info-error-received', ged.CORE,
    
    56
    -            self._nec_agent_info_error)
    
    57
    -        app.ged.remove_event_handler('agent-info-received', ged.CORE,
    
    58
    -            self._nec_agent_info)
    
    59
    -        app.ged.remove_event_handler('mam-decrypted-message-received',
    
    60
    -            ged.CORE, self._nec_mam_decrypted_message_received)
    
    61
    -        app.ged.remove_event_handler(
    
    62
    -            'archiving-313-preferences-changed-received', ged.CORE,
    
    63
    -            self._nec_archiving_313_preferences_changed_received)
    
    64
    -
    
    65
    -    def _nec_archiving_313_preferences_changed_received(self, obj):
    
    66
    -        if obj.id in self.iq_answer:
    
    67
    -            obj.answer = True
    
    68
    -
    
    69
    -    def _nec_agent_info_error(self, obj):
    
    70
    -        if obj.jid in self.mam_awaiting_disco_result:
    
    71
    -            log.warn('Unable to discover %s, ignoring those logs', obj.jid)
    
    72
    -            del self.mam_awaiting_disco_result[obj.jid]
    
    73
    -
    
    74
    -    def _nec_agent_info(self, obj):
    
    75
    -        if obj.jid not in self.mam_awaiting_disco_result:
    
    76
    -            return
    
    77
    -
    
    78
    -        for identity in obj.identities:
    
    79
    -            if identity['category'] != 'conference':
    
    80
    -                continue
    
    81
    -            # it's a groupchat
    
    82
    -            for msg_obj in self.mam_awaiting_disco_result[obj.jid]:
    
    83
    -                app.logger.insert_jid(msg_obj.with_.getStripped(),
    
    84
    -                                        type_=JIDConstant.ROOM_TYPE)
    
    85
    -                app.nec.push_incoming_event(
    
    86
    -                    ev.MamDecryptedMessageReceivedEvent(
    
    87
    -                        None, disco=True, **vars(msg_obj)))
    
    88
    -            del self.mam_awaiting_disco_result[obj.jid]
    
    89
    -            return
    
    90
    -        # it's not a groupchat
    
    91
    -        for msg_obj in self.mam_awaiting_disco_result[obj.jid]:
    
    92
    -            app.logger.insert_jid(msg_obj.with_.getStripped())
    
    93
    -            app.nec.push_incoming_event(
    
    94
    -                ev.MamDecryptedMessageReceivedEvent(
    
    95
    -                    None, disco=True, **vars(msg_obj)))
    
    96
    -        del self.mam_awaiting_disco_result[obj.jid]
    
    97
    -
    
    98
    -    @staticmethod
    
    99
    -    def parse_iq(stanza):
    
    100
    -        if not nbxmpp.isResultNode(stanza):
    
    101
    -            log.error('Error on MAM query: %s', stanza.getError())
    
    102
    -            raise InvalidMamIQ
    
    103
    -
    
    104
    -        fin = stanza.getTag('fin')
    
    105
    -        if fin is None:
    
    106
    -            log.error('Malformed MAM query result received: %s', stanza)
    
    107
    -            raise InvalidMamIQ
    
    108
    -
    
    109
    -        set_ = fin.getTag('set', namespace=nbxmpp.NS_RSM)
    
    110
    -        if set_ is None:
    
    111
    -            log.error(
    
    112
    -                'Malformed MAM query result received (no "set" Node): %s',
    
    113
    -                stanza)
    
    114
    -            raise InvalidMamIQ
    
    115
    -        return fin, set_
    
    116
    -
    
    117
    -    def parse_from_jid(self, stanza):
    
    118
    -        jid = stanza.getFrom()
    
    119
    -        if jid is None:
    
    120
    -            # No from means, iq from our own archive
    
    121
    -            jid = self.get_own_jid().getStripped()
    
    122
    -        else:
    
    123
    -            jid = jid.getStripped()
    
    124
    -        return jid
    
    125
    -
    
    126
    -    def _result_finished(self, conn, stanza, query_id, start_date, groupchat):
    
    127
    -        try:
    
    128
    -            fin, set_ = self.parse_iq(stanza)
    
    129
    -        except InvalidMamIQ:
    
    130
    -            return
    
    131
    -
    
    132
    -        last = set_.getTagData('last')
    
    133
    -        if last is None:
    
    134
    -            log.info('End of MAM query, no items retrieved')
    
    135
    -            return
    
    136
    -
    
    137
    -        jid = self.parse_from_jid(stanza)
    
    138
    -        complete = fin.getAttr('complete')
    
    139
    -        app.logger.set_archive_timestamp(jid, last_mam_id=last)
    
    140
    -        if complete != 'true':
    
    141
    -            self.mam_query_ids.remove(query_id)
    
    142
    -            query_id = self.get_query_id()
    
    143
    -            query = self.get_archive_query(query_id, jid=jid, after=last)
    
    144
    -            self._send_archive_query(query, query_id, groupchat=groupchat)
    
    145
    -        else:
    
    146
    -            self.mam_query_ids.remove(query_id)
    
    147
    -            if start_date is not None:
    
    148
    -                app.logger.set_archive_timestamp(
    
    149
    -                    jid,
    
    150
    -                    last_mam_id=last,
    
    151
    -                    oldest_mam_timestamp=start_date.timestamp())
    
    152
    -            log.info('End of MAM query, last mam id: %s', last)
    
    153
    -
    
    154
    -    def _intervall_result_finished(self, conn, stanza, query_id,
    
    155
    -                                   start_date, end_date, event_id):
    
    156
    -        try:
    
    157
    -            fin, set_ = self.parse_iq(stanza)
    
    158
    -        except InvalidMamIQ:
    
    159
    -            return
    
    160
    -
    
    161
    -        self.mam_query_ids.remove(query_id)
    
    162
    -        jid = self.parse_from_jid(stanza)
    
    163
    -        if start_date:
    
    164
    -            timestamp = start_date.timestamp()
    
    165
    -        else:
    
    166
    -            timestamp = ArchiveState.ALL
    
    167
    -
    
    168
    -        last = set_.getTagData('last')
    
    169
    -        if last is None:
    
    170
    -            app.nec.push_incoming_event(ev.ArchivingIntervalFinished(
    
    171
    -                None, event_id=event_id))
    
    172
    -            app.logger.set_archive_timestamp(
    
    173
    -                jid, oldest_mam_timestamp=timestamp)
    
    174
    -            log.info('End of MAM query, no items retrieved')
    
    175
    -            return
    
    176
    -
    
    177
    -        complete = fin.getAttr('complete')
    
    178
    -        if complete != 'true':
    
    179
    -            self.request_archive_interval(event_id, start_date, end_date, last)
    
    180
    -        else:
    
    181
    -            log.info('query finished')
    
    182
    -            app.logger.set_archive_timestamp(
    
    183
    -                jid, oldest_mam_timestamp=timestamp)
    
    184
    -            app.nec.push_incoming_event(ev.ArchivingIntervalFinished(
    
    185
    -                None, event_id=event_id, stanza=stanza))
    
    186
    -
    
    187
    -    def _received_count(self, conn, stanza, query_id, event_id):
    
    188
    -        try:
    
    189
    -            _, set_ = self.parse_iq(stanza)
    
    190
    -        except InvalidMamIQ:
    
    191
    -            return
    
    192
    -
    
    193
    -        self.mam_query_ids.remove(query_id)
    
    194
    -
    
    195
    -        count = set_.getTagData('count')
    
    196
    -        log.info('message count received: %s', count)
    
    197
    -        app.nec.push_incoming_event(ev.ArchivingCountReceived(
    
    198
    -            None, event_id=event_id, count=count))
    
    199
    -
    
    200
    -    def _nec_mam_decrypted_message_received(self, obj):
    
    201
    -        if obj.conn.name != self.name:
    
    202
    -            return
    
    203
    -
    
    204
    -        namespace = self.archiving_namespace
    
    205
    -        blacklisted = False
    
    206
    -        if obj.groupchat:
    
    207
    -            namespace = muc_caps_cache.get_mam_namespace(obj.room_jid)
    
    208
    -            blacklisted = obj.room_jid in helpers.get_mam_blacklist()
    
    209
    -
    
    210
    -        if namespace != nbxmpp.NS_MAM_2 or blacklisted:
    
    211
    -            # Fallback duplicate search without stanza-id
    
    212
    -            duplicate = app.logger.search_for_duplicate(
    
    213
    -                self.name, obj.with_, obj.timestamp, obj.msgtxt)
    
    214
    -            if duplicate:
    
    215
    -                # dont propagate the event further
    
    216
    -                return True
    
    217
    -
    
    218
    -        app.logger.insert_into_logs(self.name,
    
    219
    -                                    obj.with_,
    
    220
    -                                    obj.timestamp,
    
    221
    -                                    obj.kind,
    
    222
    -                                    unread=False,
    
    223
    -                                    message=obj.msgtxt,
    
    224
    -                                    contact_name=obj.nick,
    
    225
    -                                    additional_data=obj.additional_data,
    
    226
    -                                    stanza_id=obj.unique_id)
    
    227
    -
    
    228
    -    def get_query_id(self):
    
    229
    -        query_id = self.connection.getAnID()
    
    230
    -        self.mam_query_ids.append(query_id)
    
    231
    -        return query_id
    
    232
    -
    
    233
    -    def request_archive_on_signin(self):
    
    234
    -        own_jid = self.get_own_jid().getStripped()
    
    235
    -        archive = app.logger.get_archive_timestamp(own_jid)
    
    236
    -
    
    237
    -        # Migration of last_mam_id from config to DB
    
    238
    -        if archive is not None:
    
    239
    -            mam_id = archive.last_mam_id
    
    240
    -        else:
    
    241
    -            mam_id = app.config.get_per('accounts', self.name, 'last_mam_id')
    
    242
    -
    
    243
    -        start_date = None
    
    244
    -        query_id = self.get_query_id()
    
    245
    -        if mam_id:
    
    246
    -            log.info('MAM query after: %s', mam_id)
    
    247
    -            query = self.get_archive_query(query_id, after=mam_id)
    
    248
    -        else:
    
    249
    -            # First Start, we request the last week
    
    250
    -            start_date = datetime.utcnow() - timedelta(days=7)
    
    251
    -            log.info('First start: query archive start: %s', start_date)
    
    252
    -            query = self.get_archive_query(query_id, start=start_date)
    
    253
    -        self._send_archive_query(query, query_id, start_date)
    
    254
    -
    
    255
    -    def request_archive_on_muc_join(self, jid):
    
    256
    -        archive = app.logger.get_archive_timestamp(
    
    257
    -            jid, type_=JIDConstant.ROOM_TYPE)
    
    258
    -        query_id = self.get_query_id()
    
    259
    -        start_date = None
    
    260
    -        if archive is not None:
    
    261
    -            log.info('Query Groupchat MAM Archive %s after %s:',
    
    262
    -                     jid, archive.last_mam_id)
    
    263
    -            query = self.get_archive_query(
    
    264
    -                query_id, jid=jid, after=archive.last_mam_id)
    
    265
    -        else:
    
    266
    -            # First Start, we dont request history
    
    267
    -            # Depending on what a MUC saves, there could be thousands
    
    268
    -            # of Messages even in just one day.
    
    269
    -            start_date = datetime.utcnow() - timedelta(days=1)
    
    270
    -            log.info('First join: query archive %s from: %s', jid, start_date)
    
    271
    -            query = self.get_archive_query(query_id, jid=jid, start=start_date)
    
    272
    -        self._send_archive_query(query, query_id, start_date, groupchat=True)
    
    273
    -
    
    274
    -    def request_archive_count(self, event_id, start_date, end_date):
    
    275
    -        query_id = self.get_query_id()
    
    276
    -        query = self.get_archive_query(
    
    277
    -            query_id, start=start_date, end=end_date, max_=0)
    
    278
    -        self.connection.SendAndCallForResponse(
    
    279
    -            query, self._received_count, {'query_id': query_id,
    
    280
    -                                          'event_id': event_id})
    
    281
    -
    
    282
    -    def request_archive_interval(self, event_id, start_date,
    
    283
    -                                 end_date, after=None):
    
    284
    -        query_id = self.get_query_id()
    
    285
    -        query = self.get_archive_query(query_id, start=start_date,
    
    286
    -                                       end=end_date, after=after, max_=30)
    
    287
    -        app.nec.push_incoming_event(ev.ArchivingQueryID(
    
    288
    -            None, event_id=event_id, query_id=query_id))
    
    289
    -        self.connection.SendAndCallForResponse(
    
    290
    -            query, self._intervall_result_finished, {'query_id': query_id,
    
    291
    -                                                     'start_date': start_date,
    
    292
    -                                                     'end_date': end_date,
    
    293
    -                                                     'event_id': event_id})
    
    294
    -
    
    295
    -    def _send_archive_query(self, query, query_id, start_date=None,
    
    296
    -                            groupchat=False):
    
    297
    -        self.connection.SendAndCallForResponse(
    
    298
    -            query, self._result_finished, {'query_id': query_id,
    
    299
    -                                           'start_date': start_date,
    
    300
    -                                           'groupchat': groupchat})
    
    301
    -
    
    302
    -    def get_archive_query(self, query_id, jid=None, start=None, end=None, with_=None,
    
    303
    -                          after=None, max_=30):
    
    304
    -        # Muc archive query?
    
    305
    -        namespace = muc_caps_cache.get_mam_namespace(jid)
    
    306
    -        if namespace is None:
    
    307
    -            # Query to our own archive
    
    308
    -            namespace = self.archiving_namespace
    
    309
    -
    
    310
    -        iq = nbxmpp.Iq('set', to=jid)
    
    311
    -        query = iq.addChild('query', namespace=namespace)
    
    312
    -        form = query.addChild(node=nbxmpp.DataForm(typ='submit'))
    
    313
    -        field = nbxmpp.DataField(typ='hidden',
    
    314
    -                                 name='FORM_TYPE',
    
    315
    -                                 value=namespace)
    
    316
    -        form.addChild(node=field)
    
    317
    -        if start:
    
    318
    -            field = nbxmpp.DataField(typ='text-single',
    
    319
    -                                     name='start',
    
    320
    -                                     value=start.strftime('%Y-%m-%dT%H:%M:%SZ'))
    
    321
    -            form.addChild(node=field)
    
    322
    -        if end:
    
    323
    -            field = nbxmpp.DataField(typ='text-single',
    
    324
    -                                     name='end',
    
    325
    -                                     value=end.strftime('%Y-%m-%dT%H:%M:%SZ'))
    
    326
    -            form.addChild(node=field)
    
    327
    -        if with_:
    
    328
    -            field = nbxmpp.DataField(typ='jid-single', name='with', value=with_)
    
    329
    -            form.addChild(node=field)
    
    330
    -
    
    331
    -        set_ = query.setTag('set', namespace=nbxmpp.NS_RSM)
    
    332
    -        set_.setTagData('max', max_)
    
    333
    -        if after:
    
    334
    -            set_.setTagData('after', after)
    
    335
    -        query.setAttr('queryid', query_id)
    
    336
    -        return iq
    
    337
    -
    
    338
    -    def request_archive_preferences(self):
    
    339
    -        if not app.account_is_connected(self.name):
    
    340
    -            return
    
    341
    -        iq = nbxmpp.Iq(typ='get')
    
    342
    -        id_ = self.connection.getAnID()
    
    343
    -        iq.setID(id_)
    
    344
    -        iq.addChild(name='prefs', namespace=self.archiving_namespace)
    
    345
    -        self.connection.send(iq)
    
    346
    -
    
    347
    -    def set_archive_preferences(self, items, default):
    
    348
    -        if not app.account_is_connected(self.name):
    
    349
    -            return
    
    350
    -        iq = nbxmpp.Iq(typ='set')
    
    351
    -        id_ = self.connection.getAnID()
    
    352
    -        self.iq_answer.append(id_)
    
    353
    -        iq.setID(id_)
    
    354
    -        prefs = iq.addChild(name='prefs', namespace=self.archiving_namespace, attrs={'default': default})
    
    355
    -        always = prefs.addChild(name='always')
    
    356
    -        never = prefs.addChild(name='never')
    
    357
    -        for item in items:
    
    358
    -            jid, preference = item
    
    359
    -            if preference == 'always':
    
    360
    -                always.addChild(name='jid').setData(jid)
    
    361
    -            else:
    
    362
    -                never.addChild(name='jid').setData(jid)
    
    363
    -        self.connection.send(iq)
    
    364
    -
    
    365
    -    def _ArchiveCB(self, con, iq_obj):
    
    366
    -        app.nec.push_incoming_event(ev.ArchivingReceivedEvent(None, conn=self,
    
    367
    -            stanza=iq_obj))
    
    368
    -        raise nbxmpp.NodeProcessed
    
    369
    -
    
    370
    -
    
    371
    -class InvalidMamIQ(Exception):
    
    372
    -    pass

  • gajim/common/modules/date_and_time.py
    1
    +# This file is part of Gajim.
    
    2
    +#
    
    3
    +# Gajim is free software; you can redistribute it and/or modify
    
    4
    +# it under the terms of the GNU General Public License as published
    
    5
    +# by the Free Software Foundation; version 3 only.
    
    6
    +#
    
    7
    +# Gajim is distributed in the hope that it will be useful,
    
    8
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    9
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    10
    +# GNU General Public License for more details.
    
    11
    +#
    
    12
    +# You should have received a copy of the GNU General Public License
    
    13
    +# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    14
    +
    
    15
    +# XEP-0082: XMPP Date and Time Profiles
    
    16
    +
    
    17
    +import re
    
    18
    +import time
    
    19
    +from datetime import datetime, timedelta, timezone, tzinfo
    
    20
    +
    
    21
    +
    
    22
    +PATTERN_DATETIME = re.compile(
    
    23
    +    r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
    
    24
    +    r'T'
    
    25
    +    r'([0-9]{2}:[0-9]{2}:[0-9]{2})'
    
    26
    +    r'(\.[0-9]{0,6})?'
    
    27
    +    r'(?:[0-9]+)?'
    
    28
    +    r'(?:(Z)|(?:([-+][0-9]{2}):([0-9]{2})))$'
    
    29
    +)
    
    30
    +
    
    31
    +PATTERN_DELAY = re.compile(
    
    32
    +    r'([0-9]{4}-[0-9]{2}-[0-9]{2})'
    
    33
    +    r'T'
    
    34
    +    r'([0-9]{2}:[0-9]{2}:[0-9]{2})'
    
    35
    +    r'(\.[0-9]{0,6})?'
    
    36
    +    r'(?:[0-9]+)?'
    
    37
    +    r'(?:(Z)|(?:([-+][0]{2}):([0]{2})))$'
    
    38
    +)
    
    39
    +
    
    40
    +
    
    41
    +ZERO = timedelta(0)
    
    42
    +HOUR = timedelta(hours=1)
    
    43
    +SECOND = timedelta(seconds=1)
    
    44
    +
    
    45
    +STDOFFSET = timedelta(seconds=-time.timezone)
    
    46
    +if time.daylight:
    
    47
    +    DSTOFFSET = timedelta(seconds=-time.altzone)
    
    48
    +else:
    
    49
    +    DSTOFFSET = STDOFFSET
    
    50
    +
    
    51
    +DSTDIFF = DSTOFFSET - STDOFFSET
    
    52
    +
    
    53
    +
    
    54
    +class LocalTimezone(tzinfo):
    
    55
    +    '''
    
    56
    +    A class capturing the platform's idea of local time.
    
    57
    +    May result in wrong values on historical times in
    
    58
    +    timezones where UTC offset and/or the DST rules had
    
    59
    +    changed in the past.
    
    60
    +    '''
    
    61
    +    def fromutc(self, dt):
    
    62
    +        assert dt.tzinfo is self
    
    63
    +        stamp = (dt - datetime(1970, 1, 1, tzinfo=self)) // SECOND
    
    64
    +        args = time.localtime(stamp)[:6]
    
    65
    +        dst_diff = DSTDIFF // SECOND
    
    66
    +        # Detect fold
    
    67
    +        fold = (args == time.localtime(stamp - dst_diff))
    
    68
    +        return datetime(*args, microsecond=dt.microsecond,
    
    69
    +                        tzinfo=self, fold=fold)
    
    70
    +
    
    71
    +    def utcoffset(self, dt):
    
    72
    +        if self._isdst(dt):
    
    73
    +            return DSTOFFSET
    
    74
    +        else:
    
    75
    +            return STDOFFSET
    
    76
    +
    
    77
    +    def dst(self, dt):
    
    78
    +        if self._isdst(dt):
    
    79
    +            return DSTDIFF
    
    80
    +        else:
    
    81
    +            return ZERO
    
    82
    +
    
    83
    +    def tzname(self, dt):
    
    84
    +        return 'local'
    
    85
    +
    
    86
    +    def _isdst(self, dt):
    
    87
    +        tt = (dt.year, dt.month, dt.day,
    
    88
    +              dt.hour, dt.minute, dt.second,
    
    89
    +              dt.weekday(), 0, 0)
    
    90
    +        stamp = time.mktime(tt)
    
    91
    +        tt = time.localtime(stamp)
    
    92
    +        return tt.tm_isdst > 0
    
    93
    +
    
    94
    +
    
    95
    +def parse_datetime(timestring, check_utc=False,
    
    96
    +                   convert='utc', epoch=False):
    
    97
    +    '''
    
    98
    +    Parse a XEP-0082 DateTime Profile String
    
    99
    +
    
    100
    +    :param timestring: a XEP-0082 DateTime profile formated string
    
    101
    +
    
    102
    +    :param check_utc:  if True, returns None if timestring is not
    
    103
    +                       a timestring expressing UTC
    
    104
    +
    
    105
    +    :param convert:    convert the given timestring to utc or local time
    
    106
    +
    
    107
    +    :param epoch:      if True, returns the time in epoch
    
    108
    +
    
    109
    +    Examples:
    
    110
    +    '2017-11-05T01:41:20Z'
    
    111
    +    '2017-11-05T01:41:20.123Z'
    
    112
    +    '2017-11-05T01:41:20.123+05:00'
    
    113
    +
    
    114
    +    return a datetime or epoch
    
    115
    +    '''
    
    116
    +    if convert not in (None, 'utc', 'local'):
    
    117
    +        raise TypeError('"%s" is not a valid value for convert')
    
    118
    +    if check_utc:
    
    119
    +        match = PATTERN_DELAY.match(timestring)
    
    120
    +    else:
    
    121
    +        match = PATTERN_DATETIME.match(timestring)
    
    122
    +
    
    123
    +    if match:
    
    124
    +        timestring = ''.join(match.groups(''))
    
    125
    +        strformat = '%Y-%m-%d%H:%M:%S%z'
    
    126
    +        if match.group(3):
    
    127
    +            # Fractional second addendum to Time
    
    128
    +            strformat = '%Y-%m-%d%H:%M:%S.%f%z'
    
    129
    +        if match.group(4):
    
    130
    +            # UTC string denoted by addition of the character 'Z'
    
    131
    +            timestring = timestring[:-1] + '+0000'
    
    132
    +        try:
    
    133
    +            date_time = datetime.strptime(timestring, strformat)
    
    134
    +        except ValueError:
    
    135
    +            pass
    
    136
    +        else:
    
    137
    +            if not check_utc and convert == 'utc':
    
    138
    +                date_time = date_time.astimezone(timezone.utc)
    
    139
    +            if convert == 'local':
    
    140
    +                date_time = date_time.astimezone(LocalTimezone())
    
    141
    +            if epoch:
    
    142
    +                return date_time.timestamp()
    
    143
    +            return date_time
    
    144
    +    return None

  • gajim/common/modules/mam.py
    1
    +# This file is part of Gajim.
    
    2
    +#
    
    3
    +# Gajim is free software; you can redistribute it and/or modify
    
    4
    +# it under the terms of the GNU General Public License as published
    
    5
    +# by the Free Software Foundation; version 3 only.
    
    6
    +#
    
    7
    +# Gajim is distributed in the hope that it will be useful,
    
    8
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    9
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    10
    +# GNU General Public License for more details.
    
    11
    +#
    
    12
    +# You should have received a copy of the GNU General Public License
    
    13
    +# along with Gajim.  If not, see <http://www.gnu.org/licenses/>.
    
    14
    +
    
    15
    +# XEP-0313: Message Archive Management
    
    16
    +
    
    17
    +import logging
    
    18
    +from datetime import datetime, timedelta
    
    19
    +
    
    20
    +import nbxmpp
    
    21
    +
    
    22
    +from gajim.common import app
    
    23
    +from gajim.common.nec import NetworkIncomingEvent
    
    24
    +from gajim.common.const import ArchiveState, JIDConstant, KindConstant
    
    25
    +from gajim.common.caps_cache import muc_caps_cache
    
    26
    +from gajim.common.modules.misc import parse_delay
    
    27
    +from gajim.common.modules.misc import parse_oob
    
    28
    +from gajim.common.modules.misc import parse_correction
    
    29
    +from gajim.common.modules.misc import parse_eme
    
    30
    +
    
    31
    +log = logging.getLogger('gajim.c.m.archiving')
    
    32
    +
    
    33
    +
    
    34
    +class MAM:
    
    35
    +    def __init__(self, con):
    
    36
    +        self._con = con
    
    37
    +        self._account = con.name
    
    38
    +
    
    39
    +        self.handlers = [
    
    40
    +            ('message', self._mam_message_received, nbxmpp.NS_MAM_1),
    
    41
    +            ('message', self._mam_message_received, nbxmpp.NS_MAM_2)
    
    42
    +        ]
    
    43
    +
    
    44
    +        self.available = False
    
    45
    +        self.archiving_namespace = None
    
    46
    +        self._mam_query_ids = {}
    
    47
    +
    
    48
    +    def _from_valid_archive(self, stanza, message, groupchat):
    
    49
    +        if groupchat:
    
    50
    +            expected_archive = message.getFrom()
    
    51
    +        else:
    
    52
    +            expected_archive = self._con.get_own_jid()
    
    53
    +
    
    54
    +        archive_jid = stanza.getFrom()
    
    55
    +        if archive_jid is None:
    
    56
    +            if groupchat:
    
    57
    +                return
    
    58
    +            # Message from our own archive
    
    59
    +            return self._con.get_own_jid()
    
    60
    +        else:
    
    61
    +            if archive_jid.bareMatch(expected_archive):
    
    62
    +                return archive_jid
    
    63
    +
    
    64
    +    @staticmethod
    
    65
    +    def _is_self_message(message, groupchat):
    
    66
    +        if groupchat:
    
    67
    +            return False
    
    68
    +        frm = message.getFrom()
    
    69
    +        to = message.getTo()
    
    70
    +        return frm.bareMatch(to)
    
    71
    +
    
    72
    +    @staticmethod
    
    73
    +    def _is_muc_pm(message, groupchat, with_):
    
    74
    +        if groupchat:
    
    75
    +            return False
    
    76
    +        muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER)
    
    77
    +        if muc_user is not None:
    
    78
    +            return muc_user.getChildren() == []
    
    79
    +        else:
    
    80
    +            # muc#user namespace was added in MUC 1.28 so we need a fallback
    
    81
    +            # Check if we know the jid, otherwise disco it
    
    82
    +            if app.logger.jid_is_room_jid(with_.getStripped()):
    
    83
    +                return True
    
    84
    +            return False
    
    85
    +
    
    86
    +    def _get_unique_id(self, result, message, groupchat, self_message, muc_pm):
    
    87
    +        stanza_id = result.getAttr('id')
    
    88
    +        if groupchat:
    
    89
    +            return stanza_id, None
    
    90
    +
    
    91
    +        origin_id = message.getOriginID()
    
    92
    +        if self_message:
    
    93
    +            return None, origin_id
    
    94
    +
    
    95
    +        if muc_pm:
    
    96
    +            return stanza_id, origin_id
    
    97
    +
    
    98
    +        if self._con.get_own_jid().bareMatch(message.getFrom()):
    
    99
    +            # message we sent
    
    100
    +            return stanza_id, origin_id
    
    101
    +
    
    102
    +        # A message we received
    
    103
    +        return stanza_id, None
    
    104
    +
    
    105
    +    def _mam_message_received(self, conn, stanza):
    
    106
    +        app.nec.push_incoming_event(
    
    107
    +            NetworkIncomingEvent('raw-mam-message-received',
    
    108
    +                                 conn=self._con,
    
    109
    +                                 stanza=stanza))
    
    110
    +
    
    111
    +        result = stanza.getTag('result', protocol=True)
    
    112
    +        queryid = result.getAttr('queryid')
    
    113
    +        forwarded = result.getTag('forwarded',
    
    114
    +                                  namespace=nbxmpp.NS_FORWARD,
    
    115
    +                                  protocol=True)
    
    116
    +        message = forwarded.getTag('message', protocol=True)
    
    117
    +
    
    118
    +        groupchat = message.getType() == 'groupchat'
    
    119
    +
    
    120
    +        archive_jid = self._from_valid_archive(stanza, message, groupchat)
    
    121
    +        if archive_jid is None:
    
    122
    +            log.warning('Message from invalid archive %s', stanza)
    
    123
    +            raise nbxmpp.NodeProcessed
    
    124
    +
    
    125
    +        log.info('Received message from archive: %s', archive_jid)
    
    126
    +        if not self._is_valid_request(archive_jid, queryid):
    
    127
    +            log.warning('Invalid MAM Message: unknown query id')
    
    128
    +            log.debug(stanza)
    
    129
    +            raise nbxmpp.NodeProcessed
    
    130
    +
    
    131
    +        # Timestamp parsing
    
    132
    +        timestamp = parse_delay(forwarded)
    
    133
    +        if timestamp is None:
    
    134
    +            raise nbxmpp.NodeProcessed
    
    135
    +
    
    136
    +        user_timestamp = parse_delay(message)
    
    137
    +
    
    138
    +        # Fix for self messaging
    
    139
    +        if not groupchat:
    
    140
    +            to = message.getTo()
    
    141
    +            if to is None:
    
    142
    +                # Some servers dont set the 'to' attribute when
    
    143
    +                # we send a message to ourself
    
    144
    +                message.setTo(self._con.get_own_jid())
    
    145
    +
    
    146
    +        event_attrs = {}
    
    147
    +
    
    148
    +        if groupchat:
    
    149
    +            event_attrs.update(self._parse_gc_attrs(message))
    
    150
    +        else:
    
    151
    +            event_attrs.update(self._parse_chat_attrs(message))
    
    152
    +
    
    153
    +        self_message = self._is_self_message(message, groupchat)
    
    154
    +        muc_pm = self._is_muc_pm(message, groupchat, event_attrs['with_'])
    
    155
    +
    
    156
    +        stanza_id, origin_id = self._get_unique_id(
    
    157
    +            result, message, groupchat, self_message, muc_pm)
    
    158
    +        message_id = message.getID()
    
    159
    +
    
    160
    +        # Check for duplicates
    
    161
    +        namespace = self.archiving_namespace
    
    162
    +        if groupchat:
    
    163
    +            namespace = muc_caps_cache.get_mam_namespace(
    
    164
    +                archive_jid.getStripped())
    
    165
    +
    
    166
    +        if namespace == nbxmpp.NS_MAM_2:
    
    167
    +            # Search only with stanza-id for duplicates on mam:2
    
    168
    +            if app.logger.find_stanza_id(self._account,
    
    169
    +                                         archive_jid.getStripped(),
    
    170
    +                                         stanza_id,
    
    171
    +                                         origin_id,
    
    172
    +                                         groupchat=groupchat):
    
    173
    +                log.info('Found duplicate with stanza-id')
    
    174
    +                raise nbxmpp.NodeProcessed
    
    175
    +
    
    176
    +        msgtxt = message.getTagData('body')
    
    177
    +
    
    178
    +        event_attrs.update(
    
    179
    +            {'conn': self._con,
    
    180
    +             'additional_data': {},
    
    181
    +             'encrypted': False,
    
    182
    +             'timestamp': timestamp,
    
    183
    +             'user_timestamp': user_timestamp,
    
    184
    +             'self_message': self_message,
    
    185
    +             'groupchat': groupchat,
    
    186
    +             'muc_pm': muc_pm,
    
    187
    +             'stanza_id': stanza_id,
    
    188
    +             'origin_id': origin_id,
    
    189
    +             'message_id': message_id,
    
    190
    +             'correct_id': None,
    
    191
    +             'archive_jid': archive_jid,
    
    192
    +             'msgtxt': msgtxt,
    
    193
    +             'message': message,
    
    194
    +             'namespace': namespace,
    
    195
    +             })
    
    196
    +
    
    197
    +        if groupchat:
    
    198
    +            event = MamGcMessageReceivedEvent(None, **event_attrs)
    
    199
    +        else:
    
    200
    +            event = MamMessageReceivedEvent(None, **event_attrs)
    
    201
    +
    
    202
    +        app.plugin_manager.extension_point(
    
    203
    +            'decrypt', self._con, event, self._decryption_finished)
    
    204
    +
    
    205
    +        if not event.encrypted:
    
    206
    +            eme = parse_eme(event.message)
    
    207
    +            if eme is not None:
    
    208
    +                event.msgtxt = eme
    
    209
    +            self._decryption_finished(event)
    
    210
    +
    
    211
    +        raise nbxmpp.NodeProcessed
    
    212
    +
    
    213
    +    def _parse_gc_attrs(self, message):
    
    214
    +        with_ = message.getFrom()
    
    215
    +        nick = message.getFrom().getResource()
    
    216
    +
    
    217
    +        # Get the real jid if we have it
    
    218
    +        real_jid = None
    
    219
    +        muc_user = message.getTag('x', namespace=nbxmpp.NS_MUC_USER)
    
    220
    +        if muc_user is not None:
    
    221
    +            real_jid = muc_user.getTagAttr('item', 'jid')
    
    222
    +            if real_jid is not None:
    
    223
    +                real_jid = nbxmpp.JID(real_jid)
    
    224
    +
    
    225
    +        return {'with_': with_,
    
    226
    +                'nick': nick,
    
    227
    +                'real_jid': real_jid,
    
    228
    +                'kind': KindConstant.GC_MSG}
    
    229
    +
    
    230
    +    def _parse_chat_attrs(self, message):
    
    231
    +        frm = message.getFrom()
    
    232
    +        to = message.getTo()
    
    233
    +        if frm.bareMatch(self._con.get_own_jid()):
    
    234
    +            with_ = to
    
    235
    +            kind = KindConstant.CHAT_MSG_SENT
    
    236
    +        else:
    
    237
    +            with_ = frm
    
    238
    +            kind = KindConstant.CHAT_MSG_RECV
    
    239
    +
    
    240
    +        return {'with_': with_,
    
    241
    +                'nick': None,
    
    242
    +                'kind': kind}
    
    243
    +
    
    244
    +    def _decryption_finished(self, event):
    
    245
    +        if not event.msgtxt:
    
    246
    +            # For example Chatstates, Receipts, Chatmarkers
    
    247
    +            log.debug(event.message.getProperties())
    
    248
    +            return
    
    249
    +        log.debug(event.msgtxt)
    
    250
    +
    
    251
    +        event.correct_id = parse_correction(event.message)
    
    252
    +        parse_oob(event.message, event.additional_data)
    
    253
    +
    
    254
    +        with_ = event.with_.getStripped()
    
    255
    +        if event.muc_pm:
    
    256
    +            # we store the message with the full JID
    
    257
    +            with_ = str(event.with_)
    
    258
    +
    
    259
    +        stanza_id = event.stanza_id
    
    260
    +        if event.self_message:
    
    261
    +            # Self messages can only be deduped with origin-id
    
    262
    +            if event.origin_id is None:
    
    263
    +                log.warning('Self message without origin-id found')
    
    264
    +                return
    
    265
    +            stanza_id = event.origin_id
    
    266
    +
    
    267
    +        if event.namespace == nbxmpp.NS_MAM_1:
    
    268
    +            if app.logger.search_for_duplicate(
    
    269
    +                    self._account, with_, event.timestamp, event.msgtxt):
    
    270
    +                log.info('Found duplicate with fallback for mam:1')
    
    271
    +                return
    
    272
    +
    
    273
    +        app.logger.insert_into_logs(self._account,
    
    274
    +                                    with_,
    
    275
    +                                    event.timestamp,
    
    276
    +                                    event.kind,
    
    277
    +                                    unread=False,
    
    278
    +                                    message=event.msgtxt,
    
    279
    +                                    contact_name=event.nick,
    
    280
    +                                    additional_data=event.additional_data,
    
    281
    +                                    stanza_id=stanza_id)
    
    282
    +
    
    283
    +        app.nec.push_incoming_event(
    
    284
    +            MamDecryptedMessageReceived(None, **vars(event)))
    
    285
    +
    
    286
    +    def _is_valid_request(self, jid, query_id):
    
    287
    +        if query_id is None:
    
    288
    +            return False
    
    289
    +
    
    290
    +        valid_id = self._mam_query_ids.get(jid.getStripped(), None)
    
    291
    +        return valid_id == query_id
    
    292
    +
    
    293
    +    def _get_query_id(self, jid):
    
    294
    +        query_id = self._con.connection.getAnID()
    
    295
    +        self._mam_query_ids[jid] = query_id
    
    296
    +        return query_id
    
    297
    +
    
    298
    +    @staticmethod
    
    299
    +    def _parse_iq(stanza):
    
    300
    +        if not nbxmpp.isResultNode(stanza):
    
    301
    +            log.error('Error on MAM query: %s', stanza.getError())
    
    302
    +            raise InvalidMamIQ
    
    303
    +
    
    304
    +        fin = stanza.getTag('fin')
    
    305
    +        if fin is None:
    
    306
    +            log.error('Malformed MAM query result received: %s', stanza)
    
    307
    +            raise InvalidMamIQ
    
    308
    +
    
    309
    +        set_ = fin.getTag('set', namespace=nbxmpp.NS_RSM)
    
    310
    +        if set_ is None:
    
    311
    +            log.error(
    
    312
    +                'Malformed MAM query result received (no "set" Node): %s',
    
    313
    +                stanza)
    
    314
    +            raise InvalidMamIQ
    
    315
    +        return fin, set_
    
    316
    +
    
    317
    +    def _get_from_jid(self, stanza):
    
    318
    +        jid = stanza.getFrom()
    
    319
    +        if jid is None:
    
    320
    +            # No from means, iq from our own archive
    
    321
    +            jid = self._con.get_own_jid().getStripped()
    
    322
    +        else:
    
    323
    +            jid = jid.getStripped()
    
    324
    +        return jid
    
    325
    +
    
    326
    +    def request_archive_count(self, start_date, end_date):
    
    327
    +        jid = self._con.get_own_jid().getStripped()
    
    328
    +        log.info('Request archive count from: %s', jid)
    
    329
    +        query_id = self._get_query_id(jid)
    
    330
    +        query = self._get_archive_query(
    
    331
    +            query_id, start=start_date, end=end_date, max_=0)
    
    332
    +        self._con.connection.SendAndCallForResponse(
    
    333
    +            query, self._received_count, {'query_id': query_id})
    
    334
    +        return query_id
    
    335
    +
    
    336
    +    def _received_count(self, conn, stanza, query_id):
    
    337
    +        try:
    
    338
    +            _, set_ = self._parse_iq(stanza)
    
    339
    +        except InvalidMamIQ:
    
    340
    +            return
    
    341
    +
    
    342
    +        jid = self._get_from_jid(stanza)
    
    343
    +        self._mam_query_ids.pop(jid)
    
    344
    +
    
    345
    +        count = set_.getTagData('count')
    
    346
    +        log.info('Received archive count: %s', count)
    
    347
    +        app.nec.push_incoming_event(ArchivingCountReceived(
    
    348
    +            None, query_id=query_id, count=count))
    
    349
    +
    
    350
    +    def request_archive_on_signin(self):
    
    351
    +        own_jid = self._con.get_own_jid().getStripped()
    
    352
    +
    
    353
    +        if own_jid in self._mam_query_ids:
    
    354
    +            log.warning('MAM request for %s already running', own_jid)
    
    355
    +            return
    
    356
    +
    
    357
    +        archive = app.logger.get_archive_timestamp(own_jid)
    
    358
    +
    
    359
    +        # Migration of last_mam_id from config to DB
    
    360
    +        if archive is not None:
    
    361
    +            mam_id = archive.last_mam_id
    
    362
    +        else:
    
    363
    +            mam_id = app.config.get_per(
    
    364
    +                'accounts', self._account, 'last_mam_id')
    
    365
    +            if mam_id:
    
    366
    +                app.config.del_per('accounts', self._account, 'last_mam_id')
    
    367
    +
    
    368
    +        start_date = None
    
    369
    +        query_id = self._get_query_id(own_jid)
    
    370
    +        if mam_id:
    
    371
    +            log.info('MAM query after: %s', mam_id)
    
    372
    +            query = self._get_archive_query(query_id, after=mam_id)
    
    373
    +        else:
    
    374
    +            # First Start, we request the last week
    
    375
    +            start_date = datetime.utcnow() - timedelta(days=7)
    
    376
    +            log.info('First start: query archive start: %s', start_date)
    
    377
    +            query = self._get_archive_query(query_id, start=start_date)
    
    378
    +        self._send_archive_query(query, query_id, start_date)
    
    379
    +
    
    380
    +    def request_archive_on_muc_join(self, jid):
    
    381
    +        archive = app.logger.get_archive_timestamp(
    
    382
    +            jid, type_=JIDConstant.ROOM_TYPE)
    
    383
    +        query_id = self._get_query_id(jid)
    
    384
    +        start_date = None
    
    385
    +        if archive is not None:
    
    386
    +            log.info('Request from archive %s after %s:',
    
    387
    +                     jid, archive.last_mam_id)
    
    388
    +            query = self._get_archive_query(
    
    389
    +                query_id, jid=jid, after=archive.last_mam_id)
    
    390
    +        else:
    
    391
    +            # First Start, we dont request history
    
    392
    +            # Depending on what a MUC saves, there could be thousands
    
    393
    +            # of Messages even in just one day.
    
    394
    +            start_date = datetime.utcnow() - timedelta(days=1)
    
    395
    +            log.info('First join: query archive %s from: %s', jid, start_date)
    
    396
    +            query = self._get_archive_query(query_id, jid=jid, start=start_date)
    
    397
    +        self._send_archive_query(query, query_id, start_date, groupchat=True)
    
    398
    +
    
    399
    +    def _send_archive_query(self, query, query_id, start_date=None,
    
    400
    +                            groupchat=False):
    
    401
    +        self._con.connection.SendAndCallForResponse(
    
    402
    +            query, self._result_finished, {'query_id': query_id,
    
    403
    +                                           'start_date': start_date,
    
    404
    +                                           'groupchat': groupchat})
    
    405
    +
    
    406
    +    def _result_finished(self, conn, stanza, query_id, start_date, groupchat):
    
    407
    +        try:
    
    408
    +            fin, set_ = self._parse_iq(stanza)
    
    409
    +        except InvalidMamIQ:
    
    410
    +            return
    
    411
    +
    
    412
    +        jid = self._get_from_jid(stanza)
    
    413
    +
    
    414
    +        last = set_.getTagData('last')
    
    415
    +        if last is None:
    
    416
    +            log.info('End of MAM query, no items retrieved')
    
    417
    +            self._mam_query_ids.pop(jid)
    
    418
    +            return
    
    419
    +
    
    420
    +        complete = fin.getAttr('complete')
    
    421
    +        app.logger.set_archive_timestamp(jid, last_mam_id=last)
    
    422
    +        if complete != 'true':
    
    423
    +            self._mam_query_ids.pop(jid)
    
    424
    +            query_id = self._get_query_id(jid)
    
    425
    +            query = self._get_archive_query(query_id, jid=jid, after=last)
    
    426
    +            self._send_archive_query(query, query_id, groupchat=groupchat)
    
    427
    +        else:
    
    428
    +            self._mam_query_ids.pop(jid)
    
    429
    +            if start_date is not None:
    
    430
    +                app.logger.set_archive_timestamp(
    
    431
    +                    jid,
    
    432
    +                    last_mam_id=last,
    
    433
    +                    oldest_mam_timestamp=start_date.timestamp())
    
    434
    +            log.info('End of MAM query, last mam id: %s', last)
    
    435
    +
    
    436
    +    def request_archive_interval(self, start_date, end_date, after=None,
    
    437
    +                                 query_id=None):
    
    438
    +        jid = self._con.get_own_jid().getStripped()
    
    439
    +        if after is None:
    
    440
    +            log.info('Request intervall from %s to %s from %s',
    
    441
    +                     start_date, end_date, jid)
    
    442
    +        else:
    
    443
    +            log.info('Query page after %s from %s',
    
    444
    +                     after, jid)
    
    445
    +        if query_id is None:
    
    446
    +            query_id = self._get_query_id(jid)
    
    447
    +        self._mam_query_ids[jid] = query_id
    
    448
    +        query = self._get_archive_query(query_id, start=start_date,
    
    449
    +                                        end=end_date, after=after, max_=30)
    
    450
    +
    
    451
    +        self._con.connection.SendAndCallForResponse(
    
    452
    +            query, self._intervall_result, {'query_id': query_id,
    
    453
    +                                            'start_date': start_date,
    
    454
    +                                            'end_date': end_date})
    
    455
    +        return query_id
    
    456
    +
    
    457
    +    def _intervall_result(self, conn, stanza, query_id,
    
    458
    +                          start_date, end_date):
    
    459
    +        try:
    
    460
    +            fin, set_ = self._parse_iq(stanza)
    
    461
    +        except InvalidMamIQ:
    
    462
    +            return
    
    463
    +
    
    464
    +        jid = self._get_from_jid(stanza)
    
    465
    +        self._mam_query_ids.pop(jid)
    
    466
    +        if start_date:
    
    467
    +            timestamp = start_date.timestamp()
    
    468
    +        else:
    
    469
    +            timestamp = ArchiveState.ALL
    
    470
    +
    
    471
    +        last = set_.getTagData('last')
    
    472
    +        if last is None:
    
    473
    +            app.nec.push_incoming_event(ArchivingIntervalFinished(
    
    474
    +                None, query_id=query_id))
    
    475
    +            app.logger.set_archive_timestamp(
    
    476
    +                jid, oldest_mam_timestamp=timestamp)
    
    477
    +            log.info('End of MAM request, no items retrieved')
    
    478
    +            return
    
    479
    +
    
    480
    +        complete = fin.getAttr('complete')
    
    481
    +        if complete != 'true':
    
    482
    +            self.request_archive_interval(start_date, end_date, last, query_id)
    
    483
    +        else:
    
    484
    +            log.info('Request finished')
    
    485
    +            app.logger.set_archive_timestamp(
    
    486
    +                jid, oldest_mam_timestamp=timestamp)
    
    487
    +            app.nec.push_incoming_event(ArchivingIntervalFinished(
    
    488
    +                None, query_id=query_id))
    
    489
    +
    
    490
    +    def _get_archive_query(self, query_id, jid=None, start=None, end=None,
    
    491
    +                           with_=None, after=None, max_=30):
    
    492
    +        # Muc archive query?
    
    493
    +        namespace = muc_caps_cache.get_mam_namespace(jid)
    
    494
    +        if namespace is None:
    
    495
    +            # Query to our own archive
    
    496
    +            namespace = self.archiving_namespace
    
    497
    +
    
    498
    +        iq = nbxmpp.Iq('set', to=jid)
    
    499
    +        query = iq.addChild('query', namespace=namespace)
    
    500
    +        form = query.addChild(node=nbxmpp.DataForm(typ='submit'))
    
    501
    +        field = nbxmpp.DataField(typ='hidden',
    
    502
    +                                 name='FORM_TYPE',
    
    503
    +                                 value=namespace)
    
    504
    +        form.addChild(node=field)
    
    505
    +        if start:
    
    506
    +            field = nbxmpp.DataField(typ='text-single',
    
    507
    +                                     name='start',
    
    508
    +                                     value=start.strftime('%Y-%m-%dT%H:%M:%SZ'))
    
    509
    +            form.addChild(node=field)
    
    510
    +        if end:
    
    511
    +            field = nbxmpp.DataField(typ='text-single',
    
    512
    +                                     name='end',
    
    513
    +                                     value=end.strftime('%Y-%m-%dT%H:%M:%SZ'))
    
    514
    +            form.addChild(node=field)
    
    515
    +        if with_:
    
    516
    +            field = nbxmpp.DataField(typ='jid-single', name='with', value=with_)
    
    517
    +            form.addChild(node=field)
    
    518
    +
    
    519
    +        set_ = query.setTag('set', namespace=nbxmpp.NS_RSM)
    
    520
    +        set_.setTagData('max', max_)
    
    521
    +        if after:
    
    522
    +            set_.setTagData('after', after)
    
    523
    +        query.setAttr('queryid', query_id)
    
    524
    +        return iq
    
    525
    +
    
    526
    +    def request_mam_preferences(self):
    
    527
    +        log.info('Request MAM preferences')
    
    528
    +        iq = nbxmpp.Iq('get', self.archiving_namespace)
    
    529
    +        iq.setQuery('prefs')
    
    530
    +        self._con.connection.SendAndCallForResponse(
    
    531
    +            iq, self._preferences_received)
    
    532
    +
    
    533
    +    def _preferences_received(self, stanza):
    
    534
    +        if not nbxmpp.isResultNode(stanza):
    
    535
    +            log.info('Error: %s', stanza.getError())
    
    536
    +            app.nec.push_incoming_event(MAMPreferenceError(
    
    537
    +                None, conn=self._con, error=stanza.getError()))
    
    538
    +            return
    
    539
    +
    
    540
    +        log.info('Received MAM preferences')
    
    541
    +        prefs = stanza.getTag('prefs', namespace=self.archiving_namespace)
    
    542
    +        if prefs is None:
    
    543
    +            log.error('Malformed stanza (no prefs node): %s', stanza)
    
    544
    +            return
    
    545
    +
    
    546
    +        rules = []
    
    547
    +        default = prefs.getAttr('default')
    
    548
    +        for item in prefs.getTag('always').getTags('jid'):
    
    549
    +            rules.append((item.getData(), 'Always'))
    
    550
    +
    
    551
    +        for item in prefs.getTag('never').getTags('jid'):
    
    552
    +            rules.append((item.getData(), 'Never'))
    
    553
    +
    
    554
    +        app.nec.push_incoming_event(MAMPreferenceReceived(
    
    555
    +            None, conn=self._con, rules=rules, default=default))
    
    556
    +
    
    557
    +    def set_mam_preferences(self, rules, default):
    
    558
    +        iq = nbxmpp.Iq(typ='set')
    
    559
    +        prefs = iq.addChild(name='prefs',
    
    560
    +                            namespace=self.archiving_namespace,
    
    561
    +                            attrs={'default': default})
    
    562
    +        always = prefs.addChild(name='always')
    
    563
    +        never = prefs.addChild(name='never')
    
    564
    +        for item in rules:
    
    565
    +            jid, archive = item
    
    566
    +            if archive:
    
    567
    +                always.addChild(name='jid').setData(jid)
    
    568
    +            else:
    
    569
    +                never.addChild(name='jid').setData(jid)
    
    570
    +
    
    571
    +        self._con.connection.SendAndCallForResponse(
    
    572
    +            iq, self._preferences_saved)
    
    573
    +
    
    574
    +    def _preferences_saved(self, stanza):
    
    575
    +        if not nbxmpp.isResultNode(stanza):
    
    576
    +            log.info('Error: %s', stanza.getError())
    
    577
    +            app.nec.push_incoming_event(MAMPreferenceError(
    
    578
    +                None, conn=self._con, error=stanza.getError()))
    
    579
    +        else:
    
    580
    +            log.info('Preferences saved')
    
    581
    +            app.nec.push_incoming_event(
    
    582
    +                MAMPreferenceSaved(None, conn=self._con))
    
    583
    +
    
    584
    +
    
    585
    +class MamMessageReceivedEvent(NetworkIncomingEvent):
    
    586
    +    name = 'mam-message-received'
    
    587
    +
    
    588
    +
    
    589
    +class MamGcMessageReceivedEvent(NetworkIncomingEvent):
    
    590
    +    name = 'mam-message-received'
    
    591
    +
    
    592
    +
    
    593
    +class MamDecryptedMessageReceived(NetworkIncomingEvent):
    
    594
    +    name = 'mam-decrypted-message-received'
    
    595
    +
    
    596
    +
    
    597
    +class MAMPreferenceError(NetworkIncomingEvent):
    
    598
    +    name = 'mam-prefs-error'
    
    599
    +
    
    600
    +
    
    601
    +class MAMPreferenceReceived(NetworkIncomingEvent):
    
    602
    +    name = 'mam-prefs-received'
    
    603
    +
    
    604
    +
    
    605
    +class MAMPreferenceSaved(NetworkIncomingEvent):
    
    606
    +    name = 'mam-prefs-saved'
    
    607
    +
    
    608
    +
    
    609
    +class ArchivingCountReceived(NetworkIncomingEvent):
    
    610
    +    name = 'archiving-count-received'
    
    611
    +
    
    612
    +
    
    613
    +class ArchivingIntervalFinished(NetworkIncomingEvent):
    
    614
    +    name = 'archiving-interval-finished'
    
    615
    +
    
    616
    +
    
    617
    +class ArchivingErrorReceived(NetworkIncomingEvent):
    
    618
    +    name = 'archiving-error-received'
    
    619
    +
    
    620
    +
    
    621
    +class InvalidMamIQ(Exception):
    
    622
    +    pass
    
    623
    +
    
    624
    +
    
    625
    +def get_instance(*args, **kwargs):
    
    626
    +    return MAM(*args, **kwargs), 'MAM'

  • gajim/common/modules/misc.py
    1
    +# This file is part of Gajim.
    
    2
    +#
    
    3
    +# Gajim is free software; you can redistribute it and/or modify
    
    4
    +# it under the terms of the GNU General Public License as published
    
    5
    +# by the Free Software Foundation; version 3 only.
    
    6
    +#
    
    7
    +# Gajim is distributed in the hope that it will be useful,
    
    8
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    9
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    10
    +# GNU General Public License for more details.
    
    11
    +#
    
    12
    +# You should have received a copy of the GNU General Public License
    
    13
    +# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    14
    +
    
    15
    +# All XEPs that dont need their own module
    
    16
    +
    
    17
    +import logging
    
    18
    +
    
    19
    +import nbxmpp
    
    20
    +
    
    21
    +from gajim.common.modules.date_and_time import parse_datetime
    
    22
    +
    
    23
    +log = logging.getLogger('gajim.c.m.misc')
    
    24
    +
    
    25
    +
    
    26
    +# XEP-0380: Explicit Message Encryption
    
    27
    +
    
    28
    +_eme_namespaces = {
    
    29
    +    'urn:xmpp:otr:0':
    
    30
    +        _('This message was encrypted with OTR '
    
    31
    +          'and could not be decrypted.'),
    
    32
    +    'jabber:x:encrypted':
    
    33
    +        _('This message was encrypted with Legacy '
    
    34
    +          'OpenPGP and could not be decrypted. You can install '
    
    35
    +          'the PGP plugin to handle those messages.'),
    
    36
    +    'urn:xmpp:openpgp:0':
    
    37
    +        _('This message was encrypted with '
    
    38
    +          'OpenPGP for XMPP and could not be decrypted.'),
    
    39
    +    'fallback':
    
    40
    +        _('This message was encrypted with %s '
    
    41
    +          'and could not be decrypted.')
    
    42
    +}
    
    43
    +
    
    44
    +
    
    45
    +def parse_eme(stanza):
    
    46
    +    enc_tag = stanza.getTag('encryption', namespace=nbxmpp.NS_EME)
    
    47
    +    if enc_tag is None:
    
    48
    +        return
    
    49
    +
    
    50
    +    ns = enc_tag.getAttr('namespace')
    
    51
    +    if ns is None:
    
    52
    +        log.warning('No namespace on EME message')
    
    53
    +        return
    
    54
    +
    
    55
    +    if ns in _eme_namespaces:
    
    56
    +        log.info('Found not decrypted message: %s', ns)
    
    57
    +        return _eme_namespaces.get(ns)
    
    58
    +
    
    59
    +    enc_name = enc_tag.getAttr('name')
    
    60
    +    log.info('Found not decrypted message: %s', enc_name or ns)
    
    61
    +    return _eme_namespaces.get('fallback') % enc_name or ns
    
    62
    +
    
    63
    +
    
    64
    +# XEP-0203: Delayed Delivery
    
    65
    +
    
    66
    +def parse_delay(stanza, epoch=True, convert='utc'):
    
    67
    +    timestamp = None
    
    68
    +    delay = stanza.getTagAttr(
    
    69
    +        'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
    
    70
    +    if delay is not None:
    
    71
    +        timestamp = parse_datetime(delay, check_utc=True,
    
    72
    +                                   epoch=epoch, convert=convert)
    
    73
    +        if timestamp is None:
    
    74
    +            log.warning('Invalid timestamp received: %s', delay)
    
    75
    +            log.warning(stanza)
    
    76
    +
    
    77
    +    return timestamp
    
    78
    +
    
    79
    +
    
    80
    +# XEP-0066: Out of Band Data
    
    81
    +
    
    82
    +def parse_oob(stanza, dict_=None, key='Gajim'):
    
    83
    +    oob_node = stanza.getTag('x', namespace=nbxmpp.NS_X_OOB)
    
    84
    +    if oob_node is None:
    
    85
    +        return
    
    86
    +    result = {}
    
    87
    +    url = oob_node.getTagData('url')
    
    88
    +    if url is not None:
    
    89
    +        result['oob_url'] = url
    
    90
    +    desc = oob_node.getTagData('desc')
    
    91
    +    if desc is not None:
    
    92
    +        result['oob_desc'] = desc
    
    93
    +
    
    94
    +    if dict_ is None:
    
    95
    +        return result
    
    96
    +
    
    97
    +    if key in dict_:
    
    98
    +        dict_[key] += result
    
    99
    +    else:
    
    100
    +        dict_[key] = result
    
    101
    +
    
    102
    +    return dict_
    
    103
    +
    
    104
    +
    
    105
    +# XEP-0308: Last Message Correction
    
    106
    +
    
    107
    +def parse_correction(stanza):
    
    108
    +    replace = stanza.getTag('replace', namespace=nbxmpp.NS_CORRECT)
    
    109
    +    if replace is not None:
    
    110
    +        id_ = replace.getAttr('id')
    
    111
    +        if id_ is not None:
    
    112
    +            return id_
    
    113
    +        log.warning('No id attr found: %s' % stanza)

  • gajim/data/gui/archiving_313_preferences_item.ui deleted
    1
    -<?xml version="1.0" encoding="UTF-8"?>
    
    2
    -<!-- Generated with glade 3.18.3 -->
    
    3
    -<interface>
    
    4
    -  <requires lib="gtk+" version="3.12"/>
    
    5
    -  <object class="GtkListStore" id="dialog_pref_liststore">
    
    6
    -    <columns>
    
    7
    -      <!-- column-name gchararray1 -->
    
    8
    -      <column type="gchararray"/>
    
    9
    -    </columns>
    
    10
    -    <data>
    
    11
    -      <row>
    
    12
    -        <col id="0" translatable="yes">Always</col>
    
    13
    -      </row>
    
    14
    -      <row>
    
    15
    -        <col id="0" translatable="yes">Never</col>
    
    16
    -      </row>
    
    17
    -    </data>
    
    18
    -  </object>
    
    19
    -  <object class="GtkDialog" id="item_dialog">
    
    20
    -    <property name="can_focus">False</property>
    
    21
    -    <property name="border_width">12</property>
    
    22
    -    <property name="resizable">False</property>
    
    23
    -    <property name="destroy_with_parent">True</property>
    
    24
    -    <property name="type_hint">dialog</property>
    
    25
    -    <signal name="destroy" handler="on_item_archiving_preferences_window_destroy" swapped="no"/>
    
    26
    -    <child internal-child="vbox">
    
    27
    -      <object class="GtkBox" id="dialog-vbox">
    
    28
    -        <property name="can_focus">False</property>
    
    29
    -        <property name="orientation">vertical</property>
    
    30
    -        <property name="spacing">20</property>
    
    31
    -        <child internal-child="action_area">
    
    32
    -          <object class="GtkButtonBox" id="dialog-action_area">
    
    33
    -            <property name="can_focus">False</property>
    
    34
    -            <property name="layout_style">end</property>
    
    35
    -            <child>
    
    36
    -              <object class="GtkButton" id="cancel_button">
    
    37
    -                <property name="label">gtk-close</property>
    
    38
    -                <property name="visible">True</property>
    
    39
    -                <property name="can_focus">True</property>
    
    40
    -                <property name="receives_default">True</property>
    
    41
    -                <property name="use_stock">True</property>
    
    42
    -                <property name="always_show_image">True</property>
    
    43
    -                <signal name="clicked" handler="on_cancel_button_clicked" swapped="no"/>
    
    44
    -              </object>
    
    45
    -              <packing>
    
    46
    -                <property name="expand">True</property>
    
    47
    -                <property name="fill">True</property>
    
    48
    -                <property name="position">0</property>
    
    49
    -              </packing>
    
    50
    -            </child>
    
    51
    -            <child>
    
    52
    -              <object class="GtkButton" id="ok_button">
    
    53
    -                <property name="label">gtk-ok</property>
    
    54
    -                <property name="visible">True</property>
    
    55
    -                <property name="can_focus">True</property>
    
    56
    -                <property name="receives_default">True</property>
    
    57
    -                <property name="use_stock">True</property>
    
    58
    -                <property name="always_show_image">True</property>
    
    59
    -                <signal name="clicked" handler="on_ok_button_clicked" swapped="no"/>
    
    60
    -              </object>
    
    61
    -              <packing>
    
    62
    -                <property name="expand">True</property>
    
    63
    -                <property name="fill">True</property>
    
    64
    -                <property name="position">1</property>
    
    65
    -              </packing>
    
    66
    -            </child>
    
    67
    -          </object>
    
    68
    -          <packing>
    
    69
    -            <property name="expand">False</property>
    
    70
    -            <property name="fill">False</property>
    
    71
    -            <property name="position">0</property>
    
    72
    -          </packing>
    
    73
    -        </child>
    
    74
    -        <child>
    
    75
    -          <object class="GtkGrid" id="dialog_grid">
    
    76
    -            <property name="visible">True</property>
    
    77
    -            <property name="can_focus">False</property>
    
    78
    -            <property name="row_spacing">5</property>
    
    79
    -            <property name="column_spacing">5</property>
    
    80
    -            <child>
    
    81
    -              <object class="GtkLabel" id="jid_label">
    
    82
    -                <property name="visible">True</property>
    
    83
    -                <property name="can_focus">False</property>
    
    84
    -                <property name="label" translatable="yes">Jabber ID:</property>
    
    85
    -                <property name="xalign">0</property>
    
    86
    -              </object>
    
    87
    -              <packing>
    
    88
    -                <property name="left_attach">0</property>
    
    89
    -                <property name="top_attach">0</property>
    
    90
    -              </packing>
    
    91
    -            </child>
    
    92
    -            <child>
    
    93
    -              <object class="GtkLabel" id="pref_label">
    
    94
    -                <property name="visible">True</property>
    
    95
    -                <property name="can_focus">False</property>
    
    96
    -                <property name="label" translatable="yes">Preference:</property>
    
    97
    -                <property name="xalign">0</property>
    
    98
    -              </object>
    
    99
    -              <packing>
    
    100
    -                <property name="left_attach">0</property>
    
    101
    -                <property name="top_attach">1</property>
    
    102
    -              </packing>
    
    103
    -            </child>
    
    104
    -            <child>
    
    105
    -              <object class="GtkEntry" id="jid_entry">
    
    106
    -                <property name="width_request">194</property>
    
    107
    -                <property name="visible">True</property>
    
    108
    -                <property name="can_focus">True</property>
    
    109
    -              </object>
    
    110
    -              <packing>
    
    111
    -                <property name="left_attach">1</property>
    
    112
    -                <property name="top_attach">0</property>
    
    113
    -              </packing>
    
    114
    -            </child>
    
    115
    -            <child>
    
    116
    -              <object class="GtkComboBox" id="pref_cb">
    
    117
    -                <property name="visible">True</property>
    
    118
    -                <property name="can_focus">False</property>
    
    119
    -                <property name="halign">start</property>
    
    120
    -                <property name="model">dialog_pref_liststore</property>
    
    121
    -                <child>
    
    122
    -                  <object class="GtkCellRendererText" id="cellrenderertext2"/>
    
    123
    -                  <attributes>
    
    124
    -                    <attribute name="text">0</attribute>
    
    125
    -                  </attributes>
    
    126
    -                </child>
    
    127
    -              </object>
    
    128
    -              <packing>
    
    129
    -                <property name="left_attach">1</property>
    
    130
    -                <property name="top_attach">1</property>
    
    131
    -              </packing>
    
    132
    -            </child>
    
    133
    -          </object>
    
    134
    -          <packing>
    
    135
    -            <property name="expand">False</property>
    
    136
    -            <property name="fill">True</property>
    
    137
    -            <property name="position">1</property>
    
    138
    -          </packing>
    
    139
    -        </child>
    
    140
    -      </object>
    
    141
    -    </child>
    
    142
    -  </object>
    
    143
    -</interface>

  • gajim/data/gui/archiving_313_preferences_window.ui → gajim/data/gui/mam_preferences.ui
    1 1
     <?xml version="1.0" encoding="UTF-8"?>
    
    2
    -<!-- Generated with glade 3.20.0 -->
    
    2
    +<!-- Generated with glade 3.22.1 -->
    
    3 3
     <interface>
    
    4 4
       <requires lib="gtk+" version="3.12"/>
    
    5
    -  <object class="GtkImage" id="add_image">
    
    6
    -    <property name="visible">True</property>
    
    7
    -    <property name="can_focus">False</property>
    
    8
    -    <property name="stock">gtk-add</property>
    
    9
    -  </object>
    
    10
    -  <object class="GtkListStore" id="archive_items_liststore">
    
    5
    +  <object class="GtkListStore" id="default_store">
    
    11 6
         <columns>
    
    12
    -      <!-- column-name jid -->
    
    7
    +      <!-- column-name text -->
    
    13 8
           <column type="gchararray"/>
    
    14
    -      <!-- column-name archive_pref -->
    
    15
    -      <column type="gchararray"/>
    
    16
    -    </columns>
    
    17
    -  </object>
    
    18
    -  <object class="GtkListStore" id="default_pref_liststore">
    
    19
    -    <columns>
    
    20
    -      <!-- column-name gchararray1 -->
    
    9
    +      <!-- column-name value -->
    
    21 10
           <column type="gchararray"/>
    
    22 11
         </columns>
    
    23 12
         <data>
    
    24 13
           <row>
    
    25 14
             <col id="0" translatable="yes">Always</col>
    
    15
    +        <col id="1">always</col>
    
    26 16
           </row>
    
    27 17
           <row>
    
    28 18
             <col id="0" translatable="yes">Roster</col>
    
    19
    +        <col id="1">roster</col>
    
    29 20
           </row>
    
    30 21
           <row>
    
    31 22
             <col id="0" translatable="yes">Never</col>
    
    23
    +        <col id="1">never</col>
    
    32 24
           </row>
    
    33 25
         </data>
    
    34 26
       </object>
    
    35
    -  <object class="GtkImage" id="remove_image">
    
    36
    -    <property name="visible">True</property>
    
    37
    -    <property name="can_focus">False</property>
    
    38
    -    <property name="stock">gtk-remove</property>
    
    27
    +  <object class="GtkListStore" id="preferences_store">
    
    28
    +    <columns>
    
    29
    +      <!-- column-name jid -->
    
    30
    +      <column type="gchararray"/>
    
    31
    +      <!-- column-name gboolean1 -->
    
    32
    +      <column type="gboolean"/>
    
    33
    +    </columns>
    
    39 34
       </object>
    
    40
    -  <object class="GtkWindow" id="archiving_313_pref">
    
    35
    +  <object class="GtkGrid" id="preferences_grid">
    
    36
    +    <property name="width_request">400</property>
    
    37
    +    <property name="height_request">300</property>
    
    38
    +    <property name="visible">True</property>
    
    41 39
         <property name="can_focus">False</property>
    
    42
    -    <property name="border_width">12</property>
    
    43
    -    <property name="window_position">center</property>
    
    44
    -    <property name="default_width">450</property>
    
    45
    -    <signal name="destroy" handler="on_archiving_preferences_window_destroy" swapped="no"/>
    
    46
    -    <signal name="key-press-event" handler="on_key_press_event" swapped="no"/>
    
    40
    +    <property name="margin_left">18</property>
    
    41
    +    <property name="margin_right">18</property>
    
    42
    +    <property name="margin_top">18</property>
    
    43
    +    <property name="margin_bottom">18</property>
    
    44
    +    <property name="row_spacing">5</property>
    
    45
    +    <property name="column_spacing">10</property>
    
    46
    +    <child>
    
    47
    +      <object class="GtkButtonBox">
    
    48
    +        <property name="visible">True</property>
    
    49
    +        <property name="can_focus">False</property>
    
    50
    +        <property name="halign">start</property>
    
    51
    +        <property name="spacing">5</property>
    
    52
    +        <property name="layout_style">start</property>
    
    53
    +        <child>
    
    54
    +          <object class="GtkButton" id="add_button">
    
    55
    +            <property name="visible">True</property>
    
    56
    +            <property name="can_focus">True</property>
    
    57
    +            <property name="receives_default">True</property>
    
    58
    +            <signal name="clicked" handler="_on_add" swapped="no"/>
    
    59
    +            <child>
    
    60
    +              <object class="GtkImage">
    
    61
    +                <property name="visible">True</property>
    
    62
    +                <property name="can_focus">False</property>
    
    63
    +                <property name="icon_name">list-add-symbolic</property>
    
    64
    +              </object>
    
    65
    +            </child>
    
    66
    +          </object>
    
    67
    +          <packing>
    
    68
    +            <property name="expand">False</property>
    
    69
    +            <property name="fill">True</property>
    
    70
    +            <property name="position">0</property>
    
    71
    +            <property name="non_homogeneous">True</property>
    
    72
    +          </packing>
    
    73
    +        </child>
    
    74
    +        <child>
    
    75
    +          <object class="GtkButton" id="remove_button">
    
    76
    +            <property name="visible">True</property>
    
    77
    +            <property name="can_focus">True</property>
    
    78
    +            <property name="receives_default">True</property>
    
    79
    +            <signal name="clicked" handler="_on_remove" swapped="no"/>
    
    80
    +            <child>
    
    81
    +              <object class="GtkImage">
    
    82
    +                <property name="visible">True</property>
    
    83
    +                <property name="can_focus">False</property>
    
    84
    +                <property name="icon_name">list-remove-symbolic</property>
    
    85
    +              </object>
    
    86
    +            </child>
    
    87
    +          </object>
    
    88
    +          <packing>
    
    89
    +            <property name="expand">True</property>
    
    90
    +            <property name="fill">True</property>
    
    91
    +            <property name="position">1</property>
    
    92
    +            <property name="non_homogeneous">True</property>
    
    93
    +          </packing>
    
    94
    +        </child>
    
    95
    +      </object>
    
    96
    +      <packing>
    
    97
    +        <property name="left_attach">0</property>
    
    98
    +        <property name="top_attach">2</property>
    
    99
    +      </packing>
    
    100
    +    </child>
    
    47 101
         <child>
    
    48
    -      <object class="GtkGrid" id="pref_grid">
    
    102
    +      <object class="GtkGrid">
    
    49 103
             <property name="visible">True</property>
    
    50 104
             <property name="can_focus">False</property>
    
    51
    -        <property name="row_spacing">5</property>
    
    52
    -        <property name="column_spacing">10</property>
    
    105
    +        <property name="halign">start</property>
    
    106
    +        <property name="column_spacing">5</property>
    
    53 107
             <child>
    
    54
    -          <object class="GtkScrolledWindow" id="scrolledwindow1">
    
    108
    +          <object class="GtkLabel" id="default_label">
    
    109
    +            <property name="visible">True</property>
    
    110
    +            <property name="can_focus">False</property>
    
    111
    +            <property name="label" translatable="yes">Default:</property>
    
    112
    +            <property name="xalign">0</property>
    
    113
    +          </object>
    
    114
    +          <packing>
    
    115
    +            <property name="left_attach">0</property>
    
    116
    +            <property name="top_attach">0</property>
    
    117
    +          </packing>
    
    118
    +        </child>
    
    119
    +        <child>
    
    120
    +          <object class="GtkComboBox" id="default_cb">
    
    121
    +            <property name="visible">True</property>
    
    122
    +            <property name="can_focus">False</property>
    
    123
    +            <property name="halign">start</property>
    
    124
    +            <property name="hexpand">False</property>
    
    125
    +            <property name="model">default_store</property>
    
    126
    +            <property name="active">0</property>
    
    127
    +            <property name="id_column">1</property>
    
    128
    +            <child>
    
    129
    +              <object class="GtkCellRendererText" id="cellrenderertext1"/>
    
    130
    +              <attributes>
    
    131
    +                <attribute name="text">0</attribute>
    
    132
    +              </attributes>
    
    133
    +            </child>
    
    134
    +          </object>
    
    135
    +          <packing>
    
    136
    +            <property name="left_attach">1</property>
    
    137
    +            <property name="top_attach">0</property>
    
    138
    +          </packing>
    
    139
    +        </child>
    
    140
    +      </object>
    
    141
    +      <packing>
    
    142
    +        <property name="left_attach">0</property>
    
    143
    +        <property name="top_attach">0</property>
    
    144
    +      </packing>
    
    145
    +    </child>
    
    146
    +    <child>
    
    147
    +      <object class="GtkButton" id="save_button">
    
    148
    +        <property name="label">Save</property>
    
    149
    +        <property name="visible">True</property>
    
    150
    +        <property name="can_focus">True</property>
    
    151
    +        <property name="receives_default">True</property>
    
    152
    +        <property name="halign">end</property>
    
    153
    +        <property name="always_show_image">True</property>
    
    154
    +        <signal name="clicked" handler="_on_save" swapped="no"/>
    
    155
    +      </object>
    
    156
    +      <packing>
    
    157
    +        <property name="left_attach">1</property>
    
    158
    +        <property name="top_attach">2</property>
    
    159
    +      </packing>
    
    160
    +    </child>
    
    161
    +    <child>
    
    162
    +      <object class="GtkOverlay" id="overlay">
    
    163
    +        <property name="visible">True</property>
    
    164
    +        <property name="can_focus">False</property>
    
    165
    +        <child>
    
    166
    +          <object class="GtkScrolledWindow">
    
    55 167
                 <property name="height_request">150</property>
    
    56 168
                 <property name="visible">True</property>
    
    57 169
                 <property name="can_focus">True</property>
    
    ... ... @@ -59,21 +171,27 @@
    59 171
                 <property name="vexpand">True</property>
    
    60 172
                 <property name="shadow_type">in</property>
    
    61 173
                 <child>
    
    62
    -              <object class="GtkTreeView" id="archive_view">
    
    174
    +              <object class="GtkTreeView" id="pref_view">
    
    63 175
                     <property name="visible">True</property>
    
    64 176
                     <property name="can_focus">True</property>
    
    65
    -                <property name="model">archive_items_liststore</property>
    
    177
    +                <property name="model">preferences_store</property>
    
    178
    +                <property name="search_column">0</property>
    
    66 179
                     <child internal-child="selection">
    
    67 180
                       <object class="GtkTreeSelection" id="treeview-selection2"/>
    
    68 181
                     </child>
    
    69 182
                     <child>
    
    70 183
                       <object class="GtkTreeViewColumn" id="treeviewcolumn1">
    
    71 184
                         <property name="title" translatable="yes">Jabber ID</property>
    
    185
    +                    <property name="expand">True</property>
    
    72 186
                         <property name="clickable">True</property>
    
    73 187
                         <property name="sort_indicator">True</property>
    
    74 188
                         <property name="sort_column_id">0</property>
    
    75 189
                         <child>
    
    76
    -                      <object class="GtkCellRendererText" id="cellrenderertext3"/>
    
    190
    +                      <object class="GtkCellRendererText" id="cellrenderertext3">
    
    191
    +                        <property name="editable">True</property>
    
    192
    +                        <property name="placeholder_text">user@example.org</property>
    
    193
    +                        <signal name="edited" handler="_jid_edited" swapped="no"/>
    
    194
    +                      </object>
    
    77 195
                           <attributes>
    
    78 196
                             <attribute name="text">0</attribute>
    
    79 197
                           </attributes>
    
    ... ... @@ -82,14 +200,17 @@
    82 200
                     </child>
    
    83 201
                     <child>
    
    84 202
                       <object class="GtkTreeViewColumn" id="treeviewcolumn2">
    
    85
    -                    <property name="title" translatable="yes">Preference</property>
    
    203
    +                    <property name="title" translatable="yes">Archive</property>
    
    86 204
                         <property name="clickable">True</property>
    
    205
    +                    <property name="alignment">0.5</property>
    
    87 206
                         <property name="sort_indicator">True</property>
    
    88 207
                         <property name="sort_column_id">1</property>
    
    89 208
                         <child>
    
    90
    -                      <object class="GtkCellRendererText" id="cellrenderertext4"/>
    
    209
    +                      <object class="GtkCellRendererToggle">
    
    210
    +                        <signal name="toggled" handler="_pref_toggled" swapped="no"/>
    
    211
    +                      </object>
    
    91 212
                           <attributes>
    
    92
    -                        <attribute name="text">1</attribute>
    
    213
    +                        <attribute name="active">1</attribute>
    
    93 214
                           </attributes>
    
    94 215
                         </child>
    
    95 216
                       </object>
    
    ... ... @@ -98,117 +219,18 @@
    98 219
                 </child>
    
    99 220
               </object>
    
    100 221
               <packing>
    
    101
    -            <property name="left_attach">0</property>
    
    102
    -            <property name="top_attach">1</property>
    
    103
    -            <property name="width">2</property>
    
    222
    +            <property name="index">-1</property>
    
    104 223
               </packing>
    
    105 224
             </child>
    
    106
    -        <child>
    
    107
    -          <object class="GtkButtonBox" id="buttonbox1">
    
    108
    -            <property name="visible">True</property>
    
    109
    -            <property name="can_focus">False</property>
    
    110
    -            <property name="halign">start</property>
    
    111
    -            <property name="spacing">5</property>
    
    112
    -            <property name="layout_style">start</property>
    
    113
    -            <child>
    
    114
    -              <object class="GtkButton" id="add_button">
    
    115
    -                <property name="visible">True</property>
    
    116
    -                <property name="can_focus">True</property>
    
    117
    -                <property name="receives_default">True</property>
    
    118
    -                <property name="image">add_image</property>
    
    119
    -                <signal name="clicked" handler="on_add_item_button_clicked" swapped="no"/>
    
    120
    -              </object>
    
    121
    -              <packing>
    
    122
    -                <property name="expand">False</property>
    
    123
    -                <property name="fill">True</property>
    
    124
    -                <property name="position">0</property>
    
    125
    -                <property name="non_homogeneous">True</property>
    
    126
    -              </packing>
    
    127
    -            </child>
    
    128
    -            <child>
    
    129
    -              <object class="GtkButton" id="remove_button">
    
    130
    -                <property name="visible">True</property>
    
    131
    -                <property name="can_focus">True</property>
    
    132
    -                <property name="receives_default">True</property>
    
    133
    -                <property name="image">remove_image</property>
    
    134
    -                <signal name="clicked" handler="on_remove_item_button_clicked" swapped="no"/>
    
    135
    -              </object>
    
    136
    -              <packing>
    
    137
    -                <property name="expand">True</property>
    
    138
    -                <property name="fill">True</property>
    
    139
    -                <property name="position">1</property>
    
    140
    -                <property name="non_homogeneous">True</property>
    
    141
    -              </packing>
    
    142
    -            </child>
    
    143
    -          </object>
    
    144
    -          <packing>
    
    145
    -            <property name="left_attach">0</property>
    
    146
    -            <property name="top_attach">2</property>
    
    147
    -          </packing>
    
    148
    -        </child>
    
    149
    -        <child>
    
    150
    -          <object class="GtkGrid" id="grid1">
    
    151
    -            <property name="visible">True</property>
    
    152
    -            <property name="can_focus">False</property>
    
    153
    -            <property name="halign">start</property>
    
    154
    -            <property name="column_spacing">5</property>
    
    155
    -            <child>
    
    156
    -              <object class="GtkLabel" id="default_label">
    
    157
    -                <property name="visible">True</property>
    
    158
    -                <property name="can_focus">False</property>
    
    159
    -                <property name="label" translatable="yes">Default:</property>
    
    160
    -                <property name="xalign">0</property>
    
    161
    -              </object>
    
    162
    -              <packing>
    
    163
    -                <property name="left_attach">0</property>
    
    164
    -                <property name="top_attach">0</property>
    
    165
    -              </packing>
    
    166
    -            </child>
    
    167
    -            <child>
    
    168
    -              <object class="GtkComboBox" id="default_cb">
    
    169
    -                <property name="visible">True</property>
    
    170
    -                <property name="can_focus">False</property>
    
    171
    -                <property name="halign">start</property>
    
    172
    -                <property name="hexpand">False</property>
    
    173
    -                <property name="model">default_pref_liststore</property>
    
    174
    -                <child>
    
    175
    -                  <object class="GtkCellRendererText" id="cellrenderertext1"/>
    
    176
    -                  <attributes>
    
    177
    -                    <attribute name="text">0</attribute>
    
    178
    -                  </attributes>
    
    179
    -                </child>
    
    180
    -              </object>
    
    181
    -              <packing>
    
    182
    -                <property name="left_attach">1</property>
    
    183
    -                <property name="top_attach">0</property>
    
    184
    -              </packing>
    
    185
    -            </child>
    
    186
    -          </object>
    
    187
    -          <packing>
    
    188
    -            <property name="left_attach">0</property>
    
    189
    -            <property name="top_attach">0</property>
    
    190
    -          </packing>
    
    191
    -        </child>
    
    192
    -        <child>
    
    193
    -          <object class="GtkButton" id="save_button">
    
    194
    -            <property name="label">gtk-save</property>
    
    195
    -            <property name="visible">True</property>
    
    196
    -            <property name="can_focus">True</property>
    
    197
    -            <property name="receives_default">True</property>
    
    198
    -            <property name="halign">end</property>
    
    199
    -            <property name="use_stock">True</property>
    
    200
    -            <property name="always_show_image">True</property>
    
    201
    -            <signal name="clicked" handler="on_save_button_clicked" swapped="no"/>
    
    202
    -          </object>
    
    203
    -          <packing>
    
    204
    -            <property name="left_attach">1</property>
    
    205
    -            <property name="top_attach">2</property>
    
    206
    -          </packing>
    
    207
    -        </child>
    
    208
    -        <child>
    
    209
    -          <placeholder/>
    
    210
    -        </child>
    
    211 225
           </object>
    
    226
    +      <packing>
    
    227
    +        <property name="left_attach">0</property>
    
    228
    +        <property name="top_attach">1</property>
    
    229
    +        <property name="width">2</property>
    
    230
    +      </packing>
    
    231
    +    </child>
    
    232
    +    <child>
    
    233
    +      <placeholder/>
    
    212 234
         </child>
    
    213 235
       </object>
    
    214 236
     </interface>

  • gajim/dialogs.py
    ... ... @@ -3871,170 +3871,6 @@ class RosterItemExchangeWindow:
    3871 3871
             self.window.destroy()
    
    3872 3872
     
    
    3873 3873
     
    
    3874
    -class Archiving313PreferencesWindow:
    
    3875
    -
    
    3876
    -    default_dict = {'always': 0, 'roster': 1, 'never': 2}
    
    3877
    -    default_dict_cb = {0: 'always', 1: 'roster', 2: 'never'}
    
    3878
    -
    
    3879
    -    def __init__(self, account):
    
    3880
    -        self.account = account
    
    3881
    -        self.idle_id = None
    
    3882
    -
    
    3883
    -        # Connect to glade
    
    3884
    -        self.xml = gtkgui_helpers.get_gtk_builder(
    
    3885
    -            'archiving_313_preferences_window.ui')
    
    3886
    -        self.window = self.xml.get_object('archiving_313_pref')
    
    3887
    -
    
    3888
    -        # Add Widgets
    
    3889
    -        for widget in ('archive_items_liststore', 'default_cb'):
    
    3890
    -            setattr(self, widget, self.xml.get_object(widget))
    
    3891
    -
    
    3892
    -        self.window.set_title(_('Archiving Preferences for %s') % self.account)
    
    3893
    -
    
    3894
    -        app.ged.register_event_handler(
    
    3895
    -            'archiving-313-preferences-changed-received', ged.GUI1,
    
    3896
    -            self._nec_archiving_313_changed_received)
    
    3897
    -        app.ged.register_event_handler(
    
    3898
    -            'archiving-error-received', ged.GUI1, self._nec_archiving_error)
    
    3899
    -
    
    3900
    -        self.default_cb.set_active(0)
    
    3901
    -        self.set_widget_state(False)
    
    3902
    -        self.window.show_all()
    
    3903
    -        self.xml.connect_signals(self)
    
    3904
    -
    
    3905
    -        self.idle_id = GLib.timeout_add_seconds(3, self._nec_archiving_error)
    
    3906
    -        app.connections[self.account].request_archive_preferences()
    
    3907
    -
    
    3908
    -    def on_key_press_event(self, widget, event):
    
    3909
    -        if event.keyval == Gdk.KEY_Escape:
    
    3910
    -            self.window.destroy()
    
    3911
    -
    
    3912
    -    def set_widget_state(self, state):
    
    3913
    -        for widget in ('default_cb', 'save_button', 'add_button',
    
    3914
    -            'remove_button'):
    
    3915
    -            self.xml.get_object(widget).set_sensitive(state)
    
    3916
    -
    
    3917
    -    def _nec_archiving_313_changed_received(self, obj):
    
    3918
    -        if obj.conn.name != self.account:
    
    3919
    -            return
    
    3920
    -        try:
    
    3921
    -            GLib.source_remove(self.idle_id)
    
    3922
    -        except Exception as e:
    
    3923
    -            log.debug(e)
    
    3924
    -        self.set_widget_state(True)
    
    3925
    -        if obj.answer:
    
    3926
    -            def on_ok(dialog):
    
    3927
    -                self.window.destroy()
    
    3928
    -            dialog = HigDialog(
    
    3929
    -                self.window, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
    
    3930
    -                _('Success!'), _('Your Archiving Preferences have been saved!'),
    
    3931
    -                on_response_ok=on_ok, on_response_cancel=on_ok)
    
    3932
    -            dialog.popup()
    
    3933
    -        self.default_cb.set_active(self.default_dict[obj.default])
    
    3934
    -        self.archive_items_liststore.clear()
    
    3935
    -        for items in obj.items:
    
    3936
    -            self.archive_items_liststore.append(items)
    
    3937
    -
    
    3938
    -    def _nec_archiving_error(self, obj=None):
    
    3939
    -        if obj and obj.conn.name != self.account:
    
    3940
    -            return
    
    3941
    -        try:
    
    3942
    -            GLib.source_remove(self.idle_id)
    
    3943
    -        except Exception as e:
    
    3944
    -            log.debug(e)
    
    3945
    -        if not obj:
    
    3946
    -            msg = _('No response from the Server')
    
    3947
    -        else:
    
    3948
    -            msg = _('Error received: {}').format(self.error_msg)
    
    3949
    -
    
    3950
    -        dialog = HigDialog(
    
    3951
    -            self.window, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
    
    3952
    -            _('Error!'), msg)
    
    3953
    -        dialog.popup()
    
    3954
    -        self.set_widget_state(True)
    
    3955
    -        return
    
    3956
    -
    
    3957
    -    def on_add_item_button_clicked(self, widget):
    
    3958
    -        key_name = 'item_archiving_preferences'
    
    3959
    -        if key_name in app.interface.instances[self.account]:
    
    3960
    -            app.interface.instances[self.account][key_name].window.present()
    
    3961
    -        else:
    
    3962
    -            app.interface.instances[self.account][key_name] = \
    
    3963
    -                ItemArchiving313PreferencesWindow(
    
    3964
    -                    self.account, self, self.window)
    
    3965
    -
    
    3966
    -    def on_remove_item_button_clicked(self, widget):
    
    3967
    -        archive_view = self.xml.get_object('archive_view')
    
    3968
    -        mod, path = archive_view.get_selection().get_selected_rows()
    
    3969
    -        if path:
    
    3970
    -            iter_ = mod.get_iter(path)
    
    3971
    -            self.archive_items_liststore.remove(iter_)
    
    3972
    -
    
    3973
    -    def on_save_button_clicked(self, widget):
    
    3974
    -        self.set_widget_state(False)
    
    3975
    -        items = []
    
    3976
    -        default = self.default_dict_cb[self.default_cb.get_active()]
    
    3977
    -        for item in self.archive_items_liststore:
    
    3978
    -            items.append((item[0].lower(), item[1].lower()))
    
    3979
    -        self.idle_id = GLib.timeout_add_seconds(3, self._nec_archiving_error)
    
    3980
    -        app.connections[self.account]. \
    
    3981
    -            set_archive_preferences(items, default)
    
    3982
    -
    
    3983
    -    def on_close_button_clicked(self, widget):
    
    3984
    -        self.window.destroy()
    
    3985
    -
    
    3986
    -    def on_archiving_preferences_window_destroy(self, widget):
    
    3987
    -        app.ged.remove_event_handler(
    
    3988
    -            'archiving-313-preferences-changed-received', ged.GUI1,
    
    3989
    -            self._nec_archiving_313_changed_received)
    
    3990
    -        app.ged.remove_event_handler(
    
    3991
    -            'archiving-error-received', ged.GUI1, self._nec_archiving_error)
    
    3992
    -        if 'archiving_preferences' in app.interface.instances[self.account]:
    
    3993
    -            del app.interface.instances[self.account]['archiving_preferences']
    
    3994
    -
    
    3995
    -
    
    3996
    -class ItemArchiving313PreferencesWindow:
    
    3997
    -
    
    3998
    -    def __init__(self, account, archive, transient):
    
    3999
    -
    
    4000
    -        self.account = account
    
    4001
    -        self.archive = archive
    
    4002
    -
    
    4003
    -        self.xml = gtkgui_helpers.get_gtk_builder(
    
    4004
    -            'archiving_313_preferences_item.ui')
    
    4005
    -        self.window = self.xml.get_object('item_dialog')
    
    4006
    -        self.window.set_transient_for(transient)
    
    4007
    -        # Add Widgets
    
    4008
    -        for widget in ('jid_entry', 'pref_cb'):
    
    4009
    -            setattr(self, widget, self.xml.get_object(widget))
    
    4010
    -
    
    4011
    -        self.window.set_title(_('Add JID'))
    
    4012
    -        self.pref_cb.set_active(0)
    
    4013
    -        self.window.show_all()
    
    4014
    -        self.xml.connect_signals(self)
    
    4015
    -
    
    4016
    -    def on_ok_button_clicked(self, widget):
    
    4017
    -        if self.pref_cb.get_active() == 0:
    
    4018
    -            pref = 'Always'
    
    4019
    -        else:
    
    4020
    -            pref = 'Never'
    
    4021
    -        text = self.jid_entry.get_text()
    
    4022
    -        if not text:
    
    4023
    -            self.window.destroy()
    
    4024
    -            return
    
    4025
    -        else:
    
    4026
    -            self.archive.archive_items_liststore.append((text, pref))
    
    4027
    -        self.window.destroy()
    
    4028
    -
    
    4029
    -    def on_cancel_button_clicked(self, widget):
    
    4030
    -        self.window.destroy()
    
    4031
    -
    
    4032
    -    def on_item_archiving_preferences_window_destroy(self, widget):
    
    4033
    -        key_name = 'item_archiving_preferences'
    
    4034
    -        if key_name in app.interface.instances[self.account]:
    
    4035
    -            del app.interface.instances[self.account][key_name]
    
    4036
    -
    
    4037
    -
    
    4038 3874
     class PrivacyListWindow:
    
    4039 3875
         """
    
    4040 3876
         Window that is used for creating NEW or EDITING already there privacy lists
    

  • gajim/groupchat_control.py
    ... ... @@ -1168,9 +1168,11 @@ class GroupchatControl(ChatControlBase):
    1168 1168
             self._update_banner_state_image()
    
    1169 1169
     
    
    1170 1170
         def _nec_mam_decrypted_message_received(self, obj):
    
    1171
    +        if obj.conn.name != self.account:
    
    1172
    +            return
    
    1171 1173
             if not obj.groupchat:
    
    1172 1174
                 return
    
    1173
    -        if obj.room_jid != self.room_jid:
    
    1175
    +        if obj.archive_jid != self.room_jid:
    
    1174 1176
                 return
    
    1175 1177
             self.print_conversation(
    
    1176 1178
                 obj.msgtxt, contact=obj.nick,
    
    ... ... @@ -1588,7 +1590,8 @@ class GroupchatControl(ChatControlBase):
    1588 1590
     
    
    1589 1591
             if muc_caps_cache.has_mam(self.room_jid):
    
    1590 1592
                 # Request MAM
    
    1591
    -            app.connections[self.account].request_archive_on_muc_join(
    
    1593
    +            con = app.connections[self.account]
    
    1594
    +            con.get_module('MAM').request_archive_on_muc_join(
    
    1592 1595
                     self.room_jid)
    
    1593 1596
     
    
    1594 1597
             app.gc_connected[self.account][self.room_jid] = True
    
    ... ... @@ -2256,6 +2259,8 @@ class GroupchatControl(ChatControlBase):
    2256 2259
                 self._nec_signed_in)
    
    2257 2260
             app.ged.remove_event_handler('decrypted-message-received', ged.GUI2,
    
    2258 2261
                 self._nec_decrypted_message_received)
    
    2262
    +        app.ged.remove_event_handler('mam-decrypted-message-received',
    
    2263
    +            ged.GUI1, self._nec_mam_decrypted_message_received)
    
    2259 2264
             app.ged.remove_event_handler('gc-stanza-message-outgoing', ged.OUT_POSTCORE,
    
    2260 2265
                 self._message_sent)
    
    2261 2266
     
    

  • gajim/gtk/__init__.py

  • gajim/history_sync.py → gajim/gtk/history_sync.py
    1
    -# -*- coding: utf-8 -*-
    
    2
    -#
    
    3 1
     # Copyright (C) 2017 Philipp Hörist <philipp AT hoerist.com>
    
    4 2
     #
    
    5 3
     # This file is part of Gajim.
    
    6 4
     #
    
    7
    -# Gajim is free software: you can redistribute it and/or modify
    
    8
    -# it under the terms of the GNU 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.
    
    5
    +# Gajim is free software; you can redistribute it and/or modify
    
    6
    +# it under the terms of the GNU General Public License as published
    
    7
    +# by the Free Software Foundation; version 3 only.
    
    11 8
     #
    
    12 9
     # Gajim is distributed in the hope that it will be useful,
    
    13 10
     # but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    14
    -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    
    11
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    15 12
     # GNU General Public License for more details.
    
    16 13
     #
    
    17 14
     # You should have received a copy of the GNU General Public License
    
    ... ... @@ -25,8 +22,8 @@ from gi.repository import Gtk, GLib
    25 22
     
    
    26 23
     from gajim.common import app
    
    27 24
     from gajim.common import ged
    
    28
    -from gajim.gtkgui_helpers import get_icon_pixmap
    
    29 25
     from gajim.common.const import ArchiveState
    
    26
    +from gajim.gtk.util import load_icon
    
    30 27
     
    
    31 28
     log = logging.getLogger('gajim.c.message_archiving')
    
    32 29
     
    
    ... ... @@ -40,7 +37,7 @@ class Pages(IntEnum):
    40 37
     class HistorySyncAssistant(Gtk.Assistant):
    
    41 38
         def __init__(self, account, parent):
    
    42 39
             Gtk.Assistant.__init__(self)
    
    43
    -        self.set_title(_('Synchronise History'))
    
    40
    +        # self.set_title(_('Synchronise History'))
    
    44 41
             self.set_resizable(False)
    
    45 42
             self.set_default_size(300, -1)
    
    46 43
             self.set_name('HistorySyncAssistant')
    
    ... ... @@ -54,7 +51,6 @@ class HistorySyncAssistant(Gtk.Assistant):
    54 51
             self.end = None
    
    55 52
             self.next = None
    
    56 53
             self.hide_buttons()
    
    57
    -        self.event_id = id(self)
    
    58 54
     
    
    59 55
             own_jid = self.con.get_own_jid().getStripped()
    
    60 56
     
    
    ... ... @@ -88,9 +84,6 @@ class HistorySyncAssistant(Gtk.Assistant):
    88 84
             app.ged.register_event_handler('archiving-count-received',
    
    89 85
                                            ged.GUI1,
    
    90 86
                                            self._received_count)
    
    91
    -        app.ged.register_event_handler('archiving-query-id',
    
    92
    -                                       ged.GUI1,
    
    93
    -                                       self._new_query_id)
    
    94 87
             app.ged.register_event_handler('archiving-interval-finished',
    
    95 88
                                            ged.GUI1,
    
    96 89
                                            self._received_finished)
    
    ... ... @@ -107,10 +100,6 @@ class HistorySyncAssistant(Gtk.Assistant):
    107 100
                 self.set_current_page(Pages.SUMMARY)
    
    108 101
                 self.summary.nothing_to_do()
    
    109 102
     
    
    110
    -        # if self.con.mam_query_ids:
    
    111
    -        #     self.set_current_page(Pages.SUMMARY)
    
    112
    -        #     self.summary.query_already_running()
    
    113
    -
    
    114 103
             self.show_all()
    
    115 104
     
    
    116 105
         def hide_buttons(self):
    
    ... ... @@ -145,33 +134,34 @@ class HistorySyncAssistant(Gtk.Assistant):
    145 134
             log.info('start: %s', self.start)
    
    146 135
             log.info('end: %s', self.end)
    
    147 136
     
    
    148
    -        self.con.request_archive_count(self.event_id, self.start, self.end)
    
    137
    +        self.query_id = self.con.get_module('MAM').request_archive_count(
    
    138
    +            self.start, self.end)
    
    149 139
     
    
    150 140
         def _received_count(self, event):
    
    151
    -        if event.event_id != self.event_id:
    
    141
    +        if event.query_id != self.query_id:
    
    152 142
                 return
    
    143
    +
    
    153 144
             if event.count is not None:
    
    154 145
                 self.download_history.count = int(event.count)
    
    155
    -        self.con.request_archive_interval(self.event_id, self.start, self.end)
    
    146
    +        self.query_id = self.con.get_module('MAM').request_archive_interval(
    
    147
    +            self.start, self.end)
    
    156 148
     
    
    157 149
         def _received_finished(self, event):
    
    158
    -        if event.event_id != self.event_id:
    
    150
    +        if event.query_id != self.query_id:
    
    159 151
                 return
    
    152
    +        self.query_id = None
    
    160 153
             log.info('query finished')
    
    161 154
             GLib.idle_add(self.download_history.finished)
    
    162 155
             self.set_current_page(Pages.SUMMARY)
    
    163 156
             self.summary.finished()
    
    164 157
     
    
    165
    -    def _new_query_id(self, event):
    
    166
    -        if event.event_id != self.event_id:
    
    167
    -            return
    
    168
    -        self.query_id = event.query_id
    
    169
    -
    
    170
    -    def _nec_mam_message_received(self, obj):
    
    171
    -        if obj.conn.name != self.account:
    
    158
    +    def _nec_mam_message_received(self, event):
    
    159
    +        if event.conn.name != self.account:
    
    172 160
                 return
    
    173 161
     
    
    174
    -        if obj.result.getAttr('queryid') != self.query_id:
    
    162
    +        result = event.stanza.getTag('result')
    
    163
    +        queryid = result.getAttr('queryid')
    
    164
    +        if queryid != self.query_id:
    
    175 165
                 return
    
    176 166
     
    
    177 167
             log.debug('received message')
    
    ... ... @@ -193,9 +183,6 @@ class HistorySyncAssistant(Gtk.Assistant):
    193 183
             app.ged.remove_event_handler('archiving-count-received',
    
    194 184
                                          ged.GUI1,
    
    195 185
                                          self._received_count)
    
    196
    -        app.ged.remove_event_handler('archiving-query-id',
    
    197
    -                                     ged.GUI1,
    
    198
    -                                     self._new_query_id)
    
    199 186
             app.ged.remove_event_handler('archiving-interval-finished',
    
    200 187
                                          ged.GUI1,
    
    201 188
                                          self._received_finished)
    
    ... ... @@ -244,9 +231,8 @@ class DownloadHistoryPage(Gtk.Box):
    244 231
             self.count = 0
    
    245 232
             self.received = 0
    
    246 233
     
    
    247
    -        pix = get_icon_pixmap('folder-download-symbolic', size=64)
    
    248
    -        image = Gtk.Image()
    
    249
    -        image.set_from_pixbuf(pix)
    
    234
    +        surface = load_icon('folder-download-symbolic', self, size=64)
    
    235
    +        image = Gtk.Image.new_from_surface(surface)
    
    250 236
     
    
    251 237
             self.progress = Gtk.ProgressBar()
    
    252 238
             self.progress.set_show_text(True)
    
    ... ... @@ -312,7 +298,7 @@ class TimeOption(Gtk.Label):
    312 298
             super().__init__(label=label)
    
    313 299
             self.date = months
    
    314 300
             if months:
    
    315
    -            self.date = timedelta(days=30*months)
    
    301
    +            self.date = timedelta(days=30 * months)
    
    316 302
     
    
    317 303
         def get_delta(self):
    
    318 304
             return self.date

  • gajim/gtk/mam_preferences.py
    1
    +# This file is part of Gajim.
    
    2
    +#
    
    3
    +# Gajim is free software; you can redistribute it and/or modify
    
    4
    +# it under the terms of the GNU General Public License as published
    
    5
    +# by the Free Software Foundation; version 3 only.
    
    6
    +#
    
    7
    +# Gajim is distributed in the hope that it will be useful,
    
    8
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    9
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    10
    +# GNU General Public License for more details.
    
    11
    +#
    
    12
    +# You should have received a copy of the GNU General Public License
    
    13
    +# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    14
    +
    
    15
    +import logging
    
    16
    +
    
    17
    +from gi.repository import Gtk
    
    18
    +from gi.repository import Gdk
    
    19
    +
    
    20
    +from gajim.common import app
    
    21
    +from gajim.common import ged
    
    22
    +from gajim.gtk.util import get_builder
    
    23
    +
    
    24
    +from gajim.dialogs import HigDialog
    
    25
    +
    
    26
    +log = logging.getLogger('gajim.gtk.mam_preferences')
    
    27
    +
    
    28
    +
    
    29
    +class MamPreferences(Gtk.ApplicationWindow):
    
    30
    +    def __init__(self, account):
    
    31
    +        Gtk.ApplicationWindow.__init__(self)
    
    32
    +        self.set_application(app.app)
    
    33
    +        self.set_position(Gtk.WindowPosition.CENTER)
    
    34
    +        self.set_show_menubar(False)
    
    35
    +        self.set_title(_('Archiving Preferences for %s') % account)
    
    36
    +
    
    37
    +        self.connect('destroy', self._on_destroy)
    
    38
    +        self.connect('key-press-event', self._on_key_press)
    
    39
    +
    
    40
    +        self.account = account
    
    41
    +        self._con = app.connections[account]
    
    42
    +
    
    43
    +        self._builder = get_builder('mam_preferences.ui')
    
    44
    +        self.add(self._builder.get_object('preferences_grid'))
    
    45
    +
    
    46
    +        self._default = self._builder.get_object('default_cb')
    
    47
    +        self._pref_store = self._builder.get_object('preferences_store')
    
    48
    +        self._overlay = self._builder.get_object('overlay')
    
    49
    +        self._spinner = Gtk.Spinner()
    
    50
    +        self._overlay.add_overlay(self._spinner)
    
    51
    +
    
    52
    +        app.ged.register_event_handler('mam-prefs-received', ged.GUI1,
    
    53
    +                                       self._mam_prefs_received)
    
    54
    +        app.ged.register_event_handler('mam-prefs-saved', ged.GUI1,
    
    55
    +                                       self._mam_prefs_saved)
    
    56
    +        app.ged.register_event_handler('mam-prefs-error', ged.GUI1,
    
    57
    +                                       self._mam_prefs_error)
    
    58
    +
    
    59
    +        self._set_grid_state(False)
    
    60
    +        self._builder.connect_signals(self)
    
    61
    +        self.show_all()
    
    62
    +
    
    63
    +        self._activate_spinner()
    
    64
    +
    
    65
    +        self._con.get_module('MAM').request_mam_preferences()
    
    66
    +
    
    67
    +    def _mam_prefs_received(self, obj):
    
    68
    +        if obj.conn.name != self.account:
    
    69
    +            return
    
    70
    +        self._disable_spinner()
    
    71
    +        self._set_grid_state(True)
    
    72
    +
    
    73
    +        self._default.set_active_id(obj.default)
    
    74
    +        self._pref_store.clear()
    
    75
    +        for item in obj.rules:
    
    76
    +            self._pref_store.append(item)
    
    77
    +
    
    78
    +    def _mam_prefs_saved(self, obj):
    
    79
    +        if obj.conn.name != self.account:
    
    80
    +            return
    
    81
    +
    
    82
    +        self._disable_spinner()
    
    83
    +
    
    84
    +        def on_ok(dialog):
    
    85
    +            self.destroy()
    
    86
    +        dialog = HigDialog(
    
    87
    +            self, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
    
    88
    +            _('Success!'), _('Your Archiving Preferences have been saved!'),
    
    89
    +            on_response_ok=on_ok, on_response_cancel=on_ok)
    
    90
    +        dialog.popup()
    
    91
    +
    
    92
    +    def _mam_prefs_error(self, obj=None):
    
    93
    +        if obj and obj.conn.name != self.account:
    
    94
    +            return
    
    95
    +
    
    96
    +        self._disable_spinner()
    
    97
    +
    
    98
    +        if not obj:
    
    99
    +            msg = _('No response from the Server')
    
    100
    +        else:
    
    101
    +            msg = _('Error received: {}').format(obj.error_msg)
    
    102
    +
    
    103
    +        dialog = HigDialog(
    
    104
    +            self, Gtk.MessageType.INFO, Gtk.ButtonsType.OK,
    
    105
    +            _('Error!'), msg)
    
    106
    +        dialog.popup()
    
    107
    +        self._set_grid_state(True)
    
    108
    +
    
    109
    +    def _set_grid_state(self, state):
    
    110
    +        self._builder.get_object('preferences_grid').set_sensitive(state)
    
    111
    +
    
    112
    +    def _jid_edited(self, renderer, path, new_text):
    
    113
    +        iter_ = self._pref_store.get_iter(path)
    
    114
    +        self._pref_store.set_value(iter_, 0, new_text)
    
    115
    +
    
    116
    +    def _pref_toggled(self, renderer, path):
    
    117
    +        iter_ = self._pref_store.get_iter(path)
    
    118
    +        current_value = self._pref_store[iter_][1]
    
    119
    +        self._pref_store.set_value(iter_, 1, not current_value)
    
    120
    +
    
    121
    +    def _on_add(self, button):
    
    122
    +        self._pref_store.append(['', False])
    
    123
    +
    
    124
    +    def _on_remove(self, button):
    
    125
    +        pref_view = self._builder.get_object('pref_view')
    
    126
    +        mod, paths = pref_view.get_selection().get_selected_rows()
    
    127
    +        for path in paths:
    
    128
    +            iter_ = mod.get_iter(path)
    
    129
    +            self._pref_store.remove(iter_)
    
    130
    +
    
    131
    +    def _on_save(self, button):
    
    132
    +        self._activate_spinner()
    
    133
    +        self._set_grid_state(False)
    
    134
    +        items = []
    
    135
    +        default = self._default.get_active_id()
    
    136
    +        for item in self._pref_store:
    
    137
    +            items.append((item[0].lower(), item[1]))
    
    138
    +        self._con.get_module('MAM').set_mam_preferences(items, default)
    
    139
    +
    
    140
    +    def _activate_spinner(self):
    
    141
    +        self._spinner.show()
    
    142
    +        self._spinner.start()
    
    143
    +
    
    144
    +    def _disable_spinner(self):
    
    145
    +        self._spinner.hide()
    
    146
    +        self._spinner.stop()
    
    147
    +
    
    148
    +    def _on_key_press(self, widget, event):
    
    149
    +        if event.keyval == Gdk.KEY_Escape:
    
    150
    +            self.destroy()
    
    151
    +
    
    152
    +    def _on_destroy(self, widget):
    
    153
    +        app.ged.remove_event_handler('mam-prefs-received', ged.GUI1,
    
    154
    +                                     self._mam_prefs_received)
    
    155
    +        app.ged.remove_event_handler('mam-prefs-saved', ged.GUI1,
    
    156
    +                                     self._mam_prefs_saved)
    
    157
    +        app.ged.remove_event_handler('mam-prefs-error', ged.GUI1,
    
    158
    +                                     self._mam_prefs_error)

  • gajim/gtk/util.py
    1
    +# This file is part of Gajim.
    
    2
    +#
    
    3
    +# Gajim is free software; you can redistribute it and/or modify
    
    4
    +# it under the terms of the GNU General Public License as published
    
    5
    +# by the Free Software Foundation; version 3 only.
    
    6
    +#
    
    7
    +# Gajim is distributed in the hope that it will be useful,
    
    8
    +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    
    9
    +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    
    10
    +# GNU General Public License for more details.
    
    11
    +#
    
    12
    +# You should have received a copy of the GNU General Public License
    
    13
    +# along with Gajim. If not, see <http://www.gnu.org/licenses/>.
    
    14
    +
    
    15
    +import os
    
    16
    +import sys
    
    17
    +import logging
    
    18
    +
    
    19
    +from gi.repository import Gtk
    
    20
    +from gi.repository import GLib
    
    21
    +import xml.etree.ElementTree as ET
    
    22
    +
    
    23
    +from gajim.common import i18n
    
    24
    +from gajim.common import configpaths
    
    25
    +
    
    26
    +_icon_theme = Gtk.IconTheme.get_default()
    
    27
    +_icon_theme.append_search_path(configpaths.get('ICONS'))
    
    28
    +
    
    29
    +log = logging.getLogger('gajim.gtk.util')
    
    30
    +
    
    31
    +
    
    32
    +def load_icon(icon_name, widget, size=16,
    
    33
    +              flags=Gtk.IconLookupFlags.FORCE_SIZE):
    
    34
    +
    
    35
    +    scale = widget.get_scale_factor()
    
    36
    +    if not scale:
    
    37
    +        log.warning('Could not determine scale factor')
    
    38
    +        scale = 1
    
    39
    +
    
    40
    +    try:
    
    41
    +        iconinfo = _icon_theme.lookup_icon_for_scale(
    
    42
    +            icon_name, size, scale, flags)
    
    43
    +        return iconinfo.load_surface(None)
    
    44
    +    except GLib.GError as e:
    
    45
    +        log.error('Unable to load icon %s: %s', icon_name, str(e))
    
    46
    +
    
    47
    +
    
    48
    +def get_builder(file_name, widget=None):
    
    49
    +    file_path = os.path.join(configpaths.get('GUI'), file_name)
    
    50
    +    builder = _translate(file_path, widget)
    
    51
    +    builder.set_translation_domain(i18n.DOMAIN)
    
    52
    +    return builder
    
    53
    +
    
    54
    +
    
    55
    +def _translate(gui_file, widget):
    
    56
    +    """
    
    57
    +    This is a workaround for non working translation on Windows
    
    58
    +    """
    
    59
    +    if sys.platform == "win32":
    
    60
    +        tree = ET.parse(gui_file)
    
    61
    +        for node in tree.iter():
    
    62
    +            if 'translatable' in node.attrib:
    
    63
    +                node.text = _(node.text)
    
    64
    +        xml_text = ET.tostring(tree.getroot(),
    
    65
    +                               encoding='unicode',
    
    66
    +                               method='xml')
    
    67
    +        if widget is not None:
    
    68
    +            builder = Gtk.Builder()
    
    69
    +            builder.add_objects_from_string(xml_text, [widget])
    
    70
    +            return builder
    
    71
    +        return Gtk.Builder.new_from_string(xml_text, -1)
    
    72
    +    else:
    
    73
    +        if widget is not None:
    
    74
    +            builder = Gtk.Builder()
    
    75
    +            builder.add_objects_from_file(gui_file, [widget])
    
    76
    +            return builder
    
    77
    +        return Gtk.Builder.new_from_file(gui_file)

  • gajim/gui_interface.py
    ... ... @@ -1113,9 +1113,9 @@ class Interface:
    1113 1113
                 # Else disable autoaway
    
    1114 1114
                 app.sleeper_state[account] = 'off'
    
    1115 1115
     
    
    1116
    -        if obj.conn.archiving_313_supported and app.config.get_per('accounts',
    
    1116
    +        if obj.conn.get_module('MAM').available and app.config.get_per('accounts',
    
    1117 1117
             account, 'sync_logs_with_server'):
    
    1118
    -            obj.conn.request_archive_on_signin()
    
    1118
    +            obj.conn.get_module('MAM').request_archive_on_signin()
    
    1119 1119
     
    
    1120 1120
             invisible_show = app.SHOW_LIST.index('invisible')
    
    1121 1121
             # We cannot join rooms if we are invisible
    

  • gajim/roster_window.py
    ... ... @@ -5404,7 +5404,7 @@ class RosterWindow:
    5404 5404
                         self.on_privacy_lists_menuitem_activate, account)
    
    5405 5405
                 else:
    
    5406 5406
                     privacy_lists_menuitem.set_sensitive(False)
    
    5407
    -            if app.connections[account].archiving_313_supported:
    
    5407
    +            if app.connections[account].get_module('MAM').available:
    
    5408 5408
                     archiving_preferences_menuitem.connect(
    
    5409 5409
                         'activate',
    
    5410 5410
                         self.on_archiving_preferences_menuitem_activate, account)
    

  • gajim/server_info.py
    ... ... @@ -174,7 +174,8 @@ class ServerInfoDialog(Gtk.Dialog):
    174 174
                 Feature('XEP-0280: Message Carbons',
    
    175 175
                         con.carbons_available, nbxmpp.NS_CARBONS, carbons_enabled),
    
    176 176
                 Feature('XEP-0313: Message Archive Management',
    
    177
    -                    con.archiving_namespace, con.archiving_namespace,
    
    177
    +                    con.get_module('MAM').archiving_namespace,
    
    178
    +                    con.get_module('MAM').archiving_namespace,
    
    178 179
                         mam_enabled),
    
    179 180
                 Feature('XEP-0363: HTTP File Upload',
    
    180 181
                         con.get_module('HTTPUpload').available,