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
|
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):
|
||||||
|
@ -1 +1,4 @@
|
|||||||
|
"""
|
||||||
|
Folder Module of offlineimap
|
||||||
|
"""
|
||||||
from . import Base, Gmail, IMAP, Maildir, LocalStatus, UIDMaps
|
from . import Base, Gmail, IMAP, Maildir, LocalStatus, UIDMaps
|
||||||
|
@ -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()
|
||||||
|
@ -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."""
|
||||||
|
@ -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')
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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."""
|
||||||
|
@ -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):
|
||||||
|
@ -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
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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")
|
||||||
|
Loading…
Reference in New Issue
Block a user