Welcome to mirror list, hosted at ThFree Co, Russian Federation.

dev.gajim.org/gajim/gajim.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorYann Leboulanger <asterix@lagaule.org>2010-08-11 20:43:41 +0400
committerYann Leboulanger <asterix@lagaule.org>2010-08-11 20:43:41 +0400
commitca43e5441c89b5b7fe45c54c56d17ff74e028ffa (patch)
tree7887440a90210c0fc7f998ea28a9396258d245db /src
parent2d9e622f656a85c5e86f852ab098ae1400ae06b0 (diff)
parentaf804641b6df77487f3863db5a474e2349100405 (diff)
merge message archiving branch. Fixes #3593
Diffstat (limited to 'src')
-rw-r--r--src/chat_control.py29
-rw-r--r--src/common/config.py1
-rw-r--r--src/common/connection.py4
-rw-r--r--src/common/connection_handlers.py87
-rw-r--r--src/common/logger.py52
-rw-r--r--src/common/message_archiving.py258
-rw-r--r--src/common/stanza_session.py125
-rw-r--r--src/common/xmpp/protocol.py6
-rw-r--r--src/dialogs.py350
-rw-r--r--src/gui_interface.py19
-rw-r--r--src/message_control.py16
-rw-r--r--src/roster_window.py27
-rw-r--r--src/session.py78
13 files changed, 1008 insertions, 44 deletions
diff --git a/src/chat_control.py b/src/chat_control.py
index 370a9730a..3fd462a00 100644
--- a/src/chat_control.py
+++ b/src/chat_control.py
@@ -46,6 +46,7 @@ from common import exceptions
from message_control import MessageControl
from conversation_textview import ConversationTextview
from message_textview import MessageTextView
+from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
from common.contacts import GC_Contact
from common.logger import constants
from common.pep import MOODS, ACTIVITIES
@@ -2202,6 +2203,18 @@ class ChatControl(ChatControlBase):
msg = _('Session negotiation cancelled')
ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
+ def print_archiving_session_details(self):
+ """
+ Print esession settings to textview
+ """
+ archiving = bool(self.session) and isinstance(self.session,
+ ArchivingStanzaSession) and self.session.archiving
+ if archiving:
+ msg = _('This session WILL be archived on server')
+ else:
+ msg = _('This session WILL NOT be archived on server')
+ ChatControlBase.print_conversation_line(self, msg, 'status', '', None)
+
def print_esession_details(self):
"""
Print esession settings to textview
@@ -2226,6 +2239,12 @@ class ChatControl(ChatControlBase):
self._show_lock_image(e2e_is_active, 'E2E', e2e_is_active, self.session and \
self.session.is_loggable(), self.session and self.session.verified_identity)
+ def print_session_details(self):
+ if isinstance(self.session, EncryptedStanzaSession):
+ self.print_esession_details()
+ elif isinstance(self.session, ArchivingStanzaSession):
+ self.print_archiving_session_details()
+
def print_conversation(self, text, frm='', tim=None, encrypted=False,
subject=None, xhtml=None, simple=False, xep0184_id=None,
displaymarking=None):
@@ -2658,6 +2677,8 @@ class ChatControl(ChatControlBase):
if want_e2e and not self.no_autonegotiation \
and gajim.HAVE_PYCRYPTO and self.contact.supports(NS_ESESSION):
self.begin_e2e_negotiation()
+ elif not self.session or not self.session.status:
+ self.begin_archiving_negotiation()
else:
self.send_chatstate('active', self.contact)
@@ -2901,7 +2922,7 @@ class ChatControl(ChatControlBase):
else:
self.begin_e2e_negotiation()
- def begin_e2e_negotiation(self):
+ def begin_negotiation(self):
self.no_autonegotiation = True
if not self.session:
@@ -2909,8 +2930,14 @@ class ChatControl(ChatControlBase):
new_sess = gajim.connections[self.account].make_new_session(fjid, type_=self.type_id)
self.set_session(new_sess)
+ def begin_e2e_negotiation(self):
+ self.begin_negotiation()
self.session.negotiate_e2e(False)
+ def begin_archiving_negotiation(self):
+ self.begin_negotiation()
+ self.session.negotiate_archiving()
+
def got_connected(self):
ChatControlBase.got_connected(self)
# Refreshing contact
diff --git a/src/common/config.py b/src/common/config.py
index 3473d6f7e..c9cb20121 100644
--- a/src/common/config.py
+++ b/src/common/config.py
@@ -371,6 +371,7 @@ class Config:
'send_idle_time': [ opt_bool, True ],
'roster_version': [opt_str, ''],
'subscription_request_msg': [opt_str, '', _('Message that is sent to contacts you want to add')],
+ 'last_archiving_time': [opt_str, '1970-01-01T00:00:00Z', _('Last time we syncronized with logs from server.')],
}, {}),
'statusmsg': ({
'message': [ opt_str, '' ],
diff --git a/src/common/connection.py b/src/common/connection.py
index 0e281d5a7..55593c9e3 100644
--- a/src/common/connection.py
+++ b/src/common/connection.py
@@ -153,6 +153,7 @@ class CommonConnection:
self.privacy_rules_supported = False
self.vcard_supported = False
self.private_storage_supported = False
+ self.archive_pref_supported = False
self.muc_jid = {} # jid of muc server for each transport type
self._stun_servers = [] # STUN servers of our jabber server
@@ -1541,6 +1542,9 @@ class Connection(CommonConnection, ConnectionHandlers):
self.connection.set_send_timeout(self.keepalives, self.send_keepalive)
self.connection.set_send_timeout2(self.pingalives, self.sendPing)
self.connection.onreceive(None)
+
+ self.request_message_archiving_preferences()
+
self.discoverInfo(gajim.config.get_per('accounts', self.name, 'hostname'),
id_prefix='Gajim_')
self.privacy_rules_requested = False
diff --git a/src/common/connection_handlers.py b/src/common/connection_handlers.py
index 15063324d..9ab712818 100644
--- a/src/common/connection_handlers.py
+++ b/src/common/connection_handlers.py
@@ -51,6 +51,11 @@ from common.pubsub import ConnectionPubSub
from common.pep import ConnectionPEP
from common.protocol.caps import ConnectionCaps
from common.protocol.bytestream import ConnectionSocks5Bytestream
+from common.message_archiving import ConnectionArchive
+from common.message_archiving import ARCHIVING_COLLECTIONS_ARRIVED
+from common.message_archiving import ARCHIVING_COLLECTION_ARRIVED
+from common.message_archiving import ARCHIVING_MODIFICATIONS_ARRIVED
+
from common import ged
from common import nec
from common.nec import NetworkEvent
@@ -363,6 +368,14 @@ class ConnectionDisco:
our_jid = gajim.get_jid_from_account(self.name)
self.send_pb_purge(our_jid, 'storage:bookmarks')
self.send_pb_delete(our_jid, 'storage:bookmarks')
+ if features.__contains__(common.xmpp.NS_ARCHIVE_AUTO):
+ self.archive_auto_supported = True
+ if features.__contains__(common.xmpp.NS_ARCHIVE_MANAGE):
+ self.archive_manage_supported = True
+ if features.__contains__(common.xmpp.NS_ARCHIVE_MANUAL):
+ self.archive_manual_supported = True
+ if features.__contains__(common.xmpp.NS_ARCHIVE_PREF):
+ self.archive_pref_supported = True
if features.__contains__(common.xmpp.NS_BYTESTREAM):
our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name) +\
'/' + self.server_resource)
@@ -701,6 +714,71 @@ class ConnectionVcard:
form = common.dataforms.ExtendForm(node=form_tag)
self.dispatch('PEP_CONFIG', (node, form))
+ elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTIONS_ARRIVED:
+ # TODO
+ print 'ARCHIVING_COLLECTIONS_ARRIVED'
+ pass
+
+ elif self.awaiting_answers[id_][0] == ARCHIVING_COLLECTION_ARRIVED:
+ def save_if_not_exists(with_, nick, direction, tim, payload):
+ assert len(payload) == 1, 'got several archiving messages in' +\
+ ' the same time %s' % ''.join(payload)
+ if payload[0].getName() == 'body':
+ gajim.logger.save_if_not_exists(with_, direction, tim,
+ msg=payload[0].getData(), nick=nick)
+ elif payload[0].getName() == 'message':
+ print 'Not implemented'
+ chat = iq_obj.getTag('chat')
+ if chat:
+ with_ = chat.getAttr('with')
+ start_ = chat.getAttr('start')
+ tim = helpers.datetime_tuple(start_)
+ tim = timegm(tim)
+ nb = 0
+ for element in chat.getChildren():
+ try:
+ secs = int(element.getAttr('secs'))
+ except TypeError:
+ secs = 0
+ if secs:
+ tim += secs
+ nick = element.getAttr('name')
+ if element.getName() == 'from':
+ save_if_not_exists(with_, nick, 'from', localtime(tim),
+ element.getPayload())
+ nb += 1
+ if element.getName() == 'to':
+ save_if_not_exists(with_, nick, 'to', localtime(tim),
+ element.getPayload())
+ nb += 1
+ set_ = chat.getTag('set')
+ first = set_.getTag('first')
+ if first:
+ try:
+ index = int(first.getAttr('index'))
+ except TypeError:
+ index = 0
+ try:
+ count = int(set_.getTagData('count'))
+ except TypeError:
+ count = 0
+ if count > index + nb:
+ # Request the next page
+ after = element.getTagData('last')
+ self.request_collection_page(with_, start_, after=after)
+
+ elif self.awaiting_answers[id_][0] == ARCHIVING_MODIFICATIONS_ARRIVED:
+ modified = iq_obj.getTag('modified')
+ if modified:
+ for element in modified.getChildren():
+ if element.getName() == 'changed':
+ with_ = element.getAttr('with')
+ start_ = element.getAttr('start')
+ self.request_collection_page(with_, start_)
+ elif element.getName() == 'removed':
+ # do nothing
+ pass
+
del self.awaiting_answers[id_]
def _vCardCB(self, con, vc):
@@ -955,11 +1033,13 @@ class ConnectionHandlersBase:
return sess
-class ConnectionHandlers(ConnectionVcard, ConnectionSocks5Bytestream,
-ConnectionDisco, ConnectionCommands, ConnectionPubSub, ConnectionPEP,
-ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
+class ConnectionHandlers(ConnectionArchive, ConnectionVcard,
+ConnectionSocks5Bytestream, ConnectionDisco, ConnectionCommands,
+ConnectionPubSub, ConnectionPEP, ConnectionCaps, ConnectionHandlersBase,
+ConnectionJingle):
def __init__(self):
global HAS_IDLE
+ ConnectionArchive.__init__(self)
ConnectionVcard.__init__(self)
ConnectionSocks5Bytestream.__init__(self)
ConnectionCommands.__init__(self)
@@ -2303,6 +2383,7 @@ ConnectionCaps, ConnectionHandlersBase, ConnectionJingle):
common.xmpp.NS_SEARCH)
con.RegisterHandler('iq', self._PrivacySetCB, 'set',
common.xmpp.NS_PRIVACY)
+ con.RegisterHandler('iq', self._ArchiveCB, ns=common.xmpp.NS_ARCHIVE)
con.RegisterHandler('iq', self._PubSubCB, 'result')
con.RegisterHandler('iq', self._PubSubErrorCB, 'error')
con.RegisterHandler('iq', self._JingleCB, 'result')
diff --git a/src/common/logger.py b/src/common/logger.py
index 2a3ffbf86..4998eae2a 100644
--- a/src/common/logger.py
+++ b/src/common/logger.py
@@ -45,6 +45,9 @@ LOG_DB_PATH = configpaths.gajimpaths['LOG_DB']
LOG_DB_FOLDER, LOG_DB_FILE = os.path.split(LOG_DB_PATH)
CACHE_DB_PATH = configpaths.gajimpaths['CACHE_DB']
+import logging
+log = logging.getLogger('gajim.c.logger')
+
class Constants:
def __init__(self):
(
@@ -142,7 +145,7 @@ class Logger:
try:
self.cur.execute("ATTACH DATABASE '%s' AS cache" % CACHE_DB_PATH)
except sqlite.Error, e:
- gajim.log.debug("Failed to attach cache database: %s" % str(e))
+ log.debug("Failed to attach cache database: %s" % str(e))
def set_synchronous(self, sync):
try:
@@ -151,7 +154,7 @@ class Logger:
else:
self.cur.execute("PRAGMA synchronous = OFF")
except sqlite.Error, e:
- gajim.log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e)))
+ log.debug("Failed to set_synchronous(%s): %s" % (sync, str(e)))
def init_vars(self):
self.open_db()
@@ -1053,3 +1056,48 @@ class Logger:
self.cur.execute('DELETE FROM roster_group WHERE account_jid_id=?',
(account_jid_id,))
self.con.commit()
+
+ def save_if_not_exists(self, with_, direction, tim, msg='', nick=None):
+ if tim:
+ time_col = int(float(time.mktime(tim)))
+ else:
+ time_col = int(float(time.time()))
+ if msg:
+ if self.jid_is_from_pm(with_) or nick:
+ # It's a groupchat message
+ if nick:
+ # It's a message from a groupchat occupent
+ type_ = 'gc_msg'
+ with_ = with_ + '/' + nick
+ else:
+ # It's a server message message, we don't log them
+ return
+ else:
+ if direction == 'from':
+ type_ = 'chat_msg_recv'
+ elif direction == 'to':
+ type_ = 'chat_msg_sent'
+ jid_id = self.get_jid_id(with_)
+ where_sql = 'jid_id = %s AND message=?' % jid_id
+ if type_ == 'gc_msg':
+ # We cannot differentiate gc message and pm messages, so look in
+ # both logs
+ with_2 = gajim.get_jid_without_resource(with_)
+ if with_ != with_2:
+ jid_id2 = self.get_jid_id(with_2)
+ where_sql = 'jid_id in (%s, %s) AND message=?' % (jid_id,
+ jid_id2)
+ start_time = time_col - 300 # 5 minutes arrount given time
+ end_time = time_col + 300 # 5 minutes arrount given time
+ self.cur.execute('''
+ SELECT log_line_id FROM logs
+ WHERE (%s)
+ AND time BETWEEN %d AND %d
+ ORDER BY time
+ ''' % (where_sql, start_time, end_time), (msg,))
+ results = self.cur.fetchall()
+ if results:
+ log.debug('Log already in DB, ignoring it')
+ return
+ log.debug('New log received from server archives, storing it')
+ self.write(type_, with_, message=msg, tim=tim)
diff --git a/src/common/message_archiving.py b/src/common/message_archiving.py
new file mode 100644
index 000000000..548240459
--- /dev/null
+++ b/src/common/message_archiving.py
@@ -0,0 +1,258 @@
+# -*- coding:utf-8 -*-
+## src/common/message_archiving.py
+##
+## Copyright (C) 2009 Anaƫl Verrier <elghinn AT free.fr>
+##
+## This file is part of Gajim.
+##
+## Gajim is free software; you can redistribute it and/or modify
+## it under the terms of the GNU General Public License as published
+## by the Free Software Foundation; version 3 only.
+##
+## Gajim is distributed in the hope that it will be useful,
+## but WITHOUT ANY WARRANTY; without even the implied warranty of
+## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+## GNU General Public License for more details.
+##
+## You should have received a copy of the GNU General Public License
+## along with Gajim. If not, see <http://www.gnu.org/licenses/>.
+##
+
+import common.xmpp
+
+import logging
+log = logging.getLogger('gajim.c.message_archiving')
+
+ARCHIVING_COLLECTIONS_ARRIVED = 'archiving_collections_arrived'
+ARCHIVING_COLLECTION_ARRIVED = 'archiving_collection_arrived'
+ARCHIVING_MODIFICATIONS_ARRIVED = 'archiving_modifications_arrived'
+
+class ConnectionArchive:
+ def __init__(self):
+ self.archive_auto_supported = False
+ self.archive_manage_supported = False
+ self.archive_manual_supported = False
+ self.archive_pref_supported = False
+ self.auto = None
+ self.method_auto = None
+ self.method_local = None
+ self.method_manual = None
+ self.default = None
+ self.items = {}
+
+ def request_message_archiving_preferences(self):
+ iq_ = common.xmpp.Iq('get')
+ iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE)
+ self.connection.send(iq_)
+
+ def set_pref(self, name, **data):
+ '''
+ data contains names and values of pref name attributes.
+ '''
+ iq_ = common.xmpp.Iq('set')
+ pref = iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE)
+ tag = pref.setTag(name)
+ for key, value in data.items():
+ if value is not None:
+ tag.setAttr(key, value)
+ self.connection.send(iq_)
+
+ def set_auto(self, save):
+ self.set_pref('auto', save=save)
+
+ def set_method(self, type, use):
+ self.set_pref('method', type=type, use=use)
+
+ def set_default(self, otr, save, expire=None):
+ self.set_pref('default', otr=otr, save=save, expire=expire)
+
+ def append_or_update_item(self, jid, otr, save, expire):
+ self.set_pref('item', jid=jid, otr=otr, save=save)
+
+ def remove_item(self, jid):
+ iq_ = common.xmpp.Iq('set')
+ itemremove = iq_.setTag('itemremove', namespace=common.xmpp.NS_ARCHIVE)
+ item = itemremove.setTag('item')
+ item.setAttr('jid', jid)
+ self.connection.send(iq_)
+
+ def stop_archiving_session(self, thread_id):
+ iq_ = common.xmpp.Iq('set')
+ pref = iq_.setTag('pref', namespace=common.xmpp.NS_ARCHIVE)
+ session = pref.setTag('session', attrs={'thread': thread_id,
+ 'save': 'false', 'otr': 'concede'})
+ self.connection.send(iq_)
+
+ def get_item_pref(self, jid):
+ jid = common.xmpp.JID(jid)
+ if unicode(jid) in self.items:
+ return self.items[jid]
+
+ if jid.getStripped() in self.items:
+ return self.items[jid.getStripped()]
+
+ if jid.getDomain() in self.items:
+ return self.items[jid.getDomain()]
+
+ return self.default
+
+ def logging_preference(self, jid, initiator_options=None):
+ otr = self.get_item_pref(jid)['otr']
+ if initiator_options:
+ if ((initiator_options == ['mustnot'] and otr == 'forbid') or
+ (initiator_options == ['may'] and otr == 'require')):
+ return None
+
+ if (initiator_options == ['mustnot'] or
+ (initiator_options[0] == 'mustnot' and
+ otr not in ('opppose', 'forbid')) or
+ (initiator_options == ['may', 'mustnot'] and
+ otr in ('require', 'prefer'))):
+ return 'mustnot'
+
+ return 'may'
+
+ if otr == 'require':
+ return ['mustnot']
+
+ if otr in ('prefer', 'approve'):
+ return ['mustnot', 'may']
+
+ if otr in ('concede', 'oppose'):
+ return ['may', 'mustnot']
+
+ # otr == 'forbid'
+ return ['may']
+
+ def _ArchiveCB(self, con, iq_obj):
+ log.debug('_ArchiveCB %s' % iq_obj.getType())
+ if iq_obj.getType() == 'error':
+ self.dispatch('ARCHIVING_ERROR', iq_obj.getErrorMsg())
+ return
+ elif iq_obj.getType() not in ('result', 'set'):
+ return
+
+ if iq_obj.getTag('pref'):
+ pref = iq_obj.getTag('pref')
+
+ if pref.getTag('auto'):
+ self.auto = pref.getTagAttr('auto', 'save')
+ log.debug('archiving preference: auto: %s' % self.auto)
+ self.dispatch('ARCHIVING_CHANGED', ('auto',
+ self.auto))
+
+ method_auto = pref.getTag('method', attrs={'type': 'auto'})
+ if method_auto:
+ self.method_auto = method_auto.getAttr('use')
+ self.dispatch('ARCHIVING_CHANGED', ('method_auto',
+ self.method_auto))
+
+ method_local = pref.getTag('method', attrs={'type': 'local'})
+ if method_local:
+ self.method_local = method_local.getAttr('use')
+ self.dispatch('ARCHIVING_CHANGED', ('method_local',
+ self.method_local))
+
+ method_manual = pref.getTag('method', attrs={'type': 'manual'})
+ if method_manual:
+ self.method_manual = method_manual.getAttr('use')
+ self.dispatch('ARCHIVING_CHANGED', ('method_manual',
+ self.method_manual))
+
+ log.debug('archiving preferences: method auto: %s, local: %s, '
+ 'manual: %s' % (self.method_auto, self.method_local,
+ self.method_manual))
+
+ if pref.getTag('default'):
+ default = pref.getTag('default')
+ log.debug('archiving preferences: default otr: %s, save: %s, '
+ 'expire: %s, unset: %s' % (default.getAttr('otr'),
+ default.getAttr('save'), default.getAttr('expire'),
+ default.getAttr('unset')))
+ self.default = {
+ 'expire': default.getAttr('expire'),
+ 'otr': default.getAttr('otr'),
+ 'save': default.getAttr('save'),
+ 'unset': default.getAttr('unset')}
+ self.dispatch('ARCHIVING_CHANGED', ('default',
+ self.default))
+ for item in pref.getTags('item'):
+ log.debug('archiving preferences for jid %s: otr: %s, save: %s, '
+ 'expire: %s' % (item.getAttr('jid'), item.getAttr('otr'),
+ item.getAttr('save'), item.getAttr('expire')))
+ self.items[item.getAttr('jid')] = {
+ 'expire': item.getAttr('expire'),
+ 'otr': item.getAttr('otr'), 'save': item.getAttr('save')}
+ self.dispatch('ARCHIVING_CHANGED', ('item',
+ item.getAttr('jid'), self.items[item.getAttr('jid')]))
+ elif iq_obj.getTag('itemremove'):
+ for item in pref.getTags('item'):
+ del self.items[item.getAttr('jid')]
+ self.dispatch('ARCHIVING_CHANGED', ('itemremove',
+ item.getAttr('jid')))
+
+ raise common.xmpp.NodeProcessed
+
+ def request_collections_list_page(self, with_='', start=None, end=None,
+ after=None, max=30, exact_match=False):
+ iq_ = common.xmpp.Iq('get')
+ list_ = iq_.setTag('list', namespace=common.xmpp.NS_ARCHIVE)
+ if with_:
+ list_.setAttr('with', with_)
+ if exact_match:
+ list_.setAttr('exactmatch', 'true')
+ if start:
+ list_.setAttr('start', start)
+ if end:
+ list_.setAttr('end', end)
+ set_ = list_.setTag('set', namespace=common.xmpp.NS_RSM)
+ set_.setTagData('max', max)
+ if after:
+ set_.setTagData('after', after)
+ id_ = self.connection.getAnID()
+ iq_.setID(id_)
+ self.awaiting_answers[id_] = (ARCHIVING_COLLECTIONS_ARRIVED, )
+ self.connection.send(iq_)
+
+ def request_collection_page(self, with_, start, end=None, after=None,
+ max=30, exact_match=False):
+ iq_ = common.xmpp.Iq('get')
+ retrieve = iq_.setTag('retrieve', namespace=common.xmpp.NS_ARCHIVE,
+ attrs={'with': with_, 'start': start})
+ if exact_match:
+ retrieve.setAttr('exactmatch', 'true')
+ set_ = retrieve.setTag('set', namespace=common.xmpp.NS_RSM)
+ set_.setTagData('max', max)
+ if after:
+ set_.setTagData('after', after)
+ id_ = self.connection.getAnID()
+ iq_.setID(id_)
+ self.awaiting_answers[id_] = (ARCHIVING_COLLECTION_ARRIVED, )
+ self.connection.send(iq_)
+
+ def remove_collection(self, with_='', start=None, end=None,
+ exact_match=False, open=False):
+ iq_ = common.xmpp.Iq('set')
+ remove = iq_.setTag('remove', namespace=common.xmpp.NS_ARCHIVE)
+ if with_:
+ remove.setAttr('with', with_)
+ if exact_match:
+ remove.setAttr('exactmatch', 'true')
+ if start:
+ remove.setAttr('start', start)
+ if end:
+ remove.setAttr('end', end)
+ if open:
+ remove.setAttr('open', 'true')
+ self.connection.send(iq_)
+
+ def request_modifications_page(self, start, max=30):
+ iq_ = common.xmpp.Iq('get')
+ moified = iq_.setTag('modified', namespace=common.xmpp.NS_ARCHIVE,
+ attrs={'start': start})
+ set_ = moified.setTag('set', namespace=common.xmpp.NS_RSM)
+ set_.setTagData('max', max)
+ id_ = self.connection.getAnID()
+ iq_.setID(id_)
+ self.awaiting_answers[id_] = (ARCHIVING_MODIFICATIONS_ARRIVED, )
+ self.connection.send(iq_)
diff --git a/src/common/stanza_session.py b/src/common/stanza_session.py
index 028bee596..c5bcf09b5 100644
--- a/src/common/stanza_session.py
+++ b/src/common/stanza_session.py
@@ -175,7 +175,123 @@ class StanzaSession(object):
self.status = None
-class EncryptedStanzaSession(StanzaSession):
+class ArchivingStanzaSession(StanzaSession):
+ def __init__(self, conn, jid, thread_id, type_='chat'):
+ StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
+ self.archiving = False
+
+ def archiving_logging_preference(self, initiator_options=None):
+ return self.conn.logging_preference(self.jid, initiator_options)
+
+ def negotiate_archiving(self):
+ self.negotiated = {}
+
+ request = xmpp.Message()
+ feature = request.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
+
+ x = xmpp.DataForm(typ='form')
+
+ x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn',
+ typ='hidden'))
+ x.addChild(node=xmpp.DataField(name='accept', value='1', typ='boolean',
+ required=True))
+
+ x.addChild(node=xmpp.DataField(name='logging', typ='list-single',
+ options=self.archiving_logging_preference(), required=True))
+
+ x.addChild(node=xmpp.DataField(name='disclosure', typ='list-single',
+ options=['never'], required=True))
+ x.addChild(node=xmpp.DataField(name='security', typ='list-single',
+ options=['none'], required=True))
+
+ feature.addChild(node=x)
+
+ self.status = 'requested-archiving'
+
+ self.send(request)
+
+ def respond_archiving(self, form):
+ field = form.getField('logging')
+ options = [x[1] for x in field.getOptions()]
+ values = field.getValues()
+
+ logging = self.archiving_logging_preference(options)
+ self.negotiated['logging'] = logging
+
+ response = xmpp.Message()
+ feature = response.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
+
+ x = xmpp.DataForm(typ='submit')
+
+ x.addChild(node=xmpp.DataField(name='FORM_TYPE', value='urn:xmpp:ssn'))
+ x.addChild(node=xmpp.DataField(name='accept', value='true'))
+
+ x.addChild(node=xmpp.DataField(name='logging', value=logging))
+
+ self.status = 'responded-archiving'
+
+ feature.addChild(node=x)
+
+ if not logging:
+ response = xmpp.Error(response, xmpp.ERR_NOT_ACCEPTABLE)
+
+ feature = xmpp.Node(xmpp.NS_FEATURE + ' feature')
+
+ n = xmpp.Node('field')
+ n['var'] = 'logging'
+ feature.addChild(node=n)
+
+ response.T.error.addChild(node=feature)
+
+ self.send(response)
+
+ def we_accept_archiving(self, form):
+ if self.negotiated['logging'] == 'mustnot':
+ self.loggable = False
+ log.debug('archiving session accepted: %s' % self.loggable)
+ self.status = 'active'
+ self.archiving = True
+ if self.control:
+ self.control.print_archiving_session_details()
+
+ def archiving_accepted(self, form):
+ negotiated = {}
+ ask_user = {}
+ not_acceptable = []
+
+ if form['logging'] not in self.archiving_logging_preference():
+ raise
+
+ self.negotiated['logging'] = form['logging']
+
+ accept = xmpp.Message()
+ feature = accept.NT.feature
+ feature.setNamespace(xmpp.NS_FEATURE)
+
+ result = xmpp.DataForm(typ='result')
+
+ result.addChild(node=xmpp.DataField(name='FORM_TYPE',
+ value='urn:xmpp:ssn'))
+ result.addChild(node=xmpp.DataField(name='accept', value='1'))
+
+ feature.addChild(node=result)
+
+ self.send(accept)
+ if self.negotiated['logging'] == 'mustnot':
+ self.loggable = False
+ log.debug('archiving session accepted: %s' % self.loggable)
+ self.status = 'active'
+ self.archiving = True
+ if self.control:
+ self.control.print_archiving_session_details()
+
+ def stop_archiving_for_session(self):
+ self.conn.stop_archiving_session(self.thread_id)
+
+
+class EncryptedStanzaSession(ArchivingStanzaSession):
"""
An encrypted stanza negotiation has several states. They arerepresented as
the following values in the 'status' attribute of the session object:
@@ -202,7 +318,8 @@ class EncryptedStanzaSession(StanzaSession):
"""
def __init__(self, conn, jid, thread_id, type_='chat'):
- StanzaSession.__init__(self, conn, jid, thread_id, type_='chat')
+ ArchivingStanzaSession.__init__(self, conn, jid, thread_id,
+ type_='chat')
self.xes = {}
self.es = {}
@@ -921,6 +1038,8 @@ class EncryptedStanzaSession(StanzaSession):
if self.control:
self.control.print_esession_details()
+ self.stop_archiving_for_session()
+
def final_steps_alice(self, form):
srs = ''
srses = secrets.secrets().retained_secrets(self.conn.name,
@@ -961,6 +1080,8 @@ class EncryptedStanzaSession(StanzaSession):
if self.control:
self.control.print_esession_details()
+ self.stop_archiving_for_session()
+
def do_retained_secret(self, k, old_srs):
"""
Calculate the new retained secret. determine if the user needs to check
diff --git a/src/common/xmpp/protocol.py b/src/common/xmpp/protocol.py
index eb06c6a28..c218514b5 100644
--- a/src/common/xmpp/protocol.py
+++ b/src/common/xmpp/protocol.py
@@ -28,6 +28,11 @@ NS_ADDRESS ='http://jabber.org/protocol/address'
NS_AGENTS ='jabber:iq:agents'
NS_AMP ='http://jabber.org/protocol/amp'
NS_AMP_ERRORS =NS_AMP+'#errors'
+NS_ARCHIVE ='urn:xmpp:archive' #XEP-0136
+NS_ARCHIVE_AUTO =NS_ARCHIVE+':auto' #XEP-0136
+NS_ARCHIVE_MANAGE =NS_ARCHIVE+':manage' #XEP-0136
+NS_ARCHIVE_MANUAL =NS_ARCHIVE+':manual' #XEP-0136
+NS_ARCHIVE_PREF =NS_ARCHIVE+':pref'
NS_ATOM ='http://www.w3.org/2005/Atom'
NS_AUTH ='jabber:iq:auth'
NS_AVATAR ='http://www.xmpp.org/extensions/xep-0084.html#ns-metadata'
@@ -102,6 +107,7 @@ NS_ROSTER ='jabber:iq:roster'
NS_ROSTERX ='http://jabber.org/protocol/rosterx' # XEP-0144
NS_ROSTER_VER ='urn:xmpp:features:rosterver' # XEP-0273
NS_RPC ='jabber:iq:rpc' # XEP-0009
+NS_RSM ='http://jabber.org/protocol/rsm'
NS_SASL ='urn:ietf:params:xml:ns:xmpp-sasl'
NS_SECLABEL ='urn:xmpp:sec-label:0'
NS_SECLABEL_CATALOG ='urn:xmpp:sec-label:catalog:0'
diff --git a/src/dialogs.py b/src/dialogs.py
index a1adc58bb..2fff8c743 100644
--- a/src/dialogs.py
+++ b/src/dialogs.py
@@ -3381,6 +3381,356 @@ class RosterItemExchangeWindow:
self.window.destroy()
+class ItemArchivingPreferencesWindow:
+ otr_name = ('approve', 'concede', 'forbid', 'oppose', 'prefer', 'require')
+ otr_index = dict([(j, i) for i, j in enumerate(otr_name)])
+ save_name = ('body', 'false', 'message', 'stream')
+ save_index = dict([(j, i) for i, j in enumerate(save_name)])
+
+ def __init__(self, account, item):
+ self.account = account
+ self.item = item
+ if self.item and self.item != 'Default':
+ self.item_config = gajim.connections[self.account].items[self.item]
+ else:
+ self.item_config = gajim.connections[self.account].default
+ self.waiting = None
+
+ # Connect to gtk builder
+ self.xml = gtkgui_helpers.get_gtk_builder(
+ 'item_archiving_preferences_window.ui')
+ self.window = self.xml.get_object('item_archiving_preferences_window')
+
+ # Add Widgets
+ for widget_to_add in ('jid_entry', 'expire_entry', 'otr_combobox',
+ 'save_combobox', 'cancel_button', 'ok_button', 'progressbar'):
+ self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
+
+ if self.item:
+ self.jid_entry.set_text(self.item)
+ expire_value = self.item_config['expire'] or ''
+ self.otr_combobox.set_active(self.otr_index[self.item_config['otr']])
+ self.save_combobox.set_active(
+ self.save_index[self.item_config['save']])
+ self.expire_entry.set_text(expire_value)
+
+ self.window.set_title(_('Archiving Preferences for %s') % self.account)
+
+ self.window.show_all()
+ self.progressbar.hide()
+ self.xml.connect_signals(self)
+
+ def update_progressbar(self):
+ if self.waiting:
+ self.progressbar.pulse()
+ return True
+ return False
+
+ def on_otr_combobox_changed(self, widget):
+ otr = self.otr_name[self.otr_combobox.get_active()]
+ if otr == 'require':
+ self.save_combobox.set_active(self.save_index['false'])
+
+ def on_ok_button_clicked(self, widget):
+ # Return directly if operation in progress
+ if self.waiting:
+ return
+
+ item = self.jid_entry.get_text()
+ otr = self.otr_name[self.otr_combobox.get_active()]
+ save = self.save_name[self.save_combobox.get_active()]
+ expire = self.expire_entry.get_text()
+
+ if self.item != 'Default':
+ try:
+ item = helpers.parse_jid(item)
+ except helpers.InvalidFormat, s:
+ pritext = _('Invalid User ID')
+ ErrorDialog(pritext, str(s))
+ return
+
+ if expire:
+ try:
+ if int(expire) < 0 or str(int(expire)) != expire:
+ raise ValueError
+ except ValueError:
+ pritext = _('Invalid expire value')
+ sectext = _('Expire must be a valid positive integer.')
+ ErrorDialog(pritext, sectext)
+ return
+
+ if not (item == self.item and expire == self.item_config['expire'] and
+ otr == self.item_config['otr'] and save == self.item_config['save']):
+ if not self.item or self.item == item:
+ if self.item == 'Default':
+ self.waiting = 'default'
+ gajim.connections[self.account].set_default(
+ otr, save, expire)
+ else:
+ self.waiting = 'item'
+ gajim.connections[self.account].append_or_update_item(
+ item, otr, save, expire)
+ else:
+ self.waiting = 'item'
+ gajim.connections[self.account].append_or_update_item(
+ item, otr, save, expire)
+ gajim.connections[self.account].remove_item(self.item)
+ self.launch_progressbar()
+ #self.window.destroy()
+
+ def on_cancel_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_item_archiving_preferences_window_destroy(self, widget):
+ if self.item:
+ key_name = 'edit_item_archiving_preferences_%s' % self.item
+ else:
+ key_name = 'new_item_archiving_preferences'
+ if key_name in gajim.interface.instances[self.account]:
+ del gajim.interface.instances[self.account][key_name]
+
+ def launch_progressbar(self):
+ self.progressbar.show()
+ self.update_progressbar_timeout_id = gobject.timeout_add(
+ 100, self.update_progressbar)
+
+ def response_arrived(self, data):
+ if self.waiting:
+ self.window.destroy()
+
+ def error_arrived(self, error):
+ if self.waiting:
+ self.waiting = None
+ self.progressbar.hide()
+ pritext = _('There is an error with the form')
+ sectext = error
+ ErrorDialog(pritext, sectext)
+
+
+class ArchivingPreferencesWindow:
+ auto_name = ('false', 'true')
+ auto_index = dict([(j, i) for i, j in enumerate(auto_name)])
+ method_foo_name = ('prefer', 'concede', 'forbid')
+ method_foo_index = dict([(j, i) for i, j in enumerate(method_foo_name)])
+
+ def __init__(self, account):
+ self.account = account
+ self.waiting = []
+
+ # Connect to glade
+ self.xml = gtkgui_helpers.get_gtk_builder(
+ 'archiving_preferences_window.ui')
+ self.window = self.xml.get_object('archiving_preferences_window')
+
+ # Add Widgets
+ for widget_to_add in ('auto_combobox', 'method_auto_combobox',
+ 'method_local_combobox', 'method_manual_combobox', 'close_button',
+ 'item_treeview', 'item_notebook', 'otr_combobox', 'save_combobox',
+ 'expire_entry', 'remove_button', 'edit_button'):
+ self.__dict__[widget_to_add] = self.xml.get_object(widget_to_add)
+
+ self.auto_combobox.set_active(
+ self.auto_index[gajim.connections[self.account].auto])
+ self.method_auto_combobox.set_active(
+ self.method_foo_index[gajim.connections[self.account].method_auto])
+ self.method_local_combobox.set_active(
+ self.method_foo_index[gajim.connections[self.account].method_local])
+ self.method_manual_combobox.set_active(
+ self.method_foo_index[gajim.connections[self.account].\
+ method_manual])
+
+ model = gtk.ListStore(str, str, str, str)
+ self.item_treeview.set_model(model)
+ col = gtk.TreeViewColumn('jid')
+ self.item_treeview.append_column(col)
+ renderer = gtk.CellRendererText()
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text=0)
+
+ col = gtk.TreeViewColumn('expire')
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text=1)
+ self.item_treeview.append_column(col)
+
+ col = gtk.TreeViewColumn('otr')
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text=2)
+ self.item_treeview.append_column(col)
+
+ col = gtk.TreeViewColumn('save')
+ col.pack_start(renderer, True)
+ col.set_attributes(renderer, text=3)
+ self.item_treeview.append_column(col)
+
+ self.fill_items()
+
+ self.current_item = None
+
+ def sort_items(model, iter1, iter2):
+ item1 = model.get_value(iter1, 0)
+ item2 = model.get_value(iter2, 0)
+ if item1 == 'Default':
+ return -1
+ if item2 == 'Default':
+ return 1
+ if '@' in item1:
+ if '@' not in item2:
+ return 1
+ elif '@' in item2:
+ return -1
+ if item1 < item2:
+ return -1
+ if item1 > item2:
+ return 1
+ # item1 == item2 ? WTF?
+ return 0
+
+ model.set_sort_column_id(0, gtk.SORT_ASCENDING)
+ model.set_sort_func(0, sort_items)
+
+ self.remove_button.set_sensitive(False)
+ self.edit_button.set_sensitive(False)
+
+ self.window.set_title(_('Archiving Preferences for %s') % self.account)
+
+ self.window.show_all()
+
+ self.xml.connect_signals(self)
+
+ def on_add_item_button_clicked(self, widget):
+ key_name = 'new_item_archiving_preferences'
+ if key_name in gajim.interface.instances[self.account]:
+ gajim.interface.instances[self.account][key_name].window.present()
+ else:
+ gajim.interface.instances[self.account][key_name] = \
+ ItemArchivingPreferencesWindow(self.account, '')
+
+ def on_remove_item_button_clicked(self, widget):
+ if not self.current_item:
+ return
+
+ self.waiting.append('itemremove')
+ sel = self.item_treeview.get_selection()
+ (model, iter_) = sel.get_selected()
+ gajim.connections[self.account].remove_item(model[iter_][0])
+ model.remove(iter_)
+ self.remove_button.set_sensitive(False)
+ self.edit_button.set_sensitive(False)
+
+ def on_edit_item_button_clicked(self, widget):
+ if not self.current_item:
+ return
+
+ key_name = 'edit_item_archiving_preferences_%s' % self.current_item
+ if key_name in gajim.interface.instances[self.account]:
+ gajim.interface.instances[self.account][key_name].window.present()
+ else:
+ gajim.interface.instances[self.account][key_name] = \
+ ItemArchivingPreferencesWindow(self.account, self.current_item)
+
+ def on_item_treeview_cursor_changed(self, widget):
+ sel = self.item_treeview.get_selection()
+ (model, iter_) = sel.get_selected()
+ item = None
+ if iter_:
+ item = model[iter_][0]
+ if self.current_item and self.current_item == item:
+ return
+
+ self.current_item = item
+ if self.current_item == 'Default':
+ self.remove_button.set_sensitive(False)
+ self.edit_button.set_sensitive(True)
+ elif self.current_item:
+ self.remove_button.set_sensitive(True)
+ self.edit_button.set_sensitive(True)
+ else:
+ self.remove_button.set_sensitive(False)
+ self.edit_button.set_sensitive(False)
+
+ def on_auto_combobox_changed(self, widget):
+ save = self.auto_name[widget.get_active()]
+ gajim.connections[self.account].set_auto(save)
+
+ def on_method_foo_combobox_changed(self, widget):
+ # We retrieve method type from widget name
+ # ('foo' in 'method_foo_combobox')
+ method_type = widget.name.split('_')[1]
+ use = self.method_foo_name[widget.get_active()]
+ self.waiting.append('method_%s' % method_type)
+ gajim.connections[self.account].set_method(method_type, use)
+
+ def get_child_window(self):
+ edit_key_name = 'edit_item_archiving_preferences_%s' % self.current_item
+ new_key_name = 'new_item_archiving_preferences'
+
+ if edit_key_name in gajim.interface.instances[self.account]:
+ return gajim.interface.instances[self.account][edit_key_name]
+
+ if new_key_name in gajim.interface.instances[self.account]:
+ return gajim.interface.instances[self.account][new_key_name]
+
+ def archiving_changed(self, data):
+ if data[0] in ('auto', 'method_auto', 'method_local', 'method_manual'):
+ if data[0] in self.waiting:
+ self.waiting.remove(data[0])
+ elif data[0] == 'default':
+ key_name = 'edit_item_archiving_preferences_%s' % \
+ self.current_item
+ if key_name in gajim.interface.instances[self.account]:
+ gajim.interface.instances[self.account][key_name].\
+ response_arrived(data[1:])
+ self.fill_items(True)
+ elif data[0] == 'item':
+ child = self.get_child_window()
+ if child:
+ is_new = not child.item
+ child.response_arrived(data[1:])
+ if is_new:
+ model = self.item_treeview.get_model()
+ model.append((data[1], data[2]['expire'], data[2]['otr'],
+ data[2]['save']))
+ return
+ self.fill_items(True)
+ elif data[0] == 'itemremove' == self.waiting:
+ if data[0] in self.waiting:
+ self.waiting.remove(data[0])
+ self.fill_items(True)
+
+ def fill_items(self, clear=False):
+ model = self.item_treeview.get_model()
+ if clear:
+ model.clear()
+ default_config = gajim.connections[self.account].default
+ expire_value = default_config['expire'] or ''
+ model.append(('Default', expire_value,
+ default_config['otr'], default_config['save']))
+ for item, item_config in \
+ gajim.connections[self.account].items.items():
+ expire_value = item_config['expire'] or ''
+ model.append((item, expire_value, item_config['otr'],
+ item_config['save']))
+
+ def archiving_error(self, error):
+ if self.waiting:
+ pritext = _('There is an error')
+ sectext = error
+ ErrorDialog(pritext, sectext)
+ self.waiting.pop()
+ else:
+ child = self.get_child_window()
+ if child:
+ child.error_arrived(error)
+ print error
+
+ def on_close_button_clicked(self, widget):
+ self.window.destroy()
+
+ def on_archiving_preferences_window_destroy(self, widget):
+ if 'archiving_preferences' in gajim.interface.instances[self.account]:
+ del gajim.interface.instances[self.account]['archiving_preferences']
+
+
class PrivacyListWindow:
"""
Window that is used for creating NEW or EDITING already there privacy lists
diff --git a/src/gui_interface.py b/src/gui_interface.py
index 4059d25b4..ac235fa0a 100644
--- a/src/gui_interface.py
+++ b/src/gui_interface.py
@@ -1573,6 +1573,11 @@ class Interface:
if gajim.connections[account].pep_supported and dbus_support.supported \
and gajim.config.get_per('accounts', account, 'publish_location'):
location_listener.enable()
+ # Start merging logs from server
+ gajim.connections[account].request_modifications_page(
+ gajim.config.get_per('accounts', account, 'last_archiving_time'))
+ gajim.config.set_per('accounts', account, 'last_archiving_time',
+ time.strftime('%Y-%m-%dT%H:%M:%SZ', time.gmtime()))
def handle_event_metacontacts(self, account, tags_list):
gajim.contacts.define_metacontacts(account, tags_list)
@@ -2064,6 +2069,18 @@ class Interface:
if pm_ctrl and hasattr(pm_ctrl, "update_contact"):
pm_ctrl.update_contact()
+ def handle_event_archiving_changed(self, account, data):
+ # ('ARCHIVING_CHANGED', account, (type, value)
+ if 'archiving_preferences' in self.instances[account]:
+ self.instances[account]['archiving_preferences'].archiving_changed(
+ data)
+
+ def handle_event_archiving_error(self, account, data):
+ # ('ARCHIVING_CHANGED', account, (error_msg,))
+ if 'archiving_preferences' in self.instances[account]:
+ self.instances[account]['archiving_preferences'].archiving_error(
+ data)
+
def create_core_handlers_list(self):
self.handlers = {
'ROSTER': [self.handle_event_roster],
@@ -2147,6 +2164,8 @@ class Interface:
'JINGLE_ERROR': [self.handle_event_jingle_error],
'PEP_RECEIVED': [self.handle_event_pep_received],
'CAPS_RECEIVED': [self.handle_event_caps_received],
+ 'ARCHIVING_CHANGED': [self.handle_event_archiving_changed],
+ 'ARCHIVING_ERROR': [self.handle_event_archiving_error],
'gmail-notify': [self.handle_event_gmail_notify],
'http-auth-received': [self.handle_event_http_auth],
'last-result-received': [self.handle_event_last_status_time],
diff --git a/src/message_control.py b/src/message_control.py
index 700ac9485..e74de2ef1 100644
--- a/src/message_control.py
+++ b/src/message_control.py
@@ -30,6 +30,7 @@ import gtkgui_helpers
from common import gajim
from common import helpers
+from common.stanza_session import EncryptedStanzaSession, ArchivingStanzaSession
# Derived types MUST register their type IDs here if custom behavor is required
TYPE_CHAT = 'chat'
@@ -200,11 +201,18 @@ class MessageControl(object):
if self.resource:
jid += '/' + self.resource
- crypto_changed = bool(session and session.enable_encryption) != \
- bool(oldsession and oldsession.enable_encryption)
+ crypto_changed = bool(session and isinstance(session,
+ EncryptedStanzaSession) and session.enable_encryption) != \
+ bool(oldsession and isinstance(oldsession,
+ EncryptedStanzaSession) and oldsession.enable_encryption)
- if crypto_changed:
- self.print_esession_details()
+ archiving_changed = bool(session and isinstance(session,
+ ArchivingStanzaSession) and session.archiving) != \
+ bool(oldsession and isinstance(oldsession,
+ ArchivingStanzaSession) and oldsession.archiving)
+
+ if crypto_changed or archiving_changed:
+ self.print_session_details()
def send_message(self, message, keyID='', type_='chat', chatstate=None,
msg_id=None, composing_xep=None, resource=None, user_nick=None,
diff --git a/src/roster_window.py b/src/roster_window.py
index b4848ec70..2a81bf507 100644
--- a/src/roster_window.py
+++ b/src/roster_window.py
@@ -2447,6 +2447,14 @@ class RosterWindow:
gajim.interface.instances[account]['xml_console'] = \
dialogs.XMLConsoleWindow(account)
+ def on_archiving_preferences_menuitem_activate(self, widget, account):
+ if 'archiving_preferences' in gajim.interface.instances[account]:
+ gajim.interface.instances[account]['archiving_preferences'].window.\
+ present()
+ else:
+ gajim.interface.instances[account]['archiving_preferences'] = \
+ dialogs.ArchivingPreferencesWindow(account)
+
def on_privacy_lists_menuitem_activate(self, widget, account):
if 'privacy_lists' in gajim.interface.instances[account]:
gajim.interface.instances[account]['privacy_lists'].window.present()
@@ -5710,6 +5718,8 @@ class RosterWindow:
advanced_menuitem_menu = xml.get_object('advanced_menuitem_menu')
xml_console_menuitem = xml.get_object('xml_console_menuitem')
+ archiving_preferences_menuitem = xml.get_object(
+ 'archiving_preferences_menuitem')
privacy_lists_menuitem = xml.get_object('privacy_lists_menuitem')
administrator_menuitem = xml.get_object('administrator_menuitem')
send_server_message_menuitem = xml.get_object(
@@ -5721,12 +5731,17 @@ class RosterWindow:
xml_console_menuitem.connect('activate',
self.on_xml_console_menuitem_activate, account)
- if gajim.connections[account] and gajim.connections[account].\
- privacy_rules_supported:
- privacy_lists_menuitem.connect('activate',
- self.on_privacy_lists_menuitem_activate, account)
- else:
- privacy_lists_menuitem.set_sensitive(False)
+ if gajim.connections[account]:
+ if gajim.connections[account].privacy_rules_supported:
+ privacy_lists_menuitem.connect('activate',
+ self.on_privacy_lists_menuitem_activate, account)
+ else:
+ privacy_lists_menuitem.set_sensitive(False)
+ if gajim.connections[account].archive_pref_supported:
+ archiving_preferences_menuitem.connect('activate',
+ self.on_archiving_preferences_menuitem_activate, account)
+ else:
+ archiving_preferences_menuitem.set_sensitive(False)
if gajim.connections[account].is_zeroconf:
administrator_menuitem.set_sensitive(False)
diff --git a/src/session.py b/src/session.py
index 2396f9883..30693ed1d 100644
--- a/src/session.py
+++ b/src/session.py
@@ -413,31 +413,40 @@ class ChatControlSession(stanza_session.EncryptedStanzaSession):
# encrypted session states. these are described in stanza_session.py
try:
- # bob responds
if form.getType() == 'form' and 'security' in form.asDict():
- # we don't support 3-message negotiation as the responder
- if 'dhkeys' in form.asDict():
- self.fail_bad_negotiation('3 message negotiation not supported '
- 'when responding', ('dhkeys',))
- return
-
- negotiated, not_acceptable, ask_user = self.verify_options_bob(form)
-
- if ask_user:
- def accept_nondefault_options(is_checked):
- self.dialog.destroy()
- negotiated.update(ask_user)
- self.respond_e2e_bob(form, negotiated, not_acceptable)
-
- def reject_nondefault_options():
- self.dialog.destroy()
- for key in ask_user.keys():
- not_acceptable.append(key)
- self.respond_e2e_bob(form, negotiated, not_acceptable)
-
- self.dialog = dialogs.YesNoDialog(_('Confirm these session '
- 'options'),
- _('''The remote client wants to negotiate a session with these features:
+ security_options = [x[1] for x in form.getField('security').\
+ getOptions()]
+ if security_options == ['none']:
+ self.respond_archiving(form)
+ else:
+ # bob responds
+
+ # we don't support 3-message negotiation as the responder
+ if 'dhkeys' in form.asDict():
+ self.fail_bad_negotiation('3 message negotiation not '
+ 'supported when responding', ('dhkeys',))
+ return
+
+ negotiated, not_acceptable, ask_user = \
+ self.verify_options_bob(form)
+
+ if ask_user:
+ def accept_nondefault_options(is_checked):
+ self.dialog.destroy()
+ negotiated.update(ask_user)
+ self.respond_e2e_bob(form, negotiated,
+ not_acceptable)
+
+ def reject_nondefault_options():
+ self.dialog.destroy()
+ for key in ask_user.keys():
+ not_acceptable.append(key)
+ self.respond_e2e_bob(form, negotiated,
+ not_acceptable)
+
+ self.dialog = dialogs.YesNoDialog(_('Confirm these '
+ 'session options'), _('''The remote client wants '
+ 'to negotiate an session with these features:
%s
@@ -445,8 +454,17 @@ Are these options acceptable?''') % (negotiation.describe_features(
ask_user)),
on_response_yes=accept_nondefault_options,
on_response_no=reject_nondefault_options)
- else:
- self.respond_e2e_bob(form, negotiated, not_acceptable)
+ else:
+ self.respond_e2e_bob(form, negotiated, not_acceptable)
+
+ return
+
+ elif self.status == 'requested-archiving' and form.getType() == \
+ 'submit':
+ try:
+ self.archiving_accepted(form)
+ except exceptions.NegotiationError, details:
+ self.fail_bad_negotiation(details)
return
@@ -483,6 +501,14 @@ Are these options acceptable?''') % (negotiation.describe_features(
self.fail_bad_negotiation(details)
return
+ elif self.status == 'responded-archiving' and form.getType() == \
+ 'result':
+ try:
+ self.we_accept_archiving(form)
+ except exceptions.NegotiationError, details:
+ self.fail_bad_negotiation(details)
+
+ return
elif self.status == 'responded-e2e' and form.getType() == 'result':
try:
self.accept_e2e_bob(form)