2002-06-21 06:04:47 +02:00
|
|
|
# Local status cache virtual folder
|
2016-06-29 03:42:57 +02:00
|
|
|
# Copyright (C) 2002-2016 John Goerzen & contributors.
|
2002-06-21 06:04:47 +02:00
|
|
|
#
|
|
|
|
# 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
|
2003-04-16 21:23:45 +02:00
|
|
|
# the Free Software Foundation; either version 2 of the License, or
|
|
|
|
# (at your option) any later version.
|
2002-06-21 06:04:47 +02:00
|
|
|
#
|
|
|
|
# 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
|
2006-08-12 06:15:55 +02:00
|
|
|
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
2002-06-21 06:04:47 +02:00
|
|
|
|
2015-01-11 21:44:24 +01:00
|
|
|
from sys import exc_info
|
2011-03-11 22:13:21 +01:00
|
|
|
import os
|
|
|
|
import threading
|
2015-01-01 21:41:11 +01:00
|
|
|
from .Base import BaseFolder
|
2012-02-05 12:17:02 +01:00
|
|
|
|
2015-01-14 22:58:25 +01:00
|
|
|
|
2002-06-21 06:04:47 +02:00
|
|
|
class LocalStatusFolder(BaseFolder):
|
2015-01-14 22:58:25 +01:00
|
|
|
"""LocalStatus backend implemented as a plain text file."""
|
2013-07-27 23:25:13 +02:00
|
|
|
|
|
|
|
cur_version = 2
|
|
|
|
magicline = "OFFLINEIMAP LocalStatus CACHE DATA - DO NOT MODIFY - FORMAT %d"
|
|
|
|
|
2011-09-16 10:54:25 +02:00
|
|
|
def __init__(self, name, repository):
|
2020-08-29 19:43:52 +02:00
|
|
|
self.sep = '.' # needs to be set before super.__init__()
|
2011-09-16 10:54:22 +02:00
|
|
|
super(LocalStatusFolder, self).__init__(name, repository)
|
2013-07-28 13:58:30 +02:00
|
|
|
self.root = repository.root
|
2011-09-16 10:54:25 +02:00
|
|
|
self.filename = os.path.join(self.getroot(), self.getfolderbasename())
|
2003-01-03 08:01:41 +01:00
|
|
|
self.savelock = threading.Lock()
|
2015-01-01 21:41:11 +01:00
|
|
|
# Should we perform fsyncs as often as possible?
|
|
|
|
self.doautosave = self.config.getdefaultboolean(
|
|
|
|
"general", "fsync", False)
|
2002-06-21 06:04:47 +02:00
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2002-07-16 04:26:58 +02:00
|
|
|
def storesmessages(self):
|
|
|
|
return 0
|
|
|
|
|
2002-06-21 06:04:47 +02:00
|
|
|
def isnewfolder(self):
|
|
|
|
return not os.path.exists(self.filename)
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2002-06-21 06:04:47 +02:00
|
|
|
def getfullname(self):
|
|
|
|
return self.filename
|
|
|
|
|
2014-08-03 14:47:26 +02:00
|
|
|
# Interface from BaseFolder
|
|
|
|
def msglist_item_initializer(self, uid):
|
|
|
|
return {'uid': uid, 'flags': set(), 'labels': set(), 'time': 0, 'mtime': 0}
|
|
|
|
|
2013-07-27 23:25:13 +02:00
|
|
|
def readstatus_v1(self, fp):
|
2015-01-01 21:41:11 +01:00
|
|
|
"""Read status folder in format version 1.
|
2013-07-27 23:25:13 +02:00
|
|
|
|
|
|
|
Arguments:
|
|
|
|
- fp: I/O object that points to the opened database file.
|
|
|
|
"""
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2016-05-08 17:34:38 +02:00
|
|
|
for line in fp:
|
2002-06-21 06:30:08 +02:00
|
|
|
line = line.strip()
|
2011-05-04 19:44:01 +02:00
|
|
|
try:
|
|
|
|
uid, flags = line.split(':')
|
2016-05-08 16:42:52 +02:00
|
|
|
uid = int(uid)
|
2011-08-16 12:16:46 +02:00
|
|
|
flags = set(flags)
|
2020-08-30 13:32:36 +02:00
|
|
|
except ValueError:
|
2020-08-29 19:43:52 +02:00
|
|
|
errstr = ("Corrupt line '%s' in cache file '%s'" %
|
|
|
|
(line, self.filename))
|
2011-05-04 19:44:01 +02:00
|
|
|
self.ui.warn(errstr)
|
2020-09-03 20:27:04 +02:00
|
|
|
raise ValueError(errstr, exc_info()[2])
|
2014-08-03 14:47:26 +02:00
|
|
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
|
|
|
self.messagelist[uid]['flags'] = flags
|
2013-07-27 23:25:13 +02:00
|
|
|
|
|
|
|
def readstatus(self, fp):
|
2015-01-01 21:41:11 +01:00
|
|
|
"""Read status file in the current format.
|
2013-07-27 23:25:13 +02:00
|
|
|
|
|
|
|
Arguments:
|
|
|
|
- fp: I/O object that points to the opened database file.
|
|
|
|
"""
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2016-05-08 17:34:38 +02:00
|
|
|
for line in fp:
|
2013-07-27 23:25:13 +02:00
|
|
|
line = line.strip()
|
|
|
|
try:
|
|
|
|
uid, flags, mtime, labels = line.split('|')
|
2016-05-08 16:42:52 +02:00
|
|
|
uid = int(uid)
|
2013-07-27 23:25:13 +02:00
|
|
|
flags = set(flags)
|
2016-05-08 16:42:52 +02:00
|
|
|
mtime = int(mtime)
|
2013-07-27 23:25:13 +02:00
|
|
|
labels = set([lb.strip() for lb in labels.split(',') if len(lb.strip()) > 0])
|
2020-08-30 13:32:36 +02:00
|
|
|
except ValueError:
|
2020-08-29 19:43:52 +02:00
|
|
|
errstr = "Corrupt line '%s' in cache file '%s'" % \
|
|
|
|
(line, self.filename)
|
2013-07-27 23:25:13 +02:00
|
|
|
self.ui.warn(errstr)
|
2020-09-03 20:27:04 +02:00
|
|
|
raise ValueError(errstr, exc_info()[2])
|
2014-08-03 14:47:26 +02:00
|
|
|
self.messagelist[uid] = self.msglist_item_initializer(uid)
|
|
|
|
self.messagelist[uid]['flags'] = flags
|
|
|
|
self.messagelist[uid]['mtime'] = mtime
|
|
|
|
self.messagelist[uid]['labels'] = labels
|
2013-07-27 23:25:13 +02:00
|
|
|
|
2014-05-06 23:47:05 +02:00
|
|
|
# Interface from BaseFolder
|
2013-07-27 23:25:13 +02:00
|
|
|
def cachemessagelist(self):
|
|
|
|
if self.isnewfolder():
|
2016-04-09 18:12:18 +02:00
|
|
|
self.dropmessagelistcache()
|
2013-07-27 23:25:13 +02:00
|
|
|
return
|
|
|
|
|
2015-01-18 03:49:15 +01:00
|
|
|
# Loop as many times as version, and update format.
|
|
|
|
for i in range(1, self.cur_version + 1):
|
2016-04-09 18:12:18 +02:00
|
|
|
self.dropmessagelistcache()
|
2015-01-18 22:09:41 +01:00
|
|
|
cachefd = open(self.filename, "rt")
|
|
|
|
line = cachefd.readline().strip()
|
2015-01-18 03:49:15 +01:00
|
|
|
|
|
|
|
# Format is up to date. break.
|
|
|
|
if line == (self.magicline % self.cur_version):
|
|
|
|
break
|
|
|
|
|
|
|
|
# Convert from format v1.
|
|
|
|
elif line == (self.magicline % 1):
|
2017-11-25 04:23:42 +01:00
|
|
|
self.ui._msg('Upgrading LocalStatus cache from version 1 '
|
2020-08-29 19:43:52 +02:00
|
|
|
'to version 2 for %s:%s' % (self.repository, self))
|
2015-01-18 22:09:41 +01:00
|
|
|
self.readstatus_v1(cachefd)
|
|
|
|
cachefd.close()
|
2013-07-27 23:25:13 +02:00
|
|
|
self.save()
|
|
|
|
|
|
|
|
# NOTE: Add other format transitions here in the future.
|
|
|
|
# elif line == (self.magicline % 2):
|
2015-01-18 03:49:15 +01:00
|
|
|
# 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()
|
2013-07-27 23:25:13 +02:00
|
|
|
|
2015-01-18 03:49:15 +01:00
|
|
|
# Something is wrong.
|
2013-07-27 23:25:13 +02:00
|
|
|
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.
|
2020-08-29 19:43:52 +02:00
|
|
|
errstr = "Cache file '%s' is empty." % self.filename
|
2013-07-27 23:25:13 +02:00
|
|
|
self.ui.warn(errstr)
|
2015-01-18 22:09:41 +01:00
|
|
|
cachefd.close()
|
2013-07-27 23:25:13 +02:00
|
|
|
return
|
|
|
|
|
2020-08-29 19:43:52 +02:00
|
|
|
assert (line == (self.magicline % self.cur_version))
|
2015-01-18 22:09:41 +01:00
|
|
|
self.readstatus(cachefd)
|
|
|
|
cachefd.close()
|
2002-06-21 06:04:47 +02:00
|
|
|
|
2016-05-12 18:27:14 +02:00
|
|
|
def openfiles(self):
|
2020-08-29 19:43:52 +02:00
|
|
|
pass # Closing files is done on a per-transaction basis.
|
2016-05-12 18:27:14 +02:00
|
|
|
|
2016-04-09 19:52:33 +02:00
|
|
|
def closefiles(self):
|
2020-08-29 19:43:52 +02:00
|
|
|
pass # Closing files is done on a per-transaction basis.
|
2016-04-09 19:52:33 +02:00
|
|
|
|
2016-06-17 19:47:37 +02:00
|
|
|
def purge(self):
|
|
|
|
"""Remove any pre-existing database."""
|
|
|
|
|
|
|
|
try:
|
|
|
|
os.unlink(self.filename)
|
|
|
|
except OSError as e:
|
2020-08-29 19:43:52 +02:00
|
|
|
self.ui.debug('', "could not remove file %s: %s" %
|
|
|
|
(self.filename, e))
|
2016-06-17 19:47:37 +02:00
|
|
|
|
2002-06-21 06:04:47 +02:00
|
|
|
def save(self):
|
2015-01-01 21:41:11 +01:00
|
|
|
"""Save changed data to disk. For this backend it is the same as saveall."""
|
|
|
|
|
2013-07-28 13:58:30 +02:00
|
|
|
self.saveall()
|
|
|
|
|
|
|
|
def saveall(self):
|
2015-01-01 21:41:11 +01:00
|
|
|
"""Saves the entire messagelist to disk."""
|
|
|
|
|
2012-08-31 22:34:53 +02:00
|
|
|
with self.savelock:
|
2015-01-18 22:09:41 +01:00
|
|
|
cachefd = open(self.filename + ".tmp", "wt")
|
|
|
|
cachefd.write((self.magicline % self.cur_version) + "\n")
|
2020-08-28 03:32:43 +02:00
|
|
|
for msg in list(self.messagelist.values()):
|
2013-07-27 23:25:13 +02:00
|
|
|
flags = ''.join(sorted(msg['flags']))
|
|
|
|
labels = ', '.join(sorted(msg['labels']))
|
2015-01-18 22:09:41 +01:00
|
|
|
cachefd.write("%s|%s|%d|%s\n" % (msg['uid'], flags, msg['mtime'], labels))
|
|
|
|
cachefd.flush()
|
2011-05-05 15:59:26 +02:00
|
|
|
if self.doautosave:
|
2015-01-18 22:09:41 +01:00
|
|
|
os.fsync(cachefd.fileno())
|
|
|
|
cachefd.close()
|
2003-01-03 08:01:41 +01:00
|
|
|
os.rename(self.filename + ".tmp", self.filename)
|
2007-03-28 22:23:18 +02:00
|
|
|
|
2011-05-05 15:59:26 +02:00
|
|
|
if self.doautosave:
|
2011-01-12 11:15:10 +01:00
|
|
|
fd = os.open(os.path.dirname(self.filename), os.O_RDONLY)
|
|
|
|
os.fsync(fd)
|
|
|
|
os.close(fd)
|
2007-03-28 22:23:18 +02:00
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2013-07-27 23:25:13 +02:00
|
|
|
def savemessage(self, uid, content, flags, rtime, mtime=0, labels=set()):
|
2011-09-16 11:44:50 +02:00
|
|
|
"""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."""
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2002-06-21 06:04:47 +02:00
|
|
|
if uid < 0:
|
|
|
|
# We cannot assign a uid.
|
|
|
|
return uid
|
|
|
|
|
2020-08-29 19:43:52 +02:00
|
|
|
if self.uidexists(uid): # already have it
|
2002-06-21 06:04:47 +02:00
|
|
|
self.savemessageflags(uid, flags)
|
|
|
|
return uid
|
|
|
|
|
2014-08-03 14:47:26 +02:00
|
|
|
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
|
2011-05-05 15:59:26 +02:00
|
|
|
self.save()
|
2002-06-21 06:04:47 +02:00
|
|
|
return uid
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2002-06-21 06:04:47 +02:00
|
|
|
def getmessageflags(self, uid):
|
|
|
|
return self.messagelist[uid]['flags']
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2006-08-22 03:09:36 +02:00
|
|
|
def getmessagetime(self, uid):
|
|
|
|
return self.messagelist[uid]['time']
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2002-06-21 06:04:47 +02:00
|
|
|
def savemessageflags(self, uid, flags):
|
|
|
|
self.messagelist[uid]['flags'] = flags
|
2011-05-05 15:59:26 +02:00
|
|
|
self.save()
|
2002-06-21 06:04:47 +02:00
|
|
|
|
2013-07-27 23:25:13 +02:00
|
|
|
def savemessagelabels(self, uid, labels, mtime=None):
|
|
|
|
self.messagelist[uid]['labels'] = labels
|
2020-08-30 13:34:05 +02:00
|
|
|
if mtime:
|
|
|
|
self.messagelist[uid]['mtime'] = mtime
|
2013-07-27 23:25:13 +02:00
|
|
|
self.save()
|
|
|
|
|
|
|
|
def savemessageslabelsbulk(self, labels):
|
|
|
|
"""Saves labels from a dictionary in a single database operation."""
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2020-08-28 03:32:43 +02:00
|
|
|
for uid, lb in list(labels.items()):
|
2013-07-27 23:25:13 +02:00
|
|
|
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."""
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2020-08-28 03:32:43 +02:00
|
|
|
for uid, mt in list(mtimes.items()):
|
2013-07-27 23:25:13 +02:00
|
|
|
self.messagelist[uid]['mtime'] = mt
|
|
|
|
self.save()
|
|
|
|
|
|
|
|
def getmessagemtime(self, uid):
|
|
|
|
return self.messagelist[uid]['mtime']
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2002-06-21 06:04:47 +02:00
|
|
|
def deletemessage(self, uid):
|
2010-03-27 16:18:25 +01:00
|
|
|
self.deletemessages([uid])
|
|
|
|
|
2014-03-16 13:27:35 +01:00
|
|
|
# Interface from BaseFolder
|
2010-03-27 16:18:25 +01:00
|
|
|
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):
|
2002-06-21 06:30:08 +02:00
|
|
|
return
|
2010-03-27 16:18:25 +01:00
|
|
|
|
|
|
|
for uid in uidlist:
|
2020-08-29 19:43:52 +02:00
|
|
|
del (self.messagelist[uid])
|
2011-05-05 15:59:26 +02:00
|
|
|
self.save()
|