docker-offlineimap/offlineimap/folder/LocalStatus.py
Rodolfo García Peñas (kix) 7ff65be690 Removed mutable arguments
This patch remves the set() mutable argument as default value,
sets the default value to None and check if the argument is none
in the code to call set().
2020-10-10 15:00:34 +02:00

282 lines
9.7 KiB
Python

# 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, content, 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()