mbnames.py 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. # Mailbox name generator
  2. # Copyright (C) 2002-2016 John Goerzen & contributors
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; either version 2 of the License, or
  7. # (at your option) any later version.
  8. #
  9. # This program is distributed in the hope that it will be useful,
  10. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. # GNU General Public License for more details.
  13. #
  14. # You should have received a copy of the GNU General Public License
  15. # along with this program; if not, write to the Free Software
  16. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  17. import re # For folderfilter.
  18. import json
  19. from threading import Lock
  20. from os import listdir, makedirs, path, unlink
  21. from sys import exc_info
  22. from configparser import NoSectionError
  23. _mbLock = Lock()
  24. _mbnames = None
  25. # Called at sync time for each folder.
  26. def add(accountname, folder_root, foldername):
  27. global _mbnames
  28. if _mbnames.is_enabled() is not True:
  29. return
  30. with _mbLock:
  31. _mbnames.addAccountFolder(accountname, folder_root, foldername)
  32. # Called once.
  33. def init(conf, ui, dry_run):
  34. global _mbnames
  35. if _mbnames is None:
  36. _mbnames = _Mbnames(conf, ui, dry_run)
  37. # Called once.
  38. def prune(accounts):
  39. global _mbnames
  40. if _mbnames.is_enabled() is True:
  41. _mbnames.prune(accounts)
  42. else:
  43. _mbnames.pruneAll()
  44. # Called once.
  45. def write():
  46. """Write the mbnames file."""
  47. global _mbnames
  48. if _mbnames.is_enabled() is not True:
  49. return
  50. if _mbnames.get_incremental() is not True:
  51. _mbnames.write()
  52. # Called as soon as all the folders are synced for the account.
  53. def writeIntermediateFile(accountname):
  54. """Write intermediate mbnames file."""
  55. global _mbnames
  56. if _mbnames.is_enabled() is not True:
  57. return
  58. _mbnames.writeIntermediateFile(accountname)
  59. if _mbnames.get_incremental() is True:
  60. _mbnames.write()
  61. class _IntermediateMbnames():
  62. """mbnames data for one account."""
  63. def __init__(self, accountname, folder_root, mbnamesdir, folderfilter,
  64. dry_run, ui):
  65. self.ui = ui
  66. self._foldernames = []
  67. self._accountname = accountname
  68. self._folder_root = folder_root
  69. self._folderfilter = folderfilter
  70. self._path = path.join(mbnamesdir, "%s.json" % accountname)
  71. self._dryrun = dry_run
  72. def add(self, foldername):
  73. if foldername not in self._foldernames:
  74. self._foldernames.append(foldername)
  75. def get_folder_root(self):
  76. return self._folder_root
  77. def write(self):
  78. """Write intermediate mbnames file in JSON format."""
  79. itemlist = []
  80. for foldername in self._foldernames:
  81. if self._folderfilter(self._accountname, foldername):
  82. itemlist.append({
  83. 'accountname': self._accountname,
  84. 'foldername': foldername,
  85. 'localfolders': self._folder_root,
  86. })
  87. if self._dryrun:
  88. self.ui.info("mbnames would write %s" % self._path)
  89. else:
  90. with open(
  91. self._path, "w", encoding='utf-8') as intermediateFD:
  92. json.dump(itemlist, intermediateFD)
  93. class _Mbnames:
  94. def __init__(self, config, ui, dry_run):
  95. self._config = config
  96. self.ui = ui
  97. self._dryrun = dry_run
  98. self._enabled = None
  99. # Keys: accountname, values: _IntermediateMbnames instance.
  100. self._intermediates = {}
  101. self._incremental = None
  102. self._mbnamesdir = None
  103. self._path = None
  104. self._folderfilter = lambda accountname, foldername: True
  105. self._func_sortkey = lambda d: (d['accountname'], d['foldername'])
  106. localeval = config.getlocaleval()
  107. mbnamesdir = path.join(config.getmetadatadir(), "mbnames")
  108. self._peritem = None
  109. self._header = None
  110. self._sep = None
  111. self._footer = None
  112. try:
  113. if not self._dryrun:
  114. makedirs(mbnamesdir)
  115. except OSError:
  116. pass
  117. self._mbnamesdir = mbnamesdir
  118. try:
  119. self._enabled = self._config.getdefaultboolean(
  120. "mbnames", "enabled", False)
  121. self._peritem = self._config.get("mbnames", "peritem", raw=1)
  122. self._header = localeval.eval(config.get("mbnames", "header"))
  123. self._sep = localeval.eval(config.get("mbnames", "sep"))
  124. self._footer = localeval.eval(config.get("mbnames", "footer"))
  125. xforms = [path.expanduser, path.expandvars]
  126. self._path = config.apply_xforms(
  127. config.get("mbnames", "filename"), xforms)
  128. if self._config.has_option("mbnames", "sort_keyfunc"):
  129. self._func_sortkey = localeval.eval(
  130. self._config.get("mbnames", "sort_keyfunc"), {'re': re})
  131. if self._config.has_option("mbnames", "folderfilter"):
  132. self._folderfilter = localeval.eval(
  133. self._config.get("mbnames", "folderfilter"), {'re': re})
  134. except NoSectionError:
  135. pass
  136. def _iterIntermediateFiles(self):
  137. for foo in listdir(self._mbnamesdir):
  138. foo = path.join(self._mbnamesdir, foo)
  139. if path.isfile(foo) and foo[-5:] == '.json':
  140. yield foo
  141. def _removeIntermediateFile(self, path):
  142. if self._dryrun:
  143. self.ui.info("mbnames would remove %s" % path)
  144. else:
  145. unlink(path)
  146. self.ui.info("removed %s" % path)
  147. def addAccountFolder(self, accountname, folder_root, foldername):
  148. """Add foldername entry for an account."""
  149. if accountname not in self._intermediates:
  150. self._intermediates[accountname] = _IntermediateMbnames(
  151. accountname,
  152. folder_root,
  153. self._mbnamesdir,
  154. self._folderfilter,
  155. self._dryrun,
  156. self.ui,
  157. )
  158. self._intermediates[accountname].add(foldername)
  159. def get_incremental(self):
  160. if self._incremental is None:
  161. self._incremental = self._config.getdefaultboolean(
  162. "mbnames", "incremental", False)
  163. return self._incremental
  164. def is_enabled(self):
  165. return self._enabled
  166. def prune(self, accounts):
  167. removals = False
  168. for intermediateFile in self._iterIntermediateFiles():
  169. filename = path.basename(intermediateFile)
  170. accountname = filename[:-5]
  171. if accountname not in accounts:
  172. removals = True
  173. self._removeIntermediateFile(intermediateFile)
  174. if removals is False:
  175. self.ui.info("no cache file to remove")
  176. def pruneAll(self):
  177. for intermediateFile in self._iterIntermediateFiles():
  178. self._removeIntermediateFile(intermediateFile)
  179. def write(self):
  180. itemlist = []
  181. for intermediateFile in self._iterIntermediateFiles():
  182. try:
  183. with open(
  184. intermediateFile, 'r', encoding="utf-8") as intermediateFD:
  185. for item in json.load(intermediateFD):
  186. itemlist.append(item)
  187. except (OSError, IOError) as e:
  188. self.ui.error("could not read intermediate mbnames file '%s':"
  189. "%s" % (intermediateFile, str(e)))
  190. except Exception as e:
  191. self.ui.error(
  192. e,
  193. exc_info()[2],
  194. ("intermediate mbnames file %s not properly read" %
  195. intermediateFile)
  196. )
  197. itemlist.sort(key=self._func_sortkey)
  198. itemlist = [self._peritem % d for d in itemlist]
  199. if self._dryrun:
  200. self.ui.info("mbnames would write %s" % self._path)
  201. else:
  202. try:
  203. with open(
  204. self._path, 'w', encoding='utf-8') as mbnamesFile:
  205. mbnamesFile.write(self._header)
  206. mbnamesFile.write(self._sep.join(itemlist))
  207. mbnamesFile.write(self._footer)
  208. except (OSError, IOError) as e:
  209. self.ui.error(
  210. e,
  211. exc_info()[2],
  212. "mbnames file %s not properly written" % self._path
  213. )
  214. def writeIntermediateFile(self, accountname):
  215. try:
  216. self._intermediates[accountname].write()
  217. except (OSError, IOError) as e:
  218. self.ui.error(
  219. e,
  220. exc_info()[2],
  221. "intermediate mbnames file %s not properly written" % self._path
  222. )