Use namedtuples for results from the DB

This makes the code much easier to read, and much less error prone.
This commit is contained in:
Philipp Hörist 2017-07-12 17:56:28 +02:00
parent 84f48a400c
commit ef38afcf90
3 changed files with 68 additions and 75 deletions

View File

@ -1424,24 +1424,24 @@ class ChatControl(ChatControlBase):
local_old_kind = None local_old_kind = None
self.conv_textview.just_cleared = True self.conv_textview.just_cleared = True
for row in rows: # row[0] time, row[1] has kind, row[2] the message, row[3] subject, row[4] additional_data for row in rows: # time, kind, message, subject, additional_data
msg = row[2] msg = row.message
additional_data = row[4] additional_data = row.additional_data
if not msg: # message is empty, we don't print it if not msg: # message is empty, we don't print it
continue continue
if row[1] in (KindConstant.CHAT_MSG_SENT, if row.kind in (KindConstant.CHAT_MSG_SENT,
KindConstant.SINGLE_MSG_SENT): KindConstant.SINGLE_MSG_SENT):
kind = 'outgoing' kind = 'outgoing'
name = self.get_our_nick() name = self.get_our_nick()
elif row[1] in (KindConstant.SINGLE_MSG_RECV, elif row.kind in (KindConstant.SINGLE_MSG_RECV,
KindConstant.CHAT_MSG_RECV): KindConstant.CHAT_MSG_RECV):
kind = 'incoming' kind = 'incoming'
name = self.contact.get_shown_name() name = self.contact.get_shown_name()
elif row[1] == KindConstant.ERROR: elif row.kind == KindConstant.ERROR:
kind = 'status' kind = 'status'
name = self.contact.get_shown_name() name = self.contact.get_shown_name()
tim = float(row[0]) tim = float(row.time)
if gajim.config.get('restored_messages_small'): if gajim.config.get('restored_messages_small'):
small_attr = ['small'] small_attr = ['small']
@ -1450,14 +1450,14 @@ class ChatControl(ChatControlBase):
xhtml = None xhtml = None
if msg.startswith('<body '): if msg.startswith('<body '):
xhtml = msg xhtml = msg
if row[3]: if row.subject:
msg = _('Subject: %(subject)s\n%(message)s') % \ msg = _('Subject: %(subject)s\n%(message)s') % \
{'subject': row[3], 'message': msg} {'subject': row.subject, 'message': msg}
ChatControlBase.print_conversation_line(self, msg, kind, name, ChatControlBase.print_conversation_line(self, msg, kind, name,
tim, small_attr, small_attr + ['restored_message'], tim, small_attr, small_attr + ['restored_message'],
small_attr + ['restored_message'], False, small_attr + ['restored_message'], False,
old_kind=local_old_kind, xhtml=xhtml, additional_data=additional_data) old_kind=local_old_kind, xhtml=xhtml, additional_data=additional_data)
if row[2].startswith('/me ') or row[2].startswith('/me\n'): if row.message.startswith('/me ') or row.message.startswith('/me\n'):
local_old_kind = None local_old_kind = None
else: else:
local_old_kind = kind local_old_kind = kind

View File

