Commit 71fc89db authored by Martin's avatar Martin

Merge tag 'upstream/0.16.11.2.git20171120' into debian/experimental

parents 09a159ed c39284db
import subprocess
__version__ = "0.16.11.1"
__version__ = "0.16.11.2"
try:
node = subprocess.Popen('git rev-parse --short=12 HEAD', shell=True,
......
......@@ -313,8 +313,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
self.was_at_the_end = True
self.correcting = False
self.last_sent_msg = None
self.last_received_txt = {} # one per name
self.last_received_id = {} # one per name
# add MessageTextView to UI and connect signals
self.msg_textview = MessageTextView()
......@@ -970,10 +968,6 @@ class ChatControlBase(MessageControl, ChatCommandProcessor, CommandTools):
if not count_as_new:
return
if kind in ('incoming', 'incoming_queue', 'outgoing'):
self.last_received_txt[name] = text
if correct_id:
self.last_received_id[name] = correct_id[0]
if kind == 'incoming':
if not self.type_id == message_control.TYPE_GC or \
app.config.get('notify_on_all_muc_messages') or \
......
......@@ -33,10 +33,12 @@ through ClientCaps objects which are hold by contact instances.
import base64
import hashlib
from collections import namedtuple
import logging
log = logging.getLogger('gajim.c.caps_cache')
import nbxmpp
from nbxmpp import (NS_XHTML_IM, NS_ESESSION, NS_CHATSTATES,
NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO,
NS_JINGLE_FILE_TRANSFER_5)
......@@ -44,7 +46,7 @@ from nbxmpp import (NS_XHTML_IM, NS_ESESSION, NS_CHATSTATES,
FEATURE_BLACKLIST = [NS_CHATSTATES, NS_XHTML_IM, NS_ESESSION,
NS_JINGLE_ICE_UDP, NS_JINGLE_RTP_AUDIO, NS_JINGLE_RTP_VIDEO,
NS_JINGLE_FILE_TRANSFER_5]
from gajim.common import app
# Query entry status codes
NEW = 0
QUERIED = 1
......@@ -56,12 +58,15 @@ FAKED = 3 # allow NullClientCaps to behave as it has a cached item
################################################################################
capscache = None
muc_caps_cache = None
def initialize(logger):
"""
Initialize this module
"""
global capscache
global muc_caps_cache
capscache = CapsCache(logger)
muc_caps_cache = MucCapsCache()
def client_supports(client_caps, requested_feature):
lookup_item = client_caps.get_cache_lookup_strategy()
......@@ -438,3 +443,60 @@ class CapsCache(object):
key = (hash_method, hash)
if key in self.__cache:
del self.__cache[key]
class MucCapsCache:
DiscoInfo = namedtuple('DiscoInfo', ['identities', 'features', 'data'])
def __init__(self):
self.cache = {}
def append(self, stanza):
jid = stanza.getFrom()
identities, features, data = [], [], []
query_childs = stanza.getQueryChildren()
if not query_childs:
app.log('gajim.muc').warning('%s returned empty disco info', jid)
return
for child in query_childs:
if child.getName() == 'identity':
attr = {}
for key in child.getAttrs().keys():
attr[key] = child.getAttr(key)
identities.append(attr)
elif child.getName() == 'feature':
features.append(child.getAttr('var'))
elif child.getName() == 'x':
if child.getNamespace() == nbxmpp.NS_DATA:
data.append(nbxmpp.DataForm(node=child))
self.cache[jid] = self.DiscoInfo(identities, features, data)
def is_cached(self, jid):
return jid in self.cache
def supports(self, jid, feature):
if jid in self.cache:
if feature in self.cache[jid].features:
return True
return False
def has_mam(self, jid):
try:
if nbxmpp.NS_MAM_2 in self.cache[jid].features:
return True
if nbxmpp.NS_MAM_1 in self.cache[jid].features:
return True
except (KeyError, AttributeError):
return False
def get_mam_namespace(self, jid):
try:
if nbxmpp.NS_MAM_2 in self.cache[jid].features:
return nbxmpp.NS_MAM_2
elif nbxmpp.NS_MAM_1 in self.cache[jid].features:
return nbxmpp.NS_MAM_1
except (KeyError, AttributeError):
return
......@@ -65,6 +65,7 @@ def create_log_db():
CREATE TABLE logs(
log_line_id INTEGER PRIMARY KEY AUTOINCREMENT UNIQUE,
account_id INTEGER,
jid_id INTEGER,
contact_name TEXT,
time INTEGER,
......@@ -80,6 +81,13 @@ def create_log_db():
marker INTEGER
);
CREATE TABLE last_archive_message(
jid_id INTEGER PRIMARY KEY UNIQUE,
last_mam_id TEXT,
oldest_mam_timestamp TEXT,
last_muc_timestamp TEXT
);
CREATE INDEX idx_logs_jid_id_time ON logs (jid_id, time DESC);
'''
)
......
......@@ -409,8 +409,6 @@ class Config:
'oauth2_client_id': [ opt_str, '0000000044077801', _('client_id for OAuth 2.0 authentication.')],
'oauth2_redirect_url': [ opt_str, 'https%3A%2F%2Fgajim.org%2Fmsnauth%2Findex.cgi', _('redirect_url for OAuth 2.0 authentication.')],
'opened_chat_controls': [opt_str, '', _('Space separated list of JIDs for which we want to re-open a chat window on next startup.')],
'last_mam_id': [opt_str, '', _('Last MAM id we are syncronized with')],
'mam_start_date': [opt_int, 0, _('The earliest date we requested MAM history for')],
}, {}),
'statusmsg': ({
'message': [ opt_str, '' ],
......
......@@ -42,6 +42,7 @@ import locale
import hmac
import hashlib
import json
from functools import partial
try:
randomsource = random.SystemRandom()
......@@ -190,6 +191,7 @@ class CommonConnection:
'hostname': socket.gethostname(),
'rand': rand
})
app.config.set_per('accounts', self.name, 'resource', resource)
return resource
def dispatch(self, event, data):
......@@ -432,7 +434,7 @@ class CommonConnection:
if obj.message is None:
return
app.logger.insert_into_logs(jid, obj.timestamp, obj.kind,
app.logger.insert_into_logs(self.name, jid, obj.timestamp, obj.kind,
message=obj.message,
subject=obj.subject,
additional_data=obj.additional_data,
......@@ -1913,8 +1915,6 @@ class Connection(CommonConnection, ConnectionHandlers):
self.archiving_namespace = nbxmpp.NS_MAM_2
elif nbxmpp.NS_MAM_1 in obj.features:
self.archiving_namespace = nbxmpp.NS_MAM_1
elif nbxmpp.NS_MAM in obj.features:
self.archiving_namespace = nbxmpp.NS_MAM
if self.archiving_namespace:
self.archiving_supported = True
self.archiving_313_supported = True
......@@ -2583,6 +2583,11 @@ class Connection(CommonConnection, ConnectionHandlers):
# Never join a room when invisible
return
self.discoverMUC(
room_jid, partial(self._join_gc, nick, show, room_jid,
password, change_nick, rejoin))
def _join_gc(self, nick, show, room_jid, password, change_nick, rejoin):
# Check time first in the FAST table
last_date = app.logger.get_room_last_message_time(
self.name, room_jid)
......@@ -2599,11 +2604,19 @@ class Connection(CommonConnection, ConnectionHandlers):
if app.config.get('send_sha_in_gc_presence'):
p = self.add_sha(p)
self.add_lang(p)
if not change_nick:
t = p.setTag(nbxmpp.NS_MUC + ' x')
if change_nick:
self.connection.send(p)
return
t = p.setTag(nbxmpp.NS_MUC + ' x')
if muc_caps_cache.has_mam(room_jid):
# The room is MAM capable dont get MUC History
t.setTag('history', {'maxchars': '0'})
else:
# Request MUC History (not MAM)
tags = {}
timeout = app.config.get_per('rooms', room_jid,
'muc_restore_timeout')
'muc_restore_timeout')
if timeout is None or timeout == -2:
timeout = app.config.get('muc_restore_timeout')
if last_date == 0 and timeout >= 0:
......@@ -2621,8 +2634,9 @@ class Connection(CommonConnection, ConnectionHandlers):
tags['maxstanzas'] = nb
if tags:
t.setTag('history', tags)
if password:
t.setTagData('password', password)
if password:
t.setTagData('password', password)
self.connection.send(p)
def _nec_gc_message_outgoing(self, obj):
......
......@@ -47,6 +47,7 @@ from gajim.common import helpers
from gajim.common import app
from gajim.common import dataforms
from gajim.common import jingle_xtls
from gajim.common.caps_cache import muc_caps_cache
from gajim.common.commands import ConnectionCommands
from gajim.common.pubsub import ConnectionPubSub
from gajim.common.protocol.caps import ConnectionCaps
......@@ -96,6 +97,29 @@ class ConnectionDisco:
id_ = self._discover(nbxmpp.NS_DISCO_INFO, jid, node, id_prefix)
self.disco_info_ids.append(id_)
def discoverMUC(self, jid, callback):
disco_info = nbxmpp.Iq(typ='get', to=jid, queryNS=nbxmpp.NS_DISCO_INFO)
self.connection.SendAndCallForResponse(
disco_info, self.received_muc_info, {'callback': callback})
def received_muc_info(self, conn, stanza, callback):
if nbxmpp.isResultNode(stanza):
app.log('gajim.muc').info(
'Received MUC DiscoInfo for %s', stanza.getFrom())
muc_caps_cache.append(stanza)
callback()
else:
error = stanza.getError()
if error == 'item-not-found':
# Groupchat does not exist
callback()
return
app.nec.push_incoming_event(
InformationEvent(None, conn=self,
level='error',
pri_txt=_('Unable to join Groupchat'),
sec_txt=error))
def request_register_agent_info(self, agent):
if not self.connection or self.connected < 2:
return None
......@@ -760,6 +784,8 @@ class ConnectionHandlersBase:
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,
......@@ -776,6 +802,8 @@ class ConnectionHandlersBase:
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,
......@@ -918,7 +946,8 @@ class ConnectionHandlersBase:
app.config.should_log(self.name, obj.jid):
show = app.logger.convert_show_values_to_db_api_values(obj.show)
if show is not None:
app.logger.insert_into_logs(nbxmpp.JID(obj.jid).getStripped(),
app.logger.insert_into_logs(self.name,
nbxmpp.JID(obj.jid).getStripped(),
time_time(),
KindConstant.STATUS,
message=obj.status,
......@@ -1044,7 +1073,8 @@ class ConnectionHandlersBase:
# if not obj.nick, it means message comes from room itself
# usually it hold description and can be send at each connection
# so don't store it in logs
app.logger.insert_into_logs(obj.jid,
app.logger.insert_into_logs(self.name,
obj.jid,
obj.timestamp,
KindConstant.GC_MSG,
message=obj.msgtxt,
......@@ -1064,7 +1094,8 @@ class ConnectionHandlersBase:
subject = msg.getSubject()
if session.is_loggable():
app.logger.insert_into_logs(nbxmpp.JID(frm).getStripped(),
app.logger.insert_into_logs(self.name,
nbxmpp.JID(frm).getStripped(),
tim,
KindConstant.ERROR,
message=error_msg,
......@@ -1266,10 +1297,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
app.nec.register_incoming_event(ArchivingErrorReceivedEvent)
app.nec.register_incoming_event(
Archiving313PreferencesChangedReceivedEvent)
app.nec.register_incoming_event(
ArchivingFinishedLegacyReceivedEvent)
app.nec.register_incoming_event(
ArchivingFinishedReceivedEvent)
app.nec.register_incoming_event(NotificationEvent)
app.ged.register_event_handler('http-auth-received', ged.CORE,
......@@ -2210,7 +2237,6 @@ ConnectionHandlersBase, ConnectionJingle, ConnectionIBBytestream):
con.RegisterHandler('iq', self._IqPingCB, 'get', nbxmpp.NS_PING)
con.RegisterHandler('iq', self._SearchCB, 'result', nbxmpp.NS_SEARCH)
con.RegisterHandler('iq', self._PrivacySetCB, 'set', nbxmpp.NS_PRIVACY)
con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM)
con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_1)
con.RegisterHandler('iq', self._ArchiveCB, ns=nbxmpp.NS_MAM_2)
con.RegisterHandler('iq', self._PubSubCB, 'result')
......
This diff is collapsed.
......@@ -36,6 +36,10 @@ class AvatarSize(IntEnum):
TOOLTIP = 125
VCARD = 200
class ArchiveState(IntEnum):
NEVER = 0
ALL = 1
THANKS = u"""\
Alexander Futász
......
......@@ -282,6 +282,10 @@ class Logger:
return [user['jid'] for user in family]
return [jid]
def get_account_id(self, account):
jid = app.get_jid_from_account(account)
return self.get_jid_id(jid, type_=JIDConstant.NORMAL_TYPE)
def get_jid_id(self, jid, kind=None, type_=None):
"""
Get the jid id from a jid.
......@@ -1084,12 +1088,19 @@ class Logger:
return True
return False
def find_stanza_id(self, stanza_id, origin_id=None):
def find_stanza_id(self, archive_jid, stanza_id, origin_id=None,
groupchat=False):
"""
Checks if a stanza-id is already in the `logs` table
:param archive_jid: The jid of the archive the stanza-id belongs to
:param stanza_id: The stanza-id
:param origin_id: The origin-id
:param groupchat: stanza-id is from a groupchat
return True if the stanza-id was found
"""
ids = []
......@@ -1101,12 +1112,19 @@ class Logger:
if not ids:
return False
archive_id = self.get_jid_id(archive_jid)
if groupchat:
column = 'jid_id'
else:
column = 'account_id'
sql = '''
SELECT stanza_id FROM logs
WHERE stanza_id IN ({values}) LIMIT 1
'''.format(values=', '.join('?' * len(ids)))
WHERE stanza_id IN ({values}) AND {archive} = ? LIMIT 1
'''.format(values=', '.join('?' * len(ids)),
archive=column)
result = self.con.execute(sql, tuple(ids)).fetchone()
result = self.con.execute(sql, tuple(ids) + (archive_id,)).fetchone()
if result is not None:
log.info('Found duplicated message, stanza-id: %s, origin-id: %s',
......@@ -1127,7 +1145,8 @@ class Logger:
"""
return self.get_jid_id(jid, kind, type_)
def insert_into_logs(self, jid, time_, kind, unread=True, **kwargs):
def insert_into_logs(self, account, jid, time_, kind,
unread=True, **kwargs):
"""
Insert a new message into the `logs` table
......@@ -1144,20 +1163,22 @@ class Logger:
a field in the `logs` table
"""
jid_id = self.get_jid_id(jid, kind=kind)
account_id = self.get_account_id(account)
if 'additional_data' in kwargs:
if not kwargs['additional_data']:
del kwargs['additional_data']
else:
kwargs['additional_data'] = json.dumps(kwargs["additional_data"])
sql = '''
INSERT INTO logs (jid_id, time, kind, {columns})
VALUES (?, ?, ?, {values})
INSERT INTO logs (account_id, jid_id, time, kind, {columns})
VALUES (?, ?, ?, ?, {values})
'''.format(columns=', '.join(kwargs.keys()),
values=', '.join('?' * len(kwargs)))
lastrowid = self.con.execute(sql, (jid_id, time_, kind) + tuple(kwargs.values())).lastrowid
lastrowid = self.con.execute(
sql, (account_id, jid_id, time_, kind) + tuple(kwargs.values())).lastrowid
log.info('Insert into DB: jid: %s, time: %s, kind: %s, stanza_id: %s',
jid, time_, kind, kwargs.get('stanza_id', None))
......@@ -1192,3 +1213,45 @@ class Logger:
'''
self.con.execute(sql, (sha, account_jid_id, jid_id))
self._timeout_commit()
def get_archive_timestamp(self, jid, type_=None):
"""
Get the last archive id/timestamp for a jid
:param jid: The jid that belongs to the avatar
"""
jid_id = self.get_jid_id(jid, type_=type_)
sql = '''SELECT * FROM last_archive_message WHERE jid_id = ?'''
return self.con.execute(sql, (jid_id,)).fetchone()
def set_archive_timestamp(self, jid, **kwargs):
"""
Set the last archive id/timestamp
:param jid: The jid that belongs to the avatar
:param last_mam_id: The last MAM result id
:param oldest_mam_timestamp: The oldest date we requested MAM
history for
:param last_muc_timestamp: The timestamp of the last message we
received in a MUC
"""
jid_id = self.get_jid_id(jid)
exists = self.get_archive_timestamp(jid)
if not exists:
sql = '''INSERT INTO last_archive_message VALUES (?, ?, ?, ?)'''
self.con.execute(sql, (jid_id,
kwargs.get('last_mam_id', None),
kwargs.get('oldest_mam_timestamp', None),
kwargs.get('last_muc_timestamp', None)))
else:
args = ' = ?, '.join(kwargs.keys()) + ' = ?'
sql = '''UPDATE last_archive_message SET {}
WHERE jid_id = ?'''.format(args)
self.con.execute(sql, tuple(kwargs.values()) + (jid_id,))
log.info('Save archive timestamps: %s', kwargs)
self._timeout_commit()
This diff is collapsed.
......@@ -242,6 +242,8 @@ class OptionsParser:
self.update_config_to_016105()
if old < [0, 16, 11, 1] and new >= [0, 16, 11, 1]:
self.update_config_to_016111()
if old < [0, 16, 11, 2] and new >= [0, 16, 11, 2]:
self.update_config_to_016112()
app.logger.init_vars()
app.logger.attach_cache_database()
......@@ -1029,3 +1031,24 @@ class OptionsParser:
log.exception('Error')
con.close()
app.config.set('version', '0.16.11.1')
def update_config_to_016112(self):
con = sqlite.connect(logger.LOG_DB_PATH)
cur = con.cursor()
try:
cur.executescript(
'''
CREATE TABLE IF NOT EXISTS last_archive_message(
jid_id INTEGER PRIMARY KEY UNIQUE,
last_mam_id TEXT,
oldest_mam_timestamp TEXT,
last_muc_timestamp TEXT
);
ALTER TABLE logs ADD COLUMN 'account_id' INTEGER;
'''
)
con.commit()
except sqlite.OperationalError:
log.exception('Error')
con.close()
app.config.set('version', '0.16.11.2')
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
width="16"
height="16"
id="svg4023"
sodipodi:version="0.32"
inkscape:version="0.92.2 5c3e80d, 2017-08-06"
sodipodi:docname="org.gajim.Gajim-symbolic.svg"
inkscape:output_extension="org.inkscape.output.svg.inkscape"
inkscape:export-filename="/home/cornelius/gajim.png"
inkscape:export-xdpi="90"
inkscape:export-ydpi="90"
version="1.1"
viewBox="-0.7 0.2 16 16">
<defs
id="defs4025" />
<sodipodi:namedview
id="base"
pagecolor="#555753"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="14"
inkscape:cx="-12.741964"
inkscape:cy="-2.4941427"
inkscape:current-layer="layer1"
showgrid="false"
inkscape:grid-bbox="true"
inkscape:document-units="px"
inkscape:window-width="1920"
inkscape:window-height="1016"
inkscape:window-x="0"
inkscape:window-y="27"
inkscape:window-maximized="1"
showguides="true"
fit-margin-top="0"
fit-margin-left="0"
fit-margin-right="0"
fit-margin-bottom="0"
showborder="true"
borderlayer="true"
viewbox-x="-0.7"
viewbox-y="0.2">
<inkscape:grid
type="xygrid"
id="grid68"
originx="-4.9243182"
originy="-2.7517627" />
</sodipodi:namedview>
<metadata
id="metadata4028">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
<dc:date />
<dc:creator>
<cc:Agent>
<dc:title>Josef Vybíral</dc:title>
</cc:Agent>
</dc:creator>
<dc:rights>
<cc:Agent>
<dc:title />
</cc:Agent>
</dc:rights>
<dc:publisher>
<cc:Agent>
<dc:title />
</cc:Agent>
</dc:publisher>
<dc:source />
<dc:subject>
<rdf:Bag>
<rdf:li>gajim</rdf:li>
<rdf:li>jabber</rdf:li>
<rdf:li>im</rdf:li>
</rdf:Bag>
</dc:subject>
<cc:license
rdf:resource="http://creativecommons.org/licenses/by/2.5/" />
</cc:Work>
<cc:License
rdf:about="http://creativecommons.org/licenses/by/2.5/">
<cc:permits
rdf:resource="http://web.resource.org/cc/Reproduction" />
<cc:permits
rdf:resource="http://web.resource.org/cc/Distribution" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Notice" />
<cc:requires
rdf:resource="http://web.resource.org/cc/Attribution" />
<cc:permits
rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
</cc:License>
</rdf:RDF>
</metadata>
<g
id="layer1"
inkscape:label="Layer 1"
inkscape:groupmode="layer"
transform="translate(-4.9243182,-29.248236)">
<path
sodipodi:nodetypes="csccccccsc"
id="path2200"
d="m 12.431363,35.902348 c -3.596104,0 -7.047591,2.144734 -6.802621,2.810805 0.03381,0.09192 2.338952,0.109837 2.419739,0.623857 l 0.665998,4.02844 c 0,0.384169 0.02928,0.797428 0.285041,0.797428 2.391902,0.539175 4.672155,0.520362 6.852052,0 0.255764,0 0.296674,-0.300871 0.296675,-0.68504 l 0.637887,-4.112731 c 0.107379,-0.43303 2.336693,-0.339017 2.39163,-0.764343 0.103066,-0.797944 -3.150297,-2.698416 -6.746401,-2.698416 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:0;fill-rule:nonzero;stroke:#bebebe;stroke-width:1.5;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;image-rendering:auto;enable-background:accumulate"
inkscape:connector-curvature="0" />
<path
sodipodi:nodetypes="csccccccsc"
id="path3941"
d="m 12.431363,30.392789 c -1.630186,0 -2.951551,1.371963 -2.951551,3.062415 0,1.080401 0.589091,1.867472 1.501919,2.57612 0.532203,0.629506 0.3362,0.543594 0.376381,1.202316 0,0.204542 0.06765,0.267362 0.26479,0.267362 0.55232,0.501159 1.118692,0.488201 1.649449,-0.01634 0.197139,0 0.215511,-0.09158 0.215511,-0.296119 0.06876,-0.853384 0.107094,-0.75079 0.487008,-1.255105 0.842182,-0.53965 1.408045,-1.376645 1.408045,-2.478234 0,-1.690453 -1.321366,-3.062416 -2.951552,-3.062415 z"
style="color:#000000;display:inline;overflow:visible;visibility:visible;opacity:1;fill:#000000;fill-opacity:0;fill-rule:nonzero;stroke:#bebebe;stroke-width:1.5;stroke-linecap:square;stroke-linejoin:miter;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;marker:none;marker-start:none;marker-mid:none;marker-end:none;enable-background:accumulate"
inkscape:connector-curvature="0" />
</g>
</svg>
......@@ -57,10 +57,6 @@ class FeaturesWindow:
_('Ability to encrypting chat messages with OpenPGP.'),
_('Requires gpg and python-gnupg (http://code.google.com/p/python-gnupg/).'),
_('Requires gpg.exe in PATH.')),
_('Network-Watcher'): (self.network_watcher_available,
_('Autodetection of network status.'),
_('Requires gnome-network-manager'),
_('Feature not available under Windows.')),
_('Password encryption'): (self.some_keyring_available,
_('Passwords can be stored securely and not just in plaintext.'),
_('Requires libsecret and a provider (such as GNOME Keyring and KSecretService).'),
......@@ -157,10 +153,6 @@ class FeaturesWindow:
def gpg_available(self):
return app.HAVE_GPG
def network_watcher_available(self):
from gajim import network_watcher
return network_watcher.supported
def some_keyring_available(self):
if os.name == 'nt':
return True
......
......@@ -47,6 +47,7 @@ from gajim import vcard
from gajim import cell_renderer_image
from gajim import dataforms_widget
from gajim.common.const import AvatarSize
from gajim.common.caps_cache import muc_caps_cache
import nbxmpp
from enum import IntEnum, unique
......@@ -478,6 +479,8 @@ class GroupchatControl(ChatControlBase):
self._nec_gc_presence_received)
app.ged.register_event_handler('gc-message-received', ged.GUI1,
self._nec_gc_message_received)
app.ged.register_event_handler('mam-decrypted-message-received',
ged.GUI1, self._nec_mam_decrypted_message_received)
app.ged.register_event_handler('vcard-published', ged.GUI1,
self._nec_vcard_published)
app.ged.register_event_handler('update-gc-avatar', ged.GUI1,
......@@ -1053,6 +1056,17 @@ class GroupchatControl(ChatControlBase):
obj.contact.name, obj.contact.avatar_sha)
self.draw_avatar(obj.contact)
def _nec_mam_decrypted_message_received(self, obj):
if not obj.groupchat:
return
if obj.room_jid != self.room_jid:
return
self.print_conversation(
obj.msgtxt, contact=obj.nick,
tim=obj.timestamp, encrypted=obj.encrypted,
msg_stanza_id=obj.unique_id,
additional_data=obj.additional_data)
def _nec_gc_message_received(self, obj):
if obj.room_jid != self.room_jid or obj.conn.name != self.account:
return
......@@ -1455,6 +1469,11 @@ class GroupchatControl(ChatControlBase):
GLib.source_remove(self.autorejoin)
self.autorejoin = None
if muc_caps_cache.has_mam(self.room_jid):
# Request MAM
app.connections[self.account].request_archive_on_muc_join(