Merge pull request #22 from thekix/master

Multiple style patches and bug about remotepasseval
This commit is contained in:
Rodolfo García Peñas (kix) 2020-11-04 22:20:43 +01:00 committed by GitHub
commit 319e5a03b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 744 additions and 327 deletions

View File

@ -1,19 +1,21 @@
# Base folder support """
# Copyright (C) 2002-2016 John Goerzen & contributors 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 This program is free software; you can redistribute it and/or modify
# the Free Software Foundation; either version 2 of the License, or it under the terms of the GNU General Public License as published by
# (at your option) any later version. 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 This program is distributed in the hope that it will be useful,
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details. 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 You should have received a copy of the GNU General Public License
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 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 os.path
import re import re
@ -27,6 +29,9 @@ import offlineimap.accounts
class BaseFolder: class BaseFolder:
"""
Base Folder Class
"""
__hash__ = None __hash__ = None
def __init__(self, name, repository): def __init__(self, name, repository):
@ -130,6 +135,12 @@ class BaseFolder:
return self.repository.should_sync_folder(self.ffilter_name) return self.repository.should_sync_folder(self.ffilter_name)
def dofsync(self): def dofsync(self):
"""
Call and returns _dofsync()
Returns: Call and returns _dofsync()
"""
return self._dofsync return self._dofsync
def suggeststhreads(self): def suggeststhreads(self):
@ -196,6 +207,13 @@ class BaseFolder:
return self.sep return self.sep
def getfullname(self): 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(): if self.getroot():
return self.getroot() + self.getsep() + self.getname() return self.getroot() + self.getsep() + self.getname()
else: else:
@ -371,6 +389,12 @@ class BaseFolder:
OfflineImapError.ERROR.MESSAGE) OfflineImapError.ERROR.MESSAGE)
def getmaxsize(self): 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" % return self.config.getdefaultint("Account %s" %
self.accountname, "maxsize", None) self.accountname, "maxsize", None)
@ -396,19 +420,41 @@ class BaseFolder:
OfflineImapError.ERROR.MESSAGE) OfflineImapError.ERROR.MESSAGE)
def get_min_uid_file(self): 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(), startuiddir = os.path.join(self.config.getmetadatadir(),
'Repository-' + self.repository.name, 'StartUID') 'Repository-' + self.repository.name,
'StartUID')
if not os.path.exists(startuiddir): if not os.path.exists(startuiddir):
os.mkdir(startuiddir, 0o700) os.mkdir(startuiddir, 0o700)
return os.path.join(startuiddir, self.getfolderbasename()) return os.path.join(startuiddir, self.getfolderbasename())
def save_min_uid(self, min_uid): 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() uidfile = self.get_min_uid_file()
fd = open(uidfile, 'wt') fd = open(uidfile, 'wt')
fd.write(str(min_uid) + "\n") fd.write(str(min_uid) + "\n")
fd.close() fd.close()
def retrieve_min_uid(self): def retrieve_min_uid(self):
"""
Retrieve the min UID file
Returns: min UID of file
"""
uidfile = self.get_min_uid_file() uidfile = self.get_min_uid_file()
if not os.path.exists(uidfile): if not os.path.exists(uidfile):
return None return None
@ -594,8 +640,8 @@ class BaseFolder:
def addmessageheader(self, content, linebreak, headername, headervalue): def addmessageheader(self, content, linebreak, headername, headervalue):
"""Adds new header to the provided message. """Adds new header to the provided message.
WARNING: This function is a bit tricky, and modifying it in the wrong way, WARNING: This function is a bit tricky, and modifying it in the wrong
may easily lead to data-loss. way, may easily lead to data-loss.
Arguments: Arguments:
- content: message content, headers and body as a single string - content: message content, headers and body as a single string
@ -605,8 +651,8 @@ class BaseFolder:
.. note:: .. note::
The following documentation will not get displayed correctly after being The following documentation will not get displayed correctly after
processed by Sphinx. View the source of this method to read it. 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 This has to deal with strange corner cases where the header is
missing or empty. Here are illustrations for all the cases, missing or empty. Here are illustrations for all the cases,
@ -659,7 +705,8 @@ class BaseFolder:
self.ui.debug('', 'addmessageheader: headers were missing') self.ui.debug('', 'addmessageheader: headers were missing')
else: else:
self.ui.debug('', self.ui.debug('',
'addmessageheader: headers end at position %d' % insertionpoint) 'addmessageheader: headers end at position %d' %
insertionpoint)
mark = '==>EOH<==' mark = '==>EOH<=='
contextstart = max(0, insertionpoint - 100) contextstart = max(0, insertionpoint - 100)
contextend = min(len(content), insertionpoint + 100) contextend = min(len(content), insertionpoint + 100)
@ -693,9 +740,11 @@ class BaseFolder:
self.ui.debug('', self.ui.debug('',
'addmessageheader: insertionpoint = %d' % insertionpoint) 'addmessageheader: insertionpoint = %d' % insertionpoint)
headers = content[0: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 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:] return headers + new_header + content[insertionpoint:]
def __find_eoh(self, content): def __find_eoh(self, content):
@ -746,7 +795,8 @@ class BaseFolder:
- contents: message itself - contents: message itself
- name: name of the header to be searched - 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) self.ui.debug('', 'getmessageheaderlist: called to get %s' % name)
@ -769,7 +819,8 @@ class BaseFolder:
if type(header_list) != type([]): if type(header_list) != type([]):
header_list = [header_list] 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): if not len(header_list):
return content return content
@ -783,7 +834,8 @@ class BaseFolder:
for h in headers.split('\n'): for h in headers.split('\n'):
keep_it = True keep_it = True
for trim_h in header_list: 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 keep_it = False
break break
if keep_it: if keep_it:
@ -873,9 +925,10 @@ class BaseFolder:
# IMAP servers ... # IMAP servers ...
self.deletemessage(uid) self.deletemessage(uid)
else: else:
raise OfflineImapError("Trying to save msg (uid %d) on folder " msg = "Trying to save msg (uid %d) on folder " \
"%s returned invalid uid %d" % (uid, dstfolder.getvisiblename(), "%s returned invalid uid %d" % \
new_uid), OfflineImapError.ERROR.MESSAGE) (uid, dstfolder.getvisiblename(), new_uid)
raise OfflineImapError(msg, OfflineImapError.ERROR.MESSAGE)
except KeyboardInterrupt: # Bubble up CTRL-C. except KeyboardInterrupt: # Bubble up CTRL-C.
raise raise
except OfflineImapError as e: except OfflineImapError as e:
@ -884,7 +937,8 @@ class BaseFolder:
self.ui.error(e, exc_info()[2]) self.ui.error(e, exc_info()[2])
except Exception as e: except Exception as e:
self.ui.error(e, exc_info()[2], 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. raise # Raise on unknown errors, so we can fix those.
def __syncmessagesto_copy(self, dstfolder, statusfolder): def __syncmessagesto_copy(self, dstfolder, statusfolder):
@ -929,24 +983,28 @@ class BaseFolder:
break break
if uid == 0: 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 continue
if uid > 0 and dstfolder.uidexists(uid): 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) flags = self.getmessageflags(uid)
rtime = self.getmessagetime(uid) rtime = self.getmessagetime(uid)
statusfolder.savemessage(uid, None, flags, rtime) statusfolder.savemessage(uid, None, flags, rtime)
continue 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(). # Exceptions are caught in copymessageto().
if self.suggeststhreads(): if self.suggeststhreads():
self.waitforthread() self.waitforthread()
thread = threadutil.InstanceLimitedThread( thread = threadutil.InstanceLimitedThread(
self.getinstancelimitnamespace(), self.getinstancelimitnamespace(),
target=self.copymessageto, 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) args=(uid, dstfolder, statusfolder)
) )
thread.start() thread.start()
@ -1015,9 +1073,10 @@ class BaseFolder:
# skip them. # skip them.
skipped_keywords = list(selfkeywords - knownkeywords) skipped_keywords = list(selfkeywords - knownkeywords)
selfkeywords &= knownkeywords selfkeywords &= knownkeywords
self.ui.warn("Unknown keywords skipped: %s\n" msg = "Unknown keywords skipped: %s\n" \
"You may want to change your configuration to include " "You may want to change your configuration to include " \
"those\n" % skipped_keywords) "those\n" % skipped_keywords
self.ui.warn(msg)
keywordletterset = set([keywordmap[keyw] for keyw in selfkeywords]) keywordletterset = set([keywordmap[keyw] for keyw in selfkeywords])
@ -1125,16 +1184,16 @@ class BaseFolder:
break break
try: try:
action(dstfolder, statusfolder) action(dstfolder, statusfolder)
except (KeyboardInterrupt): except KeyboardInterrupt:
raise raise
except OfflineImapError as e: except OfflineImapError as e:
if e.severity > OfflineImapError.ERROR.FOLDER: if e.severity > OfflineImapError.ERROR.FOLDER:
raise raise
self.ui.error(e, exc_info()[2], "while syncing %s [account %s]" % msg = "while syncing %s [account %s]" % (self, self.accountname)
(self, self.accountname)) self.ui.error(e, exc_info()[2], msg)
except Exception as e: except Exception as e:
self.ui.error(e, exc_info()[2], "while syncing %s [account %s]" % msg = "while syncing %s [account %s]" % (self, self.accountname)
(self, self.accountname)) self.ui.error(e, exc_info()[2], msg)
raise # Raise unknown Exceptions so we can fix them. raise # Raise unknown Exceptions so we can fix them.
def __eq__(self, other): def __eq__(self, other):

View File

@ -1 +1,4 @@
"""
Folder Module of offlineimap
"""
from . import Base, Gmail, IMAP, Maildir, LocalStatus, UIDMaps from . import Base, Gmail, IMAP, Maildir, LocalStatus, UIDMaps

View File

@ -18,9 +18,10 @@
import datetime import datetime
import hashlib import hashlib
import hmac import hmac
import socket
import json import json
import urllib.request, urllib.parse, urllib.error import urllib.request
import urllib.parse
import urllib.error
import time import time
import errno import errno
import socket import socket
@ -39,7 +40,7 @@ except ImportError:
have_gss = False have_gss = False
class IMAPServer(): class IMAPServer:
"""Initializes all variables from an IMAPRepository() instance """Initializes all variables from an IMAPRepository() instance
Various functions, such as acquireconnection() return an IMAP4 Various functions, such as acquireconnection() return an IMAP4
@ -331,7 +332,8 @@ class IMAPServer():
except imapobj.error as e: except imapobj.error as e:
raise OfflineImapError("Failed to start " raise OfflineImapError("Failed to start "
"TLS connection: %s" % str(e), "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. # All __authn_* procedures are helpers that do authentication.
# They are class methods that take one parameter, IMAP object. # They are class methods that take one parameter, IMAP object.
@ -776,7 +778,7 @@ class IdleThread:
self.thread = Thread(target=self.noop) self.thread = Thread(target=self.noop)
else: else:
self.thread = Thread(target=self.__idle) self.thread = Thread(target=self.__idle)
self.thread.setDaemon(1) self.thread.setDaemon(True)
def start(self): def start(self):
self.thread.start() self.thread.start()

View File

@ -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
# This program is free software; you can redistribute it and/or modify the Free Software Foundation; either version 2 of the License, or
# it under the terms of the GNU General Public License as published by (at your option) any later version.
# 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 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 re
import os.path import os.path
from sys import exc_info from sys import exc_info
@ -26,6 +25,9 @@ from offlineimap.error import OfflineImapError
class BaseRepository(CustomConfig.ConfigHelperMixin): class BaseRepository(CustomConfig.ConfigHelperMixin):
"""
Base Class for Repository
"""
def __init__(self, reposname, account): def __init__(self, reposname, account):
self.ui = getglobalui() self.ui = getglobalui()
self.account = account self.account = account
@ -34,7 +36,8 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
self.localeval = account.getlocaleval() self.localeval = account.getlocaleval()
self._accountname = self.account.getname() self._accountname = self.account.getname()
self._readonly = self.getconfboolean('readonly', False) 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): if not os.path.exists(self.uiddir):
os.mkdir(self.uiddir, 0o700) os.mkdir(self.uiddir, 0o700)
self.mapdir = os.path.join(self.uiddir, 'UIDMapping') 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 False), applies only to local Maildir mailboxes and does nothing
on all other repository types.""" on all other repository types."""
pass
def connect(self): def connect(self):
"""Establish a connection to the remote, if necessary. This exists """Establish a connection to the remote, if necessary. This exists
so that IMAP connections can all be established up front, gathering 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 trap in order to validate the password, and that's the point of
this function.""" this function."""
pass
def holdordropconnections(self): def holdordropconnections(self):
pass """
Hold the drop connections functions.
Returns: None
"""
def dropconnections(self): def dropconnections(self):
pass """
Drop connections functions.
Returns: None
"""
def getaccount(self): def getaccount(self):
"""
This patch returns the account
Returns: The account
"""
return self.account return self.account
def getname(self): def getname(self):
"""
Get the repository name
Returns: String with the repository name
"""
return self.name return self.name
def __str__(self): def __str__(self):
@ -104,9 +125,21 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
return self._accountname return self._accountname
def getuiddir(self): def getuiddir(self):
"""
The FolderValidity directory
Returns: The FolderValidity directory
"""
return self.uiddir return self.uiddir
def getmapdir(self): def getmapdir(self):
"""
Get the map dir (UIDMapping)
Returns: The UIDMapping directory
"""
return self.mapdir return self.mapdir
# Interface from CustomConfig.ConfigHelperMixin # Interface from CustomConfig.ConfigHelperMixin
@ -124,6 +157,12 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
return self._readonly return self._readonly
def getlocaleval(self): def getlocaleval(self):
"""
Get the account local eval.
Returns: LocalEval class for account.
"""
return self.account.getlocaleval() return self.account.getlocaleval()
def getfolders(self): def getfolders(self):
@ -135,12 +174,26 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
"""Forgets the cached list of folders, if any. Useful to run """Forgets the cached list of folders, if any. Useful to run
after a sync run.""" after a sync run."""
pass
def getsep(self): def getsep(self):
"""
Get the separator.
This function is not implemented.
Returns: None
"""
raise NotImplementedError raise NotImplementedError
def getkeywordmap(self): def getkeywordmap(self):
"""
Get the keyword map.
This function is not implemented.
Returns: None
"""
raise NotImplementedError raise NotImplementedError
def should_sync_folder(self, fname): def should_sync_folder(self, fname):
@ -154,14 +207,34 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
It is disabled by either setting the whole repository It is disabled by either setting the whole repository
'readonly' or by using the 'createfolders' setting.""" '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): 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 raise NotImplementedError
def deletefolder(self, foldername): def deletefolder(self, foldername):
"""
Remove the selected folder.
This function is not implemented
Args:
foldername: Folder to delete
Returns: None
"""
raise NotImplementedError raise NotImplementedError
def getfolder(self, foldername, decode=True): def getfolder(self, foldername, decode=True):
@ -182,9 +255,10 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
Configuring nametrans on BOTH repositories could lead to infinite folder Configuring nametrans on BOTH repositories could lead to infinite folder
creation cycles.""" 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. # Quick exit if no folder creation is enabled on either side.
return return None
remote_repo = self remote_repo = self
remote_hash, local_hash = {}, {} remote_hash, local_hash = {}, {}
@ -204,13 +278,14 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
# Apply remote nametrans and fix serparator. # Apply remote nametrans and fix serparator.
local_name = remote_folder.getvisiblename().replace( local_name = remote_folder.getvisiblename().replace(
remote_repo.getsep(), local_repo.getsep()) 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: try:
local_repo.makefolder(local_name) local_repo.makefolder(local_name)
# Need to refresh list. # Need to refresh list.
local_repo.forgetfolders() local_repo.forgetfolders()
except OfflineImapError as e: except OfflineImapError as exc:
self.ui.error(e, exc_info()[2], self.ui.error(exc, exc_info()[2],
"Creating folder %s on repository %s" % "Creating folder %s on repository %s" %
(local_name, local_repo)) (local_name, local_repo))
raise raise
@ -226,13 +301,15 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
# Apply reverse nametrans and fix serparator. # Apply reverse nametrans and fix serparator.
remote_name = local_folder.getvisiblename().replace( remote_name = local_folder.getvisiblename().replace(
local_repo.getsep(), remote_repo.getsep()) 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 # Would the remote filter out the new folder name? In this case
# don't create it. # don't create it.
if not remote_repo.should_sync_folder(remote_name): if not remote_repo.should_sync_folder(remote_name):
self.ui.debug('', "Not creating folder '%s' (repository '%s" msg = "Not creating folder '%s' (repository '%s') " \
"') as it would be filtered out on that repository." % "as it would be filtered out on that repository." % \
(remote_name, self)) (remote_name, self)
self.ui.debug('', msg)
continue continue
# nametrans sanity check! Does remote nametrans lead to the # 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. # Get IMAPFolder and see if the reverse nametrans works fine.
# TODO: getfolder() works only because we succeed in getting # TODO: getfolder() works only because we succeed in getting
# inexisting folders which I would like to change. Take care! # 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( loop_name = tmp_remotefolder.getvisiblename().replace(
remote_repo.getsep(), local_repo.getsep()) remote_repo.getsep(), local_repo.getsep())
if local_name != loop_name: if local_name != loop_name:
raise OfflineImapError("INFINITE FOLDER CREATION DETECTED! " msg = "INFINITE FOLDER CREATION DETECTED! "\
"Folder '%s' (repository '%s') would be created as fold" "Folder '%s' (repository '%s') would be created as " \
"er '%s' (repository '%s'). The latter becomes '%s' in " "folder '%s' (repository '%s'). The latter " \
"return, leading to infinite folder creation cycles.\n " "becomes '%s' in return, leading to infinite " \
"SOLUTION: 1) Do set your nametrans rules on both repos" "folder creation cycles.\n "\
"itories so they lead to identical names if applied bac" "SOLUTION: 1) Do set your nametrans rules on both " \
"k and forth. 2) Use folderfilter settings on a reposit" "repositories so they lead to identical names if " \
"ory to prevent some folders from being created on the " "applied back and forth. " \
"other side." % "2) Use folderfilter settings on a repository to " \
(local_folder.getname(), local_repo, remote_name, "prevent some folders from being created on the " \
remote_repo, "other side." % \
loop_name), (local_folder.getname(), local_repo, remote_name,
OfflineImapError.ERROR.REPO) remote_repo, loop_name)
raise OfflineImapError(msg, OfflineImapError.ERROR.REPO)
# End sanity check, actually create the folder. # End sanity check, actually create the folder.
try: try:
remote_repo.makefolder(remote_name) remote_repo.makefolder(remote_name)
# Need to refresh list. # Need to refresh list.
self.forgetfolders() self.forgetfolders()
except OfflineImapError as e: except OfflineImapError as exc:
self.ui.error(e, exc_info()[2], "Creating folder %s on " msg = "Creating folder %s on repository %s" % \
"repository %s" % (remote_name, remote_repo)) (remote_name, remote_repo)
self.ui.error(exc, exc_info()[2], msg)
raise raise
status_repo.makefolder(local_name.replace( status_repo.makefolder(local_name.replace(
local_repo.getsep(), status_repo.getsep())) local_repo.getsep(), status_repo.getsep()))
@ -284,14 +364,10 @@ class BaseRepository(CustomConfig.ConfigHelperMixin):
def startkeepalive(self): def startkeepalive(self):
"""The default implementation will do nothing.""" """The default implementation will do nothing."""
pass
def stopkeepalive(self): def stopkeepalive(self):
"""Stop keep alive, but don't bother waiting """Stop keep alive, but don't bother waiting
for the threads to terminate.""" for the threads to terminate."""
pass
def getlocalroot(self): def getlocalroot(self):
""" Local root folder for storing messages. """ Local root folder for storing messages.
Will not be set for remote repositories.""" Will not be set for remote repositories."""

View File

@ -1,20 +1,22 @@
# Gmail IMAP repository support """
# Copyright (C) 2008-2016 Riccardo Murri <riccardo.murri@gmail.com> & Gmail IMAP repository support
# contributors 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 This program is free software; you can redistribute it and/or modify
# the Free Software Foundation; either version 2 of the License, or it under the terms of the GNU General Public License as published by
# (at your option) any later version. 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 This program is distributed in the hope that it will be useful,
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details. 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 You should have received a copy of the GNU General Public License
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 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.repository.IMAP import IMAPRepository
from offlineimap import folder, OfflineImapError from offlineimap import folder, OfflineImapError
@ -38,7 +40,7 @@ class GmailRepository(IMAPRepository):
We first check the usual IMAP settings, and then fall back to We first check the usual IMAP settings, and then fall back to
imap.gmail.com if nothing is specified.""" imap.gmail.com if nothing is specified."""
try: try:
return super(GmailRepository, self).gethost() return super().gethost()
except OfflineImapError: except OfflineImapError:
# Nothing was configured, cache and return hardcoded # Nothing was configured, cache and return hardcoded
# one. See the parent class (IMAPRepository) for how this # 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 https://accounts.google.com/o/oauth2/token if nothing is
specified.""" specified."""
url = super(GmailRepository, self).getoauth2_request_url() url = super().getoauth2_request_url()
if url is None: if url is None:
# Nothing was configured, cache and return hardcoded one. # Nothing was configured, use a hardcoded one.
self.setoauth2_request_url("https://accounts.google.com/o/oauth2/token") url = "https://accounts.google.com/o/oauth2/token"
else:
self.setoauth2_request_url(url) self.setoauth2_request_url(url)
return self.oauth2_request_url return self.oauth2_request_url
def getport(self): def getport(self):
@ -67,13 +70,13 @@ class GmailRepository(IMAPRepository):
This Gmail implementation first checks for the usual IMAP settings This Gmail implementation first checks for the usual IMAP settings
and falls back to 993 if nothing is specified.""" and falls back to 993 if nothing is specified."""
port = super(GmailRepository, self).getport() port = super().getport()
if port is None: if port is not None:
return 993
else:
return port return port
return 993
def getssl(self): def getssl(self):
ssl = self.getconfboolean('ssl', None) ssl = self.getconfboolean('ssl', None)
@ -82,8 +85,8 @@ class GmailRepository(IMAPRepository):
# GMail. Maybe this should look more similar to gethost & # GMail. Maybe this should look more similar to gethost &
# we could just rely on the global "ssl = yes" default. # we could just rely on the global "ssl = yes" default.
return True return True
else:
return ssl return ssl
def getpreauthtunnel(self): def getpreauthtunnel(self):
return None return None
@ -96,12 +99,16 @@ class GmailRepository(IMAPRepository):
return folder.Gmail.GmailFolder return folder.Gmail.GmailFolder
def gettrashfolder(self): def gettrashfolder(self):
# Where deleted mail should be moved """
Where deleted mail should be moved
"""
return self.getconf('trashfolder', '[Gmail]/Trash') return self.getconf('trashfolder', '[Gmail]/Trash')
def getspamfolder(self): def getspamfolder(self):
# Depending on the IMAP settings (Settings -> Forwarding and """
# POP/IMAP -> IMAP Access -> "When I mark a message in IMAP as Depending on the IMAP settings (Settings -> Forwarding and
# deleted") GMail might also deletes messages upon EXPUNGE in POP/IMAP -> IMAP Access -> "When I mark a message in IMAP as
# the Spam folder. deleted") GMail might also deletes messages upon EXPUNGE in
the Spam folder.
"""
return self.getconf('spamfolder', '[Gmail]/Spam') return self.getconf('spamfolder', '[Gmail]/Spam')

View File

@ -1,31 +1,30 @@
# Maildir repository support """
# Copyright (C) 2002-2015 John Goerzen & contributors Maildir repository support
# <jgoerzen@complete.org> 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 This program is free software; you can redistribute it and/or modify
# the Free Software Foundation; either version 2 of the License, or it under the terms of the GNU General Public License as published by
# (at your option) any later version. 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 This program is distributed in the hope that it will be useful,
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details. 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 You should have received a copy of the GNU General Public License
# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA 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.repository.Maildir import MaildirRepository
from offlineimap.folder.GmailMaildir import GmailMaildirFolder from offlineimap.folder.GmailMaildir import GmailMaildirFolder
class GmailMaildirRepository(MaildirRepository): class GmailMaildirRepository(MaildirRepository):
def __init__(self, reposname, account): """
"""Initialize a MaildirRepository object. Takes a path name GMail Maildir Repository Class
to the directory holding all the Maildir directories.""" """
super(GmailMaildirRepository, self).__init__(reposname, account)
def getfoldertype(self): def getfoldertype(self):
return GmailMaildirFolder return GmailMaildirFolder

View File

@ -1,21 +1,22 @@
""" IMAP repository support """ """
IMAP repository support
# Copyright (C) 2002-2019 John Goerzen & contributors 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
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 os
import netrc import netrc
import errno import errno
@ -29,6 +30,9 @@ from offlineimap.utils.distro_utils import get_os_sslcertfile, \
class IMAPRepository(BaseRepository): class IMAPRepository(BaseRepository):
"""
IMAP Repository Class, children of BaseRepository
"""
def __init__(self, reposname, account): def __init__(self, reposname, account):
self.idlefolders = None self.idlefolders = None
BaseRepository.__init__(self, reposname, account) BaseRepository.__init__(self, reposname, account)
@ -60,7 +64,7 @@ class IMAPRepository(BaseRepository):
self.kathread = ExitNotifyThread(target=self.imapserver.keepalive, self.kathread = ExitNotifyThread(target=self.imapserver.keepalive,
name="Keep alive " + self.getname(), name="Keep alive " + self.getname(),
args=(keepalivetime, self.kaevent)) args=(keepalivetime, self.kaevent))
self.kathread.setDaemon(1) self.kathread.setDaemon(True)
self.kathread.start() self.kathread.start()
def stopkeepalive(self): def stopkeepalive(self):
@ -92,11 +96,25 @@ class IMAPRepository(BaseRepository):
return self.copy_ignore_eval(foldername) return self.copy_ignore_eval(foldername)
def getholdconnectionopen(self): 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(): if self.getidlefolders():
return True return True
return self.getconfboolean("holdconnectionopen", False) return self.getconfboolean("holdconnectionopen", False)
def getkeepalive(self): 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) num = self.getconfint("keepalive", 0)
if num == 0 and self.getidlefolders(): if num == 0 and self.getidlefolders():
return 29 * 60 return 29 * 60
@ -107,9 +125,9 @@ class IMAPRepository(BaseRepository):
This requires that self.imapserver has been initialized with an This requires that self.imapserver has been initialized with an
acquireconnection() or it will still be `None`""" acquireconnection() or it will still be `None`"""
assert self.imapserver.delim is not None, "'%s' " \ assert self.imapserver.delim is not None, \
"repository called getsep() before the folder separator was " \ "'%s' repository called getsep() before the folder separator was " \
"queried from the server" % self "queried from the server" % self
return self.imapserver.delim return self.imapserver.delim
def gethost(self): def gethost(self):
@ -124,12 +142,12 @@ class IMAPRepository(BaseRepository):
host = self.getconf('remotehosteval') host = self.getconf('remotehosteval')
try: try:
host = self.localeval.eval(host) host = self.localeval.eval(host)
except Exception as e: except Exception as exc:
raise OfflineImapError( raise OfflineImapError(
"remotehosteval option for repository " "remotehosteval option for repository "
"'%s' failed:\n%s" % (self, e), "'%s' failed:\n%s" % (self, exc),
OfflineImapError.ERROR.REPO, OfflineImapError.ERROR.REPO,
exc_info()[2]) exc_info()[2]) from exc
if host: if host:
self._host = host self._host = host
return self._host return self._host
@ -141,7 +159,8 @@ class IMAPRepository(BaseRepository):
# No success. # No success.
raise OfflineImapError("No remote host for repository " raise OfflineImapError("No remote host for repository "
"'%s' specified." % self, OfflineImapError.ERROR.REPO) "'%s' specified." % self,
OfflineImapError.ERROR.REPO)
def get_remote_identity(self): def get_remote_identity(self):
"""Remote identity is used for certain SASL mechanisms """Remote identity is used for certain SASL mechanisms
@ -154,6 +173,13 @@ class IMAPRepository(BaseRepository):
return identity return identity
def get_auth_mechanisms(self): 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"] supported = ["GSSAPI", "XOAUTH2", "CRAM-MD5", "PLAIN", "LOGIN"]
# Mechanisms are ranged from the strongest to the # Mechanisms are ranged from the strongest to the
# weakest ones. # weakest ones.
@ -164,16 +190,22 @@ class IMAPRepository(BaseRepository):
mechs = self.getconflist('auth_mechanisms', r',\s*', mechs = self.getconflist('auth_mechanisms', r',\s*',
default) default)
for m in mechs: for mech in mechs:
if m not in supported: if mech not in supported:
raise OfflineImapError("Repository %s: " % self + raise OfflineImapError("Repository %s: " % self +
"unknown authentication mechanism '%s'" % m, "unknown authentication mechanism '%s'"
OfflineImapError.ERROR.REPO) % mech, OfflineImapError.ERROR.REPO)
self.ui.debug('imap', "Using authentication mechanisms %s" % mechs) self.ui.debug('imap', "Using authentication mechanisms %s" % mechs)
return mechs return mechs
def getuser(self): 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 localeval = self.localeval
if self.config.has_option(self.getsection(), 'remoteusereval'): if self.config.has_option(self.getsection(), 'remoteusereval'):
@ -198,7 +230,8 @@ class IMAPRepository(BaseRepository):
return netrcentry[0] return netrcentry[0]
try: try:
netrcentry = netrc.netrc('/etc/netrc').authenticators(self.gethost()) netrcentry = netrc.netrc('/etc/netrc')\
.authenticators(self.gethost())
except IOError as inst: except IOError as inst:
if inst.errno not in (errno.ENOENT, errno.EACCES): if inst.errno not in (errno.ENOENT, errno.EACCES):
raise raise
@ -207,6 +240,12 @@ class IMAPRepository(BaseRepository):
return netrcentry[0] return netrcentry[0]
def getport(self): def getport(self):
"""
Returns remoteporteval value or None if not found.
Returns: Returns remoteporteval int value or None if not found.
"""
port = None port = None
if self.config.has_option(self.getsection(), 'remoteporteval'): if self.config.has_option(self.getsection(), 'remoteporteval'):
@ -217,16 +256,40 @@ class IMAPRepository(BaseRepository):
return self.getconfint('remoteport', None) return self.getconfint('remoteport', None)
def getipv6(self): 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) return self.getconfboolean('ipv6', None)
def getssl(self): 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) return self.getconfboolean('ssl', True)
def getsslclientcert(self): 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] xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
return self.getconf_xform('sslclientcert', xforms, None) return self.getconf_xform('sslclientcert', xforms, None)
def getsslclientkey(self): 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] xforms = [os.path.expanduser, os.path.expandvars, os.path.abspath]
return self.getconf_xform('sslclientkey', xforms, None) return self.getconf_xform('sslclientkey', xforms, None)
@ -254,8 +317,8 @@ class IMAPRepository(BaseRepository):
# Can't use above cacertfile because of abspath. # Can't use above cacertfile because of abspath.
conf_sslacertfile = self.getconf('sslcacertfile', None) conf_sslacertfile = self.getconf('sslcacertfile', None)
if conf_sslacertfile == "OS-DEFAULT" or \ if conf_sslacertfile == "OS-DEFAULT" or \
conf_sslacertfile == None or \ conf_sslacertfile is None or \
conf_sslacertfile == '': conf_sslacertfile == '':
cacertfile = get_os_sslcertfile() cacertfile = get_os_sslcertfile()
if cacertfile is None: if cacertfile is None:
searchpath = get_os_sslcertfile_searchpath() searchpath = get_os_sslcertfile_searchpath()
@ -277,12 +340,30 @@ class IMAPRepository(BaseRepository):
return cacertfile return cacertfile
def gettlslevel(self): 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') return self.getconf('tls_level', 'tls_compat')
def getsslversion(self): 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) return self.getconf('ssl_version', None)
def getstarttls(self): 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) return self.getconfboolean('starttls', True)
def get_ssl_fingerprint(self): def get_ssl_fingerprint(self):
@ -292,12 +373,29 @@ class IMAPRepository(BaseRepository):
comma-separated fingerprints in hex form.""" comma-separated fingerprints in hex form."""
value = self.getconf('cert_fingerprint', "") 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): def setoauth2_request_url(self, url):
"""
Set the OAUTH2 URL request.
Args:
url: OAUTH2 URL request
Returns: None
"""
self.oauth2_request_url = url self.oauth2_request_url = url
def getoauth2_request_url(self): 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. if self.oauth2_request_url is not None: # Use cached value if possible.
return self.oauth2_request_url return self.oauth2_request_url
@ -305,6 +403,14 @@ class IMAPRepository(BaseRepository):
return self.oauth2_request_url return self.oauth2_request_url
def getoauth2_refresh_token(self): 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) refresh_token = self.getconf('oauth2_refresh_token', None)
if refresh_token is None: if refresh_token is None:
refresh_token = self.localeval.eval( refresh_token = self.localeval.eval(
@ -315,6 +421,13 @@ class IMAPRepository(BaseRepository):
return refresh_token return refresh_token
def getoauth2_access_token(self): 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) access_token = self.getconf('oauth2_access_token', None)
if access_token is None: if access_token is None:
access_token = self.localeval.eval( access_token = self.localeval.eval(
@ -325,6 +438,13 @@ class IMAPRepository(BaseRepository):
return access_token return access_token
def getoauth2_client_id(self): 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) client_id = self.getconf('oauth2_client_id', None)
if client_id is None: if client_id is None:
client_id = self.localeval.eval( client_id = self.localeval.eval(
@ -335,6 +455,13 @@ class IMAPRepository(BaseRepository):
return client_id return client_id
def getoauth2_client_secret(self): 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) client_secret = self.getconf('oauth2_client_secret', None)
if client_secret is None: if client_secret is None:
client_secret = self.localeval.eval( client_secret = self.localeval.eval(
@ -345,18 +472,51 @@ class IMAPRepository(BaseRepository):
return client_secret return client_secret
def getpreauthtunnel(self): 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) return self.getconf('preauthtunnel', None)
def gettransporttunnel(self): 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) return self.getconf('transporttunnel', None)
def getreference(self): 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', '""') return self.getconf('reference', '""')
def getdecodefoldernames(self): 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) return self.getconfboolean('decodefoldernames', False)
def getidlefolders(self): 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: if self.idlefolders is None:
self.idlefolders = self.localeval.eval( self.idlefolders = self.localeval.eval(
self.getconf('idlefolders', '[]') self.getconf('idlefolders', '[]')
@ -364,11 +524,25 @@ class IMAPRepository(BaseRepository):
return self.idlefolders return self.idlefolders
def getmaxconnections(self): 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()) num1 = len(self.getidlefolders())
num2 = self.getconfint('maxconnections', 1) num2 = self.getconfint('maxconnections', 1)
return max(num1, num2) return max(num1, num2)
def getexpunge(self): 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) return self.getconfboolean('expunge', True)
def getpassword(self): def getpassword(self):
@ -388,7 +562,21 @@ class IMAPRepository(BaseRepository):
# 1. Evaluate Repository 'remotepasseval'. # 1. Evaluate Repository 'remotepasseval'.
passwd = self.getconf('remotepasseval', None) passwd = self.getconf('remotepasseval', None)
if passwd is not 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'. # 2. Read password from Repository 'remotepass'.
password = self.getconf('remotepass', None) password = self.getconf('remotepass', None)
if password is not None: if password is not None:
@ -398,9 +586,10 @@ class IMAPRepository(BaseRepository):
# 3. Read password from file specified in Repository 'remotepassfile'. # 3. Read password from file specified in Repository 'remotepassfile'.
passfile = self.getconf('remotepassfile', None) passfile = self.getconf('remotepassfile', None)
if passfile is not None: if passfile is not None:
fd = open(os.path.expanduser(passfile), 'r', 'utf-8') file_desc = open(os.path.expanduser(passfile), 'r',
password = fd.readline().strip() encoding='utf-8')
fd.close() password = file_desc.readline().strip()
file_desc.close()
return password.encode('UTF-8') return password.encode('UTF-8')
# 4. Read password from ~/.netrc. # 4. Read password from ~/.netrc.
try: try:
@ -415,7 +604,8 @@ class IMAPRepository(BaseRepository):
return netrcentry[2] return netrcentry[2]
# 5. Read password from /etc/netrc. # 5. Read password from /etc/netrc.
try: try:
netrcentry = netrc.netrc('/etc/netrc').authenticators(self.gethost()) netrcentry = netrc.netrc('/etc/netrc')\
.authenticators(self.gethost())
except IOError as inst: except IOError as inst:
if inst.errno not in (errno.ENOENT, errno.EACCES): if inst.errno not in (errno.ENOENT, errno.EACCES):
raise raise
@ -433,6 +623,13 @@ class IMAPRepository(BaseRepository):
return self.getfoldertype()(self.imapserver, foldername, self, decode) return self.getfoldertype()(self.imapserver, foldername, self, decode)
def getfoldertype(self): def getfoldertype(self):
"""
This function returns the folder type, in this case
folder.IMAP.IMAPFolder
Returns: folder.IMAP.IMAPFolder
"""
return folder.IMAP.IMAPFolder return folder.IMAP.IMAPFolder
def connect(self): def connect(self):
@ -455,25 +652,26 @@ class IMAPRepository(BaseRepository):
listfunction = imapobj.lsub listfunction = imapobj.lsub
try: try:
result, listresult = listfunction(directory=self.imapserver.reference, pattern='"*"') result, listresult = \
listfunction(directory=self.imapserver.reference, pattern='"*"')
if result != 'OK': if result != 'OK':
raise OfflineImapError("Could not list the folders for" raise OfflineImapError("Could not list the folders for"
" repository %s. Server responded: %s" % " repository %s. Server responded: %s" %
(self.name, self, str(listresult)), (self.name, str(listresult)),
OfflineImapError.ERROR.FOLDER) OfflineImapError.ERROR.FOLDER)
finally: finally:
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
for s in listresult: for fldr in listresult:
if s is None or (isinstance(s, str) and s == ''): if fldr is None or (isinstance(fldr, str) and fldr == ''):
# Bug in imaplib: empty strings in results from # Bug in imaplib: empty strings in results from
# literals. TODO: still relevant? # literals. TODO: still relevant?
continue continue
try: try:
flags, delim, name = imaputil.imapsplit(s) flags, delim, name = imaputil.imapsplit(fldr)
except ValueError: except ValueError:
self.ui.error( self.ui.error(
"could not correctly parse server response; got: %s" % s) "could not correctly parse server response; got: %s" % fldr)
raise raise
flaglist = [x.lower() for x in imaputil.flagsplit(flags)] flaglist = [x.lower() for x in imaputil.flagsplit(flags)]
if '\\noselect' in flaglist: if '\\noselect' in flaglist:
@ -486,12 +684,13 @@ class IMAPRepository(BaseRepository):
try: try:
for foldername in self.folderincludes: for foldername in self.folderincludes:
try: try:
imapobj.select(imaputil.utf8_IMAP(foldername), readonly=True) imapobj.select(imaputil.utf8_IMAP(foldername),
except OfflineImapError as e: readonly=True)
except OfflineImapError as exc:
# couldn't select this folderinclude, so ignore folder. # couldn't select this folderinclude, so ignore folder.
if e.severity > OfflineImapError.ERROR.FOLDER: if exc.severity > OfflineImapError.ERROR.FOLDER:
raise raise
self.ui.error(e, exc_info()[2], self.ui.error(exc, exc_info()[2],
'Invalid folderinclude:') 'Invalid folderinclude:')
continue continue
retval.append(self.getfoldertype()( retval.append(self.getfoldertype()(
@ -504,17 +703,22 @@ class IMAPRepository(BaseRepository):
retval.sort(key=lambda x: str.lower(x.getvisiblename())) retval.sort(key=lambda x: str.lower(x.getvisiblename()))
else: else:
# do foldersort in a python3-compatible way # 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): def cmp2key(mycmp):
"""Converts a cmp= function into a key= function """Converts a cmp= function into a key= function
We need to keep cmp functions for backward compatibility""" We need to keep cmp functions for backward compatibility"""
class K: class K:
"""
Class to compare getvisiblename() between two objects.
"""
def __init__(self, obj, *args): def __init__(self, obj, *args):
self.obj = obj self.obj = obj
def __cmp__(self, other): def __cmp__(self, other):
return mycmp(self.obj.getvisiblename(), other.obj.getvisiblename()) return mycmp(self.obj.getvisiblename(),
other.obj.getvisiblename())
return K return K
@ -532,21 +736,26 @@ class IMAPRepository(BaseRepository):
try: try:
result = imapobj.delete(foldername) result = imapobj.delete(foldername)
if result[0] != 'OK': if result[0] != 'OK':
raise OfflineImapError("Folder '%s'[%s] could not be deleted. " msg = "Folder '%s'[%s] could not be deleted. "\
"Server responded: %s" % (foldername, self, str(result)), "Server responded: %s" % (foldername, self, str(result))
OfflineImapError.ERROR.FOLDER) raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER)
finally: finally:
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
def makefolder(self, foldername): 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 This will not update the list cached in :meth:`getfolders`. You
will need to invoke :meth:`forgetfolders` to force new caching will need to invoke :meth:`forgetfolders` to force new caching
when you are done creating folders yourself. 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 == '': if foldername == '':
return return
@ -558,15 +767,25 @@ class IMAPRepository(BaseRepository):
return return
parts = foldername.split(self.getsep()) 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: for folder_path in folder_paths:
try: try:
self.makefolder_single(folder_path) self.makefolder_single(folder_path)
except OfflineImapError as e: except OfflineImapError as exc:
if '[ALREADYEXISTS]' not in e.reason: if '[ALREADYEXISTS]' not in exc.reason:
raise raise
def makefolder_single(self, foldername): def makefolder_single(self, foldername):
"""
Create a IMAP folder.
Args:
foldername: Folder's name to create
Returns: None
"""
self.ui.makefolder(self, foldername) self.ui.makefolder(self, foldername)
if self.account.dryrun: if self.account.dryrun:
return return
@ -577,13 +796,18 @@ class IMAPRepository(BaseRepository):
result = imapobj.create(foldername) result = imapobj.create(foldername)
if result[0] != 'OK': if result[0] != 'OK':
raise OfflineImapError("Folder '%s'[%s] could not be created. " msg = "Folder '%s'[%s] could not be created. "\
"Server responded: %s" % (foldername, self, str(result)), "Server responded: %s" % (foldername, self, str(result))
OfflineImapError.ERROR.FOLDER) raise OfflineImapError(msg, OfflineImapError.ERROR.FOLDER)
finally: finally:
self.imapserver.releaseconnection(imapobj) self.imapserver.releaseconnection(imapobj)
class MappedIMAPRepository(IMAPRepository): 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): def getfoldertype(self):
return folder.UIDMaps.MappedIMAPFolder return folder.UIDMaps.MappedIMAPFolder

View File

@ -1,20 +1,21 @@
# Local status cache repository support """
# Copyright (C) 2002-2017 John Goerzen & contributors 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
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 os
from offlineimap.folder.LocalStatus import LocalStatusFolder from offlineimap.folder.LocalStatus import LocalStatusFolder
@ -24,6 +25,9 @@ from offlineimap.error import OfflineImapError
class LocalStatusRepository(BaseRepository): class LocalStatusRepository(BaseRepository):
"""
Local Status Repository Class, child of Base Repository Class
"""
def __init__(self, reposname, account): def __init__(self, reposname, account):
BaseRepository.__init__(self, reposname, account) BaseRepository.__init__(self, reposname, account)
@ -57,26 +61,44 @@ class LocalStatusRepository(BaseRepository):
return self.LocalStatusFolderClass(foldername, self) # Instantiate. return self.LocalStatusFolderClass(foldername, self) # Instantiate.
def setup_backend(self, backend): def setup_backend(self, backend):
"""
Setup the backend.
Args:
backend: backend to use
Returns: None
"""
if backend in list(self.backends.keys()): if backend in list(self.backends.keys()):
self._backend = backend self._backend = backend
self.root = self.backends[backend]['root'] self.root = self.backends[backend]['root']
self.LocalStatusFolderClass = self.backends[backend]['class'] self.LocalStatusFolderClass = self.backends[backend]['class']
def import_other_backend(self, folder): 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. # Skip folder's own type.
if dic['class'] == type(folder): if dic['class'] == type(folder):
continue continue
repobk = LocalStatusRepository(self.name, self.account) 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) folderbk = dic['class'](folder.name, repobk)
# If backend contains data, import it to folder. # If backend contains data, import it to folder.
if not folderbk.isnewfolder(): if not folderbk.isnewfolder():
self.ui._msg("Migrating LocalStatus cache from %s to %s " self.ui._msg("Migrating LocalStatus cache from %s to %s "
"status folder for %s:%s" % "status folder for %s:%s" %
(bk, self._backend, self.name, folder.name)) (bkend, self._backend, self.name, folder.name))
folderbk.cachemessagelist() folderbk.cachemessagelist()
folder.messagelist = folderbk.messagelist folder.messagelist = folderbk.messagelist
@ -130,8 +152,6 @@ class LocalStatusRepository(BaseRepository):
the file names that we have available. TODO: need to store a the file names that we have available. TODO: need to store a
list of folder names somehow?""" list of folder names somehow?"""
pass
def forgetfolders(self): def forgetfolders(self):
"""Forgets the cached list of folders, if any. Useful to run """Forgets the cached list of folders, if any. Useful to run
after a sync run.""" after a sync run."""

View File

@ -1,20 +1,22 @@
# Maildir repository support """
# Copyright (C) 2002-2015 John Goerzen & contributors Maildir repository support
#
# 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-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 import os
from offlineimap import folder from offlineimap import folder
from offlineimap.ui import getglobalui from offlineimap.ui import getglobalui
@ -23,6 +25,9 @@ from offlineimap.repository.Base import BaseRepository
class MaildirRepository(BaseRepository): class MaildirRepository(BaseRepository):
"""
Maildir Repository Class
"""
def __init__(self, reposname, account): def __init__(self, reposname, account):
"""Initialize a MaildirRepository object. Takes a path name """Initialize a MaildirRepository object. Takes a path name
to the directory holding all the Maildir directories.""" to the directory holding all the Maildir directories."""
@ -32,7 +37,8 @@ class MaildirRepository(BaseRepository):
self.root = self.getlocalroot() self.root = self.getlocalroot()
self.folders = None self.folders = None
self.ui = getglobalui() 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 = [] self.folder_atimes = []
# Create the top-level folder if it doesn't exist # Create the top-level folder if it doesn't exist
@ -41,19 +47,19 @@ class MaildirRepository(BaseRepository):
# Create the keyword->char mapping # Create the keyword->char mapping
self.keyword2char = dict() self.keyword2char = dict()
for c in 'abcdefghijklmnopqrstuvwxyz': for char in 'abcdefghijklmnopqrstuvwxyz':
confkey = 'customflag_' + c confkey = 'customflag_' + char
keyword = self.getconf(confkey, None) keyword = self.getconf(confkey, None)
if keyword is not None: if keyword is not None:
self.keyword2char[keyword] = c self.keyword2char[keyword] = char
def _append_folder_atimes(self, foldername): def _append_folder_atimes(self, foldername):
"""Store the atimes of a folder's new|cur in self.folder_atimes""" """Store the atimes of a folder's new|cur in self.folder_atimes"""
p = os.path.join(self.root, foldername) path = os.path.join(self.root, foldername)
new = os.path.join(p, 'new') new = os.path.join(path, 'new')
cur = os.path.join(p, 'cur') cur = os.path.join(path, 'cur')
atimes = (p, os.path.getatime(new), os.path.getatime(cur)) atimes = (path, os.path.getatime(new), os.path.getatime(cur))
self.folder_atimes.append(atimes) self.folder_atimes.append(atimes)
def restore_atime(self): def restore_atime(self):
@ -75,6 +81,15 @@ class MaildirRepository(BaseRepository):
return self.getconf_xform('localfolders', xforms) return self.getconf_xform('localfolders', xforms)
def debug(self, msg): 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) self.ui.debug('maildir', msg)
def getsep(self): def getsep(self):
@ -107,24 +122,26 @@ class MaildirRepository(BaseRepository):
assert component not in ['new', 'cur', 'tmp'], \ assert component not in ['new', 'cur', 'tmp'], \
"When using nested folders (/ as a Maildir separator), " \ "When using nested folders (/ as a Maildir separator), " \
"folder names may not contain 'new', 'cur', 'tmp'." "folder names may not contain 'new', 'cur', 'tmp'."
assert foldername.find('../') == -1, "Folder names may not contain ../" assert foldername.find('../') == -1, \
assert not foldername.startswith('/'), "Folder names may not begin with /" "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 # If we're using hierarchical folders, it's possible that
# sub-folders may be created before higher-up ones. # sub-folders may be created before higher-up ones.
self.debug("makefolder: calling makedirs '%s'" % full_path) self.debug("makefolder: calling makedirs '%s'" % full_path)
try: try:
os.makedirs(full_path, 0o700) os.makedirs(full_path, 0o700)
except OSError as e: except OSError as exc:
if e.errno == 17 and os.path.isdir(full_path): if exc.errno == 17 and os.path.isdir(full_path):
self.debug("makefolder: '%s' already a directory" % foldername) self.debug("makefolder: '%s' already a directory" % foldername)
else: else:
raise raise
for subdir in ['cur', 'new', 'tmp']: for subdir in ['cur', 'new', 'tmp']:
try: try:
os.mkdir(os.path.join(full_path, subdir), 0o700) os.mkdir(os.path.join(full_path, subdir), 0o700)
except OSError as e: except OSError as exc:
if e.errno == 17 and os.path.isdir(full_path): if exc.errno == 17 and os.path.isdir(full_path):
self.debug("makefolder: '%s' already has subdir %s" % self.debug("makefolder: '%s' already has subdir %s" %
(foldername, subdir)) (foldername, subdir))
else: else:
@ -142,11 +159,12 @@ class MaildirRepository(BaseRepository):
# getfolders() will scan and cache the values *if* necessary # getfolders() will scan and cache the values *if* necessary
folders = self.getfolders() folders = self.getfolders()
for f in folders: for foldr in folders:
if foldername == f.name: if foldername == foldr.name:
return f return foldr
raise OfflineImapError("getfolder() asked for a nonexisting " 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): def _getfolders_scandir(self, root, extension=None):
"""Recursively scan folder 'root'; return a list of MailDirFolder """Recursively scan folder 'root'; return a list of MailDirFolder
@ -193,14 +211,15 @@ class MaildirRepository(BaseRepository):
self.debug(" This is maildir folder '%s'." % foldername) self.debug(" This is maildir folder '%s'." % foldername)
if self.getconfboolean('restoreatime', False): if self.getconfboolean('restoreatime', False):
self._append_folder_atimes(foldername) self._append_folder_atimes(foldername)
fd = self.getfoldertype()(self.root, foldername, file_desc = self.getfoldertype()(self.root, foldername,
self.getsep(), self) self.getsep(), self)
retval.append(fd) retval.append(file_desc)
if self.getsep() == '/' and dirname != '': if self.getsep() == '/' and dirname != '':
# Recursively check sub-directories for folders too. # Recursively check sub-directories for folders too.
retval.extend(self._getfolders_scandir(root, foldername)) 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 return retval
def getfolders(self): def getfolders(self):
@ -209,6 +228,12 @@ class MaildirRepository(BaseRepository):
return self.folders return self.folders
def getfoldertype(self): def getfoldertype(self):
"""
Returns a folder MaildirFolder type.
Returns: A MaildirFolder class
"""
return folder.Maildir.MaildirFolder return folder.Maildir.MaildirFolder
def forgetfolders(self): def forgetfolders(self):