@ -121,6 +121,20 @@ class Logger:
gajim.ged.register_event_handler('gc-message-received', gajim.ged.register_event_handler('gc-message-received',
ged.POSTCORE, self._nec_gc_message_received) ged.POSTCORE, self._nec_gc_message_received)
@staticmethod
def namedtuple_factory(cursor, row):
"""
Usage:
con.row_factory = namedtuple_factory
"""
fields = [col[0] for col in cursor.description]
Row = namedtuple("Row", fields)
named_row = Row(*row)
if 'additional_data' in fields:
named_row = named_row._replace(
additional_data=json.loads(named_row.additional_data))
return named_row
def dispatch(self, event, error): def dispatch(self, event, error):
gajim.ged.raise_event(event, None, str(error)) gajim.ged.raise_event(event, None, str(error))
@ -145,6 +159,7 @@ class Logger:
self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0, self.con = sqlite.connect(LOG_DB_FILE, timeout=20.0,
isolation_level='IMMEDIATE') isolation_level='IMMEDIATE')
os.chdir(back) os.chdir(back)
self.con.row_factory = self.namedtuple_factory
self.cur = self.con.cursor() self.cur = self.con.cursor()
self.set_synchronous(False) self.set_synchronous(False)
@ -191,18 +206,16 @@ class Logger:
def get_jids_already_in_db(self): def get_jids_already_in_db(self):
try: try:
self.cur.execute('SELECT jid FROM jids') self.cur.execute('SELECT jid FROM jids')
# list of tuples: [('aaa@bbb',), ('cc@dd',)]
rows = self.cur.fetchall() rows = self.cur.fetchall()
except sqlite.DatabaseError: except sqlite.DatabaseError:
raise exceptions.DatabaseMalformed(LOG_DB_PATH) raise exceptions.DatabaseMalformed(LOG_DB_PATH)
self.jids_already_in = [] self.jids_already_in = []
for row in rows: for row in rows:
# row[0] is first item of row (the only result here, the jid) if not row.jid:
if row[0] == '':
# malformed jid, ignore line # malformed jid, ignore line
pass pass
else: else:
self.jids_already_in.append(row[0]) self.jids_already_in.append(row.jid)
def get_jids_in_db(self): def get_jids_in_db(self):
return self.jids_already_in return self.jids_already_in
@ -232,7 +245,7 @@ class Logger:
if row is None: if row is None:
return None return None
else: else:
if row[0] == JIDConstant.ROOM_TYPE: if row.type == JIDConstant.ROOM_TYPE:
return True return True
return False return False
@ -253,7 +266,7 @@ class Logger:
self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid]) self.cur.execute('SELECT jid_id FROM jids WHERE jid=?', [jid])
row = self.cur.fetchone() row = self.cur.fetchone()
if row: if row:
return row[0] return row.jid_id
# oh! a new jid :), we add it now # oh! a new jid :), we add it now
if typestr == 'ROOM': if typestr == 'ROOM':
typ = JIDConstant.ROOM_TYPE typ = JIDConstant.ROOM_TYPE
@ -476,8 +489,8 @@ class Logger:
except Exception: except Exception:
unread_results = [] unread_results = []
for message in unread_results: for message in unread_results:
msg_log_id = message[0] msg_log_id = message.message_id
shown = message[1] shown = message.shown
# here we get infos for that message, and related jid from jids table # here we get infos for that message, and related jid from jids table
# do NOT change order of SELECTed things, unless you change function(s) # do NOT change order of SELECTed things, unless you change function(s)
# that called this function # that called this function
@ -488,15 +501,13 @@ class Logger:
WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id WHERE logs.log_line_id = %d AND logs.jid_id = jids.jid_id
''' % msg_log_id ''' % msg_log_id
) )
results = self.cur.fetchall() results = self.cur.fetchone()
if len(results) == 0: if len(results) == 0:
# Log line is no more in logs table. remove it from unread_messages # Log line is no more in logs table. remove it from unread_messages
self.set_read_messages([msg_log_id]) self.set_read_messages([msg_log_id])
continue continue
results[0] = list(results[0])
results[0][5] = json.loads(results[0][5]) all_messages.append((results, shown))
results[0].append(shown)
all_messages.append(results[0])
return all_messages return all_messages
def write(self, kind, jid, message=None, show=None, tim=None, subject=None, def write(self, kind, jid, message=None, show=None, tim=None, subject=None,
@ -619,12 +630,7 @@ class Logger:
KindConstant.CHAT_MSG_SENT, KindConstant.ERROR, timeout, KindConstant.CHAT_MSG_SENT, KindConstant.ERROR, timeout,
restore_how_many, pending_how_many), jid_tuple) restore_how_many, pending_how_many), jid_tuple)
results = self.cur.fetchall() messages = self.cur.fetchall()
messages = []
for entry in results:
additional_data = json.loads(entry[4])
parsed_entry = entry[:4] + (additional_data, ) + entry[5:]
messages.append(parsed_entry)
except sqlite.DatabaseError: except sqlite.DatabaseError:
self.dispatch('DB_ERROR', self.dispatch('DB_ERROR',
exceptions.DatabaseMalformed(LOG_DB_PATH)) exceptions.DatabaseMalformed(LOG_DB_PATH))
@ -643,10 +649,6 @@ class Logger:
start_of_day = int(time.mktime(local_time)) start_of_day = int(time.mktime(local_time))
return start_of_day return start_of_day
Message = namedtuple('Message',
['contact_name', 'time', 'kind', 'show', 'message', 'subject',
'additional_data', 'log_line_id'])
def get_conversation_for_date(self, jid, year, month, day, account): def get_conversation_for_date(self, jid, year, month, day, account):
""" """
Load the complete conversation with a given jid on a specific date Load the complete conversation with a given jid on a specific date
@ -680,11 +682,7 @@ class Logger:
ORDER BY time ORDER BY time
''' % (where_sql, start_of_day, last_second_of_day), jid_tuple) ''' % (where_sql, start_of_day, last_second_of_day), jid_tuple)
results = [self.Message(*row) for row in self.cur.fetchall()] return self.cur.fetchall()
for message in results:
message._replace(additional_data=json.loads(message.additional_data))
return results
def search_log(self, jid, query, account, year=None, month=None, day=None): def search_log(self, jid, query, account, year=None, month=None, day=None):
""" """
@ -728,11 +726,7 @@ class Logger:
ORDER BY time ORDER BY time
''' % (where_sql, like_sql), jid_tuple) ''' % (where_sql, like_sql), jid_tuple)
results = [self.Message(*row) for row in self.cur.fetchall()] return self.cur.fetchall()
for message in results:
message._replace(additional_data=json.loads(message.additional_data))
return results
def get_days_with_logs(self, jid, year, month, max_day, account): def get_days_with_logs(self, jid, year, month, max_day, account):
""" """
@ -756,7 +750,7 @@ class Logger:
# and take only one of the same values (distinct) # and take only one of the same values (distinct)
# Now we have timestamps of time 0:00 of every day with logs # Now we have timestamps of time 0:00 of every day with logs
self.cur.execute(''' self.cur.execute('''
SELECT DISTINCT time/(86400)*86400 FROM logs SELECT DISTINCT time/(86400)*86400 as time FROM logs
WHERE (%s) WHERE (%s)
AND time BETWEEN %d AND %d AND time BETWEEN %d AND %d
AND kind NOT IN (%d, %d) AND kind NOT IN (%d, %d)
@ -767,7 +761,7 @@ class Logger:
# convert timestamps to day of month # convert timestamps to day of month
for line in result: for line in result:
days_with_logs[0:0]=[time.gmtime(line[0])[2]] days_with_logs[0:0]=[time.gmtime(line.time)[2]]
return days_with_logs return days_with_logs
@ -788,7 +782,7 @@ class Logger:
where_sql = 'jid_id = ?' where_sql = 'jid_id = ?'
jid_tuple = (jid_id,) jid_tuple = (jid_id,)
self.cur.execute(''' self.cur.execute('''
SELECT MAX(time) FROM logs SELECT MAX(time) as time FROM logs
WHERE (%s) WHERE (%s)
AND kind NOT IN (%d, %d) AND kind NOT IN (%d, %d)
''' % (where_sql, KindConstant.STATUS, KindConstant.GCSTATUS), ''' % (where_sql, KindConstant.STATUS, KindConstant.GCSTATUS),
@ -796,7 +790,7 @@ class Logger:
results = self.cur.fetchone() results = self.cur.fetchone()
if results is not None: if results is not None:
result = results[0] result = results.time
else: else:
result = None result = None
return result return result
@ -819,7 +813,7 @@ class Logger:
results = self.cur.fetchone() results = self.cur.fetchone()
if results is not None: if results is not None:
result = results[0] result = results.time
else: else:
result = None result = None
return result return result
@ -870,10 +864,9 @@ class Logger:
return return
self.cur.execute( self.cur.execute(
'SELECT type from transports_cache WHERE transport = "%s"' % jid) 'SELECT type from transports_cache WHERE transport = "%s"' % jid)
results = self.cur.fetchall() results = self.cur.fetchone()
if results: if results:
result = results[0][0] if results.type == type_id:
if result == type_id:
return return
sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\ sql = 'UPDATE transports_cache SET type = %d WHERE transport = "%s"' %\
(type_id, jid) (type_id, jid)
@ -893,8 +886,8 @@ class Logger:
return {} return {}
answer = {} answer = {}
for result in results: for result in results:
answer[result[0]] = self.convert_api_values_to_human_transport_type( answer[result.transport] = self.convert_api_values_to_human_transport_type(
result[1]) result.type)
return answer return answer
# A longer note here: # A longer note here:
@ -924,16 +917,16 @@ class Logger:
# list of corrupted entries that will be removed # list of corrupted entries that will be removed
to_be_removed = [] to_be_removed = []
for hash_method, hash_, data in self.cur: for row in self.cur:
# for each row: unpack the data field # for each row: unpack the data field
# (format: (category, type, name, category, type, name, ... # (format: (category, type, name, category, type, name, ...
# ..., 'FEAT', feature1, feature2, ...).join(' ')) # ..., 'FEAT', feature1, feature2, ...).join(' '))
# NOTE: if there's a need to do more gzip, put that to a function # NOTE: if there's a need to do more gzip, put that to a function
try: try:
data = GzipFile(fileobj=BytesIO(data)).read().decode('utf-8').split('\0') data = GzipFile(fileobj=BytesIO(row.data)).read().decode('utf-8').split('\0')
except IOError: except IOError:
# This data is corrupted. It probably contains non-ascii chars # This data is corrupted. It probably contains non-ascii chars
to_be_removed.append((hash_method, hash_)) to_be_removed.append((row.hash_method, row.hash))
continue continue
i = 0 i = 0
identities = list() identities = list()
@ -952,7 +945,7 @@ class Logger:
i += 1 i += 1
# yield the row # yield the row
yield hash_method, hash_, identities, features yield row.hash_method, row.hash, identities, features
for hash_method, hash_ in to_be_removed: for hash_method, hash_ in to_be_removed:
sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND sql = '''DELETE FROM caps_cache WHERE hash_method = "%s" AND
hash = "%s"''' % (hash_method, hash_) hash = "%s"''' % (hash_method, hash_)
@ -1091,9 +1084,10 @@ class Logger:
SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask SELECT j.jid, re.jid_id, re.name, re.subscription, re.ask
FROM roster_entry re, jids j FROM roster_entry re, jids j
WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,)) WHERE re.account_jid_id=? AND j.jid_id=re.jid_id''', (account_jid_id,))
for jid, jid_id, name, subscription, ask in self.cur: for row in self.cur:
jid = jid #jid, jid_id, name, subscription, ask
name = name jid = row.jid
name = row.name
data[jid] = {} data[jid] = {}
if name: if name:
data[jid]['name'] = name data[jid]['name'] = name
@ -1101,14 +1095,14 @@ class Logger:
data[jid]['name'] = None data[jid]['name'] = None
data[jid]['subscription'] = \ data[jid]['subscription'] = \
self.convert_db_api_values_to_human_subscription_values( self.convert_db_api_values_to_human_subscription_values(
subscription) row.subscription)
data[jid]['groups'] = [] data[jid]['groups'] = []
data[jid]['resources'] = {} data[jid]['resources'] = {}
if ask: if row.ask:
data[jid]['ask'] = 'subscribe' data[jid]['ask'] = 'subscribe'
else: else:
data[jid]['ask'] = None data[jid]['ask'] = None
data[jid]['id'] = jid_id data[jid]['id'] = row.jid_id
# Then we add group for roster entries # Then we add group for roster entries
for jid in data: for jid in data:
@ -1116,8 +1110,8 @@ class Logger:
SELECT group_name FROM roster_group SELECT group_name FROM roster_group
WHERE account_jid_id=? AND jid_id=?''', WHERE account_jid_id=? AND jid_id=?''',
(account_jid_id, data[jid]['id'])) (account_jid_id, data[jid]['id']))
for (group_name,) in self.cur: for row in self.cur:
group_name = group_name group_name = row.group_name
data[jid]['groups'].append(group_name) data[jid]['groups'].append(group_name)
del data[jid]['id'] del data[jid]['id']

