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