merge elghinn's branch (roster versioning) to trunk. Fixes #4661, #3190

This commit is contained in:
Yann Leboulanger 2009-07-10 15:05:01 +02:00
commit 278a8bc59e
10 changed files with 263 additions and 17 deletions

View File

@ -96,6 +96,22 @@ def create_log_db():
jid_id INTEGER PRIMARY KEY UNIQUE, jid_id INTEGER PRIMARY KEY UNIQUE,
time INTEGER time INTEGER
); );
CREATE TABLE IF NOT EXISTS roster_entry(
account_jid_id INTEGER,
jid_id INTEGER,
name TEXT,
subscription INTEGER,
ask BOOLEAN,
PRIMARY KEY (account_jid_id, jid_id)
);
CREATE TABLE IF NOT EXISTS roster_group(
account_jid_id INTEGER,
jid_id INTEGER,
group_name TEXT,
PRIMARY KEY (account_jid_id, jid_id, group_name)
);
''' '''
) )

View File

@ -336,6 +336,7 @@ class Config:
'ignore_unknown_contacts': [ opt_bool, False ], 'ignore_unknown_contacts': [ opt_bool, False ],
'send_os_info': [ opt_bool, True ], 'send_os_info': [ opt_bool, True ],
'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')], 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')],
'roster_version': [opt_str, ''],
}, {}), }, {}),
'statusmsg': ({ 'statusmsg': ({
'message': [ opt_str, '' ], 'message': [ opt_str, '' ],

View File

@ -1970,6 +1970,11 @@ class Connection(ConnectionHandlers):
self.connection.SendAndCallForResponse(iq, _on_response) self.connection.SendAndCallForResponse(iq, _on_response)
def load_roster_from_db(self):
roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name))
self.dispatch('ROSTER', roster)
# END Connection # END Connection
# vim: se ts=3: # vim: se ts=3:

View File

@ -65,6 +65,7 @@ VCARD_PUBLISHED = 'vcard_published'
VCARD_ARRIVED = 'vcard_arrived' VCARD_ARRIVED = 'vcard_arrived'
AGENT_REMOVED = 'agent_removed' AGENT_REMOVED = 'agent_removed'
METACONTACTS_ARRIVED = 'metacontacts_arrived' METACONTACTS_ARRIVED = 'metacontacts_arrived'
ROSTER_ARRIVED = 'roster_arrived'
PRIVACY_ARRIVED = 'privacy_arrived' PRIVACY_ARRIVED = 'privacy_arrived'
PEP_CONFIG = 'pep_config' PEP_CONFIG = 'pep_config'
HAS_IDLE = True HAS_IDLE = True
@ -1172,7 +1173,18 @@ class ConnectionVcard:
if iq_obj.getErrorCode() not in ('403', '406', '404'): if iq_obj.getErrorCode() not in ('403', '406', '404'):
self.private_storage_supported = False self.private_storage_supported = False
# We can now continue connection by requesting the roster # We can now continue connection by requesting the roster
self.connection.initRoster() version = gajim.config.get_per('accounts', self.name,
'roster_version')
iq_id = self.connection.initRoster(version=version)
self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, )
elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED:
if iq_obj.getType() == 'result':
if not iq_obj.getTag('query'):
account_jid = gajim.get_jid_from_account(self.name)
roster_data = gajim.logger.get_roster(account_jid)
roster = self.connection.getRoster(force=True)
roster.setRaw(roster_data)
self._getRoster()
elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED: elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED:
if iq_obj.getType() != 'error': if iq_obj.getType() != 'error':
self.privacy_rules_supported = True self.privacy_rules_supported = True
@ -1556,6 +1568,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
def _rosterSetCB(self, con, iq_obj): def _rosterSetCB(self, con, iq_obj):
log.debug('rosterSetCB') log.debug('rosterSetCB')
version = iq_obj.getTagAttr('query', 'ver')
for item in iq_obj.getTag('query').getChildren(): for item in iq_obj.getTag('query').getChildren():
try: try:
jid = helpers.parse_jid(item.getAttr('jid')) jid = helpers.parse_jid(item.getAttr('jid'))
@ -1569,6 +1582,12 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
for group in item.getTags('group'): for group in item.getTags('group'):
groups.append(group.getData()) groups.append(group.getData())
self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups)) self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups))
account_jid = gajim.get_jid_from_account(self.name)
gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask,
groups)
if version:
gajim.config.set_per('accounts', self.name, 'roster_version',
version)
if not self.connection or self.connected < 2: if not self.connection or self.connected < 2:
raise common.xmpp.NodeProcessed raise common.xmpp.NodeProcessed
reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()}, reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()},
@ -2484,7 +2503,7 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
self.connection.send(result) self.connection.send(result)
raise common.xmpp.NodeProcessed raise common.xmpp.NodeProcessed
def _getRosterCB(self, con, iq_obj): def _getRoster(self):
log.debug('getRosterCB') log.debug('getRosterCB')
if not self.connection: if not self.connection:
return return
@ -2507,6 +2526,8 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
gajim.proxy65_manager.resolve(proxy, self.connection, our_jid) gajim.proxy65_manager.resolve(proxy, self.connection, our_jid)
def _on_roster_set(self, roster): def _on_roster_set(self, roster):
roster_version = roster.version
received_from_server = roster.received_from_server
raw_roster = roster.getRaw() raw_roster = roster.getRaw()
roster = {} roster = {}
our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name))
@ -2545,7 +2566,16 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
# we can't determine which iconset to use # we can't determine which iconset to use
self.discoverInfo(jid) self.discoverInfo(jid)
self.dispatch('ROSTER', roster) gajim.logger.replace_roster(self.name, roster_version, roster)
if received_from_server:
for contact in gajim.contacts.iter_contacts(self.name):
if not contact.is_groupchat() and contact.jid not in roster:
self.dispatch('ROSTER_INFO', (self.name,
(contact.jid, None, None, None, ())))
for jid in roster:
self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'],
roster[jid]['subscription'], roster[jid]['ask'],
roster[jid]['groups']))
def _send_first_presence(self, signed = ''): def _send_first_presence(self, signed = ''):
show = self.continue_connect_info[0] show = self.continue_connect_info[0]
@ -2689,8 +2719,6 @@ class ConnectionHandlers(ConnectionVcard, ConnectionBytestream, ConnectionDisco,
common.xmpp.NS_MUC_OWNER) common.xmpp.NS_MUC_OWNER)
con.RegisterHandler('iq', self._MucAdminCB, 'result', con.RegisterHandler('iq', self._MucAdminCB, 'result',
common.xmpp.NS_MUC_ADMIN) common.xmpp.NS_MUC_ADMIN)
con.RegisterHandler('iq', self._getRosterCB, 'result',
common.xmpp.NS_ROSTER)
con.RegisterHandler('iq', self._PrivateCB, 'result', con.RegisterHandler('iq', self._PrivateCB, 'result',
common.xmpp.NS_PRIVATE) common.xmpp.NS_PRIVATE)
con.RegisterHandler('iq', self._HttpAuthCB, 'get', con.RegisterHandler('iq', self._HttpAuthCB, 'get',

View File

@ -92,6 +92,13 @@ class Constants:
self.TYPE_MRIM, self.TYPE_MRIM,
) = range(14) ) = range(14)
(
self.SUBSCRIPTION_NONE,
self.SUBSCRIPTION_TO,
self.SUBSCRIPTION_FROM,
self.SUBSCRIPTION_BOTH,
) = range(4)
constants = Constants() constants = Constants()
class Logger: class Logger:
@ -331,6 +338,28 @@ class Logger:
if type_id == constants.TYPE_MRIM: if type_id == constants.TYPE_MRIM:
return 'mrim' return 'mrim'
def convert_human_subscription_values_to_db_api_values(self, sub):
'''converts from string style to constant ints for db'''
if sub == 'none':
return constants.SUBSCRIPTION_NONE
if sub == 'to':
return constants.SUBSCRIPTION_TO
if sub == 'from':
return constants.SUBSCRIPTION_FROM
if sub == 'both':
return constants.SUBSCRIPTION_BOTH
def convert_db_api_values_to_human_subscription_values(self, sub):
'''converts from constant ints for db to string style'''
if sub == constants.SUBSCRIPTION_NONE:
return 'none'
if sub == constants.SUBSCRIPTION_TO:
return 'to'
if sub == constants.SUBSCRIPTION_FROM:
return 'from'
if sub == constants.SUBSCRIPTION_BOTH:
return 'both'
def commit_to_db(self, values, write_unread = False): def commit_to_db(self, values, write_unread = False):
sql = 'INSERT INTO logs (jid_id, contact_name, time, kind, show, message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)' sql = 'INSERT INTO logs (jid_id, contact_name, time, kind, show, message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)'
try: try:
@ -799,4 +828,112 @@ class Logger:
except sqlite.OperationalError, e: except sqlite.OperationalError, e:
print >> sys.stderr, str(e) print >> sys.stderr, str(e)
def replace_roster(self, account_name, roster_version, roster):
''' Replace current roster in DB by a new one.
accout_name is the name of the account to change
roster_version is the version of the new roster
roster is the new version '''
gajim.config.set_per('accounts', account_name, 'roster_version', '')
account_jid = gajim.get_jid_from_account(account_name)
account_jid_id = self.get_jid_id(account_jid)
# Delete old roster
sql = 'DELETE FROM roster_entry WHERE account_jid_id = %d' % (
account_jid_id)
sql = 'DELETE FROM roster_group WHERE account_jid_id = %d' % (
account_jid_id)
# Fill roster tables with the new roster
for jid in roster:
self.add_or_update_contact(account_jid, jid, roster[jid]['name'],
roster[jid]['subscription'], roster[jid]['ask'],
roster[jid]['groups'])
gajim.config.set_per('accounts', account_name, 'roster_version',
roster_version)
def del_contact(self, account_jid, jid):
''' Remove jid from account_jid roster. '''
try:
account_jid_id = self.get_jid_id(account_jid)
jid_id = self.get_jid_id(jid)
except exceptions.PysqliteOperationalError, e:
raise exceptions.PysqliteOperationalError(str(e))
sql = 'DELETE FROM roster_group WHERE account_jid_id=%d AND jid_id=%d' % (account_jid_id, jid_id)
self.cur.execute(sql)
sql = 'DELETE FROM roster_entry WHERE account_jid_id=%d AND jid_id=%d' % (account_jid_id, jid_id)
self.simple_commit(sql)
def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups):
''' Add or update a contact from account_jid roster. '''
if sub == 'remove':
self.del_contact(account_jid, jid)
return
try:
account_jid_id = self.get_jid_id(account_jid)
jid_id = self.get_jid_id(jid)
except exceptions.PysqliteOperationalError, e:
raise exceptions.PysqliteOperationalError(str(e))
# Update groups information
# First we delete all previous groups information
sql = 'DELETE FROM roster_group WHERE account_jid_id=%d AND jid_id=%d' % (account_jid_id, jid_id)
self.cur.execute(sql)
# Then we add all new groups information
for group in groups:
sql = 'INSERT INTO roster_group VALUES("%d", "%d", "%s")' % (
account_jid_id, jid_id, group)
self.cur.execute(sql)
if name is None:
name = ''
sql = 'REPLACE INTO roster_entry VALUES("%d", "%d", "%s", "%s", "%d")'\
% (account_jid_id, jid_id, name,
self.convert_human_subscription_values_to_db_api_values(sub),
bool(ask))
self.simple_commit(sql)
def get_roster(self, account_jid):
''' Return the accound_jid roster in NonBlockingRoster format. '''
data = {}
account_jid_id = self.get_jid_id(account_jid)
# First we fill data with roster_entry informations
self.cur.execute('SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask FROM roster_entry re, jids j WHERE re.account_jid_id="%(account_jid_id)s" AND j.jid_id=re.jid_id' % {'account_jid_id': account_jid_id})
for jid, jid_id, name, subscription, ask in self.cur:
data[jid] = {}
if name:
data[jid]['name'] = name
else:
data[jid]['name'] = None
data[jid]['subscription'] = self.convert_db_api_values_to_human_subscription_values(subscription)
data[jid]['groups'] = []
data[jid]['resources'] = {}
if ask:
data[jid]['ask'] = 'subscribe'
else:
data[jid]['ask'] = None
data[jid]['id'] = jid_id
# Then we add group for roster entries
for jid in data:
self.cur.execute('SELECT group_name FROM roster_group WHERE account_jid_id="%(account_jid_id)s" AND jid_id="%(jid_id)s"' % {'account_jid_id': account_jid_id, 'jid_id': data[jid]['id']})
for (group_name,) in self.cur:
data[jid]['groups'].append(group_name)
del data[jid]['id']
return data
def remove_roster(self, account_jid):
account_jid_id = self.get_jid_id(account_jid)
sql = 'DELETE FROM roster_group WHERE account_jid_id=%d' % (
account_jid_id)
self.cur.execute(sql)
sql = 'DELETE FROM roster_entry WHERE account_jid_id=%d' % (
account_jid_id)
self.simple_commit(sql)
# vim: se ts=3: # vim: se ts=3:

