Add ability to migrate status data across backends

If when we request a LocalStatus folder, the folder has to be created,
we look whether the other backend has data, and if it does we migrate
it to the new backend.

The old backend data is left untouched, so that if you change back say
from sqlite to plaintext, the older data is still there.  That should
not lead to data loss, only a slower sync while the status folder gets
updated.

Signed-off-by: Eygene Ryabinkin <rea@codelabs.ru>
This commit is contained in:
Abdo Roig-Maranges 2013-07-28 14:03:46 +02:00 committed by Eygene Ryabinkin
parent 214137eb7b
commit 1b954c3b4c
3 changed files with 54 additions and 49 deletions

View File

@ -14,6 +14,8 @@ OfflineIMAP v6.5.6 (YYYY-MM-DD)
to IMAP server (Abdó Roig-Maranges)
* Allow to sync GMail labels and implement GmailMaildir repository that
adds mechanics to change message labels (Abdó Roig-Maranges)
* Allow to migrate status data across differend backends
(Abdó Roig-Maranges)
OfflineIMAP v6.5.5 (2013-10-07)

View File

@ -144,34 +144,6 @@ class LocalStatusSQLiteFolder(BaseFolder):
self.connection = sqlite.connect(self.filename,
check_same_thread = False)
if from_ver == 0:
# from_ver==0: no db existent: plain text migration?
self.__create_db()
# below was derived from repository.getfolderfilename() logic
plaintextfilename = os.path.join(
self.repository.account.getaccountmeta(),
'LocalStatus',
self.getfolderbasename())
# MIGRATE from plaintext if needed
# TODO: adopt for plain-text v2
if os.path.exists(plaintextfilename):
self.ui._msg('Migrating LocalStatus cache from plain text '
'to sqlite database for %s:%s' %\
(self.repository, self))
file = open(plaintextfilename, "rt")
line = file.readline().strip()
data = []
for line in file.xreadlines():
uid, flags = line.strip().split(':')
uid = long(uid)
flags = ''.join(sorted(flags))
data.append((uid,flags))
self.connection.executemany('INSERT INTO status (id,flags) VALUES (?,?)',
data)
self.connection.commit()
file.close()
os.rename(plaintextfilename, plaintextfilename + ".old")
# Upgrade from database version 1 to version 2
# This change adds labels and mtime columns, to be used by Gmail IMAP and Maildir folders.
if from_ver <= 1:

View File

@ -25,20 +25,21 @@ import re
class LocalStatusRepository(BaseRepository):
def __init__(self, reposname, account):
BaseRepository.__init__(self, reposname, account)
# Root directory in which the LocalStatus folders reside
self.root = os.path.join(account.getaccountmeta(), 'LocalStatus')
# statusbackend can be 'plain' or 'sqlite'
backend = self.account.getconf('status_backend', 'plain')
if backend == 'sqlite':
self._backend = 'sqlite'
self.LocalStatusFolderClass = LocalStatusSQLiteFolder
self.root += '-sqlite'
elif backend == 'plain':
self._backend = 'plain'
self.LocalStatusFolderClass = LocalStatusFolder
else:
raise SyntaxWarning("Unknown status_backend '%s' for account '%s'" \
% (backend, account.name))
# class and root for all backends
self.backends = {}
self.backends['sqlite'] = {
'class': LocalStatusSQLiteFolder,
'root': os.path.join(account.getaccountmeta(), 'LocalStatus-sqlite')
}
self.backends['plain'] = {
'class': LocalStatusFolder,
'root': os.path.join(account.getaccountmeta(), 'LocalStatus')
}
# Set class and root for the configured backend
self.setup_backend(self.account.getconf('status_backend', 'plain'))
if not os.path.exists(self.root):
os.mkdir(self.root, 0o700)
@ -46,16 +47,41 @@ class LocalStatusRepository(BaseRepository):
# self._folders is a dict of name:LocalStatusFolders()
self._folders = {}
def setup_backend(self, backend):
if backend in self.backends.keys():
self._backend = backend
self.root = self.backends[backend]['root']
self.LocalStatusFolderClass = self.backends[backend]['class']
else:
raise SyntaxWarning("Unknown status_backend '%s' for account '%s'" \
% (backend, account.name))
def import_other_backend(self, folder):
for bk, dic in self.backends.items():
# skip folder's own type
if dic['class'] == type(folder):
continue
repobk = LocalStatusRepository(self.name, self.account)
repobk.setup_backend(bk) # fake the backend
folderbk = dic['class'](folder.name, repobk)
# if backend contains data, import it to folder.
if not folderbk.isnewfolder():
self.ui._msg('Migrating LocalStatus cache from %s to %s ' % (bk, self._backend) + \
'status folder for %s:%s' % (self.name, folder.name))
folderbk.cachemessagelist()
folder.messagelist = folderbk.messagelist
folder.saveall()
break
def getsep(self):
return '.'
def makefolder(self, foldername):
"""Create a LocalStatus Folder
Empty Folder for plain backend. NoOp for sqlite backend as those
are created on demand."""
if self._backend == 'sqlite':
return # noop for sqlite which creates on-demand
"""Create a LocalStatus Folder"""
if self.account.dryrun:
return # bail out in dry-run mode
@ -65,7 +91,7 @@ class LocalStatusRepository(BaseRepository):
folder.save()
# Invalidate the cache.
self._folders = {}
self.forgetfolders()
def getfolder(self, foldername):
"""Return the Folder() object for a foldername"""
@ -73,6 +99,11 @@ class LocalStatusRepository(BaseRepository):
return self._folders[foldername]
folder = self.LocalStatusFolderClass(foldername, self)
# if folder is empty, try to import data from an other backend
if folder.isnewfolder():
self.import_other_backend(folder)
self._folders[foldername] = folder
return folder