Improvements to the SQlite-based local status folder

* Do not inherit LocalStatusSQLiteFolder class from the plaintext
    one.

  * Use some functions already in BaseFolder in both, plaintext and
    sqlite classes.

  * Add a saveall method. The idea is that saveall dumps the entire
    messagelist to disk, while save only commits the uncommited
    changes. Right now, save is noop for sqlite, and equivalent to
    saveall for plaintext, but it enables to be more clever on when we
    commit to disk in the future.

Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
Abdo Roig-Maranges 2013-07-28 13:58:30 +02:00 committed by Eygene Ryabinkin
parent 09556d645e
commit 214137eb7b
3 changed files with 63 additions and 24 deletions

View File

@ -36,6 +36,7 @@ class BaseFolder(object):
# Save original name for folderfilter operations # Save original name for folderfilter operations
self.ffilter_name = name self.ffilter_name = name
# Top level dir name is always '' # Top level dir name is always ''
self.root = None
self.name = name if not name == self.getsep() else '' self.name = name if not name == self.getsep() else ''
self.repository = repository self.repository = repository
self.visiblename = repository.nametrans(name) self.visiblename = repository.nametrans(name)

View File

@ -29,6 +29,7 @@ class LocalStatusFolder(BaseFolder):
def __init__(self, name, repository): def __init__(self, name, repository):
self.sep = '.' #needs to be set before super.__init__() self.sep = '.' #needs to be set before super.__init__()
super(LocalStatusFolder, self).__init__(name, repository) super(LocalStatusFolder, self).__init__(name, repository)
self.root = repository.root
self.filename = os.path.join(self.getroot(), self.getfolderbasename()) self.filename = os.path.join(self.getroot(), self.getfolderbasename())
self.messagelist = {} self.messagelist = {}
self.savelock = threading.Lock() self.savelock = threading.Lock()
@ -47,14 +48,6 @@ class LocalStatusFolder(BaseFolder):
def getname(self): def getname(self):
return self.name return self.name
# Interface from BaseFolder
def getroot(self):
return self.repository.root
# Interface from BaseFolder
def getsep(self):
return self.sep
# Interface from BaseFolder # Interface from BaseFolder
def getfullname(self): def getfullname(self):
return self.filename return self.filename
@ -178,6 +171,11 @@ class LocalStatusFolder(BaseFolder):
def save(self): def save(self):
"""Save changed data to disk. For this backend it is the same as saveall"""
self.saveall()
def saveall(self):
"""Saves the entire messagelist to disk"""
with self.savelock: with self.savelock:
file = open(self.filename + ".tmp", "wt") file = open(self.filename + ".tmp", "wt")
file.write((self.magicline % self.cur_version) + "\n") file.write((self.magicline % self.cur_version) + "\n")

View File

