merge message archiving branch. Fixes #3593

This commit is contained in:
Yann Leboulanger 2010-08-11 18:43:41 +02:00
commit ca43e5441c
16 changed files with 1550 additions and 40 deletions

View File

@ -9,6 +9,13 @@
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="archiving_preferences_menuitem">
<property name="visible">True</property>
<property name="label" translatable="yes">Edit Archi_ving Preferences</property>
<property name="use_underline">True</property>
</object>
</child>
<child>
<object class="GtkMenuItem" id="privacy_lists_menuitem">
<property name="label" translatable="yes">Edit _Privacy Lists...</property>

View File

@ -0,0 +1,312 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">No</col>
</row>
<row>
<col id="0" translatable="yes">Yes</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore2">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Prefer</col>
</row>
<row>
<col id="0" translatable="yes">Concede</col>
</row>
<row>
<col id="0" translatable="yes">Forbid</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore3">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Prefer</col>
</row>
<row>
<col id="0" translatable="yes">Concede</col>
</row>
<row>
<col id="0" translatable="yes">Forbid</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore4">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">Prefer</col>
</row>
<row>
<col id="0" translatable="yes">Concede</col>
</row>
<row>
<col id="0" translatable="yes">Forbid</col>
</row>
</data>
</object>
<object class="GtkWindow" id="archiving_preferences_window">
<property name="border_width">12</property>
<signal name="destroy" handler="on_archiving_preferences_window_destroy"/>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTable" id="table1">
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<child>
<object class="GtkLabel" id="label4">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;i&gt;Method Manual&lt;/i&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label3">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;i&gt;Method Local&lt;/i&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label2">
<property name="visible">True</property>
<property name="label" translatable="yes">&lt;i&gt;Method Auto&lt;/i&gt;</property>
<property name="use_markup">True</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="method_manual_combobox">
<property name="visible">True</property>
<property name="model">liststore4</property>
<signal name="changed" handler="on_method_foo_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext4"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="method_local_combobox">
<property name="visible">True</property>
<property name="model">liststore3</property>
<signal name="changed" handler="on_method_foo_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext3"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="method_auto_combobox">
<property name="visible">True</property>
<property name="model">liststore2</property>
<signal name="changed" handler="on_method_foo_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label5">
<property name="visible">True</property>
<property name="label" translatable="yes">Auto</property>
</object>
</child>
<child>
<object class="GtkComboBox" id="auto_combobox">
<property name="visible">True</property>
<property name="model">liststore1</property>
<signal name="changed" handler="on_auto_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkVBox" id="vbox2">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkScrolledWindow" id="scrolledwindow1">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="hscrollbar_policy">automatic</property>
<property name="vscrollbar_policy">automatic</property>
<child>
<object class="GtkTreeView" id="item_treeview">
<property name="visible">True</property>
<property name="can_focus">True</property>
<signal name="cursor_changed" handler="on_item_treeview_cursor_changed"/>
</object>
</child>
</object>
<packing>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox2">
<property name="visible">True</property>
<property name="layout_style">spread</property>
<child>
<object class="GtkButton" id="add_button">
<property name="label">gtk-add</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_add_item_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="remove_button">
<property name="label">gtk-remove</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_remove_item_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton" id="edit_button">
<property name="label">gtk-edit</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_edit_item_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="close_button">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="can_default">True</property>
<property name="receives_default">False</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_close_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">2</property>
</packing>
</child>
</object>
</child>
</object>
</interface>

View File