View File

@ -1,19 +1,20 @@
# Copyright (C) 2002-2016 John Goerzen & contributors. """
# 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
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 sys import exc_info
from configparser import NoSectionError from configparser import NoSectionError
from offlineimap.repository.IMAP import IMAPRepository, MappedIMAPRepository from offlineimap.repository.IMAP import IMAPRepository, MappedIMAPRepository
@ -59,19 +60,19 @@ class Repository:
config = account.getconfig() config = account.getconfig()
try: try:
repostype = config.get('Repository ' + name, 'type').strip() repostype = config.get('Repository ' + name, 'type').strip()
except NoSectionError: except NoSectionError as exc:
errstr = ("Could not find section '%s' in configuration. Required " errstr = ("Could not find section '%s' in configuration. Required "
"for account '%s'." % ('Repository %s' % name, account)) "for account '%s'." % ('Repository %s' % name, account))
raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO, raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO,
exc_info()[2]) exc_info()[2]) from exc
try: try:
repo = typemap[repostype] repo = typemap[repostype]
except KeyError: except KeyError as exc:
errstr = "'%s' repository not supported for '%s' repositories." % \ errstr = "'%s' repository not supported for '%s' repositories." % \
(repostype, reqtype) (repostype, reqtype)
raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO, raise OfflineImapError(errstr, OfflineImapError.ERROR.REPO,
exc_info()[2]) exc_info()[2]) from exc
return repo(name, account) return repo(name, account)
@ -83,4 +84,3 @@ class Repository:
:param account: :class:`Account` :param account: :class:`Account`
:param reqtype: 'remote', 'local', or 'status' :param reqtype: 'remote', 'local', or 'status'
""" """
pass

