Merge branch 'next'
This commit is contained in:
commit
e63302395e
@ -15,6 +15,17 @@ New Features
|
||||
|
||||
Changes
|
||||
-------
|
||||
|
||||
|
||||
* Indicate progress when copying many messages (slightly change log format)
|
||||
|
||||
* Output how long an account sync took (min:sec).
|
||||
|
||||
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.
|
||||
|
153
docs/FAQ.rst
153
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.
|
||||
|
136
docs/MANUAL.rst
136
docs/MANUAL.rst
@ -6,7 +6,7 @@
|
||||
Powerful IMAP/Maildir synchronization and reader support
|
||||
--------------------------------------------------------
|
||||
|
||||
:Author: John Goerzen <jgoerzen@complete.org>
|
||||
:Author: John Goerzen <jgoerzen@complete.org> & 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,128 @@ 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
|
||||
==========
|
||||
|
@ -1,5 +1,4 @@
|
||||
# Copyright (C) 2003 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
# 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)
|
||||
@ -267,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)
|
||||
@ -281,7 +279,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)
|
||||
|
@ -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()
|
||||
|
@ -1,6 +1,5 @@
|
||||
# OfflineIMAP initialization code
|
||||
# Copyright (C) 2002-2007 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
# 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:
|
||||
|
@ -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."""
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -1,6 +1,5 @@
|
||||
# Copyright (C) 2002, 2003 John Goerzen
|
||||
# Copyright (C) 2002-2011 John Goerzen & contributors
|
||||
# Thread support module
|
||||
# <jgoerzen@complete.org>
|
||||
#
|
||||
# This program is free software; you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
@ -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
|
||||
|
@ -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')
|
||||
|
@ -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))
|
||||
|
@ -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
|
||||
|
@ -1,6 +1,5 @@
|
||||
# UI base class
|
||||
# Copyright (C) 2002 John Goerzen
|
||||
# <jgoerzen@complete.org>
|
||||
# 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
|
||||
@ -47,6 +46,9 @@ class UIBase:
|
||||
s.debugmessages = {}
|
||||
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"""
|
||||
@ -117,29 +119,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()
|
||||
@ -223,29 +228,33 @@ 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(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:
|
||||
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):
|
||||
@ -284,12 +293,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:
|
||||
@ -400,14 +408,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
|
||||
|
Loading…
Reference in New Issue
Block a user