@ -0,0 +1,227 @@
<?xml version="1.0"?>
<interface>
<requires lib="gtk+" version="2.16"/>
<!-- interface-naming-policy project-wide -->
<object class="GtkWindow" id="item_archiving_preferences_window">
<property name="border_width">12</property>
<signal name="destroy" handler="on_item_archiving_preferences_window_destroy"/>
<child>
<object class="GtkVBox" id="vbox1">
<property name="visible">True</property>
<property name="orientation">vertical</property>
<property name="spacing">12</property>
<child>
<object class="GtkTable" id="table3">
<property name="visible">True</property>
<property name="n_rows">4</property>
<property name="n_columns">2</property>
<child>
<object class="GtkLabel" id="label8">
<property name="visible">True</property>
<property name="label" translatable="yes">expire</property>
</object>
<packing>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkEntry" id="expire_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">1</property>
<property name="bottom_attach">2</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="otr_combobox">
<property name="visible">True</property>
<property name="model">liststore2</property>
<signal name="changed" handler="on_otr_combobox_changed"/>
<child>
<object class="GtkCellRendererText" id="cellrenderertext2"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkComboBox" id="save_combobox">
<property name="visible">True</property>
<property name="model">liststore1</property>
<child>
<object class="GtkCellRendererText" id="cellrenderertext1"/>
<attributes>
<attribute name="text">0</attribute>
</attributes>
</child>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label10">
<property name="visible">True</property>
<property name="label" translatable="yes">save</property>
</object>
<packing>
<property name="top_attach">3</property>
<property name="bottom_attach">4</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label9">
<property name="visible">True</property>
<property name="label" translatable="yes">otr</property>
</object>
<packing>
<property name="top_attach">2</property>
<property name="bottom_attach">3</property>
</packing>
</child>
<child>
<object class="GtkLabel" id="label1">
<property name="visible">True</property>
<property name="label" translatable="yes">jid</property>
</object>
</child>
<child>
<object class="GtkEntry" id="jid_entry">
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="invisible_char">&#x2022;</property>
</object>
<packing>
<property name="left_attach">1</property>
<property name="right_attach">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHBox" id="hbox1">
<property name="visible">True</property>
<child>
<object class="GtkProgressBar" id="progressbar"/>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkHButtonBox" id="hbuttonbox1">
<property name="visible">True</property>
<property name="layout_style">end</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">gtk-cancel</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_cancel_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="ok_button">
<property name="label">gtk-ok</property>
<property name="visible">True</property>
<property name="can_focus">True</property>
<property name="receives_default">True</property>
<property name="use_stock">True</property>
<signal name="clicked" handler="on_ok_button_clicked"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="position">1</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
</object>
<object class="GtkListStore" id="liststore1">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">body</col>
</row>
<row>
<col id="0" translatable="yes">false</col>
</row>
<row>
<col id="0" translatable="yes">message</col>
</row>
<row>
<col id="0" translatable="yes">stream</col>
</row>
</data>
</object>
<object class="GtkListStore" id="liststore2">
<columns>
<!-- column-name item text -->
<column type="gchararray"/>
</columns>
<data>
<row>
<col id="0" translatable="yes">approve</col>
</row>
<row>
<col id="0" translatable="yes">concede</col>
</row>
<row>
<col id="0" translatable="yes">forbid</col>
</row>
<row>
<col id="0" translatable="yes">oppose</col>
</row>
<row>
<col id="0" translatable="yes">prefer</col>
</row>
<row>
<col id="0" translatable="yes">require</col>
</row>
</data>
</object>
</interface>

View File

@ -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

View File

@ -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, '' ],

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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_)

View File

@ -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

View File

@ -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'

View File

@ -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

View File

@ -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],

View File

@ -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,

View File

@ -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)

View File

@ -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
security_options = [x[1] for x in form.getField('security').\
getOptions()]
if security_options == ['none']:
self.respond_archiving(form)
else:
# bob responds
negotiated, not_acceptable, ask_user = self.verify_options_bob(form)
# 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
if ask_user:
def accept_nondefault_options(is_checked):
self.dialog.destroy()
negotiated.update(ask_user)
self.respond_e2e_bob(form, negotiated, not_acceptable)
negotiated, not_acceptable, ask_user = \
self.verify_options_bob(form)
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)
if ask_user:
def accept_nondefault_options(is_checked):
self.dialog.destroy()
negotiated.update(ask_user)
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:
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
@ -482,6 +500,14 @@ Are these options acceptable?''') % (negotiation.describe_features(
except exceptions.NegotiationError, details:
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: