2020-11-01 08:43:16 +01:00
|
|
|
""" Base repository support
|
|
|
|
Copyright (C) 2002-2017 John Goerzen & contributors
|
2016-07-28 00:42:35 +02:00
|
|
|
|
2020-11-01 08:43:16 +01: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
|
|
|
|
the Free Software Foundation; either version 2 of the License, or
|
|
|
|
(at your option) any later version.
|
2002-06-19 06:39:00 +02:00
|
|
|
|
2020-11-01 08:43:16 +01: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
|
|
|
|
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
|
|
"""
|
2011-08-29 15:08:26 +02:00
|
|
|
import re
|
2003-04-18 04:18:34 +02:00
|
|
|
import os.path
|
2011-08-14 13:38:54 +02:00
|
|
|
from sys import exc_info
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2011-03-03 11:05:15 +01:00
|
|
|
from offlineimap import CustomConfig
|
|
|
|
from offlineimap.ui import getglobalui
|
2011-08-14 13:38:54 +02:00
|
|
|
from offlineimap.error import OfflineImapError
|
2003-04-18 04:18:34 +02:00
|
|
|
|
2011-09-16 10:54:23 +02:00
|
|
|
|
2016-07-28 00:42:35 +02:00
|
|
|
class BaseRepository(CustomConfig.ConfigHelperMixin):
|
2003-04-18 04:18:34 +02:00
|
|
|
def __init__(self, reposname, account):
|
2011-03-03 11:43:22 +01:00
|
|
|
self.ui = getglobalui()
|
2003-04-18 04:18:34 +02:00
|
|
|
self.account = account
|
|
|
|
self.config = account.getconfig()
|
|
|
|
self.name = reposname
|
|
|
|
self.localeval = account.getlocaleval()
|
2011-09-16 10:54:23 +02:00
|
|
|
self._accountname = self.account.getname()
|
2012-05-08 16:31:19 +02:00
|
|
|
self._readonly = self.getconfboolean('readonly', False)
|
2020-10-31 16:43:52 +01:00
|
|
|
self.uiddir = os.path.join(self.config.getmetadatadir(),
|
|
|
|
'Repository-' + self.name)
|
2003-04-18 04:18:34 +02:00
|
|
|
if not os.path.exists(self.uiddir):
|
2012-02-05 11:31:54 +01:00
|
|
|
os.mkdir(self.uiddir, 0o700)
|
2003-04-18 04:18:34 +02:00
|
|
|
self.mapdir = os.path.join(self.uiddir, 'UIDMapping')
|
|
|
|
if not os.path.exists(self.mapdir):
|
2012-02-05 11:31:54 +01:00
|
|
|
os.mkdir(self.mapdir, 0o700)
|
2015-01-11 00:17:08 +01:00
|
|
|
# FIXME: self.uiddir variable name is lying about itself.
|
2003-04-18 04:18:34 +02:00
|
|
|
self.uiddir = os.path.join(self.uiddir, 'FolderValidity')
|
|
|
|
if not os.path.exists(self.uiddir):
|
2012-02-05 11:31:54 +01:00
|
|
|
os.mkdir(self.uiddir, 0o700)
|
2003-04-18 04:18:34 +02:00
|
|
|
|
2011-08-29 15:08:26 +02:00
|
|
|
self.nametrans = lambda foldername: foldername
|
|
|
|
self.folderfilter = lambda foldername: 1
|
|
|
|
self.folderincludes = []
|
2012-02-06 17:33:50 +01:00
|
|
|
self.foldersort = None
|
2015-01-27 18:56:54 +01:00
|
|
|
self.newmail_hook = None
|
2011-08-29 15:08:26 +02:00
|
|
|
if self.config.has_option(self.getsection(), 'nametrans'):
|
|
|
|
self.nametrans = self.localeval.eval(
|
|
|
|
self.getconf('nametrans'), {'re': re})
|
|
|
|
if self.config.has_option(self.getsection(), 'folderfilter'):
|
|
|
|
self.folderfilter = self.localeval.eval(
|
|
|
|
self.getconf('folderfilter'), {'re': re})
|
|
|
|
if self.config.has_option(self.getsection(), 'folderincludes'):
|
|
|
|
self.folderincludes = self.localeval.eval(
|
|
|
|
self.getconf('folderincludes'), {'re': re})
|
|
|
|
if self.config.has_option(self.getsection(), 'foldersort'):
|
|
|
|
self.foldersort = self.localeval.eval(
|
|
|
|
self.getconf('foldersort'), {'re': re})
|
|
|
|
|
New restoreatime patch from Ben Kibbey
From: Ben Kibbey
Subject: Re: Removed restoratime from OfflineIMAP
On Wed, May 03, 2006 at 10:08:35PM -0500, John Goerzen wrote:
> Hi Ben,
>
> Thanks for your restoreatime patch.
>
> However, I have received this bug report:
>
> http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365933
>
> After looking at the problem, here's what's going on.
>
> The person is using IMAP as the local repository as well.
>
> You really need to move the atime save and restore code from accounts.py
> into the repository/Maildir.py. Then, for any new call you add to the
> Maildir repository (that will be called from outside Maildir.py), you
> need to add a corresponding default function to repository/Base.py, and
> also make sure that on folders (such as IMAP) where atime restoration
> makes no sense, no error is generated.
>
> Let me know if that doesn't make sense to you. If you get it fixed, I'd
> be happy to re-apply it to a future version of OfflineIMAP.
>
> -- John Goerzen
>
Attached is a new diff that should work though not really tested
(v4.0.14). In repository/Base.py restore_atime() will call
self.restore_folder_atimes() only if the folder type is Maildir. Let me
know if it has any more problems.
2006-09-06 03:33:07 +02:00
|
|
|
def restore_atime(self):
|
2011-09-15 15:06:30 +02:00
|
|
|
"""Sets folders' atime back to their values after a sync
|
New restoreatime patch from Ben Kibbey
From: Ben Kibbey
Subject: Re: Removed restoratime from OfflineIMAP
On Wed, May 03, 2006 at 10:08:35PM -0500, John Goerzen wrote:
> Hi Ben,
>
> Thanks for your restoreatime patch.
>
> However, I have received this bug report:
>
> http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365933
>
> After looking at the problem, here's what's going on.
>
> The person is using IMAP as the local repository as well.
>
> You really need to move the atime save and restore code from accounts.py
> into the repository/Maildir.py. Then, for any new call you add to the
> Maildir repository (that will be called from outside Maildir.py), you
> need to add a corresponding default function to repository/Base.py, and
> also make sure that on folders (such as IMAP) where atime restoration
> makes no sense, no error is generated.
>
> Let me know if that doesn't make sense to you. If you get it fixed, I'd
> be happy to re-apply it to a future version of OfflineIMAP.
>
> -- John Goerzen
>
Attached is a new diff that should work though not really tested
(v4.0.14). In repository/Base.py restore_atime() will call
self.restore_folder_atimes() only if the folder type is Maildir. Let me
know if it has any more problems.
2006-09-06 03:33:07 +02:00
|
|
|
|
2011-09-15 15:06:30 +02:00
|
|
|
Controlled by the 'restoreatime' config parameter (default
|
|
|
|
False), applies only to local Maildir mailboxes and does nothing
|
|
|
|
on all other repository types."""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2011-09-15 15:06:30 +02:00
|
|
|
pass
|
New restoreatime patch from Ben Kibbey
From: Ben Kibbey
Subject: Re: Removed restoratime from OfflineIMAP
On Wed, May 03, 2006 at 10:08:35PM -0500, John Goerzen wrote:
> Hi Ben,
>
> Thanks for your restoreatime patch.
>
> However, I have received this bug report:
>
> http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=365933
>
> After looking at the problem, here's what's going on.
>
> The person is using IMAP as the local repository as well.
>
> You really need to move the atime save and restore code from accounts.py
> into the repository/Maildir.py. Then, for any new call you add to the
> Maildir repository (that will be called from outside Maildir.py), you
> need to add a corresponding default function to repository/Base.py, and
> also make sure that on folders (such as IMAP) where atime restoration
> makes no sense, no error is generated.
>
> Let me know if that doesn't make sense to you. If you get it fixed, I'd
> be happy to re-apply it to a future version of OfflineIMAP.
>
> -- John Goerzen
>
Attached is a new diff that should work though not really tested
(v4.0.14). In repository/Base.py restore_atime() will call
self.restore_folder_atimes() only if the folder type is Maildir. Let me
know if it has any more problems.
2006-09-06 03:33:07 +02:00
|
|
|
|
2007-07-05 06:04:14 +02:00
|
|
|
def connect(self):
|
|
|
|
"""Establish a connection to the remote, if necessary. This exists
|
|
|
|
so that IMAP connections can all be established up front, gathering
|
|
|
|
passwords as needed. It was added in order to support the
|
|
|
|
error recovery -- we need to connect first outside of the error
|
|
|
|
trap in order to validate the password, and that's the point of
|
|
|
|
this function."""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2007-07-05 06:04:14 +02:00
|
|
|
pass
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def holdordropconnections(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def dropconnections(self):
|
|
|
|
pass
|
|
|
|
|
|
|
|
def getaccount(self):
|
|
|
|
return self.account
|
|
|
|
|
|
|
|
def getname(self):
|
|
|
|
return self.name
|
|
|
|
|
2011-04-27 12:15:51 +02:00
|
|
|
def __str__(self):
|
|
|
|
return self.name
|
|
|
|
|
2011-09-16 10:54:23 +02:00
|
|
|
@property
|
|
|
|
def accountname(self):
|
|
|
|
"""Account name as string"""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2011-09-16 10:54:23 +02:00
|
|
|
return self._accountname
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getuiddir(self):
|
|
|
|
return self.uiddir
|
|
|
|
|
|
|
|
def getmapdir(self):
|
|
|
|
return self.mapdir
|
|
|
|
|
2014-10-05 09:49:08 +02:00
|
|
|
# Interface from CustomConfig.ConfigHelperMixin
|
2003-04-18 04:18:34 +02:00
|
|
|
def getsection(self):
|
|
|
|
return 'Repository ' + self.name
|
|
|
|
|
2014-10-05 09:49:08 +02:00
|
|
|
# Interface from CustomConfig.ConfigHelperMixin
|
2003-04-18 04:18:34 +02:00
|
|
|
def getconfig(self):
|
|
|
|
return self.config
|
|
|
|
|
2012-05-08 16:31:19 +02:00
|
|
|
@property
|
|
|
|
def readonly(self):
|
|
|
|
"""Is the repository readonly?"""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2012-05-08 16:31:19 +02:00
|
|
|
return self._readonly
|
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def getlocaleval(self):
|
|
|
|
return self.account.getlocaleval()
|
2013-07-21 21:00:23 +02:00
|
|
|
|
2002-06-19 06:39:00 +02:00
|
|
|
def getfolders(self):
|
|
|
|
"""Returns a list of ALL folders on this server."""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2002-06-19 06:39:00 +02:00
|
|
|
return []
|
2002-06-19 08:16:19 +02:00
|
|
|
|
2007-07-06 18:46:29 +02:00
|
|
|
def forgetfolders(self):
|
|
|
|
"""Forgets the cached list of folders, if any. Useful to run
|
|
|
|
after a sync run."""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2007-07-06 18:46:29 +02:00
|
|
|
pass
|
|
|
|
|
2002-06-19 08:16:19 +02:00
|
|
|
def getsep(self):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2015-11-20 20:09:09 +01:00
|
|
|
def getkeywordmap(self):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2012-09-01 02:30:46 +02:00
|
|
|
def should_sync_folder(self, fname):
|
|
|
|
"""Should this folder be synced?"""
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2012-09-01 02:30:46 +02:00
|
|
|
return fname in self.folderincludes or self.folderfilter(fname)
|
|
|
|
|
2016-11-20 13:54:10 +01:00
|
|
|
def should_create_folders(self):
|
2012-05-08 16:41:21 +02:00
|
|
|
"""Is folder creation enabled on this repository?
|
|
|
|
|
|
|
|
It is disabled by either setting the whole repository
|
2012-08-21 16:59:52 +02:00
|
|
|
'readonly' or by using the 'createfolders' setting."""
|
2015-01-01 21:41:11 +01:00
|
|
|
|
2020-10-31 16:43:52 +01:00
|
|
|
return (not self._readonly) and self.getconfboolean('createfolders',
|
|
|
|
True)
|
2012-05-08 16:41:21 +02:00
|
|
|
|
2002-06-20 04:55:24 +02:00
|
|
|
def makefolder(self, foldername):
|
2015-01-14 22:58:25 +01:00
|
|
|
"""Create a new folder."""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2002-06-19 08:16:19 +02:00
|
|
|
raise NotImplementedError
|
|
|
|
|
2002-06-20 04:55:24 +02:00
|
|
|
def deletefolder(self, foldername):
|
|
|
|
raise NotImplementedError
|
|
|
|
|
2017-10-26 18:23:15 +02:00
|
|
|
def getfolder(self, foldername, decode=True):
|
|
|
|
"""Get the folder for this repo.
|
|
|
|
|
|
|
|
WARNING: the signature changes whether it's remote or local:
|
|
|
|
- remote types have the decode arg
|
|
|
|
- local types don't have the decode arg
|
|
|
|
"""
|
2002-06-19 08:16:19 +02:00
|
|
|
raise NotImplementedError
|
2012-01-05 14:05:51 +01:00
|
|
|
|
2016-11-20 13:54:10 +01:00
|
|
|
def sync_folder_structure(self, local_repo, status_repo):
|
|
|
|
"""Sync the folders structure.
|
2008-03-03 05:17:45 +01:00
|
|
|
|
2011-08-14 13:38:54 +02:00
|
|
|
It does NOT sync the contents of those folders. nametrans rules
|
2016-11-20 13:54:10 +01:00
|
|
|
in both directions will be honored
|
|
|
|
|
|
|
|
Configuring nametrans on BOTH repositories could lead to infinite folder
|
|
|
|
creation cycles."""
|
2015-01-08 17:13:33 +01:00
|
|
|
|
2020-10-31 16:43:52 +01:00
|
|
|
if not self.should_create_folders()\
|
|
|
|
and not local_repo.should_create_folders():
|
2016-04-09 19:52:33 +02:00
|
|
|
# Quick exit if no folder creation is enabled on either side.
|
2012-05-08 16:41:21 +02:00
|
|
|
return
|
|
|
|
|
2016-11-20 13:54:10 +01:00
|
|
|
remote_repo = self
|
|
|
|
remote_hash, local_hash = {}, {}
|
2016-04-09 17:35:15 +02:00
|
|
|
|
2016-11-20 13:54:10 +01:00
|
|
|
for folder in remote_repo.getfolders():
|
|
|
|
remote_hash[folder.getname()] = folder
|
|
|
|
|
|
|
|
for folder in local_repo.getfolders():
|
2016-11-29 21:18:12 +01:00
|
|
|
local_hash[folder.getname()] = folder
|
|
|
|
|
|
|
|
# Create new folders from remote to local.
|
2020-08-28 03:32:43 +02:00
|
|
|
for remote_name, remote_folder in list(remote_hash.items()):
|
2016-11-29 21:18:12 +01:00
|
|
|
# Don't create on local_repo, if it is readonly.
|
|
|
|
if not local_repo.should_create_folders():
|
|
|
|
break
|
|
|
|
|
|
|
|
# Apply remote nametrans and fix serparator.
|
2016-11-30 21:33:52 +01:00
|
|
|
local_name = remote_folder.getvisiblename().replace(
|
2016-11-29 21:18:12 +01:00
|
|
|
remote_repo.getsep(), local_repo.getsep())
|
2020-10-31 16:43:52 +01:00
|
|
|
if remote_folder.sync_this \
|
|
|
|
and local_name not in list(local_hash.keys()):
|
2016-11-29 21:18:12 +01:00
|
|
|
try:
|
|
|
|
local_repo.makefolder(local_name)
|
|
|
|
# Need to refresh list.
|
|
|
|
local_repo.forgetfolders()
|
|
|
|
except OfflineImapError as e:
|
|
|
|
self.ui.error(e, exc_info()[2],
|
2020-08-29 19:49:37 +02:00
|
|
|
"Creating folder %s on repository %s" %
|
|
|
|
(local_name, local_repo))
|
2016-11-29 21:18:12 +01:00
|
|
|
raise
|
|
|
|
status_repo.makefolder(local_name.replace(
|
|
|
|
local_repo.getsep(), status_repo.getsep()))
|
2016-11-20 13:54:10 +01:00
|
|
|
|
|
|
|
# Create new folders from local to remote.
|
2020-08-28 03:32:43 +02:00
|
|
|
for local_name, local_folder in list(local_hash.items()):
|
2016-11-20 13:54:10 +01:00
|
|
|
if not remote_repo.should_create_folders():
|
2012-01-05 14:05:51 +01:00
|
|
|
# Don't create missing folder on readonly repo.
|
|
|
|
break
|
|
|
|
|
2016-11-29 21:18:12 +01:00
|
|
|
# Apply reverse nametrans and fix serparator.
|
2016-11-30 21:33:52 +01:00
|
|
|
remote_name = local_folder.getvisiblename().replace(
|
2016-11-29 21:18:12 +01:00
|
|
|
local_repo.getsep(), remote_repo.getsep())
|
2020-10-31 16:43:52 +01:00
|
|
|
if local_folder.sync_this \
|
|
|
|
and remote_name not in list(remote_hash.keys()):
|
2016-11-20 13:54:10 +01:00
|
|
|
# Would the remote filter out the new folder name? In this case
|
|
|
|
# don't create it.
|
|
|
|
if not remote_repo.should_sync_folder(remote_name):
|
2020-10-31 16:43:52 +01:00
|
|
|
msg = "Not creating folder '%s' (repository '%s') " \
|
|
|
|
"as it would be filtered out on that repository." % \
|
|
|
|
(remote_name, self)
|
|
|
|
self.ui.debug('', msg)
|
2011-09-19 21:25:50 +02:00
|
|
|
continue
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2016-11-29 21:18:12 +01:00
|
|
|
# nametrans sanity check! Does remote nametrans lead to the
|
|
|
|
# original local name?
|
2016-11-20 13:54:10 +01:00
|
|
|
#
|
2016-11-29 21:18:12 +01:00
|
|
|
# Apply remote nametrans to see if we end up with the same
|
|
|
|
# name. We have:
|
|
|
|
# - remote_name: local_name -> reverse nametrans + separator
|
|
|
|
# We want local_name == loop_name from:
|
|
|
|
# - remote_name -> remote (nametrans + separator) -> loop_name
|
2016-11-20 13:54:10 +01:00
|
|
|
#
|
|
|
|
# Get IMAPFolder and see if the reverse nametrans works fine.
|
|
|
|
# TODO: getfolder() works only because we succeed in getting
|
|
|
|
# inexisting folders which I would like to change. Take care!
|
2020-10-31 16:43:52 +01:00
|
|
|
tmp_remotefolder = remote_repo.getfolder(remote_name,
|
|
|
|
decode=False)
|
2016-11-29 21:18:12 +01:00
|
|
|
loop_name = tmp_remotefolder.getvisiblename().replace(
|
2016-11-20 13:54:10 +01:00
|
|
|
remote_repo.getsep(), local_repo.getsep())
|
2016-11-29 21:18:12 +01:00
|
|
|
if local_name != loop_name:
|
2020-10-31 16:43:52 +01:00
|
|
|
msg = "INFINITE FOLDER CREATION DETECTED! "\
|
|
|
|
"Folder '%s' (repository '%s') would be created as " \
|
|
|
|
"folder '%s' (repository '%s'). The latter " \
|
|
|
|
"becomes '%s' in return, leading to infinite " \
|
|
|
|
"folder creation cycles.\n "\
|
|
|
|
"SOLUTION: 1) Do set your nametrans rules on both " \
|
|
|
|
"repositories so they lead to identical names if " \
|
|
|
|
"applied back and forth. " \
|
|
|
|
"2) Use folderfilter settings on a repository to " \
|
|
|
|
"prevent some folders from being created on the " \
|
|
|
|
"other side." % \
|
|
|
|
(local_folder.getname(), local_repo, remote_name,
|
|
|
|
remote_repo, loop_name)
|
|
|
|
raise OfflineImapError(msg, OfflineImapError.ERROR.REPO)
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2016-06-17 19:47:37 +02:00
|
|
|
# End sanity check, actually create the folder.
|
2011-08-14 13:38:54 +02:00
|
|
|
try:
|
2016-11-20 13:54:10 +01:00
|
|
|
remote_repo.makefolder(remote_name)
|
|
|
|
# Need to refresh list.
|
|
|
|
self.forgetfolders()
|
2012-02-05 10:14:23 +01:00
|
|
|
except OfflineImapError as e:
|
2020-10-31 16:43:52 +01:00
|
|
|
msg = "Creating folder %s on repository %s" % \
|
|
|
|
(remote_name, remote_repo)
|
|
|
|
self.ui.error(e, exc_info()[2], msg)
|
2016-11-20 13:54:10 +01:00
|
|
|
raise
|
2016-11-28 11:42:17 +01:00
|
|
|
status_repo.makefolder(local_name.replace(
|
|
|
|
local_repo.getsep(), status_repo.getsep()))
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2002-06-19 08:16:19 +02:00
|
|
|
# Find deleted folders.
|
2011-09-30 08:58:08 +02:00
|
|
|
# TODO: We don't delete folders right now.
|
2016-11-20 13:54:10 +01:00
|
|
|
return None
|
2002-06-19 08:16:19 +02:00
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
def startkeepalive(self):
|
|
|
|
"""The default implementation will do nothing."""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
pass
|
|
|
|
|
2008-08-03 00:04:32 +02:00
|
|
|
def stopkeepalive(self):
|
|
|
|
"""Stop keep alive, but don't bother waiting
|
2003-04-18 04:18:34 +02:00
|
|
|
for the threads to terminate."""
|
2016-11-20 13:54:10 +01:00
|
|
|
|
2003-04-18 04:18:34 +02:00
|
|
|
pass
|
2011-10-27 16:56:18 +02:00
|
|
|
|
2013-08-27 16:11:57 +02:00
|
|
|
def getlocalroot(self):
|
2016-05-08 15:11:50 +02:00
|
|
|
""" Local root folder for storing messages.
|
|
|
|
Will not be set for remote repositories."""
|
2013-08-27 16:11:57 +02:00
|
|
|
|
2016-11-20 13:54:10 +01:00
|
|
|
return None
|