gajim
changeset 10773:1076fc9700f5
merge elghinn's branch (roster versioning) to trunk. Fixes #4661, #3190
| author | Yann Leboulanger <asterix@lagaule.org> |
|---|---|
| date | Fri, 10 Jul 2009 15:05:01 +0200 |
| parents | a736d5846aab 4e609a89ac14 |
| children | b2d16bc55260 |
| files | configure.ac src/common/check_paths.py src/common/config.py src/common/connection.py src/common/connection_handlers.py src/common/defs.py src/common/optparser.py src/common/xmpp/client_nb.py src/common/xmpp/roster_nb.py src/config.py src/gajim.py src/roster_window.py |
| diffstat | 10 files changed, 263 insertions(+), 17 deletions(-) [+] |
line diff
1.1 --- a/src/common/check_paths.py Thu Jul 09 19:06:08 2009 +0200 1.2 +++ b/src/common/check_paths.py Fri Jul 10 15:05:01 2009 +0200 1.3 @@ -96,6 +96,22 @@ 1.4 jid_id INTEGER PRIMARY KEY UNIQUE, 1.5 time INTEGER 1.6 ); 1.7 + 1.8 + CREATE TABLE IF NOT EXISTS roster_entry( 1.9 + account_jid_id INTEGER, 1.10 + jid_id INTEGER, 1.11 + name TEXT, 1.12 + subscription INTEGER, 1.13 + ask BOOLEAN, 1.14 + PRIMARY KEY (account_jid_id, jid_id) 1.15 + ); 1.16 + 1.17 + CREATE TABLE IF NOT EXISTS roster_group( 1.18 + account_jid_id INTEGER, 1.19 + jid_id INTEGER, 1.20 + group_name TEXT, 1.21 + PRIMARY KEY (account_jid_id, jid_id, group_name) 1.22 + ); 1.23 ''' 1.24 ) 1.25
2.1 --- a/src/common/config.py Thu Jul 09 19:06:08 2009 +0200 2.2 +++ b/src/common/config.py Fri Jul 10 15:05:01 2009 +0200 2.3 @@ -336,6 +336,7 @@ 2.4 'ignore_unknown_contacts': [ opt_bool, False ], 2.5 'send_os_info': [ opt_bool, True ], 2.6 'log_encrypted_sessions': [opt_bool, True, _('When negotiating an encrypted session, should Gajim assume you want your messages to be logged?')], 2.7 + 'roster_version': [opt_str, ''], 2.8 }, {}), 2.9 'statusmsg': ({ 2.10 'message': [ opt_str, '' ],
3.1 --- a/src/common/connection.py Thu Jul 09 19:06:08 2009 +0200 3.2 +++ b/src/common/connection.py Fri Jul 10 15:05:01 2009 +0200 3.3 @@ -1970,6 +1970,11 @@ 3.4 3.5 self.connection.SendAndCallForResponse(iq, _on_response) 3.6 3.7 + def load_roster_from_db(self): 3.8 + roster = gajim.logger.get_roster(gajim.get_jid_from_account(self.name)) 3.9 + self.dispatch('ROSTER', roster) 3.10 + 3.11 + 3.12 # END Connection 3.13 3.14 # vim: se ts=3:
4.1 --- a/src/common/connection_handlers.py Thu Jul 09 19:06:08 2009 +0200 4.2 +++ b/src/common/connection_handlers.py Fri Jul 10 15:05:01 2009 +0200 4.3 @@ -65,6 +65,7 @@ 4.4 VCARD_ARRIVED = 'vcard_arrived' 4.5 AGENT_REMOVED = 'agent_removed' 4.6 METACONTACTS_ARRIVED = 'metacontacts_arrived' 4.7 +ROSTER_ARRIVED = 'roster_arrived' 4.8 PRIVACY_ARRIVED = 'privacy_arrived' 4.9 PEP_CONFIG = 'pep_config' 4.10 HAS_IDLE = True 4.11 @@ -1172,7 +1173,18 @@ 4.12 if iq_obj.getErrorCode() not in ('403', '406', '404'): 4.13 self.private_storage_supported = False 4.14 # We can now continue connection by requesting the roster 4.15 - self.connection.initRoster() 4.16 + version = gajim.config.get_per('accounts', self.name, 4.17 + 'roster_version') 4.18 + iq_id = self.connection.initRoster(version=version) 4.19 + self.awaiting_answers[iq_id] = (ROSTER_ARRIVED, ) 4.20 + elif self.awaiting_answers[id_][0] == ROSTER_ARRIVED: 4.21 + if iq_obj.getType() == 'result': 4.22 + if not iq_obj.getTag('query'): 4.23 + account_jid = gajim.get_jid_from_account(self.name) 4.24 + roster_data = gajim.logger.get_roster(account_jid) 4.25 + roster = self.connection.getRoster(force=True) 4.26 + roster.setRaw(roster_data) 4.27 + self._getRoster() 4.28 elif self.awaiting_answers[id_][0] == PRIVACY_ARRIVED: 4.29 if iq_obj.getType() != 'error': 4.30 self.privacy_rules_supported = True 4.31 @@ -1556,6 +1568,7 @@ 4.32 4.33 def _rosterSetCB(self, con, iq_obj): 4.34 log.debug('rosterSetCB') 4.35 + version = iq_obj.getTagAttr('query', 'ver') 4.36 for item in iq_obj.getTag('query').getChildren(): 4.37 try: 4.38 jid = helpers.parse_jid(item.getAttr('jid')) 4.39 @@ -1569,6 +1582,12 @@ 4.40 for group in item.getTags('group'): 4.41 groups.append(group.getData()) 4.42 self.dispatch('ROSTER_INFO', (jid, name, sub, ask, groups)) 4.43 + account_jid = gajim.get_jid_from_account(self.name) 4.44 + gajim.logger.add_or_update_contact(account_jid, jid, name, sub, ask, 4.45 + groups) 4.46 + if version: 4.47 + gajim.config.set_per('accounts', self.name, 'roster_version', 4.48 + version) 4.49 if not self.connection or self.connected < 2: 4.50 raise common.xmpp.NodeProcessed 4.51 reply = common.xmpp.Iq(typ='result', attrs={'id': iq_obj.getID()}, 4.52 @@ -2484,7 +2503,7 @@ 4.53 self.connection.send(result) 4.54 raise common.xmpp.NodeProcessed 4.55 4.56 - def _getRosterCB(self, con, iq_obj): 4.57 + def _getRoster(self): 4.58 log.debug('getRosterCB') 4.59 if not self.connection: 4.60 return 4.61 @@ -2507,6 +2526,8 @@ 4.62 gajim.proxy65_manager.resolve(proxy, self.connection, our_jid) 4.63 4.64 def _on_roster_set(self, roster): 4.65 + roster_version = roster.version 4.66 + received_from_server = roster.received_from_server 4.67 raw_roster = roster.getRaw() 4.68 roster = {} 4.69 our_jid = helpers.parse_jid(gajim.get_jid_from_account(self.name)) 4.70 @@ -2545,7 +2566,16 @@ 4.71 # we can't determine which iconset to use 4.72 self.discoverInfo(jid) 4.73 4.74 - self.dispatch('ROSTER', roster) 4.75 + gajim.logger.replace_roster(self.name, roster_version, roster) 4.76 + if received_from_server: 4.77 + for contact in gajim.contacts.iter_contacts(self.name): 4.78 + if not contact.is_groupchat() and contact.jid not in roster: 4.79 + self.dispatch('ROSTER_INFO', (self.name, 4.80 + (contact.jid, None, None, None, ()))) 4.81 + for jid in roster: 4.82 + self.dispatch('ROSTER_INFO', (jid, roster[jid]['name'], 4.83 + roster[jid]['subscription'], roster[jid]['ask'], 4.84 + roster[jid]['groups'])) 4.85 4.86 def _send_first_presence(self, signed = ''): 4.87 show = self.continue_connect_info[0] 4.88 @@ -2689,8 +2719,6 @@ 4.89 common.xmpp.NS_MUC_OWNER) 4.90 con.RegisterHandler('iq', self._MucAdminCB, 'result', 4.91 common.xmpp.NS_MUC_ADMIN) 4.92 - con.RegisterHandler('iq', self._getRosterCB, 'result', 4.93 - common.xmpp.NS_ROSTER) 4.94 con.RegisterHandler('iq', self._PrivateCB, 'result', 4.95 common.xmpp.NS_PRIVATE) 4.96 con.RegisterHandler('iq', self._HttpAuthCB, 'get',
5.1 --- a/src/common/logger.py Thu Jul 09 19:06:08 2009 +0200 5.2 +++ b/src/common/logger.py Fri Jul 10 15:05:01 2009 +0200 5.3 @@ -92,6 +92,13 @@ 5.4 self.TYPE_MRIM, 5.5 ) = range(14) 5.6 5.7 + ( 5.8 + self.SUBSCRIPTION_NONE, 5.9 + self.SUBSCRIPTION_TO, 5.10 + self.SUBSCRIPTION_FROM, 5.11 + self.SUBSCRIPTION_BOTH, 5.12 + ) = range(4) 5.13 + 5.14 constants = Constants() 5.15 5.16 class Logger: 5.17 @@ -331,6 +338,28 @@ 5.18 if type_id == constants.TYPE_MRIM: 5.19 return 'mrim' 5.20 5.21 + def convert_human_subscription_values_to_db_api_values(self, sub): 5.22 + '''converts from string style to constant ints for db''' 5.23 + if sub == 'none': 5.24 + return constants.SUBSCRIPTION_NONE 5.25 + if sub == 'to': 5.26 + return constants.SUBSCRIPTION_TO 5.27 + if sub == 'from': 5.28 + return constants.SUBSCRIPTION_FROM 5.29 + if sub == 'both': 5.30 + return constants.SUBSCRIPTION_BOTH 5.31 + 5.32 + def convert_db_api_values_to_human_subscription_values(self, sub): 5.33 + '''converts from constant ints for db to string style''' 5.34 + if sub == constants.SUBSCRIPTION_NONE: 5.35 + return 'none' 5.36 + if sub == constants.SUBSCRIPTION_TO: 5.37 + return 'to' 5.38 + if sub == constants.SUBSCRIPTION_FROM: 5.39 + return 'from' 5.40 + if sub == constants.SUBSCRIPTION_BOTH: 5.41 + return 'both' 5.42 + 5.43 def commit_to_db(self, values, write_unread = False): 5.44 sql = 'INSERT INTO logs (jid_id, contact_name, time, kind, show, message, subject) VALUES (?, ?, ?, ?, ?, ?, ?)' 5.45 try: 5.46 @@ -799,4 +828,112 @@ 5.47 except sqlite.OperationalError, e: 5.48 print >> sys.stderr, str(e) 5.49 5.50 + def replace_roster(self, account_name, roster_version, roster): 5.51 + ''' Replace current roster in DB by a new one. 5.52 + accout_name is the name of the account to change 5.53 + roster_version is the version of the new roster 5.54 + roster is the new version ''' 5.55 + gajim.config.set_per('accounts', account_name, 'roster_version', '') 5.56 + account_jid = gajim.get_jid_from_account(account_name) 5.57 + account_jid_id = self.get_jid_id(account_jid) 5.58 + 5.59 + # Delete old roster 5.60 + sql = 'DELETE FROM roster_entry WHERE account_jid_id = %d' % ( 5.61 + account_jid_id) 5.62 + sql = 'DELETE FROM roster_group WHERE account_jid_id = %d' % ( 5.63 + account_jid_id) 5.64 + 5.65 + # Fill roster tables with the new roster 5.66 + for jid in roster: 5.67 + self.add_or_update_contact(account_jid, jid, roster[jid]['name'], 5.68 + roster[jid]['subscription'], roster[jid]['ask'], 5.69 + roster[jid]['groups']) 5.70 + gajim.config.set_per('accounts', account_name, 'roster_version', 5.71 + roster_version) 5.72 + 5.73 + def del_contact(self, account_jid, jid): 5.74 + ''' Remove jid from account_jid roster. ''' 5.75 + try: 5.76 + account_jid_id = self.get_jid_id(account_jid) 5.77 + jid_id = self.get_jid_id(jid) 5.78 + except exceptions.PysqliteOperationalError, e: 5.79 + raise exceptions.PysqliteOperationalError(str(e)) 5.80 + sql = 'DELETE FROM roster_group WHERE account_jid_id=%d AND jid_id=%d' % (account_jid_id, jid_id) 5.81 + self.cur.execute(sql) 5.82 + sql = 'DELETE FROM roster_entry WHERE account_jid_id=%d AND jid_id=%d' % (account_jid_id, jid_id) 5.83 + self.simple_commit(sql) 5.84 + 5.85 + def add_or_update_contact(self, account_jid, jid, name, sub, ask, groups): 5.86 + ''' Add or update a contact from account_jid roster. ''' 5.87 + if sub == 'remove': 5.88 + self.del_contact(account_jid, jid) 5.89 + return 5.90 + 5.91 + try: 5.92 + account_jid_id = self.get_jid_id(account_jid) 5.93 + jid_id = self.get_jid_id(jid) 5.94 + except exceptions.PysqliteOperationalError, e: 5.95 + raise exceptions.PysqliteOperationalError(str(e)) 5.96 + 5.97 + # Update groups information 5.98 + # First we delete all previous groups information 5.99 + sql = 'DELETE FROM roster_group WHERE account_jid_id=%d AND jid_id=%d' % (account_jid_id, jid_id) 5.100 + self.cur.execute(sql) 5.101 + # Then we add all new groups information 5.102 + for group in groups: 5.103 + sql = 'INSERT INTO roster_group VALUES("%d", "%d", "%s")' % ( 5.104 + account_jid_id, jid_id, group) 5.105 + self.cur.execute(sql) 5.106 + 5.107 + if name is None: 5.108 + name = '' 5.109 + 5.110 + sql = 'REPLACE INTO roster_entry VALUES("%d", "%d", "%s", "%s", "%d")'\ 5.111 + % (account_jid_id, jid_id, name, 5.112 + self.convert_human_subscription_values_to_db_api_values(sub), 5.113 + bool(ask)) 5.114 + self.simple_commit(sql) 5.115 + 5.116 + def get_roster(self, account_jid): 5.117 + ''' Return the accound_jid roster in NonBlockingRoster format. ''' 5.118 + data = {} 5.119 + account_jid_id = self.get_jid_id(account_jid) 5.120 + 5.121 + # First we fill data with roster_entry informations 5.122 + 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}) 5.123 + for jid, jid_id, name, subscription, ask in self.cur: 5.124 + data[jid] = {} 5.125 + if name: 5.126 + data[jid]['name'] = name 5.127 + else: 5.128 + data[jid]['name'] = None 5.129 + data[jid]['subscription'] = self.convert_db_api_values_to_human_subscription_values(subscription) 5.130 + data[jid]['groups'] = [] 5.131 + data[jid]['resources'] = {} 5.132 + if ask: 5.133 + data[jid]['ask'] = 'subscribe' 5.134 + else: 5.135 + data[jid]['ask'] = None 5.136 + data[jid]['id'] = jid_id 5.137 + 5.138 + # Then we add group for roster entries 5.139 + for jid in data: 5.140 + 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']}) 5.141 + for (group_name,) in self.cur: 5.142 + data[jid]['groups'].append(group_name) 5.143 + del data[jid]['id'] 5.144 + 5.145 + return data 5.146 + 5.147 + def remove_roster(self, account_jid): 5.148 + account_jid_id = self.get_jid_id(account_jid) 5.149 + 5.150 + sql = 'DELETE FROM roster_group WHERE account_jid_id=%d' % ( 5.151 + account_jid_id) 5.152 + self.cur.execute(sql) 5.153 + 5.154 + sql = 'DELETE FROM roster_entry WHERE account_jid_id=%d' % ( 5.155 + account_jid_id) 5.156 + self.simple_commit(sql) 5.157 + 5.158 # vim: se ts=3:
6.1 --- a/src/common/optparser.py Thu Jul 09 19:06:08 2009 +0200 6.2 +++ b/src/common/optparser.py Fri Jul 10 15:05:01 2009 +0200 6.3 @@ -198,6 +198,8 @@ 6.4 self.update_config_to_01214() 6.5 if old < [0, 12, 1, 5] and new >= [0, 12, 1, 5]: 6.6 self.update_config_to_01215() 6.7 + if old < [0, 12, 3, 1] and new >= [0, 12, 3, 1]: 6.8 + self.update_config_to_01231() 6.9 6.10 gajim.logger.init_vars() 6.11 gajim.config.set('version', new_version) 6.12 @@ -671,4 +673,39 @@ 6.13 gajim.config.set_per('soundevents', evt, 'path', path) 6.14 gajim.config.set('version', '0.12.1.5') 6.15 6.16 + def update_config_to_01231(self): 6.17 + back = os.getcwd() 6.18 + os.chdir(logger.LOG_DB_FOLDER) 6.19 + con = sqlite.connect(logger.LOG_DB_FILE) 6.20 + os.chdir(back) 6.21 + cur = con.cursor() 6.22 + try: 6.23 + cur.executescript( 6.24 + ''' 6.25 + CREATE TABLE IF NOT EXISTS roster_entry( 6.26 + account_jid_id INTEGER, 6.27 + jid_id INTEGER, 6.28 + name TEXT, 6.29 + subscription INTEGER, 6.30 + ask BOOLEAN, 6.31 + PRIMARY KEY (account_jid_id, jid_id) 6.32 + ); 6.33 + 6.34 + CREATE TABLE IF NOT EXISTS roster_group( 6.35 + account_jid_id INTEGER, 6.36 + jid_id INTEGER, 6.37 + group_name TEXT, 6.38 + PRIMARY KEY (account_jid_id, jid_id, group_name) 6.39 + ); 6.40 + ''' 6.41 + ) 6.42 + con.commit() 6.43 + except sqlite.OperationalError: 6.44 + pass 6.45 + con.close() 6.46 + gajim.config.set('version', '0.12.3.1') 6.47 + 6.48 + 6.49 + 6.50 + 6.51 # vim: se ts=3:
7.1 --- a/src/common/xmpp/client_nb.py Thu Jul 09 19:06:08 2009 +0200 7.2 +++ b/src/common/xmpp/client_nb.py Fri Jul 10 15:05:01 2009 +0200 7.3 @@ -503,16 +503,16 @@ 7.4 self.NonBlockingBind.NonBlockingBind(self._Resource, self._on_sasl_auth) 7.5 return True 7.6 7.7 - def initRoster(self): 7.8 + def initRoster(self, version=''): 7.9 ''' Plug in the roster. ''' 7.10 if not self.__dict__.has_key('NonBlockingRoster'): 7.11 - roster_nb.NonBlockingRoster.get_instance().PlugIn(self) 7.12 + return roster_nb.NonBlockingRoster.get_instance(version=version).PlugIn(self) 7.13 7.14 - def getRoster(self, on_ready=None): 7.15 + def getRoster(self, on_ready=None, force=False): 7.16 ''' Return the Roster instance, previously plugging it in and 7.17 requesting roster from server if needed. ''' 7.18 if self.__dict__.has_key('NonBlockingRoster'): 7.19 - return self.NonBlockingRoster.getRoster(on_ready) 7.20 + return self.NonBlockingRoster.getRoster(on_ready, force) 7.21 return None 7.22 7.23 def sendPresence(self, jid=None, typ=None, requestRoster=0):
8.1 --- a/src/common/xmpp/roster_nb.py Thu Jul 09 19:06:08 2009 +0200 8.2 +++ b/src/common/xmpp/roster_nb.py Fri Jul 10 15:05:01 2009 +0200 8.3 @@ -36,20 +36,28 @@ 8.4 You can also use mapping interface for access to the internal representation of 8.5 contacts in roster. 8.6 ''' 8.7 - def __init__(self): 8.8 + def __init__(self, version=''): 8.9 ''' Init internal variables. ''' 8.10 PlugIn.__init__(self) 8.11 + self.version = version 8.12 self._data = {} 8.13 self.set=None 8.14 self._exported_methods=[self.getRoster] 8.15 + self.received_from_server = False 8.16 8.17 def Request(self,force=0): 8.18 ''' Request roster from server if it were not yet requested 8.19 (or if the 'force' argument is set). ''' 8.20 if self.set is None: self.set=0 8.21 elif not force: return 8.22 - self._owner.send(Iq('get',NS_ROSTER)) 8.23 + 8.24 + iq = Iq('get',NS_ROSTER) 8.25 + iq.setTagAttr('query', 'ver', self.version) 8.26 + id_ = self._owner.getAnID() 8.27 + iq.setID(id_) 8.28 + self._owner.send(iq) 8.29 log.info('Roster requested from server') 8.30 + return id_ 8.31 8.32 def RosterIqHandler(self,dis,stanza): 8.33 ''' Subscription tracker. Used internally for setting items state in 8.34 @@ -60,6 +68,10 @@ 8.35 return 8.36 query = stanza.getTag('query') 8.37 if query: 8.38 + self.received_from_server = True 8.39 + self.version = stanza.getTagAttr('query', 'ver') 8.40 + if self.version is None: 8.41 + self.version = '' 8.42 for item in query.getTags('item'): 8.43 jid=item.getAttr('jid') 8.44 if item.getAttr('subscription')=='remove': 8.45 @@ -188,6 +200,11 @@ 8.46 def getRaw(self): 8.47 '''Returns the internal data representation of the roster.''' 8.48 return self._data 8.49 + def setRaw(self, data): 8.50 + '''Returns the internal data representation of the roster.''' 8.51 + self._data = data 8.52 + self._data[self._owner.User+'@'+self._owner.Server]={'resources':{},'name':None,'ask':None,'subscription':None,'groups':None,} 8.53 + self.set=1 8.54 # copypasted methods for roster.py from constructor to here 8.55 8.56 8.57 @@ -199,7 +216,7 @@ 8.58 self._owner.RegisterHandler('iq', self.RosterIqHandler, 'set', NS_ROSTER) 8.59 self._owner.RegisterHandler('presence', self.PresenceHandler) 8.60 if request: 8.61 - self.Request() 8.62 + return self.Request() 8.63 8.64 def _on_roster_set(self, data): 8.65 if data: 8.66 @@ -212,16 +229,18 @@ 8.67 self.on_ready = None 8.68 return True 8.69 8.70 - def getRoster(self, on_ready=None): 8.71 + def getRoster(self, on_ready=None, force=False): 8.72 ''' Requests roster from server if neccessary and returns self. ''' 8.73 + return_self = True 8.74 if not self.set: 8.75 self.on_ready = on_ready 8.76 self._owner.onreceive(self._on_roster_set) 8.77 - return 8.78 - if on_ready: 8.79 + return_self = False 8.80 + elif on_ready: 8.81 on_ready(self) 8.82 - on_ready = None 8.83 - else: 8.84 + return_self = False 8.85 + if return_self or force: 8.86 return self 8.87 + return None 8.88 8.89 # vim: se ts=3:
9.1 --- a/src/config.py Thu Jul 09 19:06:08 2009 +0200 9.2 +++ b/src/config.py Fri Jul 10 15:05:01 2009 +0200 9.3 @@ -2616,6 +2616,7 @@ 9.4 gajim.interface.roster.close_all(self.account, force = True) 9.5 gajim.connections[self.account].disconnect(on_purpose = True) 9.6 del gajim.connections[self.account] 9.7 + gajim.logger.remove_roster(gajim.get_jid_from_account(self.account)) 9.8 gajim.config.del_per('accounts', self.account) 9.9 gajim.interface.save_config() 9.10 del gajim.interface.instances[self.account]
10.1 --- a/src/gajim.py Thu Jul 09 19:06:08 2009 +0200 10.2 +++ b/src/gajim.py Fri Jul 10 15:05:01 2009 +0200 10.3 @@ -3435,6 +3435,8 @@ 10.4 gtk.window_set_default_icon(pix) 10.5 10.6 self.roster = roster_window.RosterWindow() 10.7 + for account in gajim.connections: 10.8 + gajim.connections[account].load_roster_from_db() 10.9 10.10 self.init_emoticons() 10.11 self.make_regexps()
