Commit ebbe06d5 authored by Philipp Hörist's avatar Philipp Hörist Committed by Philipp Hörist
Browse files

Refactor MAM into own module

- Rework the MAM Preference dialog
- Move MAM Preference dialog into a new gtk module
- Refactor all MAM code into own module
- Refactor the MAM code itself so we can easier test it in the future
- Add a misc module for smaller XEPs and move EME, Last Message Correction
Delay, OOB into it
- Add dedicated module for XEP-0082 Time Profiles
parent 72ee9af7
......@@ -29,6 +29,7 @@
from gajim import disco
from gajim.history_sync import HistorySyncAssistant
from gajim.server_info import ServerInfoDialog
from gajim.gtk.mam_preferences import MamPreferences
# General Actions
......@@ -181,14 +182,13 @@ def on_import_contacts(action, param):
# Advanced Actions
def on_archiving_preferences(action, param):
def on_mam_preferences(action, param):
account = param.get_string()
if 'archiving_preferences' in interface.instances[account]:
interface.instances[account]['archiving_preferences'].window.\
present()
window = app.get_app_window(MamPreferences, account)
if window is None:
MamPreferences(account)
else:
interface.instances[account]['archiving_preferences'] = \
dialogs.Archiving313PreferencesWindow(account)
window.present()
def on_history_sync(action, param):
......
......@@ -356,7 +356,7 @@ def _get_account_actions(self, account):
('-profile', app_actions.on_profile, 'feature', 's'),
('-xml-console', app_actions.on_xml_console, 'always', 's'),
('-server-info', app_actions.on_server_info, 'online', 's'),
('-archive', app_actions.on_archiving_preferences, 'feature', 's'),
('-archive', app_actions.on_mam_preferences, 'feature', 's'),
('-sync-history', app_actions.on_history_sync, 'online', 's'),
('-privacylists', app_actions.on_privacy_lists, 'feature', 's'),
('-send-server-message',
......
......@@ -809,8 +809,13 @@ def _on_authentication_button_clicked(self, widget):
def _nec_mam_decrypted_message_received(self, obj):
if obj.conn.name != self.account:
return
if obj.with_ != self.contact.jid:
return
if obj.muc_pm:
if not obj.with_ == self.contact.get_full_jid():
return
else:
if not obj.with_.bareMatch(self.contact.jid):
return
kind = '' # incoming
if obj.kind == KindConstant.CHAT_MSG_SENT:
......
......@@ -595,11 +595,17 @@ def prefers_app_menu():
return False
return app.prefers_app_menu()
def get_app_window(cls):
def get_app_window(cls, account=None):
for win in app.get_windows():
if isinstance(cls, str):
if type(win).__name__ == cls:
if account is not None:
if account != win.account:
continue
return win
elif isinstance(win, cls):
if account is not None:
if account != win.account:
continue
return win
return None
......@@ -305,7 +305,6 @@ class Config:
'use_keyring': [opt_bool, True, _('If true, Gajim will use the Systems Keyring to store account passwords.')],
'pgp_encoding': [ opt_str, '', _('Sets the encoding used by python-gnupg'), True],
'remote_commands': [opt_bool, False, _('If true, Gajim will execute XEP-0146 Commands.')],
'mam_blacklist': [opt_str, '', _('All non-compliant MAM Groupchats')],
}, {})
__options_per_key = {
......
......@@ -121,9 +121,6 @@ def __init__(self, name):
self.privacy_rules_supported = False
self.vcard_supported = False
self.private_storage_supported = False
self.archiving_namespace = None
self.archiving_supported = False
self.archiving_313_supported = False
self.roster_supported = True
self.blocking_supported = False
self.addressing_supported = False
......@@ -1611,12 +1608,11 @@ def _nec_agent_info_received(self, obj):
if obj.fjid == our_jid:
if nbxmpp.NS_MAM_2 in obj.features:
self.archiving_namespace = nbxmpp.NS_MAM_2
self.get_module('MAM').archiving_namespace = nbxmpp.NS_MAM_2
elif nbxmpp.NS_MAM_1 in obj.features:
self.archiving_namespace = nbxmpp.NS_MAM_1
if self.archiving_namespace:
self.archiving_supported = True
self.archiving_313_supported = True
self.get_module('MAM').archiving_namespace = nbxmpp.NS_MAM_1
if self.get_module('MAM').archiving_namespace:
self.get_module('MAM').available = True
get_action(self.name + '-archive').set_enabled(True)
for identity in obj.identities:
if identity['category'] == 'pubsub':
......
......@@ -45,8 +45,8 @@
from gajim.common.protocol.caps import ConnectionCaps
from gajim.common.protocol.bytestream import ConnectionSocks5Bytestream
from gajim.common.protocol.bytestream import ConnectionIBBytestream
from gajim.common.message_archiving import ConnectionArchive313
from gajim.common.connection_handlers_events import *
from gajim.common.modules.misc import parse_eme
from gajim.common import ged
from gajim.common.nec import NetworkEvent
......@@ -295,7 +295,9 @@ def __init__(self):
# XEPs that are based on Message
self._message_namespaces = set([nbxmpp.NS_HTTP_AUTH,
nbxmpp.NS_PUBSUB_EVENT,
nbxmpp.NS_ROSTERX])
nbxmpp.NS_ROSTERX,
nbxmpp.NS_MAM_1,
nbxmpp.NS_MAM_2])
app.ged.register_event_handler('iq-error-received', ged.CORE,
self._nec_iq_error_received)
......@@ -303,10 +305,6 @@ def __init__(self):
self._nec_presence_received)
app.ged.register_event_handler('message-received', ged.CORE,
self._nec_message_received)
app.ged.register_event_handler('mam-message-received', ged.CORE,
self._nec_message_received)
app.ged.register_event_handler('mam-gc-message-received', ged.CORE,
self._nec_message_received)
app.ged.register_event_handler('decrypted-message-received', ged.CORE,
self._nec_decrypted_message_received)
app.ged.register_event_handler('gc-message-received', ged.CORE,
......@@ -319,10 +317,6 @@ def cleanup(self):
self._nec_presence_received)
app.ged.remove_event_handler('message-received', ged.CORE,
self._nec_message_received)
app.ged.remove_event_handler('mam-message-received', ged.CORE,
self._nec_message_received)
app.ged.remove_event_handler('mam-gc-message-received', ged.CORE,
self._nec_message_received)
app.ged.remove_event_handler('decrypted-message-received', ged.CORE,
self._nec_decrypted_message_received)
app.ged.remove_event_handler('gc-message-received', ged.CORE,
......@@ -460,37 +454,15 @@ def _nec_message_received(self, obj):
app.plugin_manager.extension_point(
'decrypt', self, obj, self._on_message_received)
if not obj.encrypted:
# XEP-0380
enc_tag = obj.stanza.getTag('encryption', namespace=nbxmpp.NS_EME)
if enc_tag:
ns = enc_tag.getAttr('namespace')
if ns:
if ns == 'urn:xmpp:otr:0':
obj.msgtxt = _('This message was encrypted with OTR '
'and could not be decrypted.')
elif ns == 'jabber:x:encrypted':
obj.msgtxt = _('This message was encrypted with Legacy '
'OpenPGP and could not be decrypted. You can install '
'the PGP plugin to handle those messages.')
elif ns == 'urn:xmpp:openpgp:0':
obj.msgtxt = _('This message was encrypted with '
'OpenPGP for XMPP and could not be decrypted.')
else:
enc_name = enc_tag.getAttr('name')
if not enc_name:
enc_name = ns
obj.msgtxt = _('This message was encrypted with %s '
'and could not be decrypted.') % enc_name
eme = parse_eme(obj.stanza)
if eme is not None:
obj.msgtxt = eme
self._on_message_received(obj)
def _on_message_received(self, obj):
if isinstance(obj, MessageReceivedEvent):
app.nec.push_incoming_event(
DecryptedMessageReceivedEvent(
None, conn=self, msg_obj=obj, stanza_id=obj.unique_id))
else:
app.nec.push_incoming_event(
MamDecryptedMessageReceivedEvent(None, **vars(obj)))
app.nec.push_incoming_event(
DecryptedMessageReceivedEvent(
None, conn=self, msg_obj=obj, stanza_id=obj.unique_id))
def _nec_decrypted_message_received(self, obj):
if obj.conn.name != self.name:
......@@ -564,7 +536,7 @@ def _nec_decrypted_message_received(self, obj):
def _check_for_mam_compliance(self, room_jid, stanza_id):
namespace = muc_caps_cache.get_mam_namespace(room_jid)
if stanza_id is None and namespace == nbxmpp.NS_MAM_2:
helpers.add_to_mam_blacklist(room_jid)
log.warning('%s announces mam:2 without stanza-id')
def _nec_gc_message_received(self, obj):
if obj.conn.name != self.name:
......@@ -743,11 +715,10 @@ def make_new_session(self, jid, thread_id=None, type_='chat', cls=None):
return sess
class ConnectionHandlers(ConnectionArchive313,
ConnectionSocks5Bytestream, ConnectionDisco, ConnectionCaps,
ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
class ConnectionHandlers(ConnectionSocks5Bytestream, ConnectionDisco,
ConnectionCaps, ConnectionHandlersBase,
ConnectionJingle, ConnectionIBBytestream):
def __init__(self):
ConnectionArchive313.__init__(self)
ConnectionSocks5Bytestream.__init__(self)
ConnectionIBBytestream.__init__(self)
......@@ -772,9 +743,6 @@ def __init__(self):
app.nec.register_incoming_event(StreamConflictReceivedEvent)
app.nec.register_incoming_event(MessageReceivedEvent)
app.nec.register_incoming_event(ArchivingErrorReceivedEvent)
app.nec.register_incoming_event(
Archiving313PreferencesChangedReceivedEvent)
app.nec.register_incoming_event(NotificationEvent)
app.ged.register_event_handler('roster-set-received',
......@@ -799,7 +767,6 @@ def __init__(self):
def cleanup(self):
ConnectionHandlersBase.cleanup(self)
ConnectionCaps.cleanup(self)
ConnectionArchive313.cleanup(self)
app.ged.remove_event_handler('roster-set-received',
ged.CORE, self._nec_roster_set_received)
app.ged.remove_event_handler('roster-received', ged.CORE,
......@@ -1343,8 +1310,6 @@ def _register_handlers(self, con, con_type):
con.RegisterHandler('iq', self._DiscoverItemsGetCB, 'get',
nbxmpp.NS_DISCO_ITEMS)
con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_1)
con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_2)
con.RegisterHandler('iq', self._JingleCB, 'result')
con.RegisterHandler('iq', self._JingleCB, 'error')
con.RegisterHandler('iq', self._JingleCB, 'set', nbxmpp.NS_JINGLE)
......
......@@ -77,7 +77,10 @@ def get_jid_resource(self, check_fake_jid=False):
del self.conn.groupchat_jids[self.id_]
else:
self.fjid = helpers.get_full_jid_from_iq(self.stanza)
self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
if self.fjid is None:
self.jid = None
else:
self.jid, self.resource = app.get_room_and_nick_from_fjid(self.fjid)
def get_id(self):
self.id_ = self.stanza.getID()
......@@ -630,240 +633,6 @@ class BeforeChangeShowEvent(nec.NetworkIncomingEvent):
name = 'before-change-show'
base_network_events = []
class MamMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'mam-message-received'
base_network_events = ['raw-mam-message-received']
def __init__(self, name, base_event):
'''
Pre-Generated attributes on self:
:conn: Connection instance
:stanza: Complete stanza Node
:forwarded: Forwarded Node
:result: Result Node
'''
self._set_base_event_vars_as_attributes(base_event)
self.additional_data = {}
self.encrypted = False
self.groupchat = False
self.nick = None
self.self_message = None
self.muc_pm = None
def generate(self):
account = self.conn.name
archive_jid = self.stanza.getFrom()
own_jid = self.conn.get_own_jid()
if archive_jid and not archive_jid.bareMatch(own_jid):
# MAM Message not from our Archive
return False
self.msg_ = self.forwarded.getTag('message', protocol=True)
if self.msg_.getType() == 'groupchat':
return False
# use stanza-id as unique-id
self.unique_id, origin_id = self.get_unique_id()
self.message_id = self.msg_.getID()
# Check for duplicates
if app.logger.find_stanza_id(account,
own_jid.getStripped(),
self.unique_id, origin_id):
return
self.msgtxt = self.msg_.getTagData('body')
frm = self.msg_.getFrom()
# Some servers dont set the 'to' attribute when
# we send a message to ourself
to = self.msg_.getTo()
if to is None:
to = own_jid
if frm.bareMatch(own_jid):
self.with_ = to
self.kind = KindConstant.CHAT_MSG_SENT
else:
self.with_ = frm
self.kind = KindConstant.CHAT_MSG_RECV
delay = self.forwarded.getTagAttr(
'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
if delay is None:
log.error('Received MAM message without timestamp')
log.error(self.stanza)
return
self.timestamp = helpers.parse_datetime(
delay, check_utc=True, epoch=True)
if self.timestamp is None:
log.error('Received MAM message with invalid timestamp: %s', delay)
log.error(self.stanza)
return
# Save timestamp added by the user
user_delay = self.msg_.getTagAttr(
'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
if user_delay is not None:
self.user_timestamp = helpers.parse_datetime(
user_delay, check_utc=True, epoch=True)
if self.user_timestamp is None:
log.warning('Received MAM message with '
'invalid user timestamp: %s', user_delay)
log.warning(self.stanza)
log.debug('Received mam-message: unique id: %s', self.unique_id)
return True
def get_unique_id(self):
stanza_id = self.get_stanza_id(self.result, query=True)
if self._is_self_message(self.msg_) or self._is_muc_pm(self.msg_):
origin_id = self.msg_.getOriginID()
return stanza_id, origin_id
if self.conn.get_own_jid().bareMatch(self.msg_.getFrom()):
# message we sent
origin_id = self.msg_.getOriginID()
return stanza_id, origin_id
# A message we received
return stanza_id, None
class MamGcMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'mam-gc-message-received'
base_network_events = ['raw-mam-message-received']
def __init__(self, name, base_event):
'''
Pre-Generated attributes on self:
:conn: Connection instance
:stanza: Complete stanza Node
:forwarded: Forwarded Node
:result: Result Node
:muc_pm: True, if this is a MUC PM
propagated to MamDecryptedMessageReceivedEvent
'''
self._set_base_event_vars_as_attributes(base_event)
self.additional_data = {}
self.encrypted = False
self.groupchat = True
self.kind = KindConstant.GC_MSG
def generate(self):
account = self.conn.name
self.msg_ = self.forwarded.getTag('message', protocol=True)
if self.msg_.getType() != 'groupchat':
return False
try:
self.room_jid = self.stanza.getFrom().getStripped()
except AttributeError:
log.warning('Received GC MAM message '
'without from attribute\n%s', self.stanza)
return False
self.unique_id = self.get_stanza_id(self.result, query=True)
self.message_id = self.msg_.getID()
# Check for duplicates
if app.logger.find_stanza_id(account,
self.room_jid,
self.unique_id,
groupchat=True):
return
self.msgtxt = self.msg_.getTagData('body')
self.with_ = self.msg_.getFrom().getStripped()
self.nick = self.msg_.getFrom().getResource()
# Get the real jid if we have it
self.real_jid = None
muc_user = self.msg_.getTag('x', namespace=nbxmpp.NS_MUC_USER)
if muc_user is not None:
self.real_jid = muc_user.getTagAttr('item', 'jid')
delay = self.forwarded.getTagAttr(
'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
if delay is None:
log.error('Received MAM message without timestamp')
log.error(self.stanza)
return
self.timestamp = helpers.parse_datetime(
delay, check_utc=True, epoch=True)
if self.timestamp is None:
log.error('Received MAM message with invalid timestamp: %s', delay)
log.error(self.stanza)
return
# Save timestamp added by the user
user_delay = self.msg_.getTagAttr(
'delay', 'stamp', namespace=nbxmpp.NS_DELAY2)
if user_delay is not None:
self.user_timestamp = helpers.parse_datetime(
user_delay, check_utc=True, epoch=True)
if self.user_timestamp is None:
log.warning('Received MAM message with '
'invalid user timestamp: %s', user_delay)
log.warning(self.stanza)
log.debug('Received mam-gc-message: unique id: %s', self.unique_id)
return True
class MamDecryptedMessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'mam-decrypted-message-received'
base_network_events = []
def generate(self):
self.correct_id = None
if not self.msgtxt:
# For example Chatstates, Receipts, Chatmarkers
log.debug('Received MAM message without text')
return
replace = self.msg_.getTag('replace', namespace=nbxmpp.NS_CORRECT)
if replace is not None:
self.correct_id = replace.getAttr('id')
self.get_oob_data(self.msg_)
if self.groupchat:
return True
if not self.muc_pm:
# muc_pm = False, means only there was no muc#user namespace
# This could still be a muc pm, we check the database if we
# know this jid. If not we disco it.
self.muc_pm = app.logger.jid_is_room_jid(self.with_.getStripped())
if self.muc_pm is None:
# Check if this event is triggered after a disco, so we dont
# run into an endless loop
if hasattr(self, 'disco'):
log.error('JID not known even after sucessful disco')
log.error(self.with_.getStripped())
return
# we don't know this JID, we need to disco it.
server = self.with_.getDomain()
if server not in self.conn.mam_awaiting_disco_result:
self.conn.mam_awaiting_disco_result[server] = [self]
self.conn.discoverInfo(server)
else:
self.conn.mam_awaiting_disco_result[server].append(self)
return
if self.muc_pm:
self.with_ = str(self.with_)
else:
self.with_ = self.with_.getStripped()
return True
class MessageReceivedEvent(nec.NetworkIncomingEvent, HelperEvent):
name = 'message-received'
base_network_events = ['raw-message-received']
......@@ -968,30 +737,6 @@ def generate(self):
return
self.forwarded = True
result = self.stanza.getTag('result', protocol=True)
if result and result.getNamespace() in (nbxmpp.NS_MAM_1,
nbxmpp.NS_MAM_2):
if result.getAttr('queryid') not in self.conn.mam_query_ids:
log.warning('Invalid MAM Message: unknown query id')
log.debug(self.stanza)
return
forwarded = result.getTag('forwarded',
namespace=nbxmpp.NS_FORWARD,
protocol=True)
if not forwarded:
log.warning('Invalid MAM Message: no forwarded child')
return
app.nec.push_incoming_event(
NetworkEvent('raw-mam-message-received',
conn=self.conn,
stanza=self.stanza,
forwarded=forwarded,
result=result))
return
# Mediated invitation?
muc_user = self.stanza.getTag('x', namespace=nbxmpp.NS_MUC_USER)
if muc_user:
......@@ -1085,7 +830,7 @@ def get_unique_id(self):
return
# Messages we receive live
if self.conn.archiving_namespace != nbxmpp.NS_MAM_2:
if self.conn.get_module('MAM').archiving_namespace != nbxmpp.NS_MAM_2:
# Only mam:2 ensures valid stanza-id
return
......@@ -1498,77 +1243,6 @@ def generate(self):
self.sid = self.jingle_session.sid
return True
class ArchivingReceivedEvent(nec.NetworkIncomingEvent):
name = 'archiving-received'
base_network_events = []
def generate(self):
self.type_ = self.stanza.getType()
if self.type_ not in ('result', 'set', 'error'):
return
return True
class ArchivingErrorReceivedEvent(nec.NetworkIncomingEvent):
name = 'archiving-error-received'
base_network_events = ['archiving-received']
def generate(self):
self.conn = self.base_event.conn
self.stanza = self.base_event.stanza
self.type_ = self.base_event.type_
if self.type_ == 'error':
self.error_msg = self.stanza.getErrorMsg()
return True
class ArchivingCountReceived(nec.NetworkIncomingEvent):
name = 'archiving-count-received'
base_network_events = []
def generate(self):
return True
class ArchivingIntervalFinished(nec.NetworkIncomingEvent):
name = 'archiving-interval-finished'
base_network_events = []
def generate(self):
return True
class ArchivingQueryID(nec.NetworkIncomingEvent):
name = 'archiving-query-id'
base_network_events = []
def generate(self):
return True
class Archiving313PreferencesChangedReceivedEvent(nec.NetworkIncomingEvent):
name = 'archiving-313-preferences-changed-received'
base_network_events = ['archiving-received']
def generate(self):