From b50668434e56dcff3babe2e2dc8ba2b7366449be Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 29 Sep 2011 11:45:38 +0200 Subject: [PATCH 01/11] docs/*: Documentation enhancements Improve wordings and descriptions all around. Signed-off-by: Sebastian Spaeth --- docs/FAQ.rst | 153 +++++++++++++++++++++--------------------------- docs/MANUAL.rst | 115 +++++++++++++++++++++++++++++++++--- 2 files changed, 173 insertions(+), 95 deletions(-) diff --git a/docs/FAQ.rst b/docs/FAQ.rst index 3181a8a..e3a586b 100644 --- a/docs/FAQ.rst +++ b/docs/FAQ.rst @@ -49,50 +49,40 @@ increase performance of the sync. Don’t set the number too high. If you do that, things might actually slow down as your link gets saturated. Also, too many connections can cause mail servers to have excessive load. Administrators might take unkindly to this, and the -server might bog down. There are many variables in the optimal setting; -experimentation may help. +server might bog down. There are many variables in the optimal setting; experimentation may help. -An informal benchmark yields these results for my setup:: - - * 10 minutes with MacOS X Mail.app “manual cache” - * 5 minutes with GNUS agent sync - * 20 seconds with OfflineIMAP 1.x - * 9 seconds with OfflineIMAP 2.x - * 3 seconds with OfflineIMAP 3.x “cold start” - * 2 seconds with OfflineIMAP 3.x “held connection” +See the Performance section in the MANUAL for some tips. What platforms does OfflineIMAP support? ---------------------------------------- -It should run on most platforms supported by Python, which are quite a few. I -do not support Windows myself, but some have made it work there. Use on -Windows +It should run on most platforms supported by Python, with one exception: we do not support Windows, but some have made it work there. -These answers have been reported by OfflineIMAP users. I do not run OfflineIMAP -on Windows myself, so I can’t directly address their accuracy. +The following has been reported by OfflineIMAP users. We do not test +OfflineIMAP on Windows, so we can’t directly address their accuracy. The basic answer is that it’s possible and doesn’t require hacking OfflineIMAP source code. However, it’s not necessarily trivial. The information below is -based in instructions submitted by Chris Walker. +based in instructions submitted by Chris Walker:: -First, you must run OfflineIMAP in the Cygwin environment. The Windows -filesystem is not powerful enough to accomodate Maildir by itself. - -Next, you’ll need to mount your Maildir directory in a special way. There is -information for doing that at http://barnson.org/node/295. That site gives this -example:: - - mount -f -s -b -o managed "d:/tmp/mail" "/home/of/mail" - -That URL also has more details on making OfflineIMAP work with Windows. + First, you must run OfflineIMAP in the Cygwin environment. The Windows + filesystem is not powerful enough to accomodate Maildir by itself. + + Next, you’ll need to mount your Maildir directory in a special + way. There is information for doing that at + http://barnson.org/node/295. That site gives this example:: + + mount -f -s -b -o managed "d:/tmp/mail" "/home/of/mail" + + That URL also has more details on making OfflineIMAP work with Windows. Does OfflineIMAP support mbox, mh, or anything else other than Maildir? ----------------------------------------------------------------------- -Not directly. Maildir was the easiest to implement. I’m not planning to write -mbox code for OfflineIMAP, though if someone sent me well-written mbox support -and pledged to support it, I’d commit it to the tree. +Not directly. Maildir was the easiest to implement. We are not planning +to write an mbox-backend, though if someone sent me well-written mbox +support and pledged to support it, it would be committed it to the tree. However, OfflineIMAP can directly sync accounts on two different IMAP servers together. So you could install an IMAP server on your local machine that @@ -105,22 +95,25 @@ point your mail readers to that IMAP server on localhost. What is the UID validity problem for folder? -------------------------------------------- -IMAP servers use a unique ID (UID) to refer to a specific message. This number -is guaranteed to be unique to a particular message forever. No other message in -the same folder will ever get the same UID. UIDs are an integral part of -`OfflineIMAP`_'s synchronization scheme; they are used to match up messages on -your computer to messages on the server. +IMAP servers use a folders UIDVALIDITY value in combination with a +unique ID (UID) to refer to a specific message. This is guaranteed to +be unique to a particular message forever. No other message in the same +folder will ever get the same UID as long as UIDVALIDITY remains +unchanged. UIDs are an integral part of `OfflineIMAP`_'s +synchronization scheme; they are used to match up messages on your +computer to messages on the server. -Sometimes, the UIDs on the server might get reset. Usually this will happen if -you delete and then recreate a folder. When you create a folder, the server -will often start the UID back from 1. But `OfflineIMAP`_ might still have the -UIDs from the previous folder by the same name stored. `OfflineIMAP`_ will -detect this condition and skip the folder. This is GOOD, because it prevents -data loss. +Sometimes, the UIDs on the server might get reset. Usually this will +happen if you delete and then recreate a folder. When you create a +folder, the server will often start the UID back from 1. But +`OfflineIMAP`_ might still have the UIDs from the previous folder by the +same name stored. `OfflineIMAP`_ will detect this condition because of +the changed UIDVALIDITY value and skip the folder. This is GOOD, +because it prevents data loss. -You can fix it by removing your local folder and cache data. For instance, if -your folders are under `~/Folders` and the folder with the problem is INBOX, -you'd type this:: +In the IMAP<->Maildir case, you can fix it by removing your local folder +and cache data. For instance, if your folders are under `~/Folders` and +the folder with the problem is INBOX, you'd type this:: rm -r ~/Folders/INBOX rm -r ~/.offlineimap/Account-AccountName/LocalStatus/INBOX @@ -146,12 +139,10 @@ This question comes up frequently on the `mailing list`_. You can find a detail discussion of the problem there http://lists.complete.org/offlineimap@complete.org/2003/04/msg00012.html.gz. -How do I add or delete a folder? --------------------------------- +How do I automatically delete a folder? +--------------------------------------- -OfflineIMAP does not currently provide this feature. However, if you create a -new folder on the remote server, OfflineIMAP will detect this and create the -corresponding folder locally automatically. +OfflineIMAP does not currently provide this feature. You will have to delete folders manually. See next entry too. May I delete local folders? --------------------------- @@ -175,6 +166,7 @@ It will perform a check on startup and abort if another `OfflineIMAP`_ is already running. If you need to schedule synchronizations, you'll probably find autorefresh settings more convenient than cron. Alternatively, you can set a separate metadata directory for each instance. +In the future, we will lock each account individually rather than having one global lock. Can I copy messages between folders? --------------------------------------- @@ -197,10 +189,7 @@ next run it will upload the message to second server and delete on first, etc. Does OfflineIMAP support POP? ----------------------------- -No. POP is not robust enough to do a completely reliable multi-machine sync -like OfflineIMAP can do. - -OfflineIMAP will never support POP. +No. How is OfflineIMAP conformance? ------------------------------- @@ -214,17 +203,21 @@ How is OfflineIMAP conformance? Can I force OfflineIMAP to sync a folder right now? --------------------------------------------------- -Yes, if you use the `Blinkenlights` UI. That UI shows the active accounts +Yes, + 1) if you use the `Blinkenlights` UI. That UI shows the active accounts as follows:: - 4: [active] *Control: . - 3: [ 4:36] personal: - 2: [ 3:37] work: . - 1: [ 6:28] uni: + 4: [active] *Control: . + 3: [ 4:36] personal: + 2: [ 3:37] work: . + 1: [ 6:28] uni: -Simply press the appropriate digit (`3` for `personal`, etc.) to resync that -account immediately. This will be ignored if a resync is already in progress -for that account. + Simply press the appropriate digit (`3` for `personal`, etc.) to + resync that account immediately. This will be ignored if a resync is + already in progress for that account. + + 2) while in sleep mode, you can also send a SIGUSR1. See the `Signals + on UNIX`_ section in the MANUAL for details. Configuration Questions ======================= @@ -248,10 +241,12 @@ what folders are present on the IMAP server and synchronize them. You can use the folderfilter and nametrans configuration file options to request only certain folders and rename them as they come in if you like. +Also you can configure OfflineImap to only synchronize "subscribed" folders. + How do I prevent certain folders from being synced? --------------------------------------------------- -Use the folderfilter option. +Use the folderfilter option. See the MANUAL for details and examples. What is the mailbox name recorder (mbnames) for? ------------------------------------------------ @@ -261,9 +256,11 @@ Some mail readers, such as mutt, are not capable of automatically determining th Does OfflineIMAP verify SSL certificates? ----------------------------------------- -By default, no. However, as of version 6.3.2, it is possible to enforce verification -of SSL certificate on a per-repository basis by setting the `sslcacertfile` option in the -config file. (See the example offlineimap.conf for details.) +You can verify an imapserver's certificate by specifying the CA +certificate on a per-repository basis by setting the `sslcacertfile` +option in the config file. (See the example offlineimap.conf for +details.) If you do not specify any CA certificate, you will be presented with the server's certificate fingerprint and add that to the configuration file, to make sure it remains unchanged. +No verification happens if connecting via STARTTLS. How do I generate an `sslcacertfile` file? ------------------------------------------ @@ -293,23 +290,6 @@ with the IMAP RFCs. Some servers provide imperfect compatibility that may be good enough for general clients. OfflineIMAP needs more features, specifically support for UIDs, in order to do its job accurately and completely. -Microsoft Exchange ------------------- - -Several users have reported problems with Microsoft Exchange servers in -conjunction with OfflineIMAP. This generally seems to be related to the -Exchange servers not properly following the IMAP standards. - -Mark Biggers has posted some information to the OfflineIMAP `mailing list`_ -about how he made it work. - -Other users have indicated that older (5.5) releases of Exchange are so bad -that they will likely not work at all. - -I do not have access to Exchange servers for testing, so any problems with it, -if they can even be solved at all, will require help from OfflineIMAP users to -find and fix. - Client Notes ============ @@ -494,12 +474,13 @@ To send a patch, we recommend using 'git send-email'. Where from should my patches be based on? ----------------------------------------- -Depends. If you're not sure, it should start off of the master branch. master is -the branch where new patches should be based on by default. +Depends. If you're not sure, it should start off of the master +branch. master is the branch where new patches should be based on by +default. -Obvious materials for next release (e.g. new features) start off of current -next. Also, next is the natural branch to write patches on top of commits not -already in master. +Obvious materials for next release (e.g. new features) start off of +current next. Also, next is the natural branch to write patches on top +of commits not already in master. A fix for a very old bug or security issue may start off of maint. This isn't needed since such fix are backported by the maintainer, though. diff --git a/docs/MANUAL.rst b/docs/MANUAL.rst index a5a1fb3..22ddc95 100644 --- a/docs/MANUAL.rst +++ b/docs/MANUAL.rst @@ -6,7 +6,7 @@ Powerful IMAP/Maildir synchronization and reader support -------------------------------------------------------- -:Author: John Goerzen +:Author: John Goerzen & contributors :Date: 2011-01-15 :Copyright: GPL v2 :Manual section: 1 @@ -22,10 +22,8 @@ emails between them, so that you can read the same mailbox from multiple computers. The REMOTE repository is some IMAP server, while LOCAL can be either a local Maildir or another IMAP server. -Missing folders will be automatically created on the LOCAL side, however -NO folders will currently be created on the REMOTE repository -automatically (it will sync your emails from local folders if -corresponding REMOTE folders already exist). +Missing folders will be automatically created on both sides if +needed. No folders will be deleted at the moment. Configuring OfflineImap in basic mode is quite easy, however it provides an amazing amount of flexibility for those with special needs. You can @@ -159,12 +157,10 @@ Blinkenlights Blinkenlights is an interface designed to be sleek, fun to watch, and informative of the overall picture of what OfflineIMAP is doing. - Blinkenlights contains a row of "LEDs" with command buttons and a log. The log shows more detail about what is happening and is color-coded to match the color of the lights. - Each light in the Blinkenlights interface represents a thread of execution -- that is, a particular task that OfflineIMAP is performing right now. The colors indicate what task the particular thread is performing, and are as follows: @@ -232,7 +228,7 @@ English-speaking world. One version ran in its entirety as follows: TTYUI ---------- +------ TTYUI interface is for people running in terminals. It prints out basic status messages and is generally friendly to use on a console or xterm. @@ -245,7 +241,7 @@ Basic is designed for situations in which OfflineIMAP will be run non-attended and the status of its execution will be logged. This user interface is not capable of reading a password from the keyboard; account passwords must be specified using one of the configuration file -options. +options. For example, it will not print periodic sleep announcements and tends to be a tad less verbose, in general. Quiet @@ -367,6 +363,107 @@ accounts will abort any current sleep and will exit after a currently running synchronization has finished. This signal can be used to gracefully exit out of a running offlineimap "daemon". +Folder filtering and Name translation +===================================== + +OfflineImap provides advanced and potentially complex possibilities for +filtering and translating folder names. If you don't need this, you can +safely skip this section. + +folderfilter +------------ + +If you do not want to synchronize all your filters, you can specify a folderfilter function that determines which folders to include in a sync and which to exclude. Typically, you would set a folderfilter option on the remote repository only, and it would be a lambda or any other python function. + +If the filter function returns True, the folder will be synced, if it +returns False, it. The folderfilter operates on the *UNTRANSLATED* name +(before any nametrans translation takes place). + +Example 1: synchronizing only INBOX and Sent:: + + folderfilter = lambda foldername: foldername in ['INBOX', 'Sent'] + +Example 2: synchronizing everything except Trash:: + + folderfilter = lambda foldername: foldername not in ['Trash'] + +Example 3: Using a regular expression to exclude Trash and all folders +containing the characters "Del":: + + folderfilter = lambda foldername: not re.search('(^Trash$|Del)', foldername) + +If folderfilter is not specified, ALL remote folders will be +synchronized. + +You can span multiple lines by indenting the others. (Use backslashes +at the end when required by Python syntax) For instance:: + + folderfilter = lambda foldername: foldername in + ['INBOX', 'Sent Mail', 'Deleted Items', + 'Received'] + +You only need a folderfilter option on the local repository if you want to prevent some folders on the local repository to be created on the remote one. + +Even if you filtered out folders, You can specify folderincludes to +include additional folders. It should return a Python list. This might +be used to include a folder that was excluded by your folderfilter rule, +to include a folder that your server does not specify with its LIST +option, or to include a folder that is outside your basic reference. The +'reference' value will not be prefixed to this folder name, even if you +have specified one. For example:: + + folderincludes = ['debian.user', 'debian.personal'] + +nametrans +---------- + +Sometimes, folders need to have different names on the remote and the +local repositories. To achieve this you can specify a folder name +translator. This must be a eval-able Python expression that takes a +foldername arg and returns the new value. I suggest a lambda. This +example below will remove "INBOX." from the leading edge of folders +(great for Courier IMAP users):: + + nametrans = lambda foldername: re.sub('^INBOX\.', '', foldername) + +Using Courier remotely and want to duplicate its mailbox naming +locally? Try this:: + + nametrans = lambda foldername: re.sub('^INBOX\.*', '.', foldername) + + +WARNING: you MUST construct nametrans rules such that it NEVER returns +the same value for two folders, UNLESS the second values are +filtered out by folderfilter below. That is, two filters on one side may never point to the same folder on the other side. Failure to follow this rule +will result in undefined behavior. See also *Sharing a maildir with multiple IMAP servers* in the `PITFALLS & ISSUES`_ section. + +Where to put nametrans rules, on the remote and/or local repository? +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +If you never intend to create new folders on the LOCAL repository that need to be synced to the REMOTE repository, it is sufficient to create a nametrans rule on the remote Repository section. This will be used to determine the names of new folder names on the LOCAL repository, and to match existing folders that correspond. + +*IF* you create folders on the local repository, that are supposed to be automatically created on the remote repository, you will need to create a nametrans rule that provides the reverse name translation. + +(A nametrans rule provides only a one-way translation of names and in order to know which names folders on the LOCAL side would have on the REMOTE side, you need to specify the reverse nametrans rule on the local repository) + +OfflineImap will complain if it needs to create a new folder on the +remote side and a back-and-forth nametrans-lation does not yield the +original foldername (as that could potentially lead to infinite folder +creation cycles). + +What folder separators do I need to use in nametrans rules? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +**Q:** If I sync from an IMAP server with folder separator '/' to a Maildir using the default folder separator '.' which do I need to use in nametrans rules?:: + nametrans = lambda f: "INBOX/" + f +or:: + nametrans = lambda f: "INBOX." + f + +**A:** Generally use the folder separator as defined in the repository you write the nametrans rule for. That is, use '/' in the above case. We will pass in the untranslated name of the IMAP folder as parameter (here `f`). The translated name will ultimately have all folder separators be replaced with the destination repositories' folder separator. + +So if ̀f` was "Sent", the first nametrans yields the translated name "INBOX/Sent" to be used on the other side. As that repository uses the folder separator '.' rather than '/', the ultimate name to be used will be "INBOX.Sent". + +(As a final note, the smart will see that both variants of the above nametrans rule would have worked identically in this case) KNOWN BUGS ========== From 8970a1500b6b85c7f0c3a9e2c112e63261d2d120 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 29 Sep 2011 15:43:01 +0200 Subject: [PATCH 02/11] UIBase: Fix and cleanup register/unregisterthread Registering a thread (associating it with a certain account name) would fail if it was already registered. However, as we a) never unregister most threads (bad) and b) single-threaded mode reuses threads, we failed when syncing multiple accounts in single-threading mode. This commit cleans up the functions to not make re-registering a thread fatal (it could be legitimate, however it *should* not occur). Future work needs to be done to unregister new threads at the appropriate places. Signed-off-by: Sebastian Spaeth --- Changelog.draft.rst | 4 ++++ offlineimap/ui/UIBase.py | 42 ++++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/Changelog.draft.rst b/Changelog.draft.rst index b532734..7ced439 100644 --- a/Changelog.draft.rst +++ b/Changelog.draft.rst @@ -18,3 +18,7 @@ Changes Bug Fixes --------- + +* Syncing multiple accounts in single-threaded mode would fail as we try + to "register" a thread as belonging to two accounts which was + fatal. Make it non-fatal (it can be legitimate). diff --git a/offlineimap/ui/UIBase.py b/offlineimap/ui/UIBase.py index 52b1788..77594f2 100644 --- a/offlineimap/ui/UIBase.py +++ b/offlineimap/ui/UIBase.py @@ -47,6 +47,7 @@ class UIBase: s.debugmessages = {} s.debugmsglen = 50 s.threadaccounts = {} + """dict linking active threads (k) to account names (v)""" s.logfile = None s.exc_queue = Queue() """saves all occuring exceptions, so we can output them at the end""" @@ -117,29 +118,32 @@ class UIBase: if exc_traceback: self._msg(traceback.format_tb(exc_traceback)) - def registerthread(s, account): - """Provides a hint to UIs about which account this particular - thread is processing.""" - if s.threadaccounts.has_key(threading.currentThread()): - raise ValueError, "Thread %s already registered (old %s, new %s)" %\ - (threading.currentThread().getName(), - s.getthreadaccount(s), account) - s.threadaccounts[threading.currentThread()] = account - s.debug('thread', "Register new thread '%s' (account '%s')" %\ - (threading.currentThread().getName(), account)) + def registerthread(self, account): + """Register current thread as being associated with an account name""" + cur_thread = threading.currentThread() + if cur_thread in self.threadaccounts: + # was already associated with an old account, update info + self.debug('thread', "Register thread '%s' (previously '%s', now " + "'%s')" % (cur_thread.getName(), + self.getthreadaccount(cur_thread), account)) + else: + self.debug('thread', "Register new thread '%s' (account '%s')" %\ + (cur_thread.getName(), account)) + self.threadaccounts[cur_thread] = account - def unregisterthread(s, thr): - """Recognizes a thread has exited.""" - if s.threadaccounts.has_key(thr): - del s.threadaccounts[thr] - s.debug('thread', "Unregister thread '%s'" % thr.getName()) + def unregisterthread(self, thr): + """Unregister a thread as being associated with an account name""" + if self.threadaccounts.has_key(thr): + del self.threadaccounts[thr] + self.debug('thread', "Unregister thread '%s'" % thr.getName()) - def getthreadaccount(s, thr = None): + def getthreadaccount(self, thr = None): + """Get name of account for a thread (current if None)""" if not thr: thr = threading.currentThread() - if s.threadaccounts.has_key(thr): - return s.threadaccounts[thr] - return '*Control' + if thr in self.threadaccounts: + return self.threadaccounts[thr] + return '*Control' # unregistered thread is '*Control' def debug(s, debugtype, msg): thisthread = threading.currentThread() From 642880b4040ee362727738cbe3e913f0000ad867 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 29 Sep 2011 16:02:51 +0200 Subject: [PATCH 03/11] Don't use global variable profiledir Rather than setting a global threadutil/profiledir variable, we make set_profiledir a class function that sets the class variable profiledir. While touching theprofiledir code, add warning to the user if the profile directory existed before. Signed-off-by: Sebastian Spaeth --- offlineimap/init.py | 14 ++++++++------ offlineimap/threadutil.py | 29 +++++++++++++++-------------- 2 files changed, 23 insertions(+), 20 deletions(-) diff --git a/offlineimap/init.py b/offlineimap/init.py index 063a2c0..27aeebd 100644 --- a/offlineimap/init.py +++ b/offlineimap/init.py @@ -1,6 +1,5 @@ # OfflineIMAP initialization code -# Copyright (C) 2002-2007 John Goerzen -# +# Copyright (C) 2002-2011 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 @@ -157,11 +156,14 @@ class OfflineImap: if not options.singlethreading: logging.warn("Profile mode: Forcing to singlethreaded.") options.singlethreading = True - profiledir = options.profiledir - os.mkdir(profiledir) - threadutil.setprofiledir(profiledir) + if os.path.exists(options.profiledir): + logging.warn("Profile mode: Directory '%s' already exists!" % + options.profiledir) + else: + os.mkdir(options.profiledir) + threadutil.ExitNotifyThread.set_profiledir(options.profiledir) logging.warn("Profile mode: Potentially large data will be " - "created in '%s'" % profiledir) + "created in '%s'" % options.profiledir) #override a config value if options.configoverride: diff --git a/offlineimap/threadutil.py b/offlineimap/threadutil.py index 38387e9..463752f 100644 --- a/offlineimap/threadutil.py +++ b/offlineimap/threadutil.py @@ -1,6 +1,5 @@ -# Copyright (C) 2002, 2003 John Goerzen +# Copyright (C) 2002-2011 John Goerzen & contributors # Thread support module -# # # 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 @@ -20,15 +19,10 @@ from threading import Lock, Thread, BoundedSemaphore from Queue import Queue, Empty import traceback from thread import get_ident # python < 2.6 support +import os.path import sys from offlineimap.ui import getglobalui -profiledir = None - -def setprofiledir(newdir): - global profiledir - profiledir = newdir - ###################################################################### # General utilities ###################################################################### @@ -132,11 +126,14 @@ def threadexited(thread): class ExitNotifyThread(Thread): """This class is designed to alert a "monitor" to the fact that a thread has exited and to provide for the ability for it to find out why.""" + profiledir = None + """class variable that is set to the profile directory if required""" + def run(self): - global exitthreads, profiledir + global exitthreads self.threadid = get_ident() try: - if not profiledir: # normal case + if not ExitNotifyThread.profiledir: # normal case Thread.run(self) else: try: @@ -148,9 +145,8 @@ class ExitNotifyThread(Thread): prof = prof.runctx("Thread.run(self)", globals(), locals()) except SystemExit: pass - prof.dump_stats( \ - profiledir + "/" + str(self.threadid) + "_" + \ - self.getName() + ".prof") + prof.dump_stats(os.path.join(ExitNotifyThread.profiledir, + "%s_%s.prof" % (self.threadid, self.getName()))) except: self.setExitCause('EXCEPTION') if sys: @@ -194,7 +190,12 @@ class ExitNotifyThread(Thread): a call to setExitMessage(), or None if there was no such message set.""" return self.exitmessage - + + @classmethod + def set_profiledir(cls, directory): + """If set, will output profile information to 'directory'""" + cls.profiledir = directory + ###################################################################### # Instance-limited threads From eb0b546927c6eac01105e0278e5f92f099dae21e Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 29 Sep 2011 16:51:48 +0200 Subject: [PATCH 04/11] Output progress indication when copying files Output (2 of 500) when logging message copying. This required moving of self.ui.copyingmessage into a different function where we actually have the information about the progress handy. Signed-off-by: Sebastian Spaeth --- offlineimap/folder/Base.py | 8 ++++---- offlineimap/ui/Blinkenlights.py | 5 +++-- offlineimap/ui/Machine.py | 2 +- offlineimap/ui/TTY.py | 6 +----- offlineimap/ui/UIBase.py | 9 ++++----- 5 files changed, 13 insertions(+), 17 deletions(-) diff --git a/offlineimap/folder/Base.py b/offlineimap/folder/Base.py index 909db30..91ee08a 100644 --- a/offlineimap/folder/Base.py +++ b/offlineimap/folder/Base.py @@ -266,7 +266,6 @@ class BaseFolder(object): statusfolder.savemessage(uid, None, flags, rtime) return - self.ui.copyingmessage(uid, self, dstfolder) # If any of the destinations actually stores the message body, # load it up. if dstfolder.storesmessages(): @@ -331,15 +330,16 @@ class BaseFolder(object): copylist = filter(lambda uid: not \ statusfolder.uidexists(uid), self.getmessageuidlist()) - for uid in copylist: + num_to_copy = len(copylist) + for num, uid in enumerate(copylist): + 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.getcopyinstancelimit(), target = self.copymessageto, - name = "Copy message %d from %s" % (uid, - self.getvisiblename()), + name = "Copy message from %s:%s" % (self.repository, self), args = (uid, dstfolder, statusfolder)) thread.setDaemon(1) thread.start() diff --git a/offlineimap/ui/Blinkenlights.py b/offlineimap/ui/Blinkenlights.py index f0dfec1..330b9a8 100644 --- a/offlineimap/ui/Blinkenlights.py +++ b/offlineimap/ui/Blinkenlights.py @@ -54,9 +54,10 @@ class BlinkenBase: s.gettf().setcolor('blue') s.__class__.__bases__[-1].syncingmessages(s, sr, sf, dr, df) - def copyingmessage(s, uid, src, destfolder): + def copyingmessage(s, uid, num, num_to_copy, src, destfolder): s.gettf().setcolor('orange') - s.__class__.__bases__[-1].copyingmessage(s, uid, src, destfolder) + s.__class__.__bases__[-1].copyingmessage(s, uid, num, num_to_copy, src, + destfolder) def deletingmessages(s, uidlist, destlist): s.gettf().setcolor('red') diff --git a/offlineimap/ui/Machine.py b/offlineimap/ui/Machine.py index 7a480ae..50a7f99 100644 --- a/offlineimap/ui/Machine.py +++ b/offlineimap/ui/Machine.py @@ -108,7 +108,7 @@ class MachineUI(UIBase): (s.getnicename(sr), sf.getname(), s.getnicename(dr), df.getname())) - def copyingmessage(self, uid, srcfolder, destfolder): + def copyingmessage(self, uid, num, num_to_copy, srcfolder, destfolder): self._printData('copyingmessage', "%d\n%s\n%s\n%s[%s]" % \ (uid, self.getnicename(srcfolder), srcfolder.getname(), self.getnicename(destfolder), destfolder)) diff --git a/offlineimap/ui/TTY.py b/offlineimap/ui/TTY.py index cba573f..48c468f 100644 --- a/offlineimap/ui/TTY.py +++ b/offlineimap/ui/TTY.py @@ -15,7 +15,6 @@ # 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 UIBase import UIBase from getpass import getpass import sys @@ -37,10 +36,7 @@ class TTYUI(UIBase): #if the next output comes from a different thread than our last one #add the info. #Most look like 'account sync foo' or 'Folder sync foo'. - try: - threadname = currentThread().name - except AttributeError: - threadname = currentThread().getName() + threadname = currentThread().getName() if (threadname == s._lastThreaddisplay \ or threadname == 'MainThread'): print " %s" % msg diff --git a/offlineimap/ui/UIBase.py b/offlineimap/ui/UIBase.py index 77594f2..587b055 100644 --- a/offlineimap/ui/UIBase.py +++ b/offlineimap/ui/UIBase.py @@ -288,12 +288,11 @@ class UIBase: s.getnicename(dr), df.getname())) - def copyingmessage(self, uid, src, destfolder): + def copyingmessage(self, uid, num, num_to_copy, src, destfolder): """Output a log line stating which message we copy""" - if self.verbose >= 0: - self._msg("Copy message %d %s[%s] -> %s[%s]" % \ - (uid, self.getnicename(src), src, - self.getnicename(destfolder), destfolder)) + if self.verbose < 0: return + self._msg("Copy message %s (%d of %d) %s:%s -> %s" % (uid, num, + num_to_copy, src.repository, src, destfolder.repository)) def deletingmessage(s, uid, destlist): if s.verbose >= 0: From ba396cf0ef3c688d34a00baa083055c9bc5e0ba9 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 29 Sep 2011 16:59:36 +0200 Subject: [PATCH 05/11] More verbose loggin Thread names are used to determine the logging header in the TTY ui. A recent change made them too terse (basically only changing the account name and not the folder names). Unbreak. Signed-off-by: Sebastian Spaeth --- Changelog.draft.rst | 4 +++- offlineimap/accounts.py | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Changelog.draft.rst b/Changelog.draft.rst index 7ced439..ae84931 100644 --- a/Changelog.draft.rst +++ b/Changelog.draft.rst @@ -15,7 +15,9 @@ New Features Changes ------- - + +* Indicate progress when copying many messages (slightly change log format) + Bug Fixes --------- diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index d28bd34..6ab451f 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -281,7 +281,7 @@ class SyncableAccount(Account): thread = InstanceLimitedThread(\ instancename = 'FOLDER_' + self.remoterepos.getname(), target = syncfolder, - name = "Folder sync [%s]" % self, + name = "Folder %s [acc: %s]" % (remotefolder, self), args = (self.name, remoterepos, remotefolder, localrepos, statusrepos, quick)) thread.setDaemon(1) From 3b647d65bedbbd3dc86ad79ce9becae84d58e78b Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 29 Sep 2011 17:22:02 +0200 Subject: [PATCH 06/11] Output sync timing Modify the UI:acct and acctdone functions to keep tab of the time inbetween. Put self.ui.acct() and acctdone() at the right places in accounts.py so that the timing happens at the right places. While modifying that loop, flatten the nested try: try: except: finally: constructs, we require python 2.5 now which copes with that. At the end of each account sync you will now see something like: *** Finished account 'test' in 0:05 Signed-off-by: Sebastian Spaeth --- Changelog.draft.rst | 2 ++ offlineimap/accounts.py | 46 +++++++++++++++++++--------------------- offlineimap/ui/UIBase.py | 22 ++++++++++++------- 3 files changed, 38 insertions(+), 32 deletions(-) diff --git a/Changelog.draft.rst b/Changelog.draft.rst index ae84931..44048b9 100644 --- a/Changelog.draft.rst +++ b/Changelog.draft.rst @@ -18,6 +18,8 @@ Changes * Indicate progress when copying many messages (slightly change log format) +* Output how long an account sync took (min:sec). + Bug Fixes --------- diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index 6ab451f..2f674c9 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -1,5 +1,4 @@ -# Copyright (C) 2003 John Goerzen -# +# Copyright (C) 2003-2011 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 @@ -196,7 +195,6 @@ class SyncableAccount(Account): def syncrunner(self): self.ui.registerthread(self.name) - self.ui.acct(self.name) accountmetadata = self.getaccountmeta() if not os.path.exists(accountmetadata): os.mkdir(accountmetadata, 0700) @@ -208,32 +206,32 @@ class SyncableAccount(Account): # Loop account sync if needed (bail out after 3 failures) looping = 3 while looping: + self.ui.acct(self) try: - try: - self.lock() - self.sync() - except (KeyboardInterrupt, SystemExit): - raise - except OfflineImapError, e: - # Stop looping and bubble up Exception if needed. - if e.severity >= OfflineImapError.ERROR.REPO: - if looping: - looping -= 1 - if e.severity >= OfflineImapError.ERROR.CRITICAL: - raise - self.ui.error(e, exc_info()[2]) - except Exception, e: - self.ui.error(e, msg = "While attempting to sync " - "account %s:\n %s"% (self, traceback.format_exc())) - else: - # after success sync, reset the looping counter to 3 - if self.refreshperiod: - looping = 3 + self.lock() + self.sync() + except (KeyboardInterrupt, SystemExit): + raise + except OfflineImapError, e: + # Stop looping and bubble up Exception if needed. + if e.severity >= OfflineImapError.ERROR.REPO: + if looping: + looping -= 1 + if e.severity >= OfflineImapError.ERROR.CRITICAL: + raise + self.ui.error(e, exc_info()[2]) + except Exception, e: + self.ui.error(e, exc_info()[2], msg = "While attempting to sync" + " account '%s'" % self) + else: + # after success sync, reset the looping counter to 3 + if self.refreshperiod: + looping = 3 finally: + self.ui.acctdone(self) self.unlock() if looping and self.sleeper() >= 2: looping = 0 - self.ui.acctdone(self.name) def getaccountmeta(self): return os.path.join(self.metadatadir, 'Account-' + self.name) diff --git a/offlineimap/ui/UIBase.py b/offlineimap/ui/UIBase.py index 587b055..dc95c30 100644 --- a/offlineimap/ui/UIBase.py +++ b/offlineimap/ui/UIBase.py @@ -1,6 +1,5 @@ # UI base class -# Copyright (C) 2002 John Goerzen -# +# Copyright (C) 2002-2011 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 @@ -48,6 +47,8 @@ class UIBase: s.debugmsglen = 50 s.threadaccounts = {} """dict linking active threads (k) to account names (v)""" + s.acct_startimes = {} + """linking active accounts with the time.time() when sync started""" s.logfile = None s.exc_queue = Queue() """saves all occuring exceptions, so we can output them at the end""" @@ -238,13 +239,18 @@ class UIBase: displaystr = '.' s._msg("Establishing connection" + displaystr) - def acct(s, accountname): - if s.verbose >= 0: - s._msg("***** Processing account %s" % accountname) + def acct(self, account): + """Output that we start syncing an account (and start counting)""" + self.acct_startimes[account] = time.time() + if self.verbose >= 0: + self._msg("*** Processing account %s" % account) - def acctdone(s, accountname): - if s.verbose >= 0: - s._msg("***** Finished processing account " + accountname) + def acctdone(self, account): + """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" % + (account, sec // 60, sec % 60)) def syncfolders(s, srcrepos, destrepos): if s.verbose >= 0: From 93b05215fba364564018c52ab257a323c25277d4 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 29 Sep 2011 17:46:46 +0200 Subject: [PATCH 07/11] Condense ui.connecting() function a bit Just tiny non-functional code cleanup. Signed-off-by: Sebastian Spaeth --- offlineimap/ui/UIBase.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/offlineimap/ui/UIBase.py b/offlineimap/ui/UIBase.py index dc95c30..b5a75dd 100644 --- a/offlineimap/ui/UIBase.py +++ b/offlineimap/ui/UIBase.py @@ -228,16 +228,14 @@ class UIBase: s._msg(offlineimap.banner) def connecting(s, hostname, port): - if s.verbose < 0: - return - if hostname == None: - hostname = '' - if port != None: - port = ":%s" % str(port) - displaystr = ' to %s%s.' % (hostname, port) - if hostname == '' and port == None: - displaystr = '.' - s._msg("Establishing connection" + displaystr) + """Log 'Establishing connection to'""" + if s.verbose < 0: return + displaystr = '' + hostname = hostname if hostname else '' + port = "%d" % port if port else '' + if hostname: + displaystr = ' to %s:%s' % (hostname, port) + s._msg("Establishing connection%s" % displaystr) def acct(self, account): """Output that we start syncing an account (and start counting)""" From ac94de3449a5322e08ff22403a65318708acc123 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Thu, 29 Sep 2011 17:58:07 +0200 Subject: [PATCH 08/11] Only output a sleeping notice once every minute Rather than every ten seconds, which is a bit chatty. Signed-off-by: Sebastian Spaeth --- offlineimap/ui/UIBase.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/offlineimap/ui/UIBase.py b/offlineimap/ui/UIBase.py index b5a75dd..4e53d32 100644 --- a/offlineimap/ui/UIBase.py +++ b/offlineimap/ui/UIBase.py @@ -407,14 +407,14 @@ class UIBase: """Sleep for sleepsecs, display remainingsecs to go. Does nothing if sleepsecs <= 0. - Display a message on the screen every 10 seconds. + Display a message on the screen if we pass a full minute. This implementation in UIBase does not support this, but some implementations return 0 for successful sleep and 1 for an 'abort', ie a request to sync immediately. """ if sleepsecs > 0: - if remainingsecs % 10 == 0: - s._msg("Next refresh in %d seconds" % remainingsecs) + if remainingsecs//60 != (remainingsecs-sleepsecs)//60: + s._msg("Next refresh in %.1f minutes" % (remainingsecs/60.0)) time.sleep(sleepsecs) return 0 From 3f7749016f0794a36b6df51189e21a2cce495dd3 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Fri, 30 Sep 2011 08:41:05 +0200 Subject: [PATCH 09/11] Reformat MANUAL line breaks Signed-off-by: Sebastian Spaeth --- docs/MANUAL.rst | 35 ++++++++++++++++++++++++++++------- 1 file changed, 28 insertions(+), 7 deletions(-) diff --git a/docs/MANUAL.rst b/docs/MANUAL.rst index 22ddc95..bb13478 100644 --- a/docs/MANUAL.rst +++ b/docs/MANUAL.rst @@ -440,11 +440,20 @@ will result in undefined behavior. See also *Sharing a maildir with multiple IMA Where to put nametrans rules, on the remote and/or local repository? ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -If you never intend to create new folders on the LOCAL repository that need to be synced to the REMOTE repository, it is sufficient to create a nametrans rule on the remote Repository section. This will be used to determine the names of new folder names on the LOCAL repository, and to match existing folders that correspond. +If you never intend to create new folders on the LOCAL repository that +need to be synced to the REMOTE repository, it is sufficient to create a +nametrans rule on the remote Repository section. This will be used to +determine the names of new folder names on the LOCAL repository, and to +match existing folders that correspond. -*IF* you create folders on the local repository, that are supposed to be automatically created on the remote repository, you will need to create a nametrans rule that provides the reverse name translation. +*IF* you create folders on the local repository, that are supposed to be + automatically created on the remote repository, you will need to create + a nametrans rule that provides the reverse name translation. -(A nametrans rule provides only a one-way translation of names and in order to know which names folders on the LOCAL side would have on the REMOTE side, you need to specify the reverse nametrans rule on the local repository) +(A nametrans rule provides only a one-way translation of names and in +order to know which names folders on the LOCAL side would have on the +REMOTE side, you need to specify the reverse nametrans rule on the local +repository) OfflineImap will complain if it needs to create a new folder on the remote side and a back-and-forth nametrans-lation does not yield the @@ -454,16 +463,28 @@ creation cycles). What folder separators do I need to use in nametrans rules? +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -**Q:** If I sync from an IMAP server with folder separator '/' to a Maildir using the default folder separator '.' which do I need to use in nametrans rules?:: +**Q:** If I sync from an IMAP server with folder separator '/' to a + Maildir using the default folder separator '.' which do I need to use + in nametrans rules?:: + nametrans = lambda f: "INBOX/" + f or:: nametrans = lambda f: "INBOX." + f -**A:** Generally use the folder separator as defined in the repository you write the nametrans rule for. That is, use '/' in the above case. We will pass in the untranslated name of the IMAP folder as parameter (here `f`). The translated name will ultimately have all folder separators be replaced with the destination repositories' folder separator. +**A:** Generally use the folder separator as defined in the repository + you write the nametrans rule for. That is, use '/' in the above + case. We will pass in the untranslated name of the IMAP folder as + parameter (here `f`). The translated name will ultimately have all + folder separators be replaced with the destination repositories' + folder separator. -So if ̀f` was "Sent", the first nametrans yields the translated name "INBOX/Sent" to be used on the other side. As that repository uses the folder separator '.' rather than '/', the ultimate name to be used will be "INBOX.Sent". +So if ̀f` was "Sent", the first nametrans yields the translated name +"INBOX/Sent" to be used on the other side. As that repository uses the +folder separator '.' rather than '/', the ultimate name to be used will +be "INBOX.Sent". -(As a final note, the smart will see that both variants of the above nametrans rule would have worked identically in this case) +(As a final note, the smart will see that both variants of the above +nametrans rule would have worked identically in this case) KNOWN BUGS ========== From 4e89bbfe6e1a95742630191800dfd9cfd76150a0 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Fri, 30 Sep 2011 08:58:08 +0200 Subject: [PATCH 10/11] Recache folder list after creating a new folder If we create a new folder we would previously not update our folder list, which led to us skipping the synchronization of those new folders during the initial run (subsequent runs would pick it up). Invalidate the folder cache when we create a folder during folder structure sync. Regetting the whole list from an IMAP server might be slightly suboptimal from a performance point, but it is easy and will lead to consistent results. Hopefully we will not have to create new folders on each new run. Reported-by: Daniel Shahaf Signed-off-by: Sebastian Spaeth --- Changelog.draft.rst | 3 +++ offlineimap/accounts.py | 2 +- offlineimap/repository/Base.py | 13 +++++++++++-- offlineimap/repository/IMAP.py | 4 ++++ offlineimap/repository/Maildir.py | 6 ++++-- 5 files changed, 23 insertions(+), 5 deletions(-) diff --git a/Changelog.draft.rst b/Changelog.draft.rst index 44048b9..5095b2e 100644 --- a/Changelog.draft.rst +++ b/Changelog.draft.rst @@ -26,3 +26,6 @@ Bug Fixes * Syncing multiple accounts in single-threaded mode would fail as we try to "register" a thread as belonging to two accounts which was fatal. Make it non-fatal (it can be legitimate). + +* New folders on the remote would be skipped on the very sync run they + are created and only by synced in subsequent runs. Fixed. diff --git a/offlineimap/accounts.py b/offlineimap/accounts.py index 2f674c9..71f08fe 100644 --- a/offlineimap/accounts.py +++ b/offlineimap/accounts.py @@ -265,7 +265,7 @@ class SyncableAccount(Account): remoterepos = self.remoterepos localrepos = self.localrepos statusrepos = self.statusrepos - # replicate the folderstructure from REMOTE to LOCAL + # replicate the folderstructure between REMOTE to LOCAL if not localrepos.getconfboolean('readonly', False): self.ui.syncfolders(remoterepos, localrepos) remoterepos.syncfoldersto(localrepos, statusrepos) diff --git a/offlineimap/repository/Base.py b/offlineimap/repository/Base.py index 0da307f..972aefb 100644 --- a/offlineimap/repository/Base.py +++ b/offlineimap/repository/Base.py @@ -144,7 +144,8 @@ class BaseRepository(object, CustomConfig.ConfigHelperMixin): src_repo = self src_folders = src_repo.getfolders() dst_folders = dst_repo.getfolders() - + # Do we need to refresh the folder list afterwards? + src_haschanged, dst_haschanged = False, False # Create hashes with the names, but convert the source folders # to the dest folder's sep. src_hash = {} @@ -160,6 +161,7 @@ class BaseRepository(object, CustomConfig.ConfigHelperMixin): if src_folder.sync_this and not src_name in dst_hash: try: dst_repo.makefolder(src_name) + dst_haschanged = True # Need to refresh list except OfflineImapError, e: self.ui.error(e, exc_info()[2], "Creating folder %s on repository %s" %\ @@ -203,6 +205,7 @@ class BaseRepository(object, CustomConfig.ConfigHelperMixin): try: src_repo.makefolder(newsrc_name) + src_haschanged = True # Need to refresh list except OfflineImapError, e: self.ui.error(e, exc_info()[2], "Creating folder %s on repository %s" %\ @@ -211,7 +214,13 @@ class BaseRepository(object, CustomConfig.ConfigHelperMixin): status_repo.makefolder(newsrc_name.replace( src_repo.getsep(), status_repo.getsep())) # Find deleted folders. - # We don't delete folders right now. + # TODO: We don't delete folders right now. + + #Forget old list of cached folders so we get new ones if needed + if src_haschanged: + self.forgetfolders() + if dst_haschanged: + dst_repo.forgetfolders() def startkeepalive(self): """The default implementation will do nothing.""" diff --git a/offlineimap/repository/IMAP.py b/offlineimap/repository/IMAP.py index 5ddf5b9..2d2962f 100644 --- a/offlineimap/repository/IMAP.py +++ b/offlineimap/repository/IMAP.py @@ -311,6 +311,10 @@ class IMAPRepository(BaseRepository): def makefolder(self, foldername): """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.""" #TODO: IMHO this existing commented out code is correct and #should be enabled, but this would change the behavior for diff --git a/offlineimap/repository/Maildir.py b/offlineimap/repository/Maildir.py index 7c3bb1e..ae09486 100644 --- a/offlineimap/repository/Maildir.py +++ b/offlineimap/repository/Maildir.py @@ -72,6 +72,10 @@ class MaildirRepository(BaseRepository): def makefolder(self, foldername): """Create new Maildir folder if necessary + This will not update the list cached in getfolders(). You will + need to invoke :meth:`forgetfolders` to force new caching when + you are done creating folders yourself. + :param foldername: A relative mailbox name. The maildir will be created in self.root+'/'+foldername. All intermediate folder levels will be created if they do not exist yet. 'cur', @@ -108,8 +112,6 @@ class MaildirRepository(BaseRepository): (foldername, subdir)) else: raise - # Invalidate the folder cache - self.folders = None def deletefolder(self, foldername): self.ui.warn("NOT YET IMPLEMENTED: DELETE FOLDER %s" % foldername) From a5eebd4b6bec419e96b01b6b72fe301c14ffbd59 Mon Sep 17 00:00:00 2001 From: Sebastian Spaeth Date: Fri, 30 Sep 2011 09:21:03 +0200 Subject: [PATCH 11/11] Make "syncing folder structure" a debug level log entry It happens quick, and clutters the log. So we can usually skip this. We will output a log entry when we actually create a new folder anyway. Signed-off-by: Sebastian Spaeth --- offlineimap/ui/UIBase.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/offlineimap/ui/UIBase.py b/offlineimap/ui/UIBase.py index 4e53d32..6d92152 100644 --- a/offlineimap/ui/UIBase.py +++ b/offlineimap/ui/UIBase.py @@ -250,10 +250,11 @@ class UIBase: self._msg("*** Finished account '%s' in %d:%02d" % (account, sec // 60, sec % 60)) - def syncfolders(s, srcrepos, destrepos): - if s.verbose >= 0: - s._msg("Copying folder structure from %s to %s" % \ - (s.getnicename(srcrepos), s.getnicename(destrepos))) + def syncfolders(self, src_repo, dst_repo): + """Log 'Copying folder structure...'""" + if self.verbose < 0: return + self.debug('', "Copying folder structure from %s to %s" % \ + (src_repo, dst_repo)) ############################## Folder syncing def syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder):