123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281 |
- # Local status cache virtual folder
- # Copyright (C) 2002-2016 John Goerzen & contributors.
- #
- # This program is free software; you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation; either version 2 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program; if not, write to the Free Software
- # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
- from sys import exc_info
- import os
- import threading
- from .Base import BaseFolder
- class LocalStatusFolder(BaseFolder):
- """LocalStatus backend implemented as a plain text file."""
- cur_version = 2
- magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT %d"
- def __init__(self, name, repository):
- self.sep = '.' # needs to be set before super.__init__()
- super(LocalStatusFolder, self).__init__(name, repository)
- self.root = repository.root
- self.filename = os.path.join(self.getroot(), self.getfolderbasename())
- self.savelock = threading.Lock()
- # Should we perform fsyncs as often as possible?
- self.doautosave = self.config.getdefaultboolean(
- "general", "fsync", False)
- # Interface from BaseFolder
- def storesmessages(self):
- return 0
- def isnewfolder(self):
- return not os.path.exists(self.filename)
- # Interface from BaseFolder
- def getfullname(self):
- return self.filename
- # Interface from BaseFolder
- def msglist_item_initializer(self, uid):
- return {'uid': uid, 'flags': set(), 'labels': set(), 'time': 0, 'mtime': 0}
- def readstatus_v1(self, fp):
- """Read status folder in format version 1.
- Arguments:
- - fp: I/O object that points to the opened database file.
- """
- for line in fp:
- line = line.strip()
- try:
- uid, flags = line.split(':')
- uid = int(uid)
- flags = set(flags)
- except ValueError:
- errstr = ("Corrupt line '%s' in cache file '%s'" %
- (line, self.filename))
- self.ui.warn(errstr)
- raise ValueError(errstr, exc_info()[2])
- self.messagelist[uid] = self.msglist_item_initializer(uid)
- self.messagelist[uid]['flags'] = flags
- def readstatus(self, fp):
- """Read status file in the current format.
- Arguments:
- - fp: I/O object that points to the opened database file.
- """
- for line in fp:
- line = line.strip()
- try:
- uid, flags, mtime, labels = line.split('|')
- uid = int(uid)
- flags = set(flags)
- mtime = int(mtime)
- labels = set([lb.strip() for lb in labels.split(',') if len(lb.strip()) > 0])
- except ValueError:
- errstr = "Corrupt line '%s' in cache file '%s'" % \
- (line, self.filename)
- self.ui.warn(errstr)
- raise ValueError(errstr, exc_info()[2])
- self.messagelist[uid] = self.msglist_item_initializer(uid)
- self.messagelist[uid]['flags'] = flags
- self.messagelist[uid]['mtime'] = mtime
- self.messagelist[uid]['labels'] = labels
- # Interface from BaseFolder
- def cachemessagelist(self):
- if self.isnewfolder():
- self.dropmessagelistcache()
- return
- # Loop as many times as version, and update format.
- for i in range(1, self.cur_version + 1):
- self.dropmessagelistcache()
- cachefd = open(self.filename, "rt")
- line = cachefd.readline().strip()
- # Format is up to date. break.
- if line == (self.magicline % self.cur_version):
- break
- # Convert from format v1.
- elif line == (self.magicline % 1):
- self.ui._msg('Upgrading LocalStatus cache from version 1 '
- 'to version 2 for %s:%s' % (self.repository, self))
- self.readstatus_v1(cachefd)
- cachefd.close()
- self.save()
- # NOTE: Add other format transitions here in the future.
- # elif line == (self.magicline % 2):
- # self.ui._msg(u'Upgrading LocalStatus cache from version 2'
- # 'to version 3 for %s:%s'% (self.repository, self))
- # self.readstatus_v2(cache)
- # cache.close()
- # cache.save()
- # Something is wrong.
- else:
- errstr = "Unrecognized cache magicline in '%s'" % self.filename
- self.ui.warn(errstr)
- raise ValueError(errstr)
- if not line:
- # The status file is empty - should not have happened,
- # but somehow did.
- errstr = "Cache file '%s' is empty." % self.filename
- self.ui.warn(errstr)
- cachefd.close()
- return
- assert (line == (self.magicline % self.cur_version))
- self.readstatus(cachefd)
- cachefd.close()
- def openfiles(self):
- pass # Closing files is done on a per-transaction basis.
- def closefiles(self):
- pass # Closing files is done on a per-transaction basis.
- def purge(self):
- """Remove any pre-existing database."""
- try:
- os.unlink(self.filename)
- except OSError as e:
- self.ui.debug('', "could not remove file %s: %s" %
- (self.filename, e))
- 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:
- cachefd = open(self.filename + ".tmp", "wt")
- cachefd.write((self.magicline % self.cur_version) + "\n")
- for msg in list(self.messagelist.values()):
- flags = ''.join(sorted(msg['flags']))
- labels = ', '.join(sorted(msg['labels']))
- cachefd.write("%s|%s|%d|%s\n" % (msg['uid'], flags, msg['mtime'], labels))
- cachefd.flush()
- if self.doautosave:
- os.fsync(cachefd.fileno())
- cachefd.close()
- os.rename(self.filename + ".tmp", self.filename)
- if self.doautosave:
- fd = os.open(os.path.dirname(self.filename), os.O_RDONLY)
- os.fsync(fd)
- os.close(fd)
- # Interface from BaseFolder
- def savemessage(self, uid, msg, flags, rtime, mtime=0, labels=None):
- """Writes a new message, with the specified uid.
- See folder/Base for detail. Note that savemessage() does not
- check against dryrun settings, so you need to ensure that
- savemessage is never called in a dryrun mode."""
- if labels is None:
- labels = set()
- if uid < 0:
- # We cannot assign a uid.
- return uid
- if self.uidexists(uid): # already have it
- self.savemessageflags(uid, flags)
- return uid
- self.messagelist[uid] = self.msglist_item_initializer(uid)
- self.messagelist[uid]['flags'] = flags
- self.messagelist[uid]['time'] = rtime
- self.messagelist[uid]['mtime'] = mtime
- self.messagelist[uid]['labels'] = labels
- self.save()
- return uid
- # Interface from BaseFolder
- def getmessageflags(self, uid):
- return self.messagelist[uid]['flags']
- # Interface from BaseFolder
- def getmessagetime(self, uid):
- return self.messagelist[uid]['time']
- # Interface from BaseFolder
- def savemessageflags(self, uid, flags):
- self.messagelist[uid]['flags'] = flags
- self.save()
- def savemessagelabels(self, uid, labels, mtime=None):
- self.messagelist[uid]['labels'] = labels
- if mtime:
- self.messagelist[uid]['mtime'] = mtime
- self.save()
- def savemessageslabelsbulk(self, labels):
- """Saves labels from a dictionary in a single database operation."""
- for uid, lb in list(labels.items()):
- self.messagelist[uid]['labels'] = lb
- self.save()
- def addmessageslabels(self, uids, labels):
- for uid in uids:
- self.messagelist[uid]['labels'] = self.messagelist[uid]['labels'] | labels
- self.save()
- def deletemessageslabels(self, uids, labels):
- for uid in uids:
- self.messagelist[uid]['labels'] = self.messagelist[uid]['labels'] - labels
- self.save()
- def getmessagelabels(self, uid):
- return self.messagelist[uid]['labels']
- def savemessagesmtimebulk(self, mtimes):
- """Saves mtimes from the mtimes dictionary in a single database operation."""
- for uid, mt in list(mtimes.items()):
- self.messagelist[uid]['mtime'] = mt
- self.save()
- def getmessagemtime(self, uid):
- return self.messagelist[uid]['mtime']
- # Interface from BaseFolder
- def deletemessage(self, uid):
- self.deletemessages([uid])
- # Interface from BaseFolder
- def deletemessages(self, uidlist):
- # Weed out ones not in self.messagelist
- uidlist = [uid for uid in uidlist if uid in self.messagelist]
- if not len(uidlist):
- return
- for uid in uidlist:
- del (self.messagelist[uid])
- self.save()
|