Merge pull request #22 from thekix/master
Multiple style patches and bug about remotepasseval
This commit is contained in:
commit
319e5a03b4
@ -1,19 +1,21 @@
|
||||
# Base folder support
|
||||
# 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
|
||||
"""
|
||||
Base folder support
|
||||
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
|
||||
"""
|
||||
|
||||
import os.path
|
||||
import re
|
||||
@ -27,6 +29,9 @@ import offlineimap.accounts
|
||||
|
||||
|
||||
class BaseFolder:
|
||||
"""
|
||||
Base Folder Class
|
||||
"""
|
||||
__hash__ = None
|
||||
|
||||
def __init__(self, name, repository):
|
||||
@ -130,6 +135,12 @@ class BaseFolder:
|
||||
return self.repository.should_sync_folder(self.ffilter_name)
|
||||
|
||||
def dofsync(self):
|
||||
"""
|
||||
Call and returns _dofsync()
|
||||
|
||||
Returns: Call and returns _dofsync()
|
||||
|
||||
"""
|
||||
return self._dofsync
|
||||
|
||||
def suggeststhreads(self):
|
||||
@ -196,6 +207,13 @@ class BaseFolder:
|
||||
return self.sep
|
||||
|
||||
def getfullname(self):
|
||||
"""
|
||||
Returns the folder full name, using the getname(). If getroot() is set
|
||||
their value is concatenated to getname() using the separator
|
||||
|
||||
Returns: The folder full name
|
||||
|
||||
"""
|
||||
if self.getroot():
|
||||
return self.getroot() + self.getsep() + self.getname()
|
||||
else:
|
||||
@ -371,6 +389,12 @@ class BaseFolder:
|
||||
OfflineImapError.ERROR.MESSAGE)
|
||||
|
||||
def getmaxsize(self):
|
||||
"""
|
||||
Get the maxsize for account name. If not found, returns None.
|
||||
|
||||
Returns: A string with the maxise of the account name
|
||||
|
||||
"""
|
||||
return self.config.getdefaultint("Account %s" %
|
||||
self.accountname, "maxsize", None)
|
||||
|
||||
@ -396,19 +420,41 @@ class BaseFolder:
|
||||
OfflineImapError.ERROR.MESSAGE)
|
||||
|
||||
def get_min_uid_file(self):
|
||||
"""
|
||||
Get the min UID file name. Create it if not found.
|
||||
|
||||
Returns: Min UID file name.
|
||||
|
||||
"""
|
||||
startuiddir = os.path.join(self.config.getmetadatadir(),
|
||||
'Repository-' + self.repository.name, 'StartUID')
|
||||
'Repository-' + self.repository.name,
|
||||
'StartUID')
|
||||
if not os.path.exists(startuiddir):
|
||||
os.mkdir(startuiddir, 0o700)
|
||||
return os.path.join(startuiddir, self.getfolderbasename())
|
||||
|
||||
def save_min_uid(self, min_uid):
|
||||
"""
|
||||
Save the min UID in the min uid file
|
||||
|
||||
Args:
|
||||
min_uid: min_uid to save
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
uidfile = self.get_min_uid_file()
|
||||
fd = open(uidfile, 'wt')
|
||||
fd.write(str(min_uid) + "\n")
|
||||
fd.close()
|
||||
|
||||
def retrieve_min_uid(self):
|
||||
"""
|
||||
Retrieve the min UID file
|
||||
|
||||
Returns: min UID of file
|
||||
|
||||
"""
|
||||
uidfile = self.get_min_uid_file()
|
||||
if not os.path.exists(uidfile):
|
||||
return None
|
||||
@ -594,8 +640,8 @@ class BaseFolder:
|
||||
def addmessageheader(self, content, linebreak, headername, headervalue):
|
||||
"""Adds new header to the provided message.
|
||||
|
||||
WARNING: This function is a bit tricky, and modifying it in the wrong way,
|
||||
may easily lead to data-loss.
|
||||
WARNING: This function is a bit tricky, and modifying it in the wrong
|
||||
way, may easily lead to data-loss.
|
||||
|
||||
Arguments:
|
||||
- content: message content, headers and body as a single string
|
||||
@ -605,8 +651,8 @@ class BaseFolder:
|
||||
|
||||
.. note::
|
||||
|
||||
The following documentation will not get displayed correctly after being
|
||||
processed by Sphinx. View the source of this method to read it.
|
||||
The following documentation will not get displayed correctly after
|
||||
being processed by Sphinx. View the source of this method to read it.
|
||||
|
||||
This has to deal with strange corner cases where the header is
|
||||
missing or empty. Here are illustrations for all the cases,
|
||||
@ -659,7 +705,8 @@ class BaseFolder:
|
||||
self.ui.debug('', 'addmessageheader: headers were missing')
|
||||
else:
|
||||
self.ui.debug('',
|
||||
'addmessageheader: headers end at position %d' % insertionpoint)
|
||||
'addmessageheader: headers end at position %d' %
|
||||
insertionpoint)
|
||||
mark = '==>EOH<=='
|
||||
contextstart = max(0, insertionpoint - 100)
|
||||
contextend = min(len(content), insertionpoint + 100)
|
||||
@ -693,9 +740,11 @@ class BaseFolder:
|
||||
self.ui.debug('',
|
||||
'addmessageheader: insertionpoint = %d' % insertionpoint)
|
||||
headers = content[0:insertionpoint]
|
||||
self.ui.debug('', 'addmessageheader: headers = %s' % repr(headers))
|
||||
self.ui.debug('',
|
||||
'addmessageheader: headers = %s' % repr(headers))
|
||||
new_header = prefix + ("%s: %s" % (headername, headervalue)) + suffix
|
||||
self.ui.debug('', 'addmessageheader: new_header = %s' % repr(new_header))
|
||||
self.ui.debug('',
|
||||
'addmessageheader: new_header = %s' % repr(new_header))
|
||||
return headers + new_header + content[insertionpoint:]
|
||||
|
||||
def __find_eoh(self, content):
|
||||
@ -746,7 +795,8 @@ class BaseFolder:
|
||||
- contents: message itself
|
||||
- name: name of the header to be searched
|
||||
|
||||
Returns: list of header values or empty list if no such header was found.
|
||||
Returns: list of header values or empty list if no such header was
|
||||
found.
|
||||
"""
|
||||
|
||||
self.ui.debug('', 'getmessageheaderlist: called to get %s' % name)
|
||||
@ -769,7 +819,8 @@ class BaseFolder:
|
||||
|
||||
if type(header_list) != type([]):
|
||||
header_list = [header_list]
|
||||
self.ui.debug('', 'deletemessageheaders: called to delete %s' % header_list)
|
||||
self.ui.debug('',
|
||||
'deletemessageheaders: called to delete %s' % header_list)
|
||||
|
||||
if not len(header_list):
|
||||
return content
|
||||
@ -783,7 +834,8 @@ class BaseFolder:
|
||||
for h in headers.split('\n'):
|
||||
keep_it = True
|
||||
for trim_h in header_list:
|
||||
if len(h) > len(trim_h) and h[0:len(trim_h) + 1] == (trim_h + ":"):
|
||||
if len(h) > len(trim_h) \
|
||||
and h[0:len(trim_h) + 1] == (trim_h + ":"):
|
||||
keep_it = False
|
||||
break
|
||||
if keep_it:
|
||||
@ -873,9 +925,10 @@ class BaseFolder:
|
||||
# IMAP servers ...
|
||||
self.deletemessage(uid)
|
||||
else:
|
||||
raise OfflineImapError("Trying to save msg (uid %d) on folder "
|
||||
"%s returned invalid uid %d" % (uid, dstfolder.getvisiblename(),
|
||||
new_uid), OfflineImapError.ERROR.MESSAGE)
|
||||
msg = "Trying to save msg (uid %d) on folder " \
|
||||
"%s returned invalid uid %d" % \
|
||||
(uid, dstfolder.getvisiblename(), new_uid)
|
||||
raise OfflineImapError(msg, OfflineImapError.ERROR.MESSAGE)
|
||||
except KeyboardInterrupt: # Bubble up CTRL-C.
|
||||
raise
|
||||
except OfflineImapError as e:
|
||||
@ -884,7 +937,8 @@ class BaseFolder:
|
||||
self.ui.error(e, exc_info()[2])
|
||||
except Exception as e:
|
||||
self.ui.error(e, exc_info()[2],
|
||||
msg="Copying message %s [acc: %s]" % (uid, self.accountname))
|
||||
msg="Copying message %s [acc: %s]" %
|
||||
(uid, self.accountname))
|
||||
raise # Raise on unknown errors, so we can fix those.
|
||||
|
||||
def __syncmessagesto_copy(self, dstfolder, statusfolder):
|
||||
@ -929,24 +983,28 @@ class BaseFolder:
|
||||
break
|
||||
|
||||
if uid == 0:
|
||||
self.ui.warn("Assertion that UID != 0 failed; ignoring message.")
|
||||
msg = "Assertion that UID != 0 failed; ignoring message."
|
||||
self.ui.warn(msg)
|
||||
continue
|
||||
|
||||
if uid > 0 and dstfolder.uidexists(uid):
|
||||
# dstfolder has message with that UID already, only update status.
|
||||
# dstfolder has message with that UID already,
|
||||
# only update status.
|
||||
flags = self.getmessageflags(uid)
|
||||
rtime = self.getmessagetime(uid)
|
||||
statusfolder.savemessage(uid, None, flags, rtime)
|
||||
continue
|
||||
|
||||
self.ui.copyingmessage(uid, num + 1, num_to_copy, self, dstfolder)
|
||||
self.ui.copyingmessage(uid, num + 1, num_to_copy, self,
|
||||
dstfolder)
|
||||
# Exceptions are caught in copymessageto().
|
||||
if self.suggeststhreads():
|
||||
self.waitforthread()
|
||||
thread = threadutil.InstanceLimitedThread(
|
||||
self.getinstancelimitnamespace(),
|
||||
target=self.copymessageto,
|
||||
name="Copy message from %s:%s" % (self.repository, self),
|
||||
name="Copy message from %s:%s" % (self.repository,
|
||||
self),
|
||||
args=(uid, dstfolder, statusfolder)
|
||||
)
|
||||
thread.start()
|
||||
@ -1015,9 +1073,10 @@ class BaseFolder:
|
||||
# skip them.
|
||||
skipped_keywords = list(selfkeywords - knownkeywords)
|
||||
selfkeywords &= knownkeywords
|
||||
self.ui.warn("Unknown keywords skipped: %s\n"
|
||||
"You may want to change your configuration to include "
|
||||
"those\n" % skipped_keywords)
|
||||
msg = "Unknown keywords skipped: %s\n" \
|
||||
"You may want to change your configuration to include " \
|
||||
"those\n" % skipped_keywords
|
||||
self.ui.warn(msg)
|
||||
|
||||
keywordletterset = set([keywordmap[keyw] for keyw in selfkeywords])
|
||||
|
||||
@ -1125,16 +1184,16 @@ class BaseFolder:
|
||||
break
|
||||
try:
|
||||
action(dstfolder, statusfolder)
|
||||
except (KeyboardInterrupt):
|
||||
except KeyboardInterrupt:
|
||||
raise
|
||||
except OfflineImapError as e:
|
||||
if e.severity > OfflineImapError.ERROR.FOLDER:
|
||||
raise
|
||||
self.ui.error(e, exc_info()[2], "while syncing %s [account %s]" %
|
||||
(self, self.accountname))
|
||||
msg = "while syncing %s [account %s]" % (self, self.accountname)
|
||||
self.ui.error(e, exc_info()[2], msg)
|
||||
except Exception as e:
|
||||
self.ui.error(e, exc_info()[2], "while syncing %s [account %s]" %
|
||||
(self, self.accountname))
|
||||
msg = "while syncing %s [account %s]" % (self, self.accountname)
|
||||
self.ui.error(e, exc_info()[2], msg)
|
||||
raise # Raise unknown Exceptions so we can fix them.
|
||||
|
||||
def __eq__(self, other):
|
||||
|
@ -1 +1,4 @@
|
||||
"""
|
||||
Folder Module of offlineimap
|
||||
"""
|
||||
from . import Base, Gmail, IMAP, Maildir, LocalStatus, UIDMaps
|
||||
|
@ -18,9 +18,10 @@
|
||||
import datetime
|
||||
import hashlib
|
||||
import hmac
|
||||
import socket
|
||||
import json
|
||||
import urllib.request, urllib.parse, urllib.error
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
import urllib.error
|
||||
import time
|
||||
import errno
|
||||
import socket
|
||||
@ -39,7 +40,7 @@ except ImportError:
|
||||
have_gss = False
|
||||
|
||||
|
||||
class IMAPServer():
|
||||
class IMAPServer:
|
||||
"""Initializes all variables from an IMAPRepository() instance
|
||||
|
||||
Various functions, such as acquireconnection() return an IMAP4
|
||||
@ -331,7 +332,8 @@ class IMAPServer():
|
||||
except imapobj.error as e:
|
||||
raise OfflineImapError("Failed to start "
|
||||
"TLS connection: %s" % str(e),
|
||||
OfflineImapError.ERROR.REPO, None, exc_info()[2])
|
||||
OfflineImapError.ERROR.REPO,
|
||||
exc_info()[2])
|
||||
|
||||
# All __authn_* procedures are helpers that do authentication.
|
||||
# They are class methods that take one parameter, IMAP object.
|
||||
@ -776,7 +778,7 @@ class IdleThread:
|
||||
self.thread = Thread(target=self.noop)
|
||||
else:
|
||||
self.thread = Thread(target=self.__idle)
|
||||
self.thread.setDaemon(1)
|
||||
self.thread.setDaemon(True)
|
||||
|
||||
def start(self):
|
||||
self.thread.start()
|
||||
|
@ -1,21 +1,20 @@
|
||||
""" Base repository support """
|
||||
""" Base repository support
|
||||
Copyright (C) 2002-2017 John Goerzen & contributors
|
||||
|
||||
# Copyright (C) 2002-2017 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
|
||||
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
|
||||
"""
|
||||
import re
|
||||
import os.path
|
||||
from sys import exc_info
|
||||
@ -26,6 +25,9 @@ from offlineimap.error import OfflineImapError
|
||||
|
||||
|
||||
class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
"""
|
||||
Base Class for Repository
|
||||
"""
|
||||
def __init__(self, reposname, account):
|
||||
self.ui = getglobalui()
|
||||
self.account = account
|
||||
@ -34,7 +36,8 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
self.localeval = account.getlocaleval()
|
||||
self._accountname = self.account.getname()
|
||||
self._readonly = self.getconfboolean('readonly', False)
|
||||
self.uiddir = os.path.join(self.config.getmetadatadir(), 'Repository-' + self.name)
|
||||
self.uiddir = os.path.join(self.config.getmetadatadir(),
|
||||
'Repository-' + self.name)
|
||||
if not os.path.exists(self.uiddir):
|
||||
os.mkdir(self.uiddir, 0o700)
|
||||
self.mapdir = os.path.join(self.uiddir, 'UIDMapping')
|
||||
@ -70,8 +73,6 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
False), applies only to local Maildir mailboxes and does nothing
|
||||
on all other repository types."""
|
||||
|
||||
pass
|
||||
|
||||
def connect(self):
|
||||
"""Establish a connection to the remote, if necessary. This exists
|
||||
so that IMAP connections can all be established up front, gathering
|
||||
@ -80,18 +81,38 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
trap in order to validate the password, and that's the point of
|
||||
this function."""
|
||||
|
||||
pass
|
||||
|
||||
def holdordropconnections(self):
|
||||
pass
|
||||
"""
|
||||
Hold the drop connections functions.
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
|
||||
def dropconnections(self):
|
||||
pass
|
||||
"""
|
||||
Drop connections functions.
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
|
||||
def getaccount(self):
|
||||
"""
|
||||
This patch returns the account
|
||||
|
||||
Returns: The account
|
||||
|
||||
"""
|
||||
return self.account
|
||||
|
||||
def getname(self):
|
||||
"""
|
||||
Get the repository name
|
||||
|
||||
Returns: String with the repository name
|
||||
|
||||
"""
|
||||
return self.name
|
||||
|
||||
def __str__(self):
|
||||
@ -104,9 +125,21 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
return self._accountname
|
||||
|
||||
def getuiddir(self):
|
||||
"""
|
||||
The FolderValidity directory
|
||||
|
||||
Returns: The FolderValidity directory
|
||||
|
||||
"""
|
||||
return self.uiddir
|
||||
|
||||
def getmapdir(self):
|
||||
"""
|
||||
Get the map dir (UIDMapping)
|
||||
|
||||
Returns: The UIDMapping directory
|
||||
|
||||
"""
|
||||
return self.mapdir
|
||||
|
||||
# Interface from CustomConfig.ConfigHelperMixin
|
||||
@ -124,6 +157,12 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
return self._readonly
|
||||
|
||||
def getlocaleval(self):
|
||||
"""
|
||||
Get the account local eval.
|
||||
|
||||
Returns: LocalEval class for account.
|
||||
|
||||
"""
|
||||
return self.account.getlocaleval()
|
||||
|
||||
def getfolders(self):
|
||||
@ -135,12 +174,26 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
"""Forgets the cached list of folders, if any. Useful to run
|
||||
after a sync run."""
|
||||
|
||||
pass
|
||||
|
||||
def getsep(self):
|
||||
"""
|
||||
Get the separator.
|
||||
|
||||
This function is not implemented.
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getkeywordmap(self):
|
||||
"""
|
||||
Get the keyword map.
|
||||
|
||||
This function is not implemented.
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def should_sync_folder(self, fname):
|
||||
@ -154,14 +207,34 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
It is disabled by either setting the whole repository
|
||||
'readonly' or by using the 'createfolders' setting."""
|
||||
|
||||
return (not self._readonly) and self.getconfboolean('createfolders', True)
|
||||
return (not self._readonly) and self.getconfboolean('createfolders',
|
||||
True)
|
||||
|
||||
def makefolder(self, foldername):
|
||||
"""Create a new folder."""
|
||||
"""
|
||||
Create a new folder.
|
||||
This function is not implemented
|
||||
|
||||
Args:
|
||||
foldername: Folder to create
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def deletefolder(self, foldername):
|
||||
"""
|
||||
Remove the selected folder.
|
||||
|
||||
This function is not implemented
|
||||
|
||||
Args:
|
||||
foldername: Folder to delete
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
def getfolder(self, foldername, decode=True):
|
||||
@ -182,9 +255,10 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
Configuring nametrans on BOTH repositories could lead to infinite folder
|
||||
creation cycles."""
|
||||
|
||||
if not self.should_create_folders() and not local_repo.should_create_folders():
|
||||
if not self.should_create_folders()\
|
||||
and not local_repo.should_create_folders():
|
||||
# Quick exit if no folder creation is enabled on either side.
|
||||
return
|
||||
return None
|
||||
|
||||
remote_repo = self
|
||||
remote_hash, local_hash = {}, {}
|
||||
@ -204,13 +278,14 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
# Apply remote nametrans and fix serparator.
|
||||
local_name = remote_folder.getvisiblename().replace(
|
||||
remote_repo.getsep(), local_repo.getsep())
|
||||
if remote_folder.sync_this and local_name not in list(local_hash.keys()):
|
||||
if remote_folder.sync_this \
|
||||
and local_name not in list(local_hash.keys()):
|
||||
try:
|
||||
local_repo.makefolder(local_name)
|
||||
# Need to refresh list.
|
||||
local_repo.forgetfolders()
|
||||
except OfflineImapError as e:
|
||||
self.ui.error(e, exc_info()[2],
|
||||
except OfflineImapError as exc:
|
||||
self.ui.error(exc, exc_info()[2],
|
||||
"Creating folder %s on repository %s" %
|
||||
(local_name, local_repo))
|
||||
raise
|
||||
@ -226,13 +301,15 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
# Apply reverse nametrans and fix serparator.
|
||||
remote_name = local_folder.getvisiblename().replace(
|
||||
local_repo.getsep(), remote_repo.getsep())
|
||||
if local_folder.sync_this and remote_name not in list(remote_hash.keys()):
|
||||
if local_folder.sync_this \
|
||||
and remote_name not in list(remote_hash.keys()):
|
||||
# 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):
|
||||
self.ui.debug('', "Not creating folder '%s' (repository '%s"
|
||||
"') as it would be filtered out on that repository." %
|
||||
(remote_name, self))
|
||||
msg = "Not creating folder '%s' (repository '%s') " \
|
||||
"as it would be filtered out on that repository." % \
|
||||
(remote_name, self)
|
||||
self.ui.debug('', msg)
|
||||
continue
|
||||
|
||||
# nametrans sanity check! Does remote nametrans lead to the
|
||||
@ -247,32 +324,35 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
# 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!
|
||||
tmp_remotefolder = remote_repo.getfolder(remote_name, decode=False)
|
||||
tmp_remotefolder = remote_repo.getfolder(remote_name,
|
||||
decode=False)
|
||||
loop_name = tmp_remotefolder.getvisiblename().replace(
|
||||
remote_repo.getsep(), local_repo.getsep())
|
||||
if local_name != loop_name:
|
||||
raise OfflineImapError("INFINITE FOLDER CREATION DETECTED! "
|
||||
"Folder '%s' (repository '%s') would be created as fold"
|
||||
"er '%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 repos"
|
||||
"itories so they lead to identical names if applied bac"
|
||||
"k and forth. 2) Use folderfilter settings on a reposit"
|
||||
"ory to prevent some folders from being created on the "
|
||||
"other side." %
|
||||
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),
|
||||
OfflineImapError.ERROR.REPO)
|
||||
remote_repo, loop_name)
|
||||
raise OfflineImapError(msg, OfflineImapError.ERROR.REPO)
|
||||
|
||||
# End sanity check, actually create the folder.
|
||||
try:
|
||||
remote_repo.makefolder(remote_name)
|
||||
# Need to refresh list.
|
||||
self.forgetfolders()
|
||||
except OfflineImapError as e:
|
||||
self.ui.error(e, exc_info()[2], "Creating folder %s on "
|
||||
"repository %s" % (remote_name, remote_repo))
|
||||
except OfflineImapError as exc:
|
||||
msg = "Creating folder %s on repository %s" % \
|
||||
(remote_name, remote_repo)
|
||||
self.ui.error(exc, exc_info()[2], msg)
|
||||
raise
|
||||
status_repo.makefolder(local_name.replace(
|
||||
local_repo.getsep(), status_repo.getsep()))
|
||||
@ -284,14 +364,10 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||
def startkeepalive(self):
|
||||
"""The default implementation will do nothing."""
|
||||
|
||||
pass
|
||||
|
||||
def stopkeepalive(self):
|
||||
"""Stop keep alive, but don't bother waiting
|
||||
for the threads to terminate."""
|
||||
|
||||
pass
|
||||
|
||||
def getlocalroot(self):
|
||||
""" Local root folder for storing messages.
|
||||
Will not be set for remote repositories."""
|
||||
|
@ -1,20 +1,22 @@
|
||||
# Gmail IMAP repository support
|
||||
# Copyright (C) 2008-2016 Riccardo Murri <riccardo.murri@gmail.com> &
|
||||
# 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
|
||||
"""
|
||||
Gmail IMAP repository support
|
||||
Copyright (C) 2008-2016 Riccardo Murri <riccardo.murri@gmail.com> &
|
||||
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 offlineimap.repository.IMAP import IMAPRepository
|
||||
from offlineimap import folder, OfflineImapError
|
||||
@ -38,7 +40,7 @@ class GmailRepository(IMAPRepository):
|
||||
We first check the usual IMAP settings, and then fall back to
|
||||
imap.gmail.com if nothing is specified."""
|
||||
try:
|
||||
return super(GmailRepository, self).gethost()
|
||||
return super().gethost()
|
||||
except OfflineImapError:
|
||||
# Nothing was configured, cache and return hardcoded
|
||||
# one. See the parent class (IMAPRepository) for how this
|
||||
@ -53,12 +55,13 @@ class GmailRepository(IMAPRepository):
|
||||
https://accounts.google.com/o/oauth2/token if nothing is
|
||||
specified."""
|
||||
|
||||
url = super(GmailRepository, self).getoauth2_request_url()
|
||||
url = super().getoauth2_request_url()
|
||||
if url is None:
|
||||
# Nothing was configured, cache and return hardcoded one.
|
||||
self.setoauth2_request_url("https://accounts.google.com/o/oauth2/token")
|
||||
else:
|
||||
# Nothing was configured, use a hardcoded one.
|
||||
url = "https://accounts.google.com/o/oauth2/token"
|
||||
|
||||
self.setoauth2_request_url(url)
|
||||
|
||||
return self.oauth2_request_url
|
||||
|
||||
def getport(self):
|
||||
@ -67,13 +70,13 @@ class GmailRepository(IMAPRepository):
|
||||
This Gmail implementation first checks for the usual IMAP settings
|
||||
and falls back to 993 if nothing is specified."""
|
||||
|
||||
port = super(GmailRepository, self).getport()
|
||||
port = super().getport()
|
||||
|
||||
if port is None:
|
||||
return 993
|
||||
else:
|
||||
if port is not None:
|
||||
return port
|
||||
|
||||
return 993
|
||||
|
||||
def getssl(self):
|
||||
ssl = self.getconfboolean('ssl', None)
|
||||
|
||||
@ -82,7 +85,7 @@ class GmailRepository(IMAPRepository):
|
||||
# GMail. Maybe this should look more similar to gethost &
|
||||
# we could just rely on the global "ssl = yes" default.
|
||||
return True
|
||||
else:
|
||||
|
||||
return ssl
|
||||
|
||||
def getpreauthtunnel(self):
|
||||
@ -96,12 +99,16 @@ class GmailRepository(IMAPRepository):
|
||||
return folder.Gmail.GmailFolder
|
||||
|
||||
def gettrashfolder(self):
|
||||
# Where deleted mail should be moved
|
||||
"""
|
||||
Where deleted mail should be moved
|
||||
"""
|
||||
return self.getconf('trashfolder', '[Gmail]/Trash')
|
||||
|
||||
def getspamfolder(self):
|
||||
# Depending on the IMAP settings (Settings -> Forwarding and
|
||||
# POP/IMAP -> IMAP Access -> "When I mark a message in IMAP as
|
||||
# deleted") GMail might also deletes messages upon EXPUNGE in
|
||||
# the Spam folder.
|
||||
"""
|
||||
Depending on the IMAP settings (Settings -> Forwarding and
|
||||
POP/IMAP -> IMAP Access -> "When I mark a message in IMAP as
|
||||
deleted") GMail might also deletes messages upon EXPUNGE in
|
||||
the Spam folder.
|
||||
"""
|
||||
return self.getconf('spamfolder', '[Gmail]/Spam')
|
||||
|
@ -1,31 +1,30 @@
|
||||
# Maildir repository support
|
||||
# Copyright (C) 2002-2015 John Goerzen & contributors
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# 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
|
||||
"""
|
||||
Maildir repository support
|
||||
Copyright (C) 2002-2015 John Goerzen & contributors
|
||||
<jgoerzen@complete.org>
|
||||
|
||||
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 offlineimap.repository.Maildir import MaildirRepository
|
||||
from offlineimap.folder.GmailMaildir import GmailMaildirFolder
|
||||
|
||||
|
||||
class GmailMaildirRepository(MaildirRepository):
|
||||
def __init__(self, reposname, account):
|
||||
"""Initialize a MaildirRepository object. Takes a path name
|
||||
to the directory holding all the Maildir directories."""
|
||||
|
||||
super(GmailMaildirRepository, self).__init__(reposname, account)
|
||||
|
||||
"""
|
||||
GMail Maildir Repository Class
|
||||
"""
|
||||
def getfoldertype(self):
|
||||
return GmailMaildirFolder
|
||||
|
@ -1,21 +1,22 @@
|
||||
""" IMAP repository support """
|
||||
"""
|
||||
IMAP repository support
|
||||
|
||||
# Copyright (C) 2002-2019 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
|
||||
Copyright (C) 2002-2019 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
|
||||
"""
|
||||
import os
|
||||
import netrc
|
||||
import errno
|
||||
@ -29,6 +30,9 @@ from offlineimap.utils.distro_utils import get_os_sslcertfile, \
|
||||
|
||||
|
||||
class IMAPRepository(BaseRepository):
|
||||
"""
|
||||
IMAP Repository Class, children of BaseRepository
|
||||
"""
|
||||
def __init__(self, reposname, account):
|
||||
self.idlefolders = None
|
||||
BaseRepository.__init__(self, reposname, account)
|
||||
@ -60,7 +64,7 @@ class IMAPRepository(BaseRepository):
|
||||
self.kathread = ExitNotifyThread(target=self.imapserver.keepalive,
|
||||
name="Keep alive " + self.getname(),
|
||||
args=(keepalivetime, self.kaevent))
|
||||
self.kathread.setDaemon(1)
|
||||
self.kathread.setDaemon(True)
|
||||
self.kathread.start()
|
||||
|
||||
def stopkeepalive(self):
|
||||
@ -92,11 +96,25 @@ class IMAPRepository(BaseRepository):
|
||||
return self.copy_ignore_eval(foldername)
|
||||
|
||||
def getholdconnectionopen(self):
|
||||
"""
|
||||
Value of holdconnectionopen or False if it is not set
|
||||
|
||||
Returns: Value of holdconnectionopen or False if it is not set
|
||||
|
||||
"""
|
||||
if self.getidlefolders():
|
||||
return True
|
||||
return self.getconfboolean("holdconnectionopen", False)
|
||||
|
||||
def getkeepalive(self):
|
||||
"""
|
||||
This function returns the keepalive value. If it is not set, then
|
||||
check if the getidlefolders is set. If getidlefolders is set, then
|
||||
returns 29 * 60
|
||||
|
||||
Returns: keepalive value
|
||||
|
||||
"""
|
||||
num = self.getconfint("keepalive", 0)
|
||||
if num == 0 and self.getidlefolders():
|
||||
return 29 * 60
|
||||
@ -107,8 +125,8 @@ class IMAPRepository(BaseRepository):
|
||||
|
||||
This requires that self.imapserver has been initialized with an
|
||||
acquireconnection() or it will still be `None`"""
|
||||
assert self.imapserver.delim is not None, "'%s' " \
|
||||
"repository called getsep() before the folder separator was " \
|
||||
assert self.imapserver.delim is not None, \
|
||||
"'%s' repository called getsep() before the folder separator was " \
|
||||
"queried from the server" % self
|
||||
return self.imapserver.delim
|
||||
|
||||
@ -124,12 +142,12 @@ class IMAPRepository(BaseRepository):
|
||||
host = self.getconf('remotehosteval')
|
||||
try:
|
||||
host = self.localeval.eval(host)
|
||||
except Exception as e:
|
||||
except Exception as exc:
|
||||
raise OfflineImapError(
|
||||
"remotehosteval option for repository "
|
||||
"'%s' failed:\n%s" % (self, e),
|
||||
"'%s' failed:\n%s" % (self, exc),
|
||||
OfflineImapError.ERROR.REPO,
|
||||
exc_info()[2])
|
||||
exc_info()[2]) from exc
|
||||
if host:
|
||||
self._host = host
|
||||
return self._host
|
||||
@ -141,7 +159,8 @@ class IMAPRepository(BaseRepository):
|
||||
|
||||
# No success.
|
||||
raise OfflineImapError("No remote host for repository "
|
||||
"'%s' specified." % self, OfflineImapError.ERROR.REPO)
|
||||
"'%s' specified." % self,
|
||||
OfflineImapError.ERROR.REPO)
|
||||
|
||||
def get_remote_identity(self):
|
||||
"""Remote identity is used for certain SASL mechanisms
|
||||
@ -154,6 +173,13 @@ class IMAPRepository(BaseRepository):
|
||||
return identity
|
||||
|
||||
def get_auth_mechanisms(self):
|
||||
"""
|
||||
Get the AUTH mechanisms. We have (ranged from the strongest to weakest)
|
||||
these methods: "GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"
|
||||
|
||||
Returns: The supported AUTH Methods
|
||||
|
||||
"""
|
||||
supported = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]
|
||||
# Mechanisms are ranged from the strongest to the
|
||||
# weakest ones.
|
||||
@ -164,16 +190,22 @@ class IMAPRepository(BaseRepository):
|
||||
mechs = self.getconflist('auth_mechanisms', r',\s*',
|
||||
default)
|
||||
|
||||
for m in mechs:
|
||||
if m not in supported:
|
||||
for mech in mechs:
|
||||
if mech not in supported:
|
||||
raise OfflineImapError("Repository %s: " % self +
|
||||
"unknown authentication mechanism '%s'" % m,
|
||||
OfflineImapError.ERROR.REPO)
|
||||
"unknown authentication mechanism '%s'"
|
||||
% mech, OfflineImapError.ERROR.REPO)
|
||||
|
||||
self.ui.debug('imap', "Using authentication mechanisms %s" % mechs)
|
||||
return mechs
|
||||
|
||||
def getuser(self):
|
||||
"""
|
||||
Returns the remoteusereval or remoteuser or netrc user value.
|
||||
|
||||
Returns: Returns the remoteusereval or remoteuser or netrc user value.
|
||||
|
||||
"""
|
||||
localeval = self.localeval
|
||||
|
||||
if self.config.has_option(self.getsection(), 'remoteusereval'):
|
||||
@ -198,7 +230,8 @@ class IMAPRepository(BaseRepository):
|
||||
return netrcentry[0]
|
||||
|
||||
try:
|
||||
netrcentry = netrc.netrc('/etc/netrc').authenticators(self.gethost())
|
||||
netrcentry = netrc.netrc('/etc/netrc')\
|
||||
.authenticators(self.gethost())
|
||||
except IOError as inst:
|
||||
if inst.errno not in (errno.ENOENT, errno.EACCES):
|
||||
raise
|
||||
@ -207,6 +240,12 @@ class IMAPRepository(BaseRepository):
|
||||
return netrcentry[0]
|
||||
|
||||
def getport(self):
|
||||
"""
|
||||
Returns remoteporteval value or None if not found.
|
||||
|
||||
Returns: Returns remoteporteval int value or None if not found.
|
||||
|
||||
"""
|
||||
port = None
|
||||
|
||||
if self.config.has_option(self.getsection(), 'remoteporteval'):
|
||||
@ -217,16 +256,40 @@ class IMAPRepository(BaseRepository):
|
||||
return self.getconfint('remoteport', None)
|
||||
|
||||
def getipv6(self):
|
||||
"""
|
||||
Returns if IPv6 is set. If not set, then return None
|
||||
|
||||
Returns: Boolean flag if IPv6 is set.
|
||||
|
||||
"""
|
||||
return self.getconfboolean('ipv6', None)
|
||||
|
||||
def getssl(self):
|
||||
"""
|
||||
Get the boolean SSL value. Default is True, used if not found.
|
||||
|
||||
Returns: Get the boolean SSL value. Default is True
|
||||
|
||||
"""
|
||||
return self.getconfboolean('ssl', True)
|
||||
|
||||
def getsslclientcert(self):
|
||||
"""
|
||||
Return the SSL client cert (sslclientcert) or None if not found
|
||||
|
||||
Returns: SSL client key (sslclientcert) or None if not found
|
||||
|
||||
"""
|
||||
xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
|
||||
return self.getconf_xform('sslclientcert', xforms, None)
|
||||
|
||||
def getsslclientkey(self):
|
||||
"""
|
||||
Return the SSL client key (sslclientkey) or None if not found
|
||||
|
||||
Returns: SSL client key (sslclientkey) or None if not found
|
||||
|
||||
"""
|
||||
xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
|
||||
return self.getconf_xform('sslclientkey', xforms, None)
|
||||
|
||||
@ -254,7 +317,7 @@ class IMAPRepository(BaseRepository):
|
||||
# Can't use above cacertfile because of abspath.
|
||||
conf_sslacertfile = self.getconf('sslcacertfile', None)
|
||||
if conf_sslacertfile == "OS-DEFAULT" or \
|
||||
conf_sslacertfile == None or \
|
||||
conf_sslacertfile is None or \
|
||||
conf_sslacertfile == '':
|
||||
cacertfile = get_os_sslcertfile()
|
||||
if cacertfile is None:
|
||||
@ -277,12 +340,30 @@ class IMAPRepository(BaseRepository):
|
||||
return cacertfile
|
||||
|
||||
def gettlslevel(self):
|
||||
"""
|
||||
Returns the TLS level (tls_level). If not set, returns 'tls_compat'
|
||||
|
||||
Returns: TLS level (tls_level). If not set, returns 'tls_compat'
|
||||
|
||||
"""
|
||||
return self.getconf('tls_level', 'tls_compat')
|
||||
|
||||
def getsslversion(self):
|
||||
"""
|
||||
Returns the SSL version. If not set, returns None.
|
||||
|
||||
Returns: SSL version. If not set, returns None.
|
||||
|
||||
"""
|
||||
return self.getconf('ssl_version', None)
|
||||
|
||||
def getstarttls(self):
|
||||
"""
|
||||
Get the value of starttls. If not set, returns True
|
||||
|
||||
Returns: Value of starttls. If not set, returns True
|
||||
|
||||
"""
|
||||
return self.getconfboolean('starttls', True)
|
||||
|
||||
def get_ssl_fingerprint(self):
|
||||
@ -292,12 +373,29 @@ class IMAPRepository(BaseRepository):
|
||||
comma-separated fingerprints in hex form."""
|
||||
|
||||
value = self.getconf('cert_fingerprint', "")
|
||||
return [f.strip().lower().replace(":", "") for f in value.split(',') if f]
|
||||
return [f.strip().lower().replace(":", "")
|
||||
for f in value.split(',') if f]
|
||||
|
||||
def setoauth2_request_url(self, url):
|
||||
"""
|
||||
Set the OAUTH2 URL request.
|
||||
|
||||
Args:
|
||||
url: OAUTH2 URL request
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
self.oauth2_request_url = url
|
||||
|
||||
def getoauth2_request_url(self):
|
||||
"""
|
||||
Returns the OAUTH2 URL request from configuration (oauth2_request_url).
|
||||
If it is not found, then returns None
|
||||
|
||||
Returns: OAUTH2 URL request (oauth2_request_url)
|
||||
|
||||
"""
|
||||
if self.oauth2_request_url is not None: # Use cached value if possible.
|
||||
return self.oauth2_request_url
|
||||
|
||||
@ -305,6 +403,14 @@ class IMAPRepository(BaseRepository):
|
||||
return self.oauth2_request_url
|
||||
|
||||
def getoauth2_refresh_token(self):
|
||||
"""
|
||||
Get the OAUTH2 refresh token from the configuration
|
||||
(oauth2_refresh_token)
|
||||
If the access token is not found, then returns None.
|
||||
|
||||
Returns: OAUTH2 refresh token (oauth2_refresh_token)
|
||||
|
||||
"""
|
||||
refresh_token = self.getconf('oauth2_refresh_token', None)
|
||||
if refresh_token is None:
|
||||
refresh_token = self.localeval.eval(
|
||||
@ -315,6 +421,13 @@ class IMAPRepository(BaseRepository):
|
||||
return refresh_token
|
||||
|
||||
def getoauth2_access_token(self):
|
||||
"""
|
||||
Get the OAUTH2 access token from the configuration (oauth2_access_token)
|
||||
If the access token is not found, then returns None.
|
||||
|
||||
Returns: OAUTH2 access token (oauth2_access_token)
|
||||
|
||||
"""
|
||||
access_token = self.getconf('oauth2_access_token', None)
|
||||
if access_token is None:
|
||||
access_token = self.localeval.eval(
|
||||
@ -325,6 +438,13 @@ class IMAPRepository(BaseRepository):
|
||||
return access_token
|
||||
|
||||
def getoauth2_client_id(self):
|
||||
"""
|
||||
Get the OAUTH2 client id (oauth2_client_id) from the configuration.
|
||||
If not found, returns None
|
||||
|
||||
Returns: OAUTH2 client id (oauth2_client_id)
|
||||
|
||||
"""
|
||||
client_id = self.getconf('oauth2_client_id', None)
|
||||
if client_id is None:
|
||||
client_id = self.localeval.eval(
|
||||
@ -335,6 +455,13 @@ class IMAPRepository(BaseRepository):
|
||||
return client_id
|
||||
|
||||
def getoauth2_client_secret(self):
|
||||
"""
|
||||
Get the OAUTH2 client secret (oauth2_client_secret) from the
|
||||
configuration. If it is not found, then returns None.
|
||||
|
||||
Returns: OAUTH2 client secret
|
||||
|
||||
"""
|
||||
client_secret = self.getconf('oauth2_client_secret', None)
|
||||
if client_secret is None:
|
||||
client_secret = self.localeval.eval(
|
||||
@ -345,18 +472,51 @@ class IMAPRepository(BaseRepository):
|
||||
return client_secret
|
||||
|
||||
def getpreauthtunnel(self):
|
||||
"""
|
||||
Get the value of preauthtunnel. If not found, then returns None.
|
||||
|
||||
Returns: Returns preauthtunnel value. If not found, returns None.
|
||||
|
||||
"""
|
||||
return self.getconf('preauthtunnel', None)
|
||||
|
||||
def gettransporttunnel(self):
|
||||
"""
|
||||
Get the value of transporttunnel. If not found, then returns None.
|
||||
|
||||
Returns: Returns transporttunnel value. If not found, returns None.
|
||||
|
||||
"""
|
||||
return self.getconf('transporttunnel', None)
|
||||
|
||||
def getreference(self):
|
||||
"""
|
||||
Get the reference value in the configuration. If the value is not found
|
||||
then returns a double quote ("") as string.
|
||||
|
||||
Returns: The reference variable. If not set, then returns '""'
|
||||
|
||||
"""
|
||||
return self.getconf('reference', '""')
|
||||
|
||||
def getdecodefoldernames(self):
|
||||
"""
|
||||
Get the boolean value of decodefoldernames configuration variable,
|
||||
if the value is not found, returns False.
|
||||
|
||||
Returns: Boolean value of decodefoldernames, else False
|
||||
|
||||
"""
|
||||
return self.getconfboolean('decodefoldernames', False)
|
||||
|
||||
def getidlefolders(self):
|
||||
"""
|
||||
Get the list of idlefolders from configuration. If the value is not
|
||||
found, returns an empty list.
|
||||
|
||||
Returns: A list of idle folders
|
||||
|
||||
"""
|
||||
if self.idlefolders is None:
|
||||
self.idlefolders = self.localeval.eval(
|
||||
self.getconf('idlefolders', '[]')
|
||||
@ -364,11 +524,25 @@ class IMAPRepository(BaseRepository):
|
||||
return self.idlefolders
|
||||
|
||||
def getmaxconnections(self):
|
||||
"""
|
||||
Get the maxconnections configuration value from configuration.
|
||||
If the value is not set, returns 1 connection
|
||||
|
||||
Returns: Integer value of maxconnections configuration variable, else 1
|
||||
|
||||
"""
|
||||
num1 = len(self.getidlefolders())
|
||||
num2 = self.getconfint('maxconnections', 1)
|
||||
return max(num1, num2)
|
||||
|
||||
def getexpunge(self):
|
||||
"""
|
||||
Get the expunge configuration value from configuration.
|
||||
If the value is not set in the configuration, then returns True
|
||||
|
||||
Returns: Boolean value of expunge configuration variable
|
||||
|
||||
"""
|
||||
return self.getconfboolean('expunge', True)
|
||||
|
||||
def getpassword(self):
|
||||
@ -388,7 +562,21 @@ class IMAPRepository(BaseRepository):
|
||||
# 1. Evaluate Repository 'remotepasseval'.
|
||||
passwd = self.getconf('remotepasseval', None)
|
||||
if passwd is not None:
|
||||
return self.localeval.eval(passwd).encode('utf-8')
|
||||
l_pass = self.localeval.eval(passwd)
|
||||
|
||||
# We need a str password
|
||||
if isinstance(l_pass, bytes):
|
||||
return l_pass.decode(encoding='utf-8')
|
||||
elif isinstance(l_pass, str):
|
||||
return l_pass
|
||||
|
||||
# If is not bytes or str, we have a problem
|
||||
raise OfflineImapError("Could not get a right password format for"
|
||||
" repository %s. Type found: %s. "
|
||||
"Please, open a bug." %
|
||||
(self.name, type(l_pass)),
|
||||
OfflineImapError.ERROR.FOLDER)
|
||||
|
||||
# 2. Read password from Repository 'remotepass'.
|
||||
password = self.getconf('remotepass', None)
|
||||
if password is not None:
|
||||
@ -398,9 +586,10 @@ class IMAPRepository(BaseRepository):
|
||||
# 3. Read password from file specified in Repository 'remotepassfile'.
|
||||
passfile = self.getconf('remotepassfile', None)
|
||||
if passfile is not None:
|
||||
fd = open(os.path.expanduser(passfile), 'r', 'utf-8')
|
||||
password = fd.readline().strip()
|
||||
fd.close()
|
||||
file_desc = open(os.path.expanduser(passfile), 'r',
|
||||
encoding='utf-8')
|
||||
password = file_desc.readline().strip()
|
||||
file_desc.close()
|
||||
return password.encode('UTF-8')
|
||||
# 4. Read password from ~/.netrc.
|
||||
try:
|
||||
@ -415,7 +604,8 @@ class IMAPRepository(BaseRepository):
|
||||
return netrcentry[2]
|
||||
# 5. Read password from /etc/netrc.
|
||||
try:
|
||||
netrcentry = netrc.netrc('/etc/netrc').authenticators(self.gethost())
|
||||
netrcentry = netrc.netrc('/etc/netrc')\
|
||||
.authenticators(self.gethost())
|
||||
except IOError as inst:
|
||||
if inst.errno not in (errno.ENOENT, errno.EACCES):
|
||||
raise
|
||||
@ -433,6 +623,13 @@ class IMAPRepository(BaseRepository):
|
||||
return self.getfoldertype()(self.imapserver, foldername, self, decode)
|
||||
|
||||
def getfoldertype(self):
|
||||
"""
|
||||
This function returns the folder type, in this case
|
||||
folder.IMAP.IMAPFolder
|
||||
|
||||
Returns: folder.IMAP.IMAPFolder
|
||||
|
||||
"""
|
||||
return folder.IMAP.IMAPFolder
|
||||
|
||||
def connect(self):
|
||||
@ -455,25 +652,26 @@ class IMAPRepository(BaseRepository):
|
||||
listfunction = imapobj.lsub
|
||||
|
||||
try:
|
||||
result, listresult = listfunction(directory=self.imapserver.reference, pattern='"*"')
|
||||
result, listresult = \
|
||||
listfunction(directory=self.imapserver.reference, pattern='"*"')
|
||||
if result != 'OK':
|
||||
raise OfflineImapError("Could not list the folders for"
|
||||
" repository %s. Server responded: %s" %
|
||||
(self.name, self, str(listresult)),
|
||||
(self.name, str(listresult)),
|
||||
OfflineImapError.ERROR.FOLDER)
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
|
||||
for s in listresult:
|
||||
if s is None or (isinstance(s, str) and s == ''):
|
||||
for fldr in listresult:
|
||||
if fldr is None or (isinstance(fldr, str) and fldr == ''):
|
||||
# Bug in imaplib: empty strings in results from
|
||||
# literals. TODO: still relevant?
|
||||
continue
|
||||
try:
|
||||
flags, delim, name = imaputil.imapsplit(s)
|
||||
flags, delim, name = imaputil.imapsplit(fldr)
|
||||
except ValueError:
|
||||
self.ui.error(
|
||||
"could not correctly parse server response; got: %s" % s)
|
||||
"could not correctly parse server response; got: %s" % fldr)
|
||||
raise
|
||||
flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
|
||||
if '\\noselect' in flaglist:
|
||||
@ -486,12 +684,13 @@ class IMAPRepository(BaseRepository):
|
||||
try:
|
||||
for foldername in self.folderincludes:
|
||||
try:
|
||||
imapobj.select(imaputil.utf8_IMAP(foldername), readonly=True)
|
||||
except OfflineImapError as e:
|
||||
imapobj.select(imaputil.utf8_IMAP(foldername),
|
||||
readonly=True)
|
||||
except OfflineImapError as exc:
|
||||
# couldn't select this folderinclude, so ignore folder.
|
||||
if e.severity > OfflineImapError.ERROR.FOLDER:
|
||||
if exc.severity > OfflineImapError.ERROR.FOLDER:
|
||||
raise
|
||||
self.ui.error(e, exc_info()[2],
|
||||
self.ui.error(exc, exc_info()[2],
|
||||
'Invalid folderinclude:')
|
||||
continue
|
||||
retval.append(self.getfoldertype()(
|
||||
@ -504,17 +703,22 @@ class IMAPRepository(BaseRepository):
|
||||
retval.sort(key=lambda x: str.lower(x.getvisiblename()))
|
||||
else:
|
||||
# do foldersort in a python3-compatible way
|
||||
# http://bytes.com/topic/python/answers/844614-python-3-sorting-comparison-function
|
||||
# http://bytes.com/topic/python/answers/ \
|
||||
# 844614-python-3-sorting-comparison-function
|
||||
def cmp2key(mycmp):
|
||||
"""Converts a cmp= function into a key= function
|
||||
We need to keep cmp functions for backward compatibility"""
|
||||
|
||||
class K:
|
||||
"""
|
||||
Class to compare getvisiblename() between two objects.
|
||||
"""
|
||||
def __init__(self, obj, *args):
|
||||
self.obj = obj
|
||||
|
||||
def __cmp__(self, other):
|
||||
return mycmp(self.obj.getvisiblename(), other.obj.getvisiblename())
|
||||
return mycmp(self.obj.getvisiblename(),
|
||||
other.obj.getvisiblename())
|
||||
|
||||
return K
|
||||
|
||||
@ -532,21 +736,26 @@ class IMAPRepository(BaseRepository):
|
||||
try:
|
||||
result = imapobj.delete(foldername)
|
||||
if result[0] != 'OK':
|
||||
raise OfflineImapError("Folder '%s'[%s] could not be deleted. "
|
||||
"Server responded: %s" % (foldername, self, str(result)),
|
||||
OfflineImapError.ERROR.FOLDER)
|
||||
msg = "Folder '%s'[%s] could not be deleted. "\
|
||||
"Server responded: %s" % (foldername, self, str(result))
|
||||
raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER)
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
|
||||
def makefolder(self, foldername):
|
||||
"""Create a folder on the IMAP server
|
||||
"""
|
||||
Create a folder on the IMAP server
|
||||
|
||||
This will not update the list cached in :meth:`getfolders`. You
|
||||
will need to invoke :meth:`forgetfolders` to force new caching
|
||||
when you are done creating folders yourself.
|
||||
|
||||
:param foldername: Full path of the folder to be created."""
|
||||
Args:
|
||||
foldername: Full path of the folder to be created
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
if foldername == '':
|
||||
return
|
||||
|
||||
@ -558,15 +767,25 @@ class IMAPRepository(BaseRepository):
|
||||
return
|
||||
|
||||
parts = foldername.split(self.getsep())
|
||||
folder_paths = [self.getsep().join(parts[:n + 1]) for n in range(len(parts))]
|
||||
folder_paths = [self.getsep().join(parts[:n + 1])
|
||||
for n in range(len(parts))]
|
||||
for folder_path in folder_paths:
|
||||
try:
|
||||
self.makefolder_single(folder_path)
|
||||
except OfflineImapError as e:
|
||||
if '[ALREADYEXISTS]' not in e.reason:
|
||||
except OfflineImapError as exc:
|
||||
if '[ALREADYEXISTS]' not in exc.reason:
|
||||
raise
|
||||
|
||||
def makefolder_single(self, foldername):
|
||||
"""
|
||||
Create a IMAP folder.
|
||||
|
||||
Args:
|
||||
foldername: Folder's name to create
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
self.ui.makefolder(self, foldername)
|
||||
if self.account.dryrun:
|
||||
return
|
||||
@ -577,13 +796,18 @@ class IMAPRepository(BaseRepository):
|
||||
|
||||
result = imapobj.create(foldername)
|
||||
if result[0] != 'OK':
|
||||
raise OfflineImapError("Folder '%s'[%s] could not be created. "
|
||||
"Server responded: %s" % (foldername, self, str(result)),
|
||||
OfflineImapError.ERROR.FOLDER)
|
||||
msg = "Folder '%s'[%s] could not be created. "\
|
||||
"Server responded: %s" % (foldername, self, str(result))
|
||||
raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER)
|
||||
finally:
|
||||
self.imapserver.releaseconnection(imapobj)
|
||||
|
||||
|
||||
class MappedIMAPRepository(IMAPRepository):
|
||||
"""
|
||||
This subclass of IMAPRepository includes only the method
|
||||
getfoldertype modified that returns folder.UIDMaps.MappedIMAPFolder
|
||||
instead of folder.IMAP.IMAPFolder
|
||||
"""
|
||||
def getfoldertype(self):
|
||||
return folder.UIDMaps.MappedIMAPFolder
|
||||
|
@ -1,20 +1,21 @@
|
||||
# Local status cache repository support
|
||||
# Copyright (C) 2002-2017 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
|
||||
"""
|
||||
Local status cache repository support
|
||||
Copyright (C) 2002-2017 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
|
||||
"""
|
||||
import os
|
||||
|
||||
from offlineimap.folder.LocalStatus import LocalStatusFolder
|
||||
@ -24,6 +25,9 @@ from offlineimap.error import OfflineImapError
|
||||
|
||||
|
||||
class LocalStatusRepository(BaseRepository):
|
||||
"""
|
||||
Local Status Repository Class, child of Base Repository Class
|
||||
"""
|
||||
def __init__(self, reposname, account):
|
||||
BaseRepository.__init__(self, reposname, account)
|
||||
|
||||
@ -57,26 +61,44 @@ class LocalStatusRepository(BaseRepository):
|
||||
return self.LocalStatusFolderClass(foldername, self) # Instantiate.
|
||||
|
||||
def setup_backend(self, backend):
|
||||
"""
|
||||
Setup the backend.
|
||||
|
||||
Args:
|
||||
backend: backend to use
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
if backend in list(self.backends.keys()):
|
||||
self._backend = backend
|
||||
self.root = self.backends[backend]['root']
|
||||
self.LocalStatusFolderClass = self.backends[backend]['class']
|
||||
|
||||
def import_other_backend(self, folder):
|
||||
for bk, dic in list(self.backends.items()):
|
||||
"""
|
||||
Import other backend
|
||||
|
||||
Args:
|
||||
folder: folder
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
for bkend, dic in list(self.backends.items()):
|
||||
# Skip folder's own type.
|
||||
if dic['class'] == type(folder):
|
||||
continue
|
||||
|
||||
repobk = LocalStatusRepository(self.name, self.account)
|
||||
repobk.setup_backend(bk) # Fake the backend.
|
||||
repobk.setup_backend(bkend) # Fake the backend.
|
||||
folderbk = dic['class'](folder.name, repobk)
|
||||
|
||||
# If backend contains data, import it to folder.
|
||||
if not folderbk.isnewfolder():
|
||||
self.ui._msg("Migrating LocalStatus cache from %s to %s "
|
||||
"status folder for %s:%s" %
|
||||
(bk, self._backend, self.name, folder.name))
|
||||
(bkend, self._backend, self.name, folder.name))
|
||||
|
||||
folderbk.cachemessagelist()
|
||||
folder.messagelist = folderbk.messagelist
|
||||
@ -130,8 +152,6 @@ class LocalStatusRepository(BaseRepository):
|
||||
the file names that we have available. TODO: need to store a
|
||||
list of folder names somehow?"""
|
||||
|
||||
pass
|
||||
|
||||
def forgetfolders(self):
|
||||
"""Forgets the cached list of folders, if any. Useful to run
|
||||
after a sync run."""
|
||||
|
@ -1,20 +1,22 @@
|
||||
# Maildir repository support
|
||||
# Copyright (C) 2002-2015 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
|
||||
"""
|
||||
Maildir repository support
|
||||
|
||||
Copyright (C) 2002-2015 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
|
||||
"""
|
||||
import os
|
||||
from offlineimap import folder
|
||||
from offlineimap.ui import getglobalui
|
||||
@ -23,6 +25,9 @@ from offlineimap.repository.Base import BaseRepository
|
||||
|
||||
|
||||
class MaildirRepository(BaseRepository):
|
||||
"""
|
||||
Maildir Repository Class
|
||||
"""
|
||||
def __init__(self, reposname, account):
|
||||
"""Initialize a MaildirRepository object. Takes a path name
|
||||
to the directory holding all the Maildir directories."""
|
||||
@ -32,7 +37,8 @@ class MaildirRepository(BaseRepository):
|
||||
self.root = self.getlocalroot()
|
||||
self.folders = None
|
||||
self.ui = getglobalui()
|
||||
self.debug("MaildirRepository initialized, sep is %s" % repr(self.getsep()))
|
||||
self.debug("MaildirRepository initialized, sep is %s" %
|
||||
repr(self.getsep()))
|
||||
self.folder_atimes = []
|
||||
|
||||
# Create the top-level folder if it doesn't exist
|
||||
@ -41,19 +47,19 @@ class MaildirRepository(BaseRepository):
|
||||
|
||||
# Create the keyword->char mapping
|
||||
self.keyword2char = dict()
|
||||
for c in 'abcdefghijklmnopqrstuvwxyz':
|
||||
confkey = 'customflag_' + c
|
||||
for char in 'abcdefghijklmnopqrstuvwxyz':
|
||||
confkey = 'customflag_' + char
|
||||
keyword = self.getconf(confkey, None)
|
||||
if keyword is not None:
|
||||
self.keyword2char[keyword] = c
|
||||
self.keyword2char[keyword] = char
|
||||
|
||||
def _append_folder_atimes(self, foldername):
|
||||
"""Store the atimes of a folder's new|cur in self.folder_atimes"""
|
||||
|
||||
p = os.path.join(self.root, foldername)
|
||||
new = os.path.join(p, 'new')
|
||||
cur = os.path.join(p, 'cur')
|
||||
atimes = (p, os.path.getatime(new), os.path.getatime(cur))
|
||||
path = os.path.join(self.root, foldername)
|
||||
new = os.path.join(path, 'new')
|
||||
cur = os.path.join(path, 'cur')
|
||||
atimes = (path, os.path.getatime(new), os.path.getatime(cur))
|
||||
self.folder_atimes.append(atimes)
|
||||
|
||||
def restore_atime(self):
|
||||
@ -75,6 +81,15 @@ class MaildirRepository(BaseRepository):
|
||||
return self.getconf_xform('localfolders', xforms)
|
||||
|
||||
def debug(self, msg):
|
||||
"""
|
||||
Debug function for the message. It calls the ui.debug function and
|
||||
prepends the string 'maildir'.
|
||||
Args:
|
||||
msg: Message to send to the debug
|
||||
|
||||
Returns: None
|
||||
|
||||
"""
|
||||
self.ui.debug('maildir', msg)
|
||||
|
||||
def getsep(self):
|
||||
@ -107,24 +122,26 @@ class MaildirRepository(BaseRepository):
|
||||
assert component not in ['new', 'cur', 'tmp'], \
|
||||
"When using nested folders (/ as a Maildir separator), " \
|
||||
"folder names may not contain 'new', 'cur', 'tmp'."
|
||||
assert foldername.find('../') == -1, "Folder names may not contain ../"
|
||||
assert not foldername.startswith('/'), "Folder names may not begin with /"
|
||||
assert foldername.find('../') == -1, \
|
||||
"Folder names may not contain ../"
|
||||
assert not foldername.startswith('/'), \
|
||||
"Folder names may not begin with /"
|
||||
|
||||
# If we're using hierarchical folders, it's possible that
|
||||
# sub-folders may be created before higher-up ones.
|
||||
self.debug("makefolder: calling makedirs '%s'" % full_path)
|
||||
try:
|
||||
os.makedirs(full_path, 0o700)
|
||||
except OSError as e:
|
||||
if e.errno == 17 and os.path.isdir(full_path):
|
||||
except OSError as exc:
|
||||
if exc.errno == 17 and os.path.isdir(full_path):
|
||||
self.debug("makefolder: '%s' already a directory" % foldername)
|
||||
else:
|
||||
raise
|
||||
for subdir in ['cur', 'new', 'tmp']:
|
||||
try:
|
||||
os.mkdir(os.path.join(full_path, subdir), 0o700)
|
||||
except OSError as e:
|
||||
if e.errno == 17 and os.path.isdir(full_path):
|
||||
except OSError as exc:
|
||||
if exc.errno == 17 and os.path.isdir(full_path):
|
||||
self.debug("makefolder: '%s' already has subdir %s" %
|
||||
(foldername, subdir))
|
||||
else:
|
||||
@ -142,11 +159,12 @@ class MaildirRepository(BaseRepository):
|
||||
|
||||
# getfolders() will scan and cache the values *if* necessary
|
||||
folders = self.getfolders()
|
||||
for f in folders:
|
||||
if foldername == f.name:
|
||||
return f
|
||||
for foldr in folders:
|
||||
if foldername == foldr.name:
|
||||
return foldr
|
||||
raise OfflineImapError("getfolder() asked for a nonexisting "
|
||||
"folder '%s'." % foldername, OfflineImapError.ERROR.FOLDER)
|
||||
"folder '%s'." % foldername,
|
||||
OfflineImapError.ERROR.FOLDER)
|
||||
|
||||
def _getfolders_scandir(self, root, extension=None):
|
||||
"""Recursively scan folder 'root'; return a list of MailDirFolder
|
||||
@ -193,14 +211,15 @@ class MaildirRepository(BaseRepository):
|
||||
self.debug(" This is maildir folder '%s'." % foldername)
|
||||
if self.getconfboolean('restoreatime', False):
|
||||
self._append_folder_atimes(foldername)
|
||||
fd = self.getfoldertype()(self.root, foldername,
|
||||
file_desc = self.getfoldertype()(self.root, foldername,
|
||||
self.getsep(), self)
|
||||
retval.append(fd)
|
||||
retval.append(file_desc)
|
||||
|
||||
if self.getsep() == '/' and dirname != '':
|
||||
# Recursively check sub-directories for folders too.
|
||||
retval.extend(self._getfolders_scandir(root, foldername))
|
||||
self.debug("_GETFOLDERS_SCANDIR RETURNING %s" % repr([x.getname() for x in retval]))
|
||||
self.debug("_GETFOLDERS_SCANDIR RETURNING %s" %
|
||||
repr([x.getname() for x in retval]))
|
||||
return retval
|
||||
|
||||
def getfolders(self):
|
||||
@ -209,6 +228,12 @@ class MaildirRepository(BaseRepository):
|
||||
return self.folders
|
||||
|
||||
def getfoldertype(self):
|
||||
"""
|
||||
Returns a folder MaildirFolder type.
|
||||
|
||||
Returns: A MaildirFolder class
|
||||
|
||||
"""
|
||||
return folder.Maildir.MaildirFolder
|
||||
|
||||
def forgetfolders(self):
|
||||
|
@ -1,19 +1,20 @@
|
||||
# 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
|
||||
"""
|
||||
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
|
||||
from configparser import NoSectionError
|
||||
from offlineimap.repository.IMAP import IMAPRepository, MappedIMAPRepository
|
||||
@ -59,19 +60,19 @@ class Repository:
|
||||
config = account.getconfig()
|
||||
try:
|
||||
repostype = config.get('Repository ' + name, 'type').strip()
|
||||
except NoSectionError:
|
||||
except NoSectionError as exc:
|
||||
errstr = ("Could not find section '%s' in configuration. Required "
|
||||
"for account '%s'." % ('Repository %s' % name, account))
|
||||
raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO,
|
||||
exc_info()[2])
|
||||
exc_info()[2]) from exc
|
||||
|
||||
try:
|
||||
repo = typemap[repostype]
|
||||
except KeyError:
|
||||
except KeyError as exc:
|
||||
errstr = "'%s' repository not supported for '%s' repositories." % \
|
||||
(repostype, reqtype)
|
||||
raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO,
|
||||
exc_info()[2])
|
||||
exc_info()[2]) from exc
|
||||
|
||||
return repo(name, account)
|
||||
|
||||
@ -83,4 +84,3 @@ class Repository:
|
||||
:param account: :class:`Account`
|
||||
:param reqtype: 'remote', 'local', or 'status'
|
||||
"""
|
||||
pass
|
||||
|
@ -1,8 +1,9 @@
|
||||
# Copyright (C) 2013-2014 Eygene A. Ryabinkin and contributors
|
||||
#
|
||||
# Collection of classes that implement const-like behaviour
|
||||
# for various objects.
|
||||
"""
|
||||
Copyright (C) 2013-2014 Eygene A. Ryabinkin and contributors
|
||||
|
||||
Collection of classes that implement const-like behaviour
|
||||
for various objects.
|
||||
"""
|
||||
import copy
|
||||
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
# Copyright 2006-2018 Eygene A. Ryabinkin & contributors.
|
||||
#
|
||||
# Module that supports distribution-specific functions.
|
||||
"""
|
||||
Copyright 2006-2018 Eygene A. Ryabinkin & contributors.
|
||||
|
||||
Module that supports distribution-specific functions.
|
||||
"""
|
||||
import platform
|
||||
import os
|
||||
|
||||
@ -68,12 +69,8 @@ def get_os_sslcertfile_searchpath():
|
||||
Returned value of None means that there is no search path
|
||||
at all.
|
||||
"""
|
||||
|
||||
os_name = get_os_name()
|
||||
|
||||
location = None
|
||||
if os_name in __DEF_OS_LOCATIONS:
|
||||
location = __DEF_OS_LOCATIONS[os_name]
|
||||
location = __DEF_OS_LOCATIONS.get(os_name, None)
|
||||
|
||||
return location
|
||||
|
||||
@ -92,9 +89,10 @@ def get_os_sslcertfile():
|
||||
if location is None:
|
||||
return None
|
||||
|
||||
for f in location:
|
||||
assert isinstance(f, str)
|
||||
if os.path.exists(f) and (os.path.isfile(f) or os.path.islink(f)):
|
||||
return f
|
||||
for l_file in location:
|
||||
assert isinstance(l_file, str)
|
||||
if os.path.exists(l_file) and (os.path.isfile(l_file) or
|
||||
os.path.islink(l_file)):
|
||||
return l_file
|
||||
|
||||
return None
|
||||
|
@ -1,6 +1,8 @@
|
||||
# Copyright 2013 Eygene A. Ryabinkin
|
||||
# Functions to perform stack tracing (for multithreaded programs
|
||||
# as well as for single-threaded ones).
|
||||
"""
|
||||
Copyright 2013 Eygene A. Ryabinkin
|
||||
Functions to perform stack tracing (for multithreaded programs
|
||||
as well as for single-threaded ones).
|
||||
"""
|
||||
|
||||
import sys
|
||||
import threading
|
||||
@ -10,14 +12,15 @@ import traceback
|
||||
def dump(out):
|
||||
""" Dumps current stack trace into I/O object 'out' """
|
||||
id2name = {}
|
||||
for th in threading.enumerate():
|
||||
id2name[th.ident] = th.name
|
||||
n = 0
|
||||
for th_en in threading.enumerate():
|
||||
id2name[th_en.ident] = th_en.name
|
||||
|
||||
count = 0
|
||||
for i, stack in list(sys._current_frames().items()):
|
||||
out.write("\n# Thread #%d (id=%d), %s\n" % (n, i, id2name[i]))
|
||||
n = n + 1
|
||||
for f, lno, name, line in traceback.extract_stack(stack):
|
||||
out.write('File: "%s", line %d, in %s' % (f, lno, name))
|
||||
out.write("\n# Thread #%d (id=%d), %s\n" % (count, i, id2name[i]))
|
||||
count = count + 1
|
||||
for file, lno, name, line in traceback.extract_stack(stack):
|
||||
out.write('File: "%s", line %d, in %s' % (file, lno, name))
|
||||
if line:
|
||||
out.write(" %s" % (line.strip()))
|
||||
out.write("\n")
|
||||
|
Loading…
Reference in New Issue
Block a user