View File

@ -198,6 +198,8 @@ class OptionsParser:
self.update_config_to_01214() self.update_config_to_01214()
if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]: if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]:
self.update_config_to_01215() self.update_config_to_01215()
if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]:
self.update_config_to_01231()
gajim.logger.init_vars() gajim.logger.init_vars()
gajim.config.set('version', new_version) gajim.config.set('version', new_version)
@ -671,4 +673,39 @@ class OptionsParser:
gajim.config.set_per('soundevents', evt, 'path', path) gajim.config.set_per('soundevents', evt, 'path', path)
gajim.config.set('version', '0.12.1.5') gajim.config.set('version', '0.12.1.5')
def update_config_to_01231(self):
back = os.getcwd()
os.chdir(logger.LOG_DB_FOLDER)
con = sqlite.connect(logger.LOG_DB_FILE)
os.chdir(back)
cur = con.cursor()
try:
cur.executescript(
'''
CREATE TABLE IF NOT EXISTS roster_entry(
account_jid_id INTEGER,
jid_id INTEGER,
name TEXT,
subscription INTEGER,
ask BOOLEAN,
PRIMARY KEY (account_jid_id, jid_id)
);
CREATE TABLE IF NOT EXISTS roster_group(
account_jid_id INTEGER,
jid_id INTEGER,
group_name TEXT,
PRIMARY KEY (account_jid_id, jid_id, group_name)
);
'''
)
con.commit()
except sqlite.OperationalError:
pass
con.close()
gajim.config.set('version', '0.12.3.1')
# vim: se ts=3: # vim: se ts=3:

