[PATCH 4/9] Inherit LocalStatusSQLFolder from LocalStatusFolder
Sebastian Spaeth
Sebastian at SSpaeth.de
Tue Apr 26 11:31:35 BST 2011
- Inherit LocalStatusSQLFolder from LocalStatusFolder
- This lets us remove all functions that are available via our ancestors
classes and are not needed.
- Don't fail if pysql import fails. Fail rather at runtime when needed.
- When creating the db file, create a metadata table which contains the
format version info, so we can upgrade nicely to other formats.
Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
offlineimap/folder/Base.py | 2 +-
offlineimap/folder/LocalStatus.py | 2 +-
offlineimap/folder/LocalStatusSQLite.py | 152 +++++++++++--------------------
3 files changed, 56 insertions(+), 100 deletions(-)
diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py
index ad11158..351cc91 100644
--- a/offlineimap/folder/Base.py
+++ b/offlineimap/folder/Base.py
@@ -22,7 +22,7 @@ import os.path
import re
import traceback
-class BaseFolder:
+class BaseFolder(object):
def __init__(self):
self.ui = getglobalui()
diff --git a/offlineimap/folder/LocalStatus.py b/offlineimap/folder/LocalStatus.py
index 2bf46b8..0fd2aaa 100644
--- a/offlineimap/folder/LocalStatus.py
+++ b/offlineimap/folder/LocalStatus.py
@@ -35,7 +35,7 @@ class LocalStatusFolder(BaseFolder):
self.savelock = threading.Lock()
self.doautosave = 1
self.accountname = accountname
- BaseFolder.__init__(self)
+ super(LocalStatusFolder, self).__init__()
def getaccountname(self):
return self.accountname
diff --git a/offlineimap/folder/LocalStatusSQLite.py b/offlineimap/folder/LocalStatusSQLite.py
index 974baf4..0f85db9 100644
--- a/offlineimap/folder/LocalStatusSQLite.py
+++ b/offlineimap/folder/LocalStatusSQLite.py
@@ -15,122 +15,81 @@
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
-from Base import BaseFolder
-import os, threading
+from LocalStatus import LocalStatusFolder
+from threading import RLock
-from pysqlite2 import dbapi2 as sqlite
+try:
+ from pysqlite2 import dbapi2 as sqlite
+except:
+ pass #fail only when needed later on
-magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
-newmagicline = "OFFLINEIMAP LocalStatus NOW IN SQLITE, DO NOT MODIFY"
-
-class LocalStatusFolder(BaseFolder):
+class LocalStatusSQLiteFolder(LocalStatusFolder):
"""LocalStatus backend implemented with an SQLite database"""
def __deinit__(self):
+ #TODO, need to invoke this when appropriate?
self.save()
self.cursor.close()
self.connection.close()
def __init__(self, root, name, repository, accountname, config):
- self.name = name
- self.root = root
- self.sep = '.'
- self.config = config
- self.dofsync = config.getdefaultboolean("general", "fsync", True)
- self.filename = repository.getfolderfilename(name)
- self.messagelist = {}
- self.repository = repository
- self.savelock = threading.Lock()
- self.doautosave = 1
- self.accountname = accountname
- BaseFolder.__init__(self)
- self.dbfilename = self.filename + '.sqlite'
-
- # MIGRATE
- if os.path.exists(self.filename):
- self.connection = sqlite.connect(self.dbfilename)
- self.cursor = self.connection.cursor()
- self.cursor.execute('CREATE TABLE status (id INTEGER PRIMARY KEY, flags VARCHAR(50))')
- if self.isnewfolder():
- self.messagelist = {}
- return
- file = open(self.filename, "rt")
- self.messagelist = {}
- line = file.readline().strip()
- assert(line == magicline)
- for line in file.xreadlines():
- line = line.strip()
- uid, flags = line.split(':')
- uid = long(uid)
- flags = [x for x in flags]
- flags.sort()
- flags = ''.join(flags)
- self.cursor.execute('INSERT INTO status (id,flags) VALUES (?,?)',
- (uid,flags))
- file.close()
- self.connection.commit()
- os.rename(self.filename, self.filename + ".old")
- self.cursor.close()
- self.connection.close()
-
- # create new
- if not os.path.exists(self.dbfilename):
- self.connection = sqlite.connect(self.dbfilename)
- self.cursor = self.connection.cursor()
- self.cursor.execute('CREATE TABLE status (id INTEGER PRIMARY KEY, flags VARCHAR(50))')
- else:
- self.connection = sqlite.connect(self.dbfilename)
- self.cursor = self.connection.cursor()
-
-
-
- def getaccountname(self):
- return self.accountname
-
- def storesmessages(self):
- return 0
+ super(LocalStatusSQLiteFolder, self).__init__(root, name, repository, accountname, config)
+
+ self.dblock = RLock()
+ """dblock protects against concurrent forbidden access of the db
+ object, e.g trying to test for existence and on-demand creation
+ of the db."""
+
+ #Try to establish connection
+ try:
+ self.connection = sqlite.connect(self.filename)
+ except NameError:
+ # sqlite import had failed
+ raise UserWarning('SQLite backend chosen, but no sqlite python '
+ 'bindings available. Please install.')
+
+ #Test if the db version is current enough and if the db is
+ #readable. Lock, so that only one thread at a time can do this,
+ #so we don't create them in parallel.
+ with self.dblock:
+ try:
+ self.cursor = self.connection.cursor()
+ self.cursor.execute('SELECT version from metadata')
+ except sqlite.DatabaseError:
+ #db file missing or corrupt, recreate it.
+ self.create_db()
def isnewfolder(self):
- return not os.path.exists(self.dbfilename)
-
- def getname(self):
- return self.name
-
- def getroot(self):
- return self.root
-
- def getsep(self):
- return self.sep
-
- def getfullname(self):
- return self.filename
+ # 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
+
+ def create_db(self):
+ """Create a new db file"""
+ self.ui.warn('Creating new Local Status db for %s:%s' \
+ % (self.repository.getname(), self.getname()))
+ self.connection = sqlite.connect(self.filename)
+ self.cursor = self.connection.cursor()
+ self.cursor.execute('CREATE TABLE metadata (key VARCHAR(50) PRIMARY KEY, value VARCHAR(128))')
+ self.cursor.execute("INSERT INTO metadata VALUES('db_version', '1')")
+ self.cursor.execute('CREATE TABLE status (id INTEGER PRIMARY KEY, flags VARCHAR(50))')
+ self.autosave() #commit if needed
def deletemessagelist(self):
- if not self.isnewfolder():
- self.cursor.close()
- self.connection.close()
- os.unlink(self.dbfilename)
+ """delete all messages in the db"""
+ self.cursor.execute('DELETE FROM status')
def cachemessagelist(self):
- return
-
- def autosave(self):
- if self.doautosave:
- self.save()
-
- def save(self):
- self.connection.commit()
-
- def getmessagelist(self):
- if self.isnewfolder():
- self.messagelist = {}
- return
-
self.messagelist = {}
self.cursor.execute('SELECT id,flags from status')
for row in self.cursor:
flags = [x for x in row[1]]
self.messagelist[row[0]] = {'uid': row[0], 'flags': flags}
+ def save(self):
+ self.connection.commit()
+
+ def getmessagelist(self):
return self.messagelist
def uidexists(self,uid):
@@ -187,9 +146,6 @@ class LocalStatusFolder(BaseFolder):
self.cursor.execute('UPDATE status SET flags=? WHERE id=?',(flags,uid))
self.autosave()
- def deletemessage(self, uid):
- self.deletemessages([uid])
-
def deletemessages(self, uidlist):
# Weed out ones not in self.messagelist
uidlist = [uid for uid in uidlist if uid in self.messagelist]
--
1.7.4.1
More information about the OfflineIMAP-project
mailing list