diff --git a/configure.ac b/configure.ac index 6e9b1e770aa131e834ba4b4454bb28b2fe250482..8df07adb7ab75f3250f02345113e102f6c881499 100644 --- a/configure.ac +++ b/configure.ac @@ -1,5 +1,5 @@ AC_INIT([Gajim - A Jabber Instant Messager], - [0.11.1.2],[http://trac.gajim.org/],[gajim]) + [0.11.1.3],[http://trac.gajim.org/],[gajim]) AC_PREREQ([2.59]) AM_INIT_AUTOMAKE([1.8]) AC_CONFIG_HEADER(config.h) diff --git a/src/common/caps.py b/src/common/caps.py index ce1d8f548f30117b838f4b11aa3f598e107ac743..a5b272ac7432e78f50bae47f6311567a47a9ab96 100644 --- a/src/common/caps.py +++ b/src/common/caps.py @@ -11,14 +11,10 @@ ## GNU General Public License for more details. ## -#import logger -#import gajim from itertools import * -import gajim import xmpp import xmpp.features_nb - -#from meta import VerboseClassType +import gajim class CapsCache(object): ''' This object keeps the mapping between caps data and real disco @@ -97,6 +93,7 @@ class CapsCache(object): ciself.node = node ciself.version = version ciself.features = set() + ciself.ext = ext ciself.exts = {} # set of tuples: (category, type, name) @@ -110,10 +107,6 @@ class CapsCache(object): # 2 == got the answer ciself.queried = 0 - def __iadd__(ciself, newfeature): - newfeature=self.__names.setdefault(newfeature, newfeature) - ciself.features.add(newfeature) - class CacheQuery(object): def __init__(cqself, proxied): cqself.proxied=proxied @@ -125,6 +118,8 @@ class CapsCache(object): def __getitem__(ciself, exts): if not exts: # (), [], None, False, whatever return ciself + if isinstance(exts, basestring): + exts=(exts,) if len(exts)==1: ext=exts[0] if ext in ciself.exts: @@ -135,12 +130,21 @@ class CapsCache(object): proxied = [ciself] proxied.extend(ciself[(e,)] for e in exts) return ciself.CacheQuery(proxied) + + def update(ciself, identities, features): + # NOTE: self refers to CapsCache object, not to CacheItem + self.identities=identities + self.features=features + self.logger.add_caps_entry( + ciself.node, ciself.version, ciself.ext, + identities, features) + self.__CacheItem = CacheItem # prepopulate data which we are sure of; note: we do not log these info - gajim = 'http://gajim.org/caps' + gajimnode = 'http://gajim.org/caps' - gajimcaps=self[(gajim, '0.11.1')] + gajimcaps=self[(gajimnode, '0.11.1')] gajimcaps.category='client' gajimcaps.type='pc' gajimcaps.features=set((xmpp.NS_BYTESTREAM, xmpp.NS_SI, @@ -152,17 +156,16 @@ class CapsCache(object): # TODO: older gajim versions # start logging data from the net - self.__logger = logger + self.logger = logger + def load_from_db(self): # get data from logger... - if self.__logger is not None: - for node, version, category, type_, name in self.__logger.get_caps_cache(): - x=self.__clients[(node, version)] - x.category=category - x.type=type_ - x.name=name - for node, version, ext, feature in self.__logger.get_caps_features_cache(): - self.__clients[(node, version)][ext]+=feature + if self.logger is not None: + for node, ver, ext, identities, features in self.logger.iter_caps_data(): + x=self[(node, ver, ext)] + x.identities=identities + x.features=features + x.queried=2 def __getitem__(self, caps): node_version = caps[:2] @@ -185,6 +188,7 @@ class CapsCache(object): # this will create proper object q.queried=1 account.discoverInfo(jid, '%s#%s' % (node, ver)) + else: for ext in exts: qq=q[ext] @@ -192,8 +196,9 @@ class CapsCache(object): # do query for node+version+ext triple qq.queried=1 account.discoverInfo(jid, '%s#%s' % (node, ext)) + else: -capscache = CapsCache() +gajim.capscache = CapsCache(gajim.logger) class ConnectionCaps(object): ''' This class highly depends on that it is a part of Connection class. ''' @@ -223,7 +228,7 @@ class ConnectionCaps(object): jid=str(presence.getFrom()) # start disco query... - capscache.preload(self, jid, node, ver, exts) + gajim.capscache.preload(self, jid, node, ver, exts) contact=gajim.contacts.get_contact_from_full_jid(self.name, jid) if contact in [None, []]: @@ -237,10 +242,7 @@ class ConnectionCaps(object): contact.caps_exts=exts def _capsDiscoCB(self, jid, node, identities, features, data): - gajim.log.debug('capsDiscoCB(jid=%r, node=%r, identities=%r, features=%r, data=%r)' %\ - (jid, node, identities, features, data)) contact=gajim.contacts.get_contact_from_full_jid(self.name, jid) - gajim.log.debug(' contact=%r' % contact) if not contact: return if not contact.caps_node: return # we didn't asked for that? if not node.startswith(contact.caps_node+'#'): return @@ -251,11 +253,9 @@ class ConnectionCaps(object): exts=(ext,) # if we don't have this info already... - caps=capscache[(node, contact.caps_ver, exts)] + caps=gajim.capscache[(node, contact.caps_ver, exts)] if caps.queried==2: return - caps.identities=set((i['category'], i['type'], i.get('name')) for i in identities) - caps.features=set(features) + identities=set((i['category'], i['type'], i.get('name')) for i in identities) + caps.update(identities, features) - gajim.log.debug('capsDiscoCB: added caps for %r:\n identities=%r\n features=%r'\ - % ((node, contact.caps_ver, exts), caps.identities, caps.features)) diff --git a/src/common/check_paths.py b/src/common/check_paths.py index 600855aab44c25ccae8955a6c6f31ad81fd3d9a2..b7f06046d978ca71c99db6533c0540d874a06003 100644 --- a/src/common/check_paths.py +++ b/src/common/check_paths.py @@ -42,6 +42,7 @@ def create_log_db(): # logs.jid_id --> jids.jid_id but Sqlite doesn't do FK etc so it's done in python code # jids.jid text column will be JID if TC-related, room_jid if GC-related, # ROOM_JID/nick if pm-related. + # also check optparser.py, which updates databases on gajim updates cur.executescript( ''' CREATE TABLE jids( @@ -74,6 +75,12 @@ def create_log_db(): ); CREATE INDEX idx_logs_jid_id_kind ON logs (jid_id, kind); + + CREATE TABLE caps_cache ( + node TEXT, + ver TEXT, + ext TEXT, + data BLOB); ''' ) diff --git a/src/common/defs.py b/src/common/defs.py index 16d2b6824c924a51d8b89c113c78b631f450feef..d3f9497d3cbfb2cce2ecaac630c8048b3b7d6943 100644 --- a/src/common/defs.py +++ b/src/common/defs.py @@ -2,7 +2,7 @@ docdir = '../' datadir = '../' -version = '0.11.1.2' +version = '0.11.1.3' import sys, os.path for base in ('.', 'common'): diff --git a/src/common/logger.py b/src/common/logger.py index fc2dedee7a247a48899416d12171ae09e5f30803..330e573189c0913ea6b91f8caac0537beb73c816 100644 --- a/src/common/logger.py +++ b/src/common/logger.py @@ -19,6 +19,8 @@ import os import sys import time import datetime +from gzip import GzipFile +from cStringIO import StringIO import exceptions import gajim @@ -633,6 +635,76 @@ class Logger: answer[result[0]] = self.convert_api_values_to_human_transport_type(result[1]) return answer - # initial interface for accessing logs of stored caps - def get_stored_caps(self): pass - def add_caps_entry(self): pass + # A longer note here: + # The database contains a blob field. Pysqlite seems to need special care for such fields. + # When storing, we need to convert string into buffer object (1). + # When retrieving, we need to convert it back to a string to decompress it. (2) + # GzipFile needs a file-like object, StringIO emulates file for plain strings. + def iter_caps_data(self): + ''' Iterate over caps cache data stored in the database. + The iterator values are pairs of (node, ver, ext, identities, features): + identities == {'category':'foo', 'type':'bar', 'name':'boo'}, + features being a list of feature namespaces. ''' + + # get data from table + # the data field contains binary object (gzipped data), this is a hack + # to get that data without trying to convert it to unicode + #tmp, self.con.text_factory = self.con.text_factory, str + try: + self.cur.execute('''SELECT node, ver, ext, data FROM caps_cache;'''); + except sqlite.OperationalError: + # might happen when there's no caps_cache table yet + # -- there's no data to read anyway then + #self.con.text_factory = tmp + return + #self.con.text_factory = tmp + + for node, ver, ext, data in self.cur: + # for each row: unpack the data field + # (format: (category, type, name, category, type, name, ... + # ..., 'FEAT', feature1, feature2, ...).join(' ')) + # NOTE: if there's a need to do more gzip, put that to a function + data=GzipFile(fileobj=StringIO(str(data))).read().split(' ') # (2) -- note above + i=0 + identities=set() + features=set() + while i<len(data) and data[i]!='FEAT': + category=data[i] + type=data[i+1] + name=data[i+2] + identities.add((category,type,name)) + i+=3 + i+=1 + while i<len(data): + features.add(data[i]) + i+=1 + if not ext: ext=None # to make '' a None + + # yield the row + yield node, ver, ext, identities, features + + def add_caps_entry(self, node, ver, ext, identities, features): + data=[] + for identity in identities: + # there is no FEAT category + if identity[0]=='FEAT': return + if len(identity)<2 or not identity[2]: + data.extend((identity[0], identity[1], '')) + else: + data.extend(identity) + data.append('FEAT') + data.extend(features) + data = ' '.join(data) + string = StringIO() # if there's a need to do more gzip, put that to a function + gzip=GzipFile(fileobj=string, mode='w') + gzip.write(data) + gzip.close() + data = string.getvalue() + self.cur.execute(''' + INSERT INTO caps_cache ( node, ver, ext, data ) + VALUES (?, ?, ?, ?); + ''', (node, ver, ext, buffer(data))) # (1) -- note above + try: + self.con.commit() + except sqlite.OperationalError, e: + print >> sys.stderr, str(e) diff --git a/src/common/optparser.py b/src/common/optparser.py index 4f0298be22bec5e548821d38b615162e81ddef12..73a74a5ea7cefa8b1f721c86a5f41847dbbe041f 100644 --- a/src/common/optparser.py +++ b/src/common/optparser.py @@ -157,9 +157,13 @@ class OptionsParser: self.update_config_to_01111() if old < [0, 11, 1, 2] and new >= [0, 11, 1, 2]: self.update_config_to_01112() + if old < [0, 11, 1, 3] and new >= [0, 11, 1, 3]: + self.update_config_to_01113() gajim.logger.init_vars() gajim.config.set('version', new_version) + + gajim.capscache.load_from_db() def update_config_x_to_09(self): # Var name that changed: @@ -402,3 +406,28 @@ class OptionsParser: self.old_values['roster_theme'] == 'gtk+': gajim.config.set('roster_theme', _('default')) gajim.config.set('version', '0.11.1.2') + + def update_config_to_01113(self): + # copy&pasted from update_config_to_01013, possibly 'FIXME see #2812' applies too + 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 caps_cache ( + node TEXT, + ver TEXT, + ext TEXT, + data BLOB + ); + ''' + ) + con.commit() + except sqlite.OperationalError, e: + pass + con.close() + gajim.config.set('version', '0.11.1.3') +