View File

@ -503,16 +503,16 @@ class NonBlockingClient:
self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth) self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth)
return True return True
def initRoster(self): def initRoster(self, version=''):
''' Plug in the roster. ''' ''' Plug in the roster. '''
if not self.__dict__.has_key('NonBlockingRoster'): if not self.__dict__.has_key('NonBlockingRoster'):
roster_nb.NonBlockingRoster.get_instance().PlugIn(self) return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self)
def getRoster(self, on_ready=None): def getRoster(self, on_ready=None, force=False):
''' Return the Roster instance, previously plugging it in and ''' Return the Roster instance, previously plugging it in and
requesting roster from server if needed. ''' requesting roster from server if needed. '''
if self.__dict__.has_key('NonBlockingRoster'): if self.__dict__.has_key('NonBlockingRoster'):
return self.NonBlockingRoster.getRoster(on_ready) return self.NonBlockingRoster.getRoster(on_ready, force)
return None return None
def sendPresence(self, jid=None, typ=None, requestRoster=0): def sendPresence(self, jid=None, typ=None, requestRoster=0):

View File

@ -36,20 +36,28 @@ class NonBlockingRoster(PlugIn):
You can also use mapping interface for access to the internal representation of You can also use mapping interface for access to the internal representation of
contacts in roster. contacts in roster.
''' '''
def __init__(self): def __init__(self, version=''):
''' Init internal variables. ''' ''' Init internal variables. '''
PlugIn.__init__(self) PlugIn.__init__(self)
self.version = version
self._data = {} self._data = {}
self.set=None self.set=None
self._exported_methods=[self.getRoster] self._exported_methods=[self.getRoster]
self.received_from_server = False
def Request(self,force=0): def Request(self,force=0):
''' Request roster from server if it were not yet requested ''' Request roster from server if it were not yet requested
(or if the 'force' argument is set). ''' (or if the 'force' argument is set). '''
if self.set is None: self.set=0 if self.set is None: self.set=0
elif not force: return elif not force: return
self._owner.send(Iq('get',NS_ROSTER))
iq = Iq('get',NS_ROSTER)
iq.setTagAttr('query', 'ver', self.version)
id_ = self._owner.getAnID()
iq.setID(id_)
self._owner.send(iq)
log.info('Roster requested from server') log.info('Roster requested from server')
return id_
def RosterIqHandler(self,dis,stanza): def RosterIqHandler(self,dis,stanza):
''' Subscription tracker. Used internally for setting items state in ''' Subscription tracker. Used internally for setting items state in
@ -60,6 +68,10 @@ class NonBlockingRoster(PlugIn):
return return
query = stanza.getTag('query') query = stanza.getTag('query')
if query: if query:
self.received_from_server = True
self.version = stanza.getTagAttr('query', 'ver')
if self.version is None:
self.version = ''
for item in query.getTags('item'): for item in query.getTags('item'):
jid=item.getAttr('jid') jid=item.getAttr('jid')
if item.getAttr('subscription')=='remove': if item.getAttr('subscription')=='remove':
@ -188,6 +200,11 @@ class NonBlockingRoster(PlugIn):
def getRaw(self): def getRaw(self):
'''Returns the internal data representation of the roster.''' '''Returns the internal data representation of the roster.'''
return self._data return self._data
def setRaw(self, data):
'''Returns the internal data representation of the roster.'''
self._data = data
self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,}
self.set=1
# copypasted methods for roster.py from constructor to here # copypasted methods for roster.py from constructor to here
@ -199,7 +216,7 @@ class NonBlockingRoster(PlugIn):
self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER) self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER)
self._owner.RegisterHandler('presence', self.PresenceHandler) self._owner.RegisterHandler('presence', self.PresenceHandler)
if request: if request:
self.Request() return self.Request()
def _on_roster_set(self, data): def _on_roster_set(self, data):
if data: if data:
@ -212,16 +229,18 @@ class NonBlockingRoster(PlugIn):
self.on_ready = None self.on_ready = None
return True return True
def getRoster(self, on_ready=None): def getRoster(self, on_ready=None, force=False):
''' Requests roster from server if neccessary and returns self. ''' ''' Requests roster from server if neccessary and returns self. '''
return_self = True
if not self.set: if not self.set:
self.on_ready = on_ready self.on_ready = on_ready
self._owner.onreceive(self._on_roster_set) self._owner.onreceive(self._on_roster_set)
return return_self = False
if on_ready: elif on_ready:
on_ready(self) on_ready(self)
on_ready = None return_self = False
else: if return_self or force:
return self return self
return None
# vim: se ts=3: # vim: se ts=3:

View File

@ -2616,6 +2616,7 @@ class RemoveAccountWindow:
gajim.interface.roster.close_all(self.account, force = True) gajim.interface.roster.close_all(self.account, force = True)
gajim.connections[self.account].disconnect(on_purpose = True) gajim.connections[self.account].disconnect(on_purpose = True)
del gajim.connections[self.account] del gajim.connections[self.account]
gajim.logger.remove_roster(gajim.get_jid_from_account(self.account))
gajim.config.del_per('accounts', self.account) gajim.config.del_per('accounts', self.account)
gajim.interface.save_config() gajim.interface.save_config()
del gajim.interface.instances[self.account] del gajim.interface.instances[self.account]

View File

@ -3435,6 +3435,8 @@ class Interface:
gtk.window_set_default_icon(pix) gtk.window_set_default_icon(pix)
self.roster = roster_window.RosterWindow() self.roster = roster_window.RosterWindow()
for account in gajim.connections:
gajim.connections[account].load_roster_from_db()
self.init_emoticons() self.init_emoticons()
self.make_regexps() self.make_regexps()