View File

@ -1,8 +1,9 @@
# Copyright (C) 2013-2014 Eygene A. Ryabinkin and contributors """
# Copyright (C) 2013-2014 Eygene A. Ryabinkin and contributors
# Collection of classes that implement const-like behaviour
# for various objects.
Collection of classes that implement const-like behaviour
for various objects.
"""
import copy import copy

View File

@ -1,7 +1,8 @@
# Copyright 2006-2018 Eygene A. Ryabinkin & contributors. """
# Copyright 2006-2018 Eygene A. Ryabinkin & contributors.
# Module that supports distribution-specific functions.
Module that supports distribution-specific functions.
"""
import platform import platform
import os import os
@ -68,12 +69,8 @@ def get_os_sslcertfile_searchpath():
Returned value of None means that there is no search path Returned value of None means that there is no search path
at all. at all.
""" """
os_name = get_os_name() os_name = get_os_name()
location = __DEF_OS_LOCATIONS.get(os_name, None)
location = None
if os_name in __DEF_OS_LOCATIONS:
location = __DEF_OS_LOCATIONS[os_name]
return location return location
@ -92,9 +89,10 @@ def get_os_sslcertfile():
if location is None: if location is None:
return None return None
for f in location: for l_file in location:
assert isinstance(f, str) assert isinstance(l_file, str)
if os.path.exists(f) and (os.path.isfile(f) or os.path.islink(f)): if os.path.exists(l_file) and (os.path.isfile(l_file) or
return f os.path.islink(l_file)):
return l_file
return None return None

View File

@ -1,6 +1,8 @@
# Copyright 2013 Eygene A. Ryabinkin """
# Functions to perform stack tracing (for multithreaded programs Copyright 2013 Eygene A. Ryabinkin
# as well as for single-threaded ones). Functions to perform stack tracing (for multithreaded programs
as well as for single-threaded ones).
"""
import sys import sys
import threading import threading
@ -10,14 +12,15 @@ import traceback
def dump(out): def dump(out):
""" Dumps current stack trace into I/O object 'out' """ """ Dumps current stack trace into I/O object 'out' """
id2name = {} id2name = {}
for th in threading.enumerate(): for th_en in threading.enumerate():
id2name[th.ident] = th.name id2name[th_en.ident] = th_en.name
n = 0
count = 0
for i, stack in list(sys._current_frames().items()): for i, stack in list(sys._current_frames().items()):
out.write("\n# Thread #%d (id=%d), %s\n" % (n, i, id2name[i])) out.write("\n# Thread #%d (id=%d), %s\n" % (count, i, id2name[i]))
n = n + 1 count = count + 1
for f, lno, name, line in traceback.extract_stack(stack): for file, lno, name, line in traceback.extract_stack(stack):
out.write('File: "%s", line %d, in %s' % (f, lno, name)) out.write('File: "%s", line %d, in %s' % (file, lno, name))
if line: if line:
out.write(" %s" % (line.strip())) out.write(" %s" % (line.strip()))
out.write("\n") out.write("\n")