View File

@ -1842,10 +1842,9 @@ class RosterWindow:
old unread messages, delete them from unread table old unread messages, delete them from unread table
""" """
results = gajim.logger.get_unread_msgs() results = gajim.logger.get_unread_msgs()
for result in results: for result, shown in results:
jid = result[4] jid = result.jid
additional_data = result[5] additional_data = result.additional_data
shown = result[6]
if gajim.contacts.get_first_contact_from_jid(account, jid) and not \ if gajim.contacts.get_first_contact_from_jid(account, jid) and not \
shown: shown:
# We have this jid in our contacts list # We have this jid in our contacts list
@ -1853,17 +1852,17 @@ class RosterWindow:
# with them # with them
session = gajim.connections[account].make_new_session(jid) session = gajim.connections[account].make_new_session(jid)
tim = float(result[2]) tim = float(result.time)
session.roster_message(jid, result[1], tim, msg_type='chat', session.roster_message(jid, result.message, tim, msg_type='chat',
msg_log_id=result[0], additional_data=additional_data) msg_log_id=result.log_line_id, additional_data=additional_data)
gajim.logger.set_shown_unread_msgs(result[0]) gajim.logger.set_shown_unread_msgs(result.log_line_id)
elif (time.time() - result[2]) > 2592000: elif (time.time() - result.time) > 2592000:
# ok, here we see that we have a message in unread messages # ok, here we see that we have a message in unread messages
# table that is older than a month. It is probably from someone # table that is older than a month. It is probably from someone
# not in our roster for accounts we usually launch, so we will # not in our roster for accounts we usually launch, so we will
# delete this id from unread message tables. # delete this id from unread message tables.
gajim.logger.set_read_messages([result[0]]) gajim.logger.set_read_messages([result.log_line_id])
def fill_contacts_and_groups_dicts(self, array, account): def fill_contacts_and_groups_dicts(self, array, account):
""" """