[PATCH 01/13] Make LocalStatus use sqlite
Sebastian Spaeth
Sebastian at SSpaeth.de
Wed Nov 24 09:56:38 GMT 2010
From: Rob Browning <rlb at defaultvalue.org>
Original patch by Stewart Smith.
Signed-off-by: Sebastian Spaeth <Sebastian at SSpaeth.de>
---
offlineimap/accounts.py | 6 +-
offlineimap/folder/Base.py | 32 +++-
offlineimap/folder/IMAP.py | 2 +-
offlineimap/folder/LocalStatus.py | 151 +++++++++++------
patch | 342 +++++++++++++++++++++++++++++++++++++
5 files changed, 470 insertions(+), 63 deletions(-)
create mode 100644 patch
diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py
index 5975332..e64965b 100644
--- a/offlineimap/accounts.py
+++ b/offlineimap/accounts.py
@@ -316,13 +316,13 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos,
ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder)
ui.loadmessagelist(localrepos, localfolder)
localfolder.cachemessagelist()
- ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys()))
+ ui.messagelistloaded(localrepos, localfolder, localfolder.getmessagecount())
# If either the local or the status folder has messages and there is a UID
# validity problem, warn and abort. If there are no messages, UW IMAPd
# loses UIDVALIDITY. But we don't really need it if both local folders are
# empty. So, in that case, just save it off.
- if len(localfolder.getmessagelist()) or len(statusfolder.getmessagelist()):
+ if localfolder.getmessagecount() or statusfolder.getmessagecount():
if not localfolder.isuidvalidityok():
ui.validityproblem(localfolder)
localrepos.restore_atime()
@@ -339,7 +339,7 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos,
ui.loadmessagelist(remoterepos, remotefolder)
remotefolder.cachemessagelist()
ui.messagelistloaded(remoterepos, remotefolder,
- len(remotefolder.getmessagelist().keys()))
+ remotefolder.getmessagecount())
#
diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py
index 0f38bc3..35ab450 100644
--- a/offlineimap/folder/Base.py
+++ b/offlineimap/folder/Base.py
@@ -130,6 +130,24 @@ class BaseFolder:
You must call cachemessagelist() before calling this function!"""
raise NotImplementedException
+ def uidexists(self,uid):
+ """Returns true if uid exists"""
+ mlist = self.getmessagelist()
+ if uid in mlist:
+ return 1
+ else:
+ return 0
+ return 0
+
+ def getmessageuidlist(self):
+ """Gets a list of UIDs.
+ You may have to call cachemessagelist() before calling this function!"""
+ return self.getmessagelist().keys()
+
+ def getmessagecount(self):
+ """Gets the number of messages."""
+ return len(self.getmessagelist().keys())
+
def getmessage(self, uid):
"""Returns the content of the specified message."""
raise NotImplementedException
@@ -238,7 +256,7 @@ class BaseFolder:
and once that succeeds, get the UID, add it to the others for real,
add it to local for real, and delete the fake one."""
- uidlist = [uid for uid in self.getmessagelist().keys() if uid < 0]
+ uidlist = [uid for uid in self.getmessageuidlist() if uid < 0]
threads = []
usethread = None
@@ -300,11 +318,10 @@ class BaseFolder:
them to dest."""
threads = []
- dest_messagelist = dest.getmessagelist()
- for uid in self.getmessagelist().keys():
+ for uid in self.getmessageuidlist():
if uid < 0: # Ignore messages that pass 1 missed.
continue
- if not uid in dest_messagelist:
+ if not dest.uidexists(uid):
if self.suggeststhreads():
self.waitforthread()
thread = InstanceLimitedThread(\
@@ -327,11 +344,10 @@ class BaseFolder:
Look for message present in dest but not in self.
If any, delete them."""
deletelist = []
- self_messagelist = self.getmessagelist()
- for uid in dest.getmessagelist().keys():
+ for uid in dest.getmessageuidlist():
if uid < 0:
continue
- if not uid in self_messagelist:
+ if not self.uidexists(uid):
deletelist.append(uid)
if len(deletelist):
UIBase.getglobalui().deletingmessages(deletelist, applyto)
@@ -354,7 +370,7 @@ class BaseFolder:
addflaglist = {}
delflaglist = {}
- for uid in self.getmessagelist().keys():
+ for uid in self.getmessageuidlist():
if uid < 0: # Ignore messages missed by pass 1
continue
selfflags = self.getmessageflags(uid)
diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
index c90d0e5..133bdff 100644
--- a/offlineimap/folder/IMAP.py
+++ b/offlineimap/folder/IMAP.py
@@ -56,7 +56,7 @@ class IMAPFolder(BaseFolder):
return self.accountname
def suggeststhreads(self):
- return 1
+ return 0
def waitforthread(self):
self.imapserver.connectionwait()
diff --git a/offlineimap/folder/LocalStatus.py b/offlineimap/folder/LocalStatus.py
index 157989d..0972a36 100644
--- a/offlineimap/folder/LocalStatus.py
+++ b/offlineimap/folder/LocalStatus.py
@@ -19,9 +19,17 @@
from Base import BaseFolder
import os, threading
+from pysqlite2 import dbapi2 as sqlite
+
magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
+newmagicline = "OFFLINEIMAP LocalStatus NOW IN SQLITE, DO NOT MODIFY"
class LocalStatusFolder(BaseFolder):
+ def __deinit__(self):
+ self.save()
+ self.cursor.close()
+ self.connection.close()
+
def __init__(self, root, name, repository, accountname, config):
self.name = name
self.root = root
@@ -30,12 +38,51 @@ class LocalStatusFolder(BaseFolder):
self.dofsync = config.getdefaultboolean("general", "fsync", True)
self.filename = os.path.join(root, name)
self.filename = repository.getfolderfilename(name)
- self.messagelist = None
+ 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
@@ -44,7 +91,7 @@ class LocalStatusFolder(BaseFolder):
return 0
def isnewfolder(self):
- return not os.path.exists(self.filename)
+ return not os.path.exists(self.dbfilename)
def getname(self):
return self.name
@@ -60,84 +107,85 @@ class LocalStatusFolder(BaseFolder):
def deletemessagelist(self):
if not self.isnewfolder():
- os.unlink(self.filename)
+ self.cursor.close()
+ self.connection.close()
+ os.unlink(self.dbfilename)
def cachemessagelist(self):
- if self.isnewfolder():
- self.messagelist = {}
- return
- file = open(self.filename, "rt")
- self.messagelist = {}
- line = file.readline().strip()
- if not line and not line.read():
- # The status file is empty - should not have happened,
- # but somehow did.
- file.close()
- return
- assert(line == magicline)
- for line in file.xreadlines():
- line = line.strip()
- uid, flags = line.split(':')
- uid = long(uid)
- flags = [x for x in flags]
- self.messagelist[uid] = {'uid': uid, 'flags': flags}
- file.close()
+ return
def autosave(self):
if self.doautosave:
self.save()
def save(self):
- self.savelock.acquire()
- try:
- file = open(self.filename + ".tmp", "wt")
- file.write(magicline + "\n")
- for msg in self.messagelist.values():
- flags = msg['flags']
- flags.sort()
- flags = ''.join(flags)
- file.write("%s:%s\n" % (msg['uid'], flags))
- file.flush()
- if self.dofsync:
- os.fsync(file.fileno())
- file.close()
- os.rename(self.filename + ".tmp", self.filename)
-
- if self.dofsync:
- try:
- fd = os.open(os.path.dirname(self.filename), os.O_RDONLY)
- os.fsync(fd)
- os.close(fd)
- except:
- pass
-
- finally:
- self.savelock.release()
+ 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}
+
return self.messagelist
+ def uidexists(self,uid):
+ self.cursor.execute('SELECT id FROM status WHERE id=:id',{'id': uid})
+ for row in self.cursor:
+ if(row[0]==uid):
+ return 1
+ return 0
+
+ def getmessageuidlist(self):
+ self.cursor.execute('SELECT id from status')
+ r = []
+ for row in self.cursor:
+ r.append(row[0])
+ return r
+
+ def getmessagecount(self):
+ self.cursor.execute('SELECT count(id) from status');
+ row = self.cursor.fetchone()
+ return row[0]
+
def savemessage(self, uid, content, flags, rtime):
if uid < 0:
# We cannot assign a uid.
return uid
- if uid in self.messagelist: # already have it
+ if self.uidexists(uid): # already have it
self.savemessageflags(uid, flags)
return uid
self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
+ flags.sort()
+ flags = ''.join(flags)
+ self.cursor.execute('INSERT INTO status (id,flags) VALUES (?,?)',
+ (uid,flags))
self.autosave()
return uid
def getmessageflags(self, uid):
- return self.messagelist[uid]['flags']
+ self.cursor.execute('SELECT flags FROM status WHERE id=:id',
+ {'id': uid})
+ for row in self.cursor:
+ flags = [x for x in row[0]]
+ return flags
+ return flags
def getmessagetime(self, uid):
return self.messagelist[uid]['time']
def savemessageflags(self, uid, flags):
- self.messagelist[uid]['flags'] = flags
+ self.messagelist[uid] = {'uid': uid, 'flags': flags}
+ flags.sort()
+ flags = ''.join(flags)
+ self.cursor.execute('UPDATE status SET flags=? WHERE id=?',(flags,uid))
self.autosave()
def deletemessage(self, uid):
@@ -151,4 +199,5 @@ class LocalStatusFolder(BaseFolder):
for uid in uidlist:
del(self.messagelist[uid])
- self.autosave()
+ #if self.uidexists(uid):
+ self.cursor.execute('DELETE FROM status WHERE id=:id', {'id': uid})
diff --git a/patch b/patch
new file mode 100644
index 0000000..f089dfe
--- /dev/null
+++ b/patch
@@ -0,0 +1,342 @@
+diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py
+index 5975332..e64965b 100644
+--- a/offlineimap/accounts.py
++++ b/offlineimap/accounts.py
+@@ -316,13 +316,13 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos,
+ ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder)
+ ui.loadmessagelist(localrepos, localfolder)
+ localfolder.cachemessagelist()
+- ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys()))
++ ui.messagelistloaded(localrepos, localfolder, localfolder.getmessagecount())
+
+ # If either the local or the status folder has messages and there is a UID
+ # validity problem, warn and abort. If there are no messages, UW IMAPd
+ # loses UIDVALIDITY. But we don't really need it if both local folders are
+ # empty. So, in that case, just save it off.
+- if len(localfolder.getmessagelist()) or len(statusfolder.getmessagelist()):
++ if localfolder.getmessagecount() or statusfolder.getmessagecount():
+ if not localfolder.isuidvalidityok():
+ ui.validityproblem(localfolder)
+ localrepos.restore_atime()
+@@ -339,7 +339,7 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos,
+ ui.loadmessagelist(remoterepos, remotefolder)
+ remotefolder.cachemessagelist()
+ ui.messagelistloaded(remoterepos, remotefolder,
+- len(remotefolder.getmessagelist().keys()))
++ remotefolder.getmessagecount())
+
+
+ #
+diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py
+index 0f38bc3..35ab450 100644
+--- a/offlineimap/folder/Base.py
++++ b/offlineimap/folder/Base.py
+@@ -130,6 +130,24 @@ class BaseFolder:
+ You must call cachemessagelist() before calling this function!"""
+ raise NotImplementedException
+
++ def uidexists(self,uid):
++ """Returns true if uid exists"""
++ mlist = self.getmessagelist()
++ if uid in mlist:
++ return 1
++ else:
++ return 0
++ return 0
++
++ def getmessageuidlist(self):
++ """Gets a list of UIDs.
++ You may have to call cachemessagelist() before calling this function!"""
++ return self.getmessagelist().keys()
++
++ def getmessagecount(self):
++ """Gets the number of messages."""
++ return len(self.getmessagelist().keys())
++
+ def getmessage(self, uid):
+ """Returns the content of the specified message."""
+ raise NotImplementedException
+@@ -238,7 +256,7 @@ class BaseFolder:
+ and once that succeeds, get the UID, add it to the others for real,
+ add it to local for real, and delete the fake one."""
+
+- uidlist = [uid for uid in self.getmessagelist().keys() if uid < 0]
++ uidlist = [uid for uid in self.getmessageuidlist() if uid < 0]
+ threads = []
+
+ usethread = None
+@@ -300,11 +318,10 @@ class BaseFolder:
+ them to dest."""
+ threads = []
+
+- dest_messagelist = dest.getmessagelist()
+- for uid in self.getmessagelist().keys():
++ for uid in self.getmessageuidlist():
+ if uid < 0: # Ignore messages that pass 1 missed.
+ continue
+- if not uid in dest_messagelist:
++ if not dest.uidexists(uid):
+ if self.suggeststhreads():
+ self.waitforthread()
+ thread = InstanceLimitedThread(\
+@@ -327,11 +344,10 @@ class BaseFolder:
+ Look for message present in dest but not in self.
+ If any, delete them."""
+ deletelist = []
+- self_messagelist = self.getmessagelist()
+- for uid in dest.getmessagelist().keys():
++ for uid in dest.getmessageuidlist():
+ if uid < 0:
+ continue
+- if not uid in self_messagelist:
++ if not self.uidexists(uid):
+ deletelist.append(uid)
+ if len(deletelist):
+ UIBase.getglobalui().deletingmessages(deletelist, applyto)
+@@ -354,7 +370,7 @@ class BaseFolder:
+ addflaglist = {}
+ delflaglist = {}
+
+- for uid in self.getmessagelist().keys():
++ for uid in self.getmessageuidlist():
+ if uid < 0: # Ignore messages missed by pass 1
+ continue
+ selfflags = self.getmessageflags(uid)
+diff --git a/offlineimap/folder/IMAP.py b/offlineimap/folder/IMAP.py
+index c90d0e5..133bdff 100644
+--- a/offlineimap/folder/IMAP.py
++++ b/offlineimap/folder/IMAP.py
+@@ -56,7 +56,7 @@ class IMAPFolder(BaseFolder):
+ return self.accountname
+
+ def suggeststhreads(self):
+- return 1
++ return 0
+
+ def waitforthread(self):
+ self.imapserver.connectionwait()
+diff --git a/offlineimap/folder/LocalStatus.py b/offlineimap/folder/LocalStatus.py
+index 157989d..0972a36 100644
+--- a/offlineimap/folder/LocalStatus.py
++++ b/offlineimap/folder/LocalStatus.py
+@@ -19,9 +19,17 @@
+ from Base import BaseFolder
+ import os, threading
+
++from pysqlite2 import dbapi2 as sqlite
++
+ magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT 1"
++newmagicline = "OFFLINEIMAP LocalStatus NOW IN SQLITE, DO NOT MODIFY"
+
+ class LocalStatusFolder(BaseFolder):
++ def __deinit__(self):
++ self.save()
++ self.cursor.close()
++ self.connection.close()
++
+ def __init__(self, root, name, repository, accountname, config):
+ self.name = name
+ self.root = root
+@@ -30,12 +38,51 @@ class LocalStatusFolder(BaseFolder):
+ self.dofsync = config.getdefaultboolean("general", "fsync", True)
+ self.filename = os.path.join(root, name)
+ self.filename = repository.getfolderfilename(name)
+- self.messagelist = None
++ 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
+@@ -44,7 +91,7 @@ class LocalStatusFolder(BaseFolder):
+ return 0
+
+ def isnewfolder(self):
+- return not os.path.exists(self.filename)
++ return not os.path.exists(self.dbfilename)
+
+ def getname(self):
+ return self.name
+@@ -60,84 +107,85 @@ class LocalStatusFolder(BaseFolder):
+
+ def deletemessagelist(self):
+ if not self.isnewfolder():
+- os.unlink(self.filename)
++ self.cursor.close()
++ self.connection.close()
++ os.unlink(self.dbfilename)
+
+ def cachemessagelist(self):
+- if self.isnewfolder():
+- self.messagelist = {}
+- return
+- file = open(self.filename, "rt")
+- self.messagelist = {}
+- line = file.readline().strip()
+- if not line and not line.read():
+- # The status file is empty - should not have happened,
+- # but somehow did.
+- file.close()
+- return
+- assert(line == magicline)
+- for line in file.xreadlines():
+- line = line.strip()
+- uid, flags = line.split(':')
+- uid = long(uid)
+- flags = [x for x in flags]
+- self.messagelist[uid] = {'uid': uid, 'flags': flags}
+- file.close()
++ return
+
+ def autosave(self):
+ if self.doautosave:
+ self.save()
+
+ def save(self):
+- self.savelock.acquire()
+- try:
+- file = open(self.filename + ".tmp", "wt")
+- file.write(magicline + "\n")
+- for msg in self.messagelist.values():
+- flags = msg['flags']
+- flags.sort()
+- flags = ''.join(flags)
+- file.write("%s:%s\n" % (msg['uid'], flags))
+- file.flush()
+- if self.dofsync:
+- os.fsync(file.fileno())
+- file.close()
+- os.rename(self.filename + ".tmp", self.filename)
+-
+- if self.dofsync:
+- try:
+- fd = os.open(os.path.dirname(self.filename), os.O_RDONLY)
+- os.fsync(fd)
+- os.close(fd)
+- except:
+- pass
+-
+- finally:
+- self.savelock.release()
++ 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}
++
+ return self.messagelist
+
++ def uidexists(self,uid):
++ self.cursor.execute('SELECT id FROM status WHERE id=:id',{'id': uid})
++ for row in self.cursor:
++ if(row[0]==uid):
++ return 1
++ return 0
++
++ def getmessageuidlist(self):
++ self.cursor.execute('SELECT id from status')
++ r = []
++ for row in self.cursor:
++ r.append(row[0])
++ return r
++
++ def getmessagecount(self):
++ self.cursor.execute('SELECT count(id) from status');
++ row = self.cursor.fetchone()
++ return row[0]
++
+ def savemessage(self, uid, content, flags, rtime):
+ if uid < 0:
+ # We cannot assign a uid.
+ return uid
+
+- if uid in self.messagelist: # already have it
++ if self.uidexists(uid): # already have it
+ self.savemessageflags(uid, flags)
+ return uid
+
+ self.messagelist[uid] = {'uid': uid, 'flags': flags, 'time': rtime}
++ flags.sort()
++ flags = ''.join(flags)
++ self.cursor.execute('INSERT INTO status (id,flags) VALUES (?,?)',
++ (uid,flags))
+ self.autosave()
+ return uid
+
+ def getmessageflags(self, uid):
+- return self.messagelist[uid]['flags']
++ self.cursor.execute('SELECT flags FROM status WHERE id=:id',
++ {'id': uid})
++ for row in self.cursor:
++ flags = [x for x in row[0]]
++ return flags
++ return flags
+
+ def getmessagetime(self, uid):
+ return self.messagelist[uid]['time']
+
+ def savemessageflags(self, uid, flags):
+- self.messagelist[uid]['flags'] = flags
++ self.messagelist[uid] = {'uid': uid, 'flags': flags}
++ flags.sort()
++ flags = ''.join(flags)
++ self.cursor.execute('UPDATE status SET flags=? WHERE id=?',(flags,uid))
+ self.autosave()
+
+ def deletemessage(self, uid):
+@@ -151,4 +199,5 @@ class LocalStatusFolder(BaseFolder):
+
+ for uid in uidlist:
+ del(self.messagelist[uid])
+- self.autosave()
++ #if self.uidexists(uid):
++ self.cursor.execute('DELETE FROM status WHERE id=:id', {'id': uid})
--
1.7.1
More information about the OfflineIMAP-project
mailing list