learn --mbnames-prune CLI option
This is usefull to remove dangling entries for removed accounts or if mbnames is not enabled anymore. Signed-off-by: Nicolas Sebrecht <nicolas.s-dev@laposte.net>
This commit is contained in:
parent
4ef05fe6e1
commit
69c0080323
@ -177,6 +177,22 @@ before running this fix as well as verify the results using the `--dry-run'
|
||||
flag first.
|
||||
|
||||
|
||||
--mbnames-prune::
|
||||
Remove dangling entries for removed accounts or if mbnames is not enabled/used
|
||||
anymore.
|
||||
+
|
||||
Internally, offlineimap build intermediate mbnames files. They are added
|
||||
automatically when mbnames is enabled. However, disabling accounts so they are
|
||||
not synced anymore does not necessarily means they should be removed from the file
|
||||
built by mbnames. It is required to start offlineimap with this CLI option each
|
||||
time accounts are removed. When run, any account not in the 'accounts'
|
||||
configuration option are removed in the mbnames file.
|
||||
+
|
||||
It is possible to manually remove intermediate files in '<metadata>/mbnames/'.
|
||||
+
|
||||
Notice this option honors --dry-run.
|
||||
|
||||
|
||||
Synchronization Performance
|
||||
---------------------------
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Copyright (C) 2003-2015 John Goerzen & contributors
|
||||
# Copyright (C) 2003-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
|
||||
@ -22,7 +22,7 @@ import six
|
||||
|
||||
try:
|
||||
from ConfigParser import SafeConfigParser, Error
|
||||
except ImportError: #python3
|
||||
except ImportError: # Python3.
|
||||
from configparser import SafeConfigParser, Error
|
||||
from offlineimap.localeval import LocalEval
|
||||
|
||||
|
@ -148,6 +148,10 @@ class OfflineImap(object):
|
||||
action="store_true", dest="migrate_fmd5", default=False,
|
||||
help="migrate FMD5 hashes from versions prior to 6.3.5")
|
||||
|
||||
parser.add_option("--mbnames-prune",
|
||||
action="store_true", dest="mbnames_prune", default=False,
|
||||
help="remove mbnames entries for accounts not in accounts")
|
||||
|
||||
parser.add_option("-V",
|
||||
action="store_true", dest="version",
|
||||
default=False,
|
||||
@ -266,8 +270,14 @@ class OfflineImap(object):
|
||||
if dtype.lower() == u'imap':
|
||||
imaplib.Debug = 5
|
||||
|
||||
if options.mbnames_prune:
|
||||
mbnames.init(config, self.ui, options.dryrun)
|
||||
mbnames.prune(config.get("general", "accounts"))
|
||||
mbnames.write()
|
||||
sys.exit(0)
|
||||
|
||||
if options.runonce:
|
||||
# Must kill the possible default option
|
||||
# Must kill the possible default option.
|
||||
if config.has_option('DEFAULT', 'autorefresh'):
|
||||
config.remove_option('DEFAULT', 'autorefresh')
|
||||
# FIXME: spaghetti code alert!
|
||||
|
@ -19,22 +19,28 @@
|
||||
import re # For folderfilter.
|
||||
import json
|
||||
from threading import Lock
|
||||
from os import listdir, makedirs, path
|
||||
from os import listdir, makedirs, path, unlink
|
||||
from sys import exc_info
|
||||
try:
|
||||
import UserDict
|
||||
except ImportError:
|
||||
# Py3
|
||||
except ImportError: # Py3.
|
||||
from collections import UserDict
|
||||
try:
|
||||
from ConfigParser import NoSectionError
|
||||
except ImportError: # Py3.
|
||||
from configparser import NoSectionError
|
||||
|
||||
|
||||
_mbLock = Lock()
|
||||
_mbnames = None
|
||||
|
||||
|
||||
def _is_enabled(conf):
|
||||
return False
|
||||
|
||||
def add(accountname, folder_root, foldername):
|
||||
global _mbnames
|
||||
if _mbnames is None:
|
||||
if _mbnames.is_enabled() is not True:
|
||||
return
|
||||
|
||||
with _mbLock:
|
||||
@ -42,15 +48,21 @@ def add(accountname, folder_root, foldername):
|
||||
|
||||
def init(conf, ui, dry_run):
|
||||
global _mbnames
|
||||
enabled = conf.getdefaultboolean("mbnames", "enabled", False)
|
||||
if enabled is True and _mbnames is None:
|
||||
if _mbnames is None:
|
||||
_mbnames = _Mbnames(conf, ui, dry_run)
|
||||
|
||||
def prune(accounts):
|
||||
global _mbnames
|
||||
if _mbnames.is_enabled() is True:
|
||||
_mbnames.prune(accounts)
|
||||
else:
|
||||
_mbnames.pruneAll(accounts)
|
||||
|
||||
def write():
|
||||
"""Write the mbnames file."""
|
||||
|
||||
global _mbnames
|
||||
if _mbnames is None:
|
||||
if _mbnames.is_enabled() is not True:
|
||||
return
|
||||
|
||||
if _mbnames.get_incremental() is not True:
|
||||
@ -60,7 +72,7 @@ def writeIntermediateFile(accountname):
|
||||
"""Write intermediate mbnames file."""
|
||||
|
||||
global _mbnames
|
||||
if _mbnames is None:
|
||||
if _mbnames.is_enabled() is not True:
|
||||
return
|
||||
|
||||
_mbnames.writeIntermediateFile(accountname)
|
||||
@ -101,17 +113,18 @@ class _IntermediateMbnames(object):
|
||||
})
|
||||
|
||||
if not self._dryrun:
|
||||
with open(self._path, "wt") as intermediateFile:
|
||||
json.dump(itemlist, intermediateFile)
|
||||
with open(self._path, "wt") as intermediateFD:
|
||||
json.dump(itemlist, intermediateFD)
|
||||
|
||||
|
||||
class _Mbnames(object):
|
||||
def __init__(self, config, ui, dry_run):
|
||||
|
||||
self._config = config
|
||||
self._dryrun = dry_run
|
||||
self.ui = ui
|
||||
self._dryrun = dry_run
|
||||
|
||||
self._enabled = None
|
||||
# Keys: accountname, values: _IntermediateMbnames instance
|
||||
self._intermediates = {}
|
||||
self._incremental = None
|
||||
@ -119,14 +132,13 @@ class _Mbnames(object):
|
||||
self._path = None
|
||||
self._folderfilter = lambda accountname, foldername: True
|
||||
self._func_sortkey = lambda d: (d['accountname'], d['foldername'])
|
||||
self._peritem = self._config.get("mbnames", "peritem", raw=1)
|
||||
|
||||
localeval = config.getlocaleval()
|
||||
self._header = localeval.eval(config.get("mbnames", "header"))
|
||||
self._sep = localeval.eval(config.get("mbnames", "sep"))
|
||||
self._footer = localeval.eval(config.get("mbnames", "footer"))
|
||||
|
||||
mbnamesdir = path.join(config.getmetadatadir(), "mbnames")
|
||||
self._peritem = None
|
||||
self._header = None
|
||||
self._sep = None
|
||||
self._footer = None
|
||||
|
||||
try:
|
||||
if not self._dryrun:
|
||||
makedirs(mbnamesdir)
|
||||
@ -134,6 +146,14 @@ class _Mbnames(object):
|
||||
pass
|
||||
self._mbnamesdir = mbnamesdir
|
||||
|
||||
try:
|
||||
self._enabled = self._config.getdefaultboolean(
|
||||
"mbnames", "enabled", False)
|
||||
self._peritem = self._config.get("mbnames", "peritem", raw=1)
|
||||
self._header = localeval.eval(config.get("mbnames", "header"))
|
||||
self._sep = localeval.eval(config.get("mbnames", "sep"))
|
||||
self._footer = localeval.eval(config.get("mbnames", "footer"))
|
||||
|
||||
xforms = [path.expanduser, path.expandvars]
|
||||
self._path = config.apply_xforms(
|
||||
config.get("mbnames", "filename"), xforms)
|
||||
@ -145,6 +165,21 @@ class _Mbnames(object):
|
||||
if self._config.has_option("mbnames", "folderfilter"):
|
||||
self._folderfilter = localeval.eval(
|
||||
self._config.get("mbnames", "folderfilter"), {'re': re})
|
||||
except NoSectionError:
|
||||
pass
|
||||
|
||||
def _iterIntermediateFiles(self):
|
||||
for foo in listdir(self._mbnamesdir):
|
||||
foo = path.join(self._mbnamesdir, foo)
|
||||
if path.isfile(foo) and foo[-5:] == '.json':
|
||||
yield foo
|
||||
|
||||
def _removeIntermediateFile(self, path):
|
||||
if self._dryrun:
|
||||
self.ui.info("would remove %s"% path)
|
||||
else:
|
||||
unlink(path)
|
||||
self.ui.info("removed %s"% path)
|
||||
|
||||
def addAccountFolder(self, accountname, folder_root, foldername):
|
||||
"""Add foldername entry for an account."""
|
||||
@ -167,22 +202,40 @@ class _Mbnames(object):
|
||||
|
||||
return self._incremental
|
||||
|
||||
def is_enabled(self):
|
||||
return self._enabled
|
||||
|
||||
def prune(self, accounts):
|
||||
removals = False
|
||||
for intermediateFile in self._iterIntermediateFiles():
|
||||
filename = path.basename(intermediateFile)
|
||||
accountname = filename[:-5]
|
||||
if accountname not in accounts:
|
||||
removals = True
|
||||
self._removeIntermediateFile(intermediateFile)
|
||||
|
||||
if removals is False:
|
||||
self.ui.info("no cache file to remove")
|
||||
|
||||
def pruneAll(self, accounts):
|
||||
for intermediateFile in self._iterIntermediateFiles():
|
||||
self._removeIntermediateFile(intermediateFile)
|
||||
|
||||
def write(self):
|
||||
itemlist = []
|
||||
|
||||
try:
|
||||
for foo in listdir(self._mbnamesdir):
|
||||
foo = path.join(self._mbnamesdir, foo)
|
||||
if path.isfile(foo) and foo[-5:] == '.json':
|
||||
for intermediateFile in self._iterIntermediateFiles():
|
||||
try:
|
||||
with open(foo, 'rt') as intermediateFile:
|
||||
for item in json.load(intermediateFile):
|
||||
with open(intermediateFile, 'rt') as intermediateFD:
|
||||
for item in json.load(intermediateFD):
|
||||
itemlist.append(item)
|
||||
except Exception as e:
|
||||
self.ui.error(
|
||||
e,
|
||||
exc_info()[2],
|
||||
"intermediate mbnames file %s not properly read"% foo
|
||||
("intermediate mbnames file %s not properly read"%
|
||||
intermediateFile)
|
||||
)
|
||||
except OSError:
|
||||
pass
|
||||
|
Loading…
Reference in New Issue
Block a user