@ -17,14 +17,14 @@
import os.path import os.path
import re import re
from threading import Lock from threading import Lock
from .LocalStatus import LocalStatusFolder from .Base import BaseFolder
try: try:
import sqlite3 as sqlite import sqlite3 as sqlite
except: except:
pass #fail only if needed later on, not on import pass #fail only if needed later on, not on import
class LocalStatusSQLiteFolder(LocalStatusFolder): class LocalStatusSQLiteFolder(BaseFolder):
"""LocalStatus backend implemented with an SQLite database """LocalStatus backend implemented with an SQLite database
As python-sqlite currently does not allow to access the same sqlite As python-sqlite currently does not allow to access the same sqlite
@ -43,9 +43,17 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
cur_version = 2 cur_version = 2
def __init__(self, name, repository): def __init__(self, name, repository):
self.sep = '.' #needs to be set before super.__init__()
super(LocalStatusSQLiteFolder, self).__init__(name, repository) super(LocalStatusSQLiteFolder, self).__init__(name, repository)
self.root = repository.root
self.filename = os.path.join(self.getroot(), self.getfolderbasename())
self.messagelist = {}
self._newfolder = False # flag if the folder is new
# dblock protects against concurrent writes in same connection # dblock protects against concurrent writes in same connection
self._dblock = Lock() self._dblock = Lock()
#Try to establish connection, no need for threadsafety in __init__ #Try to establish connection, no need for threadsafety in __init__
try: try:
self.connection = sqlite.connect(self.filename, check_same_thread = False) self.connection = sqlite.connect(self.filename, check_same_thread = False)
@ -62,13 +70,35 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
cursor = self.connection.execute("SELECT value from metadata WHERE key='db_version'") cursor = self.connection.execute("SELECT value from metadata WHERE key='db_version'")
except sqlite.DatabaseError: except sqlite.DatabaseError:
#db file missing or corrupt, recreate it. #db file missing or corrupt, recreate it.
self.__upgrade_db(0) self.__create_db()
else: else:
# fetch db version and upgrade if needed # fetch db version and upgrade if needed
version = int(cursor.fetchone()[0]) version = int(cursor.fetchone()[0])
if version < LocalStatusSQLiteFolder.cur_version: if version < LocalStatusSQLiteFolder.cur_version:
self.__upgrade_db(version) self.__upgrade_db(version)
def storesmessages(self):
return False
def getname(self):
return self.name
def getfullname(self):
return self.filename
# Interface from LocalStatusFolder
def isnewfolder(self):
return self._newfolder
# Interface from LocalStatusFolder
def deletemessagelist(self):
"""delete all messages in the db"""
self.__sql_write('DELETE FROM status')
def __sql_write(self, sql, vars=None, executemany=False): def __sql_write(self, sql, vars=None, executemany=False):
"""Execute some SQL, retrying if the db was locked. """Execute some SQL, retrying if the db was locked.
@ -123,6 +153,7 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
'LocalStatus', 'LocalStatus',
self.getfolderbasename()) self.getfolderbasename())
# MIGRATE from plaintext if needed # MIGRATE from plaintext if needed
# TODO: adopt for plain-text v2
if os.path.exists(plaintextfilename): if os.path.exists(plaintextfilename):
self.ui._msg('Migrating LocalStatus cache from plain text ' self.ui._msg('Migrating LocalStatus cache from plain text '
'to sqlite database for %s:%s' %\ 'to sqlite database for %s:%s' %\
@ -168,22 +199,12 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
% (self.repository, self)) % (self.repository, self))
self.connection.executescript(""" self.connection.executescript("""
CREATE TABLE metadata (key VARCHAR(50) PRIMARY KEY, value VARCHAR(128)); CREATE TABLE metadata (key VARCHAR(50) PRIMARY KEY, value VARCHAR(128));
INSERT INTO metadata VALUES('db_version', '1'); INSERT INTO metadata VALUES('db_version', '2');
CREATE TABLE status (id INTEGER PRIMARY KEY, flags VARCHAR(50), mtime INTEGER, labels VARCHAR(256)); CREATE TABLE status (id INTEGER PRIMARY KEY, flags VARCHAR(50), mtime INTEGER, labels VARCHAR(256));
""") """)
self.connection.commit() self.connection.commit()
self._newfolder = True
# Interface from LocalStatusFolder
def isnewfolder(self):
# testing the existence of the db file won't work. It is created
# as soon as this class instance was intitiated. So say it is a
# new folder when there are no messages at all recorded in it.
return self.getmessagecount() > 0
# Interface from LocalStatusFolder
def deletemessagelist(self):
"""delete all messages in the db"""
self.__sql_write('DELETE FROM status')
# Interface from BaseFolder # Interface from BaseFolder
def cachemessagelist(self): def cachemessagelist(self):
@ -196,8 +217,21 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
# Interface from LocalStatusFolder # Interface from LocalStatusFolder
def save(self): def save(self):
#Noop in this backend
pass pass
# Noop. every transaction commits to database!
def saveall(self):
"""Saves the entire messagelist to the database."""
data = []
for uid, msg in self.messagelist.items():
mtime = msg['mtime']
flags = ''.join(sorted(msg['flags']))
labels = ', '.join(sorted(msg['labels']))
data.append((uid, flags, mtime, labels))
self.__sql_write('INSERT OR REPLACE INTO status (id,flags,mtime,labels) VALUES (?,?,?,?)',
data, executemany=True)
# Following some pure SQLite functions, where we chose to use # Following some pure SQLite functions, where we chose to use
# BaseFolder() methods instead. Doing those on the in-memory list is # BaseFolder() methods instead. Doing those on the in-memory list is
@ -235,6 +269,12 @@ class LocalStatusSQLiteFolder(LocalStatusFolder):
# return flags # return flags
# assert False,"getmessageflags() called on non-existing message" # assert False,"getmessageflags() called on non-existing message"
# Interface from BaseFolder
def getmessagelist(self):
return self.messagelist
# Interface from BaseFolder # Interface from BaseFolder
def savemessage(self, uid, content, flags, rtime, mtime=0, labels=set()): def savemessage(self, uid, content, flags, rtime, mtime=0, labels=set()):
""" """