diff --git a/Changelog.rst b/Changelog.rst index 049a362..51bf175 100644 --- a/Changelog.rst +++ b/Changelog.rst @@ -11,7 +11,23 @@ ChangeLog on releases. And because I'm lazy, it will also be used as a draft for the releases announces. -======= +OfflineIMAP v6.4.4 (2012-01-06) +=============================== + +This is a bugfix release, fixing regressions occurring in or since 6.4.0. + +* Fix the missing folder error that occured when a new remote folder was + detected (IMAP<->Maildir) + +* Possibly fixed bug that prevented us from ever re-reading Maildir + folders, so flag changes and deletions were not detected when running + in a refresh loop. This is a regression that was introduced in about + 6.4.0. + +* Never mangle maildir file names when using nonstandard Maildir flags + (such as 'a'), note that they will still be deleted as they are not + supported in the sync to an IMAP server. + OfflineIMAP v6.4.3 (2012-01-04) =============================== diff --git a/offlineimap/__init__.py b/offlineimap/__init__.py index ea857e6..8406401 100644 --- a/offlineimap/__init__.py +++ b/offlineimap/__init__.py @@ -1,7 +1,7 @@ __all__ = ['OfflineImap'] __productname__ = 'OfflineIMAP' -__version__ = "6.4.3" +__version__ = "6.4.4" __copyright__ = "Copyright 2002-2012 John Goerzen & contributors" __author__ = "John Goerzen" __author_email__= "john@complete.org" diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index 857fbfb..09c6882 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -289,10 +289,10 @@ class SyncableAccount(Account): localrepos.getfolders() statusrepos.getfolders() + remoterepos.sync_folder_structure(localrepos, statusrepos) # replicate the folderstructure between REMOTE to LOCAL if not localrepos.getconfboolean('readonly', False): self.ui.syncfolders(remoterepos, localrepos) - remoterepos.syncfoldersto(localrepos, statusrepos) # iterate through all folders on the remote repo and sync for remotefolder in remoterepos.getfolders(): diff --git a/offlineimap/folder/Maildir.py b/offlineimap/folder/Maildir.py index df5dd2e..c9ea63d 100644 --- a/offlineimap/folder/Maildir.py +++ b/offlineimap/folder/Maildir.py @@ -64,13 +64,13 @@ class MaildirFolder(BaseFolder): self.root = root self.sep = sep self.messagelist = None - + # check if we should use a different infosep to support Win file systems self.wincompatible = self.config.getdefaultboolean( "Account "+self.accountname, "maildir-windows-compatible", False) self.infosep = '!' if self.wincompatible else ':' """infosep is the separator between maildir name and flag appendix""" - self.flagmatchre = re.compile(self.infosep + '.*2,([A-Z]+)') + self.flagmatchre = re.compile('(%s2,)(\w*)' % self.infosep) #self.ui is set in BaseFolder.init() # Cache the full folder path, as we use getfullname() very often self._fullname = os.path.join(self.getroot(), self.getname()) @@ -162,7 +162,7 @@ class MaildirFolder(BaseFolder): #identify flags in the path name flagmatch = self.flagmatchre.search(messagename) if flagmatch: - flags = set(flagmatch.group(1)) + flags = set(flagmatch.group(2)) else: flags = set() # 'filename' is 'dirannex/filename', e.g. cur/123,U=1,FMD5=1:2,S @@ -185,7 +185,7 @@ class MaildirFolder(BaseFolder): def cachemessagelist(self): if self.messagelist is None: self.messagelist = self._scanfolder() - + def getmessagelist(self): return self.messagelist @@ -259,7 +259,7 @@ class MaildirFolder(BaseFolder): self.savemessageflags(uid, flags) self.ui.debug('maildir', 'savemessage: returning uid %d' % uid) return uid - + def getmessageflags(self, uid): return self.messagelist[uid]['flags'] @@ -274,15 +274,14 @@ class MaildirFolder(BaseFolder): else: dir_prefix = 'new' - infostr = self.infosep - infomatch = re.search('(' + self.infosep + '.*)$', newname) - if infomatch: # If the info string is present.. - infostr = infomatch.group(1) - newname = newname.split(self.infosep)[0] # Strip off the info string. - infostr = re.sub('2,[A-Z]*', '', infostr) - infostr += '2,' + ''.join(sorted(flags)) + # Strip off existing infostring (preserving small letter flags, that + # dovecot uses) + infomatch = self.flagmatchre.search(newname) + if infomatch: + newname = newname[:-len(infomatch.group())] #strip off + infostr = '%s2,%s' % (self.infosep, ''.join(sorted(flags))) newname += infostr - + newfilename = os.path.join(dir_prefix, newname) if (newfilename != oldfilename): try: @@ -292,7 +291,7 @@ class MaildirFolder(BaseFolder): raise OfflineImapError("Can't rename file '%s' to '%s': %s" % ( oldfilename, newfilename, e[1]), OfflineImapError.ERROR.FOLDER) - + self.messagelist[uid]['flags'] = flags self.messagelist[uid]['filename'] = newfilename diff --git a/offlineimap/repository/Base.py b/offlineimap/repository/Base.py index 8af1f03..084841f 100644 --- a/offlineimap/repository/Base.py +++ b/offlineimap/repository/Base.py @@ -132,8 +132,8 @@ class BaseRepository(object, CustomConfig.ConfigHelperMixin): def getfolder(self, foldername): raise NotImplementedError - - def syncfoldersto(self, dst_repo, status_repo): + + def sync_folder_structure(self, dst_repo, status_repo): """Syncs the folders in this repository to those in dest. It does NOT sync the contents of those folders. nametrans rules @@ -158,6 +158,9 @@ class BaseRepository(object, CustomConfig.ConfigHelperMixin): # Find new folders on src_repo. for src_name, src_folder in src_hash.iteritems(): + # Don't create on dst_repo, if it is readonly + if dst_repo.getconfboolean('readonly', False): + break if src_folder.sync_this and not src_name in dst_hash: try: dst_repo.makefolder(src_name) @@ -171,6 +174,10 @@ class BaseRepository(object, CustomConfig.ConfigHelperMixin): status_repo.getsep())) # Find new folders on dst_repo. for dst_name, dst_folder in dst_hash.iteritems(): + if self.getconfboolean('readonly', False): + # Don't create missing folder on readonly repo. + break + if dst_folder.sync_this and not dst_name in src_hash: # nametrans sanity check! # Does nametrans back&forth lead to identical names? @@ -202,7 +209,6 @@ class BaseRepository(object, CustomConfig.ConfigHelperMixin): src_repo, newdst_name), OfflineImapError.ERROR.REPO) # end sanity check, actually create the folder - try: src_repo.makefolder(newsrc_name) src_haschanged = True # Need to refresh list diff --git a/offlineimap/repository/Maildir.py b/offlineimap/repository/Maildir.py index 70a5ca3..cdf054d 100644 --- a/offlineimap/repository/Maildir.py +++ b/offlineimap/repository/Maildir.py @@ -190,4 +190,8 @@ class MaildirRepository(BaseRepository): if self.folders == None: self.folders = self._getfolders_scandir(self.root) return self.folders - + + def forgetfolders(self): + """Forgets the cached list of folders, if any. Useful to run + after a sync run.""" + self.folders = None diff --git a/offlineimap/ui/UIBase.py b/offlineimap/ui/UIBase.py index 7dc23e5..5ef10cb 100644 --- a/offlineimap/ui/UIBase.py +++ b/offlineimap/ui/UIBase.py @@ -285,7 +285,7 @@ class UIBase(object): """Output that we finished syncing an account (in which time)""" sec = time.time() - self.acct_startimes[account] del self.acct_startimes[account] - self._msg("*** Finished account '%s' in %d:%02d" % + self.logger.info("*** Finished account '%s' in %d:%02d" % (account, sec // 60, sec % 60)) def syncfolders(self, src_repo, dst_repo):