/offlineimap/head: changeset 442
Moved account-sep branch to head
This commit is contained in:
parent
0f81229c68
commit
faf26007b1
@ -18,4 +18,4 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from offlineimap import init
|
from offlineimap import init
|
||||||
init.startup('3.99.10')
|
init.startup('3.99.12')
|
||||||
|
@ -1,3 +1,38 @@
|
|||||||
|
offlineimap (3.99.12) unstable; urgency=low
|
||||||
|
|
||||||
|
* This is a 4.0 TRACK release, and may be unstable or in flux!
|
||||||
|
* Big news: OfflineIMAP can now sync two remote IMAP servers to each
|
||||||
|
other, with no need to write a Maildir at all.
|
||||||
|
* WARNING: the format of the configuration file *AND* the local
|
||||||
|
status area changes with this release!
|
||||||
|
* Major reworking of internal management of accounts. Previously, the
|
||||||
|
account defined a local Maildir and a remote IMAP server. Now, the
|
||||||
|
account is simply a connection between two repositories. For
|
||||||
|
traditional ones, an account basically specifies a refresh interval,
|
||||||
|
a Maildir repository, and an IMAP repository.
|
||||||
|
* Added a notion of a repository to the configuration file. Repositories
|
||||||
|
currently available are IMAP and Maildir. Combined with the new account
|
||||||
|
system, this lets the user define powerful combinations without
|
||||||
|
duplicating information.
|
||||||
|
* When uploading messages to an IMAP server, OfflineIMAP generates its
|
||||||
|
own X-OfflineIMAP header rather than trying to guess the new message UID
|
||||||
|
based on the Message-Id header. This leads to greater reliability when
|
||||||
|
uploading messages, especially when dealing with duplicate messages.
|
||||||
|
This change was required to permit reliable IMAP-to-IMAP syncing, but
|
||||||
|
helps with regular IMAP-to-Maildir syncing as well.
|
||||||
|
* Local status area under ~/.offlineimap revamped. It now contains
|
||||||
|
separate subdirectories for each account and repository, and they
|
||||||
|
contain UID validity information, UID mapping (for IMAP-to-IMAP
|
||||||
|
syncing). UID validity information is no longer stored in the Maildir
|
||||||
|
itself.
|
||||||
|
* New debug type: "thread" to debug multithreading.
|
||||||
|
* preauth tunnels no longer require remoteuser, remotepass, host,
|
||||||
|
or port in the configuration file.
|
||||||
|
* Logging for preauth tunnels is more sensible.
|
||||||
|
* Fixed a logic error for syncs with a reference that returns no folders.
|
||||||
|
|
||||||
|
-- John Goerzen <jgoerzen@complete.org> Thu, 17 Apr 2003 17:20:08 -0500
|
||||||
|
|
||||||
offlineimap (3.99.11) unstable; urgency=low
|
offlineimap (3.99.11) unstable; urgency=low
|
||||||
|
|
||||||
* Curses interface can now be resized. Closes: #176342.
|
* Curses interface can now be resized. Closes: #176342.
|
||||||
|
@ -147,16 +147,52 @@ fontsize = 8
|
|||||||
# This is an account definition clause. You'll have one of these
|
# This is an account definition clause. You'll have one of these
|
||||||
# for each account listed in general/accounts above.
|
# for each account listed in general/accounts above.
|
||||||
|
|
||||||
[Test]
|
[Account Test]
|
||||||
|
|
||||||
########## Basic settings
|
########## Basic settings
|
||||||
|
|
||||||
|
# These settings specify the two folders that you will be syncing.
|
||||||
|
# You'll need to have a "Repository ..." section for each one.
|
||||||
|
|
||||||
|
localrepository = LocalExample
|
||||||
|
remoterepository = RemoteExample
|
||||||
|
|
||||||
|
########## Advanced settings
|
||||||
|
|
||||||
|
# You can have offlineimap continue running indefinately, automatically
|
||||||
|
# syncing your mail periodically. If you want that, specify how
|
||||||
|
# frequently to do that (in minutes) here.
|
||||||
|
|
||||||
|
# autorefresh = 5
|
||||||
|
|
||||||
|
[Repository LocalExample]
|
||||||
|
|
||||||
|
# This is one of the two repositories that you'll work with given the
|
||||||
|
# above example. Each repository requires a "type" declaration.
|
||||||
|
#
|
||||||
|
# The types supported are Maildir and IMAP.
|
||||||
|
#
|
||||||
|
|
||||||
|
type = Maildir
|
||||||
|
|
||||||
# Specify local repository. Your IMAP folders will be synchronized
|
# Specify local repository. Your IMAP folders will be synchronized
|
||||||
# to maildirs created under this path. OfflineIMAP will create the
|
# to maildirs created under this path. OfflineIMAP will create the
|
||||||
# maildirs for you as needed.
|
# maildirs for you as needed.
|
||||||
|
|
||||||
localfolders = ~/Test
|
localfolders = ~/Test
|
||||||
|
|
||||||
|
# You can specify the "path separator character" used for your Maildir
|
||||||
|
# folders. This is inserted in-between the components of the tree.
|
||||||
|
# It defaults to ".". If you want your Maildir folders to be nested,
|
||||||
|
# set it to "/".
|
||||||
|
|
||||||
|
sep = .
|
||||||
|
|
||||||
|
[Repository RemoteExample]
|
||||||
|
|
||||||
|
# And this is the remote repository. For now, we only support IMAP here.
|
||||||
|
|
||||||
|
type = IMAP
|
||||||
|
|
||||||
# Specify the remote hostname.
|
# Specify the remote hostname.
|
||||||
remotehost = examplehost
|
remotehost = examplehost
|
||||||
|
|
||||||
@ -198,13 +234,6 @@ remoteuser = username
|
|||||||
|
|
||||||
########## Advanced settings
|
########## Advanced settings
|
||||||
|
|
||||||
# You can have offlineimap continue running indefinately, automatically
|
|
||||||
# syncing your mail periodically. If you want that, specify how
|
|
||||||
# frequently to do that (in minutes) here.
|
|
||||||
|
|
||||||
# autorefresh = 5
|
|
||||||
|
|
||||||
|
|
||||||
# Some IMAP servers need a "reference" which often refers to the
|
# Some IMAP servers need a "reference" which often refers to the
|
||||||
# "folder root". This is most commonly needed with UW IMAP, where
|
# "folder root". This is most commonly needed with UW IMAP, where
|
||||||
# you might need to specify the directory in which your mail is
|
# you might need to specify the directory in which your mail is
|
||||||
@ -212,6 +241,40 @@ remoteuser = username
|
|||||||
#
|
#
|
||||||
# reference = Mail
|
# reference = Mail
|
||||||
|
|
||||||
|
# OfflineIMAP can use multiple connections to the server in order
|
||||||
|
# to perform multiple synchronization actions simultaneously.
|
||||||
|
# This may place a higher burden on the server. In most cases,
|
||||||
|
# setting this value to 2 or 3 will speed up the sync, but in some
|
||||||
|
# cases, it may slow things down. The safe answer is 1. You should
|
||||||
|
# probably never set it to a value more than 5.
|
||||||
|
|
||||||
|
maxconnections = 1
|
||||||
|
|
||||||
|
# OfflineIMAP normally closes IMAP server connections between refreshes if
|
||||||
|
# the global option autorefresh is specified. If you wish it to keep the
|
||||||
|
# connection open, set this to true. If not specified, the default is
|
||||||
|
# false. Keeping the connection open means a faster sync start the
|
||||||
|
# next time and may use fewer server resources on connection, but uses
|
||||||
|
# more server memory. This setting has no effect if autorefresh is not set.
|
||||||
|
|
||||||
|
holdconnectionopen = no
|
||||||
|
|
||||||
|
# If you want to have "keepalives" sent while waiting between syncs,
|
||||||
|
# specify the amount of time IN SECONDS between keepalives here. Note that
|
||||||
|
# sometimes more than this amount of time might pass, so don't make it
|
||||||
|
# tight. This setting has no effect if autorefresh and holdconnectionopen
|
||||||
|
# are not both set.
|
||||||
|
|
||||||
|
# keepalive = 60
|
||||||
|
|
||||||
|
# Normally, OfflineIMAP will expunge deleted messages from the server.
|
||||||
|
# You can disable that if you wish. This means that OfflineIMAP will
|
||||||
|
# mark them deleted on the server, but not actually delete them.
|
||||||
|
# You must use some other IMAP client to delete them if you use this
|
||||||
|
# setting; otherwise, the messgaes will just pile up there forever.
|
||||||
|
# Therefore, this setting is definately NOT recommended.
|
||||||
|
#
|
||||||
|
# expunge = no
|
||||||
# You can specify a folder translator. This must be a eval-able
|
# You can specify a folder translator. This must be a eval-able
|
||||||
# Python expression that takes a foldername arg and returns the new
|
# Python expression that takes a foldername arg and returns the new
|
||||||
# value. I suggest a lambda. This example below will remove "INBOX." from
|
# value. I suggest a lambda. This example below will remove "INBOX." from
|
||||||
@ -290,44 +353,3 @@ remoteuser = username
|
|||||||
#
|
#
|
||||||
# foldersort = lambda x, y: -cmp(x, y)
|
# foldersort = lambda x, y: -cmp(x, y)
|
||||||
|
|
||||||
# OfflineIMAP can use multiple connections to the server in order
|
|
||||||
# to perform multiple synchronization actions simultaneously.
|
|
||||||
# This may place a higher burden on the server. In most cases,
|
|
||||||
# setting this value to 2 or 3 will speed up the sync, but in some
|
|
||||||
# cases, it may slow things down. The safe answer is 1. You should
|
|
||||||
# probably never set it to a value more than 5.
|
|
||||||
|
|
||||||
maxconnections = 1
|
|
||||||
|
|
||||||
# OfflineIMAP normally closes IMAP server connections between refreshes if
|
|
||||||
# the global option autorefresh is specified. If you wish it to keep the
|
|
||||||
# connection open, set this to true. If not specified, the default is
|
|
||||||
# false. Keeping the connection open means a faster sync start the
|
|
||||||
# next time and may use fewer server resources on connection, but uses
|
|
||||||
# more server memory. This setting has no effect if autorefresh is not set.
|
|
||||||
|
|
||||||
holdconnectionopen = no
|
|
||||||
|
|
||||||
# If you want to have "keepalives" sent while waiting between syncs,
|
|
||||||
# specify the amount of time IN SECONDS between keepalives here. Note that
|
|
||||||
# sometimes more than this amount of time might pass, so don't make it
|
|
||||||
# tight. This setting has no effect if autorefresh and holdconnectionopen
|
|
||||||
# are not both set.
|
|
||||||
|
|
||||||
# keepalive = 60
|
|
||||||
|
|
||||||
# You can specify the "path separator character" used for your Maildir
|
|
||||||
# folders. This is inserted in-between the components of the tree.
|
|
||||||
# It defaults to ".". If you want your Maildir folders to be nested,
|
|
||||||
# set it to "/".
|
|
||||||
|
|
||||||
sep = .
|
|
||||||
|
|
||||||
# Normally, OfflineIMAP will expunge deleted messages from the server.
|
|
||||||
# You can disable that if you wish. This means that OfflineIMAP will
|
|
||||||
# mark them deleted on the server, but not actually delete them.
|
|
||||||
# You must use some other IMAP client to delete them if you use this
|
|
||||||
# setting; otherwise, the messgaes will just pile up there forever.
|
|
||||||
# Therefore, this setting is definately NOT recommended.
|
|
||||||
#
|
|
||||||
# expunge = no
|
|
||||||
|
@ -4,7 +4,15 @@
|
|||||||
[general]
|
[general]
|
||||||
accounts = Test
|
accounts = Test
|
||||||
|
|
||||||
[Test]
|
[Account Test]
|
||||||
|
localrepository = Main
|
||||||
|
remoterepository = Example
|
||||||
|
|
||||||
|
[Repository Main]
|
||||||
|
type = Maildir
|
||||||
localfolders = ~/Test
|
localfolders = ~/Test
|
||||||
|
|
||||||
|
[Repository Example]
|
||||||
|
type = IMAP
|
||||||
remotehost = examplehost
|
remotehost = examplehost
|
||||||
remoteuser = jgoerzen
|
remoteuser = jgoerzen
|
||||||
|
@ -18,4 +18,4 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from offlineimap import init
|
from offlineimap import init
|
||||||
init.startup('3.99.10')
|
init.startup('3.99.12')
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
<!-- -*- DocBook -*- -->
|
|
||||||
<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
|
<!DOCTYPE reference PUBLIC "-//OASIS//DTD DocBook V4.1//EN" [
|
||||||
<!ENTITY OfflineIMAP "<application>OfflineIMAP</application>">
|
<!ENTITY OfflineIMAP "<application>OfflineIMAP</application>">
|
||||||
]>
|
]>
|
||||||
@ -11,7 +10,7 @@
|
|||||||
<refentryinfo>
|
<refentryinfo>
|
||||||
<address><email>jgoerzen@complete.org</email></address>
|
<address><email>jgoerzen@complete.org</email></address>
|
||||||
<author><firstname>John</firstname><surname>Goerzen</surname></author>
|
<author><firstname>John</firstname><surname>Goerzen</surname></author>
|
||||||
<date> $Date: 2003-04-16 09:23:45 -0500 (Wed, 16 Apr 2003) $ </date>
|
<date> $Date: 2003-04-17 13:25:30 -0500 (Thu, 17 Apr 2003) $ </date>
|
||||||
</refentryinfo>
|
</refentryinfo>
|
||||||
|
|
||||||
<refmeta>
|
<refmeta>
|
||||||
@ -333,13 +332,15 @@ cd offlineimap-x.y.z</ProgramListing>
|
|||||||
|
|
||||||
<para><option>-d</option> requires one or more debugtypes,
|
<para><option>-d</option> requires one or more debugtypes,
|
||||||
separated by commas. These define what exactly will be
|
separated by commas. These define what exactly will be
|
||||||
debugged, and include two options: <property>imap</property>
|
debugged, and include three options: <property>imap</property>,
|
||||||
and <property>maildir</property>. The <property>imap</property>
|
<property>maildir</property>, and <property>thread</property>.
|
||||||
|
The <property>imap</property>
|
||||||
option will enable IMAP protocol stream and parsing debugging. Note
|
option will enable IMAP protocol stream and parsing debugging. Note
|
||||||
that the output may contain passwords, so take care to remove that
|
that the output may contain passwords, so take care to remove that
|
||||||
from the debugging output before sending it to anyone else. The
|
from the debugging output before sending it to anyone else. The
|
||||||
<property>maildir</property> option will enable debugging for
|
<property>maildir</property> option will enable debugging for
|
||||||
certain Maildir operations.
|
certain Maildir operations. And <property>thread</property>
|
||||||
|
will debug the threading model.
|
||||||
</para></listitem>
|
</para></listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
<varlistentry><term>-o</term>
|
<varlistentry><term>-o</term>
|
||||||
@ -1032,3 +1033,10 @@ rm -r ~/.offlineimap/AccountName/INBOX</programlisting>
|
|||||||
</refsect1>
|
</refsect1>
|
||||||
</refentry>
|
</refentry>
|
||||||
</reference>
|
</reference>
|
||||||
|
|
||||||
|
<!--
|
||||||
|
Local Variables:
|
||||||
|
mode: sgml
|
||||||
|
sgml-set-face: T
|
||||||
|
End:
|
||||||
|
-->
|
||||||
|
@ -54,6 +54,40 @@ class CustomConfigParser(ConfigParser):
|
|||||||
path = None
|
path = None
|
||||||
return LocalEval(path)
|
return LocalEval(path)
|
||||||
|
|
||||||
def getaccountlist(self):
|
def getsectionlist(self, key):
|
||||||
return [x for x in self.sections() if x != 'general']
|
"""Returns a list of sections that start with key + " ". That is,
|
||||||
|
if key is "Account", returns all section names that start with
|
||||||
|
"Account ", but strips off the "Account ". For instance, for
|
||||||
|
"Account Test", returns "Test"."""
|
||||||
|
|
||||||
|
key = key + ' '
|
||||||
|
return [x[len(key):] for x in self.sections() \
|
||||||
|
if x.startswith(key)]
|
||||||
|
|
||||||
|
def CustomConfigDefault():
|
||||||
|
"""Just a sample constant that won't occur anywhere else to use for the
|
||||||
|
default."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
class ConfigHelperMixin:
|
||||||
|
def _confighelper_runner(self, option, default, defaultfunc, mainfunc):
|
||||||
|
if default != CustomConfigDefault:
|
||||||
|
return apply(defaultfunc, [self.getsection(), option, default])
|
||||||
|
else:
|
||||||
|
return apply(mainfunc, [self.getsection(), option])
|
||||||
|
|
||||||
|
def getconf(self, option, default = CustomConfigDefault):
|
||||||
|
return self._confighelper_runner(option, default,
|
||||||
|
self.getconfig().getdefault,
|
||||||
|
self.getconfig().get)
|
||||||
|
|
||||||
|
def getconfboolean(self, option, default = CustomConfigDefault):
|
||||||
|
return self._confighelper_runner(option, default,
|
||||||
|
self.getconfig().getdefaultboolean,
|
||||||
|
self.getconfig().getboolean)
|
||||||
|
|
||||||
|
def getconfint(self, option, default = CustomConfigDefault):
|
||||||
|
return self._confighelper_runner(option, default,
|
||||||
|
self.getconfig().getdefaultint,
|
||||||
|
self.getconfig().getint)
|
||||||
|
|
||||||
|
@ -15,39 +15,49 @@
|
|||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from offlineimap import imapserver, repository, threadutil, mbnames
|
from offlineimap import repository, threadutil, mbnames, CustomConfig
|
||||||
from offlineimap.ui import UIBase
|
from offlineimap.ui import UIBase
|
||||||
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
|
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
|
||||||
from threading import Event
|
from threading import Event
|
||||||
import os
|
import os
|
||||||
|
|
||||||
|
def getaccountlist(customconfig):
|
||||||
|
return customconfig.getsectionlist('Account')
|
||||||
|
|
||||||
|
def AccountListGenerator(customconfig):
|
||||||
|
return [Account(customconfig, accountname)
|
||||||
|
for accountname in getaccountlist(customconfig)]
|
||||||
|
|
||||||
|
def AccountHashGenerator(customconfig):
|
||||||
|
retval = {}
|
||||||
|
for item in AccountListGenerator(customconfig):
|
||||||
|
retval[item.getname()] = item
|
||||||
|
return retval
|
||||||
|
|
||||||
mailboxes = []
|
mailboxes = []
|
||||||
|
|
||||||
class Account:
|
class Account(CustomConfig.ConfigHelperMixin):
|
||||||
def __init__(self, config, name):
|
def __init__(self, config, name):
|
||||||
self.config = config
|
self.config = config
|
||||||
self.name = name
|
self.name = name
|
||||||
self.metadatadir = config.getmetadatadir()
|
self.metadatadir = config.getmetadatadir()
|
||||||
self.localeval = config.getlocaleval()
|
self.localeval = config.getlocaleval()
|
||||||
self.server = imapserver.ConfigedIMAPServer(config, self.name)
|
|
||||||
self.ui = UIBase.getglobalui()
|
self.ui = UIBase.getglobalui()
|
||||||
if self.config.has_option(self.name, 'autorefresh'):
|
self.refreshperiod = self.getconfint('autorefresh', 0)
|
||||||
self.refreshperiod = self.config.getint(self.name, 'autorefresh')
|
if self.refreshperiod == 0:
|
||||||
else:
|
|
||||||
self.refreshperiod = None
|
self.refreshperiod = None
|
||||||
self.hold = self.config.has_option(self.name, 'holdconnectionopen') \
|
|
||||||
and self.config.getboolean(self.name, 'holdconnectionopen')
|
|
||||||
if self.config.has_option(self.name, 'keepalive'):
|
|
||||||
self.keepalive = self.config.getint(self.name, 'keepalive')
|
|
||||||
else:
|
|
||||||
self.keepalive = None
|
|
||||||
|
|
||||||
def getconf(self, option, default = None):
|
def getlocaleval(self):
|
||||||
if default != None:
|
return self.localeval
|
||||||
return self.config.get(self.name, option)
|
|
||||||
else:
|
def getconfig(self):
|
||||||
return self.config.getdefault(self.name, option,
|
return self.config
|
||||||
default)
|
|
||||||
|
def getname(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def getsection(self):
|
||||||
|
return 'Account ' + self.getname()
|
||||||
|
|
||||||
def sleeper(self):
|
def sleeper(self):
|
||||||
"""Sleep handler. Returns same value as UIBase.sleep:
|
"""Sleep handler. Returns same value as UIBase.sleep:
|
||||||
@ -58,31 +68,46 @@ class Account:
|
|||||||
|
|
||||||
if not self.refreshperiod:
|
if not self.refreshperiod:
|
||||||
return 100
|
return 100
|
||||||
|
|
||||||
|
kaobjs = []
|
||||||
|
|
||||||
|
if hasattr(self, 'localrepos'):
|
||||||
|
kaobjs.append(self.localrepos)
|
||||||
|
if hasattr(self, 'remoterepos'):
|
||||||
|
kaobjs.append(self.remoterepos)
|
||||||
|
|
||||||
|
for item in kaobjs:
|
||||||
|
item.startkeepalive()
|
||||||
|
|
||||||
refreshperiod = self.refreshperiod * 60
|
refreshperiod = self.refreshperiod * 60
|
||||||
if self.keepalive:
|
|
||||||
kaevent = Event()
|
|
||||||
kathread = ExitNotifyThread(target = self.server.keepalive,
|
|
||||||
name = "Keep alive " + self.name,
|
|
||||||
args = (self.keepalive, kaevent))
|
|
||||||
kathread.setDaemon(1)
|
|
||||||
kathread.start()
|
|
||||||
sleepresult = self.ui.sleep(refreshperiod)
|
sleepresult = self.ui.sleep(refreshperiod)
|
||||||
if sleepresult == 2:
|
if sleepresult == 2:
|
||||||
# Cancel keep-alive, but don't bother terminating threads
|
# Cancel keep-alive, but don't bother terminating threads
|
||||||
if self.keepalive:
|
for item in kaobjs:
|
||||||
kaevent.set()
|
item.stopkeepalive(abrupt = 1)
|
||||||
return sleepresult
|
return sleepresult
|
||||||
else:
|
else:
|
||||||
# Cancel keep-alive and wait for thread to terminate.
|
# Cancel keep-alive and wait for thread to terminate.
|
||||||
if self.keepalive:
|
for item in kaobjs:
|
||||||
kaevent.set()
|
item.stopkeepalive(abrupt = 0)
|
||||||
kathread.join()
|
|
||||||
return sleepresult
|
return sleepresult
|
||||||
|
|
||||||
class AccountSynchronizationMixin:
|
class AccountSynchronizationMixin:
|
||||||
def syncrunner(self):
|
def syncrunner(self):
|
||||||
self.ui.registerthread(self.name)
|
self.ui.registerthread(self.name)
|
||||||
self.ui.acct(self.name)
|
self.ui.acct(self.name)
|
||||||
|
accountmetadata = self.getaccountmeta()
|
||||||
|
if not os.path.exists(accountmetadata):
|
||||||
|
os.mkdir(accountmetadata, 0700)
|
||||||
|
|
||||||
|
self.remoterepos = repository.Base.LoadRepository(self.getconf('remoterepository'), self, 'remote')
|
||||||
|
|
||||||
|
# Connect to the local repository.
|
||||||
|
self.localrepos = repository.Base.LoadRepository(self.getconf('localrepository'), self, 'local')
|
||||||
|
|
||||||
|
# Connect to the local cache.
|
||||||
|
self.statusrepos = repository.LocalStatus.LocalStatusRepository(self.getconf('localrepository'), self)
|
||||||
|
|
||||||
if not self.refreshperiod:
|
if not self.refreshperiod:
|
||||||
self.sync()
|
self.sync()
|
||||||
self.ui.acctdone(self.name)
|
self.ui.acctdone(self.name)
|
||||||
@ -93,32 +118,23 @@ class AccountSynchronizationMixin:
|
|||||||
looping = self.sleeper() != 2
|
looping = self.sleeper() != 2
|
||||||
self.ui.acctdone(self.name)
|
self.ui.acctdone(self.name)
|
||||||
|
|
||||||
|
def getaccountmeta(self):
|
||||||
|
return os.path.join(self.metadatadir, 'Account-' + self.name)
|
||||||
|
|
||||||
def sync(self):
|
def sync(self):
|
||||||
# We don't need an account lock because syncitall() goes through
|
# We don't need an account lock because syncitall() goes through
|
||||||
# each account once, then waits for all to finish.
|
# each account once, then waits for all to finish.
|
||||||
try:
|
try:
|
||||||
accountmetadata = os.path.join(self.metadatadir, self.name)
|
remoterepos = self.remoterepos
|
||||||
if not os.path.exists(accountmetadata):
|
localrepos = self.localrepos
|
||||||
os.mkdir(accountmetadata, 0700)
|
statusrepos = self.statusrepos
|
||||||
|
|
||||||
remoterepos = repository.IMAP.IMAPRepository(self.config,
|
|
||||||
self.localeval,
|
|
||||||
self.name,
|
|
||||||
self.server)
|
|
||||||
|
|
||||||
# Connect to the Maildirs.
|
|
||||||
localrepos = repository.Maildir.MaildirRepository(os.path.expanduser(self.config.get(self.name, "localfolders")), self.name, self.config)
|
|
||||||
|
|
||||||
# Connect to the local cache.
|
|
||||||
statusrepos = repository.LocalStatus.LocalStatusRepository(accountmetadata, self.name)
|
|
||||||
|
|
||||||
self.ui.syncfolders(remoterepos, localrepos)
|
self.ui.syncfolders(remoterepos, localrepos)
|
||||||
remoterepos.syncfoldersto(localrepos)
|
remoterepos.syncfoldersto(localrepos)
|
||||||
|
|
||||||
folderthreads = []
|
folderthreads = []
|
||||||
for remotefolder in remoterepos.getfolders():
|
for remotefolder in remoterepos.getfolders():
|
||||||
thread = InstanceLimitedThread(\
|
thread = InstanceLimitedThread(\
|
||||||
instancename = 'FOLDER_' + self.name,
|
instancename = 'FOLDER_' + self.remoterepos.getname(),
|
||||||
target = syncfolder,
|
target = syncfolder,
|
||||||
name = "Folder sync %s[%s]" % \
|
name = "Folder sync %s[%s]" % \
|
||||||
(self.name, remotefolder.getvisiblename()),
|
(self.name, remotefolder.getvisiblename()),
|
||||||
@ -129,8 +145,8 @@ class AccountSynchronizationMixin:
|
|||||||
folderthreads.append(thread)
|
folderthreads.append(thread)
|
||||||
threadutil.threadsreset(folderthreads)
|
threadutil.threadsreset(folderthreads)
|
||||||
mbnames.write()
|
mbnames.write()
|
||||||
if not self.hold:
|
localrepos.holdordropconnections()
|
||||||
self.server.close()
|
remoterepos.holdordropconnections()
|
||||||
finally:
|
finally:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -166,19 +182,22 @@ def syncfolder(accountname, remoterepos, remotefolder, localrepos,
|
|||||||
|
|
||||||
statusfolder.cachemessagelist()
|
statusfolder.cachemessagelist()
|
||||||
|
|
||||||
|
# If either the local or the status folder has messages and there is a UID
|
||||||
# If either the local or the status folder has messages and
|
# validity problem, warn and abort. If there are no messages, UW IMAPd
|
||||||
# there is a UID validity problem, warn and abort.
|
# loses UIDVALIDITY. But we don't really need it if both local folders are
|
||||||
# If there are no messages, UW IMAPd loses UIDVALIDITY.
|
# empty. So, in that case, just save it off.
|
||||||
# But we don't really need it if both local folders are empty.
|
if len(localfolder.getmessagelist()) or len(statusfolder.getmessagelist()):
|
||||||
# So, in that case, save it off.
|
if not localfolder.isuidvalidityok():
|
||||||
if (len(localfolder.getmessagelist()) or \
|
ui.validityproblem(localfolder, localfolder.getsaveduidvalidity(),
|
||||||
len(statusfolder.getmessagelist())) and \
|
localfolder.getuidvalidity())
|
||||||
not localfolder.isuidvalidityok(remotefolder):
|
return
|
||||||
ui.validityproblem(remotefolder)
|
if not remotefolder.isuidvalidityok():
|
||||||
return
|
ui.validityproblem(remotefolder, remotefolder.getsaveduidvalidity(),
|
||||||
|
remotefolder.getuidvalidity())
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
localfolder.saveuidvalidity(remotefolder.getuidvalidity())
|
localfolder.saveuidvalidity()
|
||||||
|
remotefolder.saveuidvalidity()
|
||||||
|
|
||||||
# Load remote folder.
|
# Load remote folder.
|
||||||
ui.loadmessagelist(remoterepos, remotefolder)
|
ui.loadmessagelist(remoterepos, remotefolder)
|
||||||
|
@ -20,8 +20,12 @@ from threading import *
|
|||||||
from offlineimap import threadutil
|
from offlineimap import threadutil
|
||||||
from offlineimap.threadutil import InstanceLimitedThread
|
from offlineimap.threadutil import InstanceLimitedThread
|
||||||
from offlineimap.ui import UIBase
|
from offlineimap.ui import UIBase
|
||||||
|
import os.path, re
|
||||||
|
|
||||||
class BaseFolder:
|
class BaseFolder:
|
||||||
|
def __init__(self):
|
||||||
|
self.uidlock = Lock()
|
||||||
|
|
||||||
def getname(self):
|
def getname(self):
|
||||||
"""Returns name"""
|
"""Returns name"""
|
||||||
return self.name
|
return self.name
|
||||||
@ -68,15 +72,52 @@ class BaseFolder:
|
|||||||
else:
|
else:
|
||||||
return self.getname()
|
return self.getname()
|
||||||
|
|
||||||
def isuidvalidityok(self, remotefolder):
|
def getfolderbasename(self):
|
||||||
raise NotImplementedException
|
foldername = self.getname()
|
||||||
|
foldername = foldername.replace(self.repository.getsep(), '.')
|
||||||
|
foldername = re.sub('/\.$', '/dot', foldername)
|
||||||
|
foldername = re.sub('^\.$', 'dot', foldername)
|
||||||
|
return foldername
|
||||||
|
|
||||||
|
def isuidvalidityok(self):
|
||||||
|
if self.getsaveduidvalidity() != None:
|
||||||
|
return self.getsaveduidvalidity() == self.getuidvalidity()
|
||||||
|
else:
|
||||||
|
self.saveuidvalidity()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
def _getuidfilename(self):
|
||||||
|
return os.path.join(self.repository.getuiddir(),
|
||||||
|
self.getfolderbasename())
|
||||||
|
|
||||||
|
def getsaveduidvalidity(self):
|
||||||
|
if hasattr(self, '_base_saved_uidvalidity'):
|
||||||
|
return self._base_saved_uidvalidity
|
||||||
|
uidfilename = self._getuidfilename()
|
||||||
|
if not os.path.exists(uidfilename):
|
||||||
|
self._base_saved_uidvalidity = None
|
||||||
|
else:
|
||||||
|
file = open(uidfilename, "rt")
|
||||||
|
self._base_saved_uidvalidity = long(file.readline().strip())
|
||||||
|
file.close()
|
||||||
|
return self._base_saved_uidvalidity
|
||||||
|
|
||||||
|
def saveuidvalidity(self):
|
||||||
|
newval = self.getuidvalidity()
|
||||||
|
uidfilename = self._getuidfilename()
|
||||||
|
self.uidlock.acquire()
|
||||||
|
try:
|
||||||
|
file = open(uidfilename + ".tmp", "wt")
|
||||||
|
file.write("%d\n" % newval)
|
||||||
|
file.close()
|
||||||
|
os.rename(uidfilename + ".tmp", uidfilename)
|
||||||
|
self._base_saved_uidvalidity = newval
|
||||||
|
finally:
|
||||||
|
self.uidlock.release()
|
||||||
|
|
||||||
def getuidvalidity(self):
|
def getuidvalidity(self):
|
||||||
raise NotImplementedException
|
raise NotImplementedException
|
||||||
|
|
||||||
def saveuidvalidity(self, newval):
|
|
||||||
raise NotImplementedException
|
|
||||||
|
|
||||||
def cachemessagelist(self):
|
def cachemessagelist(self):
|
||||||
"""Reads the message list from disk or network and stores it in
|
"""Reads the message list from disk or network and stores it in
|
||||||
memory for later use. This list will not be re-read from disk or
|
memory for later use. This list will not be re-read from disk or
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
from Base import BaseFolder
|
from Base import BaseFolder
|
||||||
from offlineimap import imaputil, imaplib
|
from offlineimap import imaputil, imaplib
|
||||||
from offlineimap.ui import UIBase
|
from offlineimap.ui import UIBase
|
||||||
import rfc822, time, string
|
import rfc822, time, string, random, binascii
|
||||||
from StringIO import StringIO
|
from StringIO import StringIO
|
||||||
from copy import copy
|
from copy import copy
|
||||||
|
|
||||||
@ -27,9 +27,7 @@ from copy import copy
|
|||||||
class IMAPFolder(BaseFolder):
|
class IMAPFolder(BaseFolder):
|
||||||
def __init__(self, imapserver, name, visiblename, accountname, repository):
|
def __init__(self, imapserver, name, visiblename, accountname, repository):
|
||||||
self.config = imapserver.config
|
self.config = imapserver.config
|
||||||
self.expunge = 1
|
self.expunge = repository.getexpunge()
|
||||||
if self.config.has_option(accountname, 'expunge'):
|
|
||||||
self.expunge = self.config.getboolean(accountname, 'expunge')
|
|
||||||
self.name = imaputil.dequote(name)
|
self.name = imaputil.dequote(name)
|
||||||
self.root = None # imapserver.root
|
self.root = None # imapserver.root
|
||||||
self.sep = imapserver.delim
|
self.sep = imapserver.delim
|
||||||
@ -38,6 +36,8 @@ class IMAPFolder(BaseFolder):
|
|||||||
self.visiblename = visiblename
|
self.visiblename = visiblename
|
||||||
self.accountname = accountname
|
self.accountname = accountname
|
||||||
self.repository = repository
|
self.repository = repository
|
||||||
|
self.randomgenerator = random.Random()
|
||||||
|
BaseFolder.__init__(self)
|
||||||
|
|
||||||
def getaccountname(self):
|
def getaccountname(self):
|
||||||
return self.accountname
|
return self.accountname
|
||||||
@ -49,7 +49,7 @@ class IMAPFolder(BaseFolder):
|
|||||||
self.imapserver.connectionwait()
|
self.imapserver.connectionwait()
|
||||||
|
|
||||||
def getcopyinstancelimit(self):
|
def getcopyinstancelimit(self):
|
||||||
return 'MSGCOPY_' + self.accountname
|
return 'MSGCOPY_' + self.repository.getname()
|
||||||
|
|
||||||
def getvisiblename(self):
|
def getvisiblename(self):
|
||||||
return self.visiblename
|
return self.visiblename
|
||||||
@ -70,7 +70,12 @@ class IMAPFolder(BaseFolder):
|
|||||||
try:
|
try:
|
||||||
# Primes untagged_responses
|
# Primes untagged_responses
|
||||||
imapobj.select(self.getfullname(), readonly = 1)
|
imapobj.select(self.getfullname(), readonly = 1)
|
||||||
maxmsgid = long(imapobj.untagged_responses['EXISTS'][0])
|
try:
|
||||||
|
# Some mail servers do not return an EXISTS response if
|
||||||
|
# the folder is empty.
|
||||||
|
maxmsgid = long(imapobj.untagged_responses['EXISTS'][0])
|
||||||
|
except KeyError:
|
||||||
|
return
|
||||||
if maxmsgid < 1:
|
if maxmsgid < 1:
|
||||||
# No messages; return
|
# No messages; return
|
||||||
return
|
return
|
||||||
@ -108,6 +113,36 @@ class IMAPFolder(BaseFolder):
|
|||||||
def getmessageflags(self, uid):
|
def getmessageflags(self, uid):
|
||||||
return self.messagelist[uid]['flags']
|
return self.messagelist[uid]['flags']
|
||||||
|
|
||||||
|
def savemessage_getnewheader(self, content):
|
||||||
|
headername = 'X-OfflineIMAP-%s-' % str(binascii.crc32(content)).replace('-', 'x')
|
||||||
|
headername += binascii.hexlify(self.repository.getname()) + '-'
|
||||||
|
headername += binascii.hexlify(self.getname())
|
||||||
|
headervalue= '%d-' % long(time.time())
|
||||||
|
headervalue += str(self.randomgenerator.random()).replace('.', '')
|
||||||
|
return (headername, headervalue)
|
||||||
|
|
||||||
|
def savemessage_addheader(self, content, headername, headervalue):
|
||||||
|
insertionpoint = content.find("\r\n")
|
||||||
|
leader = content[0:insertionpoint]
|
||||||
|
newline = "\r\n%s: %s" % (headername, headervalue)
|
||||||
|
trailer = content[insertionpoint:]
|
||||||
|
return leader + newline + trailer
|
||||||
|
|
||||||
|
def savemessage_searchforheader(self, imapobj, headername, headervalue):
|
||||||
|
# Now find the UID it got.
|
||||||
|
headervalue = imapobj._quote(headervalue)
|
||||||
|
try:
|
||||||
|
matchinguids = imapobj.uid('search', None,
|
||||||
|
'(HEADER %s %s)' % (headername, headervalue))[1][0]
|
||||||
|
except imapobj.error:
|
||||||
|
# IMAP server doesn't implement search or had a problem.
|
||||||
|
return 0
|
||||||
|
matchinguids = matchinguids.split(' ')
|
||||||
|
if len(matchinguids) != 1 or matchinguids[0] == None:
|
||||||
|
raise ValueError, "While attempting to find UID for message with header %s, got wrong-sized matchinguids of %s" % (headername, str(matchinguids))
|
||||||
|
matchinguids.sort()
|
||||||
|
return long(matchinguids[0])
|
||||||
|
|
||||||
def savemessage(self, uid, content, flags):
|
def savemessage(self, uid, content, flags):
|
||||||
imapobj = self.imapserver.acquireconnection()
|
imapobj = self.imapserver.acquireconnection()
|
||||||
try:
|
try:
|
||||||
@ -123,9 +158,6 @@ class IMAPFolder(BaseFolder):
|
|||||||
# In order to get the new uid, we need to save off the message ID.
|
# In order to get the new uid, we need to save off the message ID.
|
||||||
|
|
||||||
message = rfc822.Message(StringIO(content))
|
message = rfc822.Message(StringIO(content))
|
||||||
mid = message.getheader('Message-Id')
|
|
||||||
if mid != None:
|
|
||||||
mid = imapobj._quote(mid)
|
|
||||||
datetuple = rfc822.parsedate(message.getheader('Date'))
|
datetuple = rfc822.parsedate(message.getheader('Date'))
|
||||||
# Will be None if missing or not in a valid format.
|
# Will be None if missing or not in a valid format.
|
||||||
if datetuple == None:
|
if datetuple == None:
|
||||||
@ -145,35 +177,31 @@ class IMAPFolder(BaseFolder):
|
|||||||
if content.find("\r\n") == -1: # Convert line endings if not already
|
if content.find("\r\n") == -1: # Convert line endings if not already
|
||||||
content = content.replace("\n", "\r\n")
|
content = content.replace("\n", "\r\n")
|
||||||
|
|
||||||
|
(headername, headervalue) = self.savemessage_getnewheader(content)
|
||||||
|
content = self.savemessage_addheader(content, headername,
|
||||||
|
headervalue)
|
||||||
|
|
||||||
assert(imapobj.append(self.getfullname(),
|
assert(imapobj.append(self.getfullname(),
|
||||||
imaputil.flagsmaildir2imap(flags),
|
imaputil.flagsmaildir2imap(flags),
|
||||||
date, content)[0] == 'OK')
|
date, content)[0] == 'OK')
|
||||||
|
|
||||||
# Checkpoint. Let it write out the messages, etc.
|
# Checkpoint. Let it write out the messages, etc.
|
||||||
assert(imapobj.check()[0] == 'OK')
|
assert(imapobj.check()[0] == 'OK')
|
||||||
if mid == None:
|
|
||||||
# No message ID in original message -- no sense trying to
|
# Keep trying until we get the UID.
|
||||||
# search for it.
|
|
||||||
return 0
|
|
||||||
# Now find the UID it got.
|
|
||||||
try:
|
try:
|
||||||
matchinguids = imapobj.uid('search', None,
|
uid = self.savemessage_searchforheader(imapobj, headername,
|
||||||
'(HEADER Message-Id %s)' % mid)[1][0]
|
headervalue)
|
||||||
except imapobj.error:
|
|
||||||
# IMAP server doesn't implement search or had a problem.
|
|
||||||
return 0
|
|
||||||
matchinguids = matchinguids.split(' ')
|
|
||||||
if len(matchinguids) != 1 or matchinguids[0] == None:
|
|
||||||
return 0
|
|
||||||
matchinguids.sort()
|
|
||||||
try:
|
|
||||||
uid = long(matchinguids[-1])
|
|
||||||
except ValueError:
|
except ValueError:
|
||||||
return 0
|
assert(imapobj.noop()[0] == 'OK')
|
||||||
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
uid = self.savemessage_searchforheader(imapobj, headername,
|
||||||
return uid
|
headervalue)
|
||||||
finally:
|
finally:
|
||||||
self.imapserver.releaseconnection(imapobj)
|
self.imapserver.releaseconnection(imapobj)
|
||||||
|
|
||||||
|
self.messagelist[uid] = {'uid': uid, 'flags': flags}
|
||||||
|
return uid
|
||||||
|
|
||||||
def savemessageflags(self, uid, flags):
|
def savemessageflags(self, uid, flags):
|
||||||
imapobj = self.imapserver.acquireconnection()
|
imapobj = self.imapserver.acquireconnection()
|
||||||
try:
|
try:
|
||||||
@ -197,9 +225,15 @@ class IMAPFolder(BaseFolder):
|
|||||||
def addmessageflags(self, uid, flags):
|
def addmessageflags(self, uid, flags):
|
||||||
self.addmessagesflags([uid], flags)
|
self.addmessagesflags([uid], flags)
|
||||||
|
|
||||||
def addmessagesflags(self, uidlist, flags):
|
def addmessagesflags_noconvert(self, uidlist, flags):
|
||||||
self.processmessagesflags('+', uidlist, flags)
|
self.processmessagesflags('+', uidlist, flags)
|
||||||
|
|
||||||
|
def addmessagesflags(self, uidlist, flags):
|
||||||
|
"""This is here for the sake of UIDMaps.py -- deletemessages must
|
||||||
|
add flags and get a converted UID, and if we don't have noconvert,
|
||||||
|
then UIDMaps will try to convert it twice."""
|
||||||
|
self.addmessagesflags_noconvert(uidlist, flags)
|
||||||
|
|
||||||
def deletemessageflags(self, uid, flags):
|
def deletemessageflags(self, uid, flags):
|
||||||
self.deletemessagesflags([uid], flags)
|
self.deletemessagesflags([uid], flags)
|
||||||
|
|
||||||
@ -254,15 +288,18 @@ class IMAPFolder(BaseFolder):
|
|||||||
self.messagelist[uid]['flags'].remove(flag)
|
self.messagelist[uid]['flags'].remove(flag)
|
||||||
|
|
||||||
def deletemessage(self, uid):
|
def deletemessage(self, uid):
|
||||||
self.deletemessages([uid])
|
self.deletemessages_noconvert([uid])
|
||||||
|
|
||||||
def deletemessages(self, uidlist):
|
def deletemessages(self, uidlist):
|
||||||
|
self.deletemessages_noconvert(uidlist)
|
||||||
|
|
||||||
|
def deletemessages_noconvert(self, uidlist):
|
||||||
# Weed out ones not in self.messagelist
|
# Weed out ones not in self.messagelist
|
||||||
uidlist = [uid for uid in uidlist if uid in self.messagelist]
|
uidlist = [uid for uid in uidlist if uid in self.messagelist]
|
||||||
if not len(uidlist):
|
if not len(uidlist):
|
||||||
return
|
return
|
||||||
|
|
||||||
self.addmessagesflags(uidlist, ['T'])
|
self.addmessagesflags_noconvert(uidlist, ['T'])
|
||||||
imapobj = self.imapserver.acquireconnection()
|
imapobj = self.imapserver.acquireconnection()
|
||||||
try:
|
try:
|
||||||
try:
|
try:
|
||||||
|
@ -27,11 +27,13 @@ class LocalStatusFolder(BaseFolder):
|
|||||||
self.root = root
|
self.root = root
|
||||||
self.sep = '.'
|
self.sep = '.'
|
||||||
self.filename = os.path.join(root, name)
|
self.filename = os.path.join(root, name)
|
||||||
|
self.filename = repository.getfolderfilename(name)
|
||||||
self.messagelist = None
|
self.messagelist = None
|
||||||
self.repository = repository
|
self.repository = repository
|
||||||
self.savelock = threading.Lock()
|
self.savelock = threading.Lock()
|
||||||
self.doautosave = 1
|
self.doautosave = 1
|
||||||
self.accountname = accountname
|
self.accountname = accountname
|
||||||
|
BaseFolder.__init__(self)
|
||||||
|
|
||||||
def getaccountname(self):
|
def getaccountname(self):
|
||||||
return self.accountname
|
return self.accountname
|
||||||
|
@ -43,10 +43,10 @@ class MaildirFolder(BaseFolder):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.root = root
|
self.root = root
|
||||||
self.sep = sep
|
self.sep = sep
|
||||||
self.uidfilename = os.path.join(self.getfullname(), "offlineimap.uidvalidity")
|
|
||||||
self.messagelist = None
|
self.messagelist = None
|
||||||
self.repository = repository
|
self.repository = repository
|
||||||
self.accountname = accountname
|
self.accountname = accountname
|
||||||
|
BaseFolder.__init__(self)
|
||||||
|
|
||||||
def getaccountname(self):
|
def getaccountname(self):
|
||||||
return self.accountname
|
return self.accountname
|
||||||
@ -55,30 +55,9 @@ class MaildirFolder(BaseFolder):
|
|||||||
return os.path.join(self.getroot(), self.getname())
|
return os.path.join(self.getroot(), self.getname())
|
||||||
|
|
||||||
def getuidvalidity(self):
|
def getuidvalidity(self):
|
||||||
if hasattr(self, 'uidvalidity'):
|
"""Maildirs have no notion of uidvalidity, so we just return a magic
|
||||||
return self.uidvalidity
|
token."""
|
||||||
if not os.path.exists(self.uidfilename):
|
return 42
|
||||||
self.uidvalidity = None
|
|
||||||
else:
|
|
||||||
file = open(self.uidfilename, "rt")
|
|
||||||
self.uidvalidity = long(file.readline().strip())
|
|
||||||
file.close()
|
|
||||||
return self.uidvalidity
|
|
||||||
|
|
||||||
def saveuidvalidity(self, newval):
|
|
||||||
file = open(self.uidfilename + ".tmp", "wt")
|
|
||||||
file.write("%d\n" % newval)
|
|
||||||
file.close()
|
|
||||||
os.rename(self.uidfilename + ".tmp", self.uidfilename)
|
|
||||||
self.uidvalidity = newval
|
|
||||||
|
|
||||||
def isuidvalidityok(self, remotefolder):
|
|
||||||
myval = self.getuidvalidity()
|
|
||||||
if myval != None:
|
|
||||||
return myval == remotefolder.getuidvalidity()
|
|
||||||
else:
|
|
||||||
self.saveuidvalidity(remotefolder.getuidvalidity())
|
|
||||||
return 1
|
|
||||||
|
|
||||||
def _scanfolder(self):
|
def _scanfolder(self):
|
||||||
"""Cache the message list. Maildir flags are:
|
"""Cache the message list. Maildir flags are:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# IMAP server support
|
# IMAP server support
|
||||||
# Copyright (C) 2002 John Goerzen
|
# Copyright (C) 2002, 2003 John Goerzen
|
||||||
# <jgoerzen@complete.org>
|
# <jgoerzen@complete.org>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
@ -48,11 +48,11 @@ class UsefulIMAP4_SSL(UsefulIMAPMixIn, imaplib.IMAP4_SSL): pass
|
|||||||
class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass
|
class UsefulIMAP4_Tunnel(UsefulIMAPMixIn, imaplib.IMAP4_Tunnel): pass
|
||||||
|
|
||||||
class IMAPServer:
|
class IMAPServer:
|
||||||
def __init__(self, config, accountname,
|
def __init__(self, config, reposname,
|
||||||
username = None, password = None, hostname = None,
|
username = None, password = None, hostname = None,
|
||||||
port = None, ssl = 1, maxconnections = 1, tunnel = None,
|
port = None, ssl = 1, maxconnections = 1, tunnel = None,
|
||||||
reference = '""'):
|
reference = '""'):
|
||||||
self.account = accountname
|
self.reposname = reposname
|
||||||
self.config = config
|
self.config = config
|
||||||
self.username = username
|
self.username = username
|
||||||
self.password = password
|
self.password = password
|
||||||
@ -80,7 +80,8 @@ class IMAPServer:
|
|||||||
if self.password != None and self.passworderror == None:
|
if self.password != None and self.passworderror == None:
|
||||||
return self.password
|
return self.password
|
||||||
|
|
||||||
self.password = UIBase.getglobalui().getpass(self.account, self.config,
|
self.password = UIBase.getglobalui().getpass(self.reposname,
|
||||||
|
self.config,
|
||||||
self.passworderror)
|
self.passworderror)
|
||||||
self.passworderror = None
|
self.passworderror = None
|
||||||
|
|
||||||
@ -152,17 +153,18 @@ class IMAPServer:
|
|||||||
|
|
||||||
self.connectionlock.release() # Release until need to modify data
|
self.connectionlock.release() # Release until need to modify data
|
||||||
|
|
||||||
UIBase.getglobalui().connecting(self.hostname, self.port)
|
|
||||||
|
|
||||||
success = 0
|
success = 0
|
||||||
while not success:
|
while not success:
|
||||||
# Generate a new connection.
|
# Generate a new connection.
|
||||||
if self.tunnel:
|
if self.tunnel:
|
||||||
|
UIBase.getglobalui().connecting('tunnel', self.tunnel)
|
||||||
imapobj = UsefulIMAP4_Tunnel(self.tunnel)
|
imapobj = UsefulIMAP4_Tunnel(self.tunnel)
|
||||||
success = 1
|
success = 1
|
||||||
elif self.usessl:
|
elif self.usessl:
|
||||||
|
UIBase.getglobalui().connecting(self.hostname, self.port)
|
||||||
imapobj = UsefulIMAP4_SSL(self.hostname, self.port)
|
imapobj = UsefulIMAP4_SSL(self.hostname, self.port)
|
||||||
else:
|
else:
|
||||||
|
UIBase.getglobalui().connecting(self.hostname, self.port)
|
||||||
imapobj = UsefulIMAP4(self.hostname, self.port)
|
imapobj = UsefulIMAP4(self.hostname, self.port)
|
||||||
|
|
||||||
if not self.tunnel:
|
if not self.tunnel:
|
||||||
@ -258,39 +260,34 @@ class ConfigedIMAPServer(IMAPServer):
|
|||||||
object and an account name. The passwordhash is used if
|
object and an account name. The passwordhash is used if
|
||||||
passwords for certain accounts are known. If the password for this
|
passwords for certain accounts are known. If the password for this
|
||||||
account is listed, it will be obtained from there."""
|
account is listed, it will be obtained from there."""
|
||||||
def __init__(self, config, accountname, passwordhash = {}):
|
def __init__(self, repository, passwordhash = {}):
|
||||||
"""Initialize the object. If the account is not a tunnel,
|
"""Initialize the object. If the account is not a tunnel,
|
||||||
the password is required."""
|
the password is required."""
|
||||||
host = config.get(accountname, "remotehost")
|
self.repos = repository
|
||||||
user = config.get(accountname, "remoteuser")
|
self.config = self.repos.getconfig()
|
||||||
port = None
|
usetunnel = self.repos.getpreauthtunnel()
|
||||||
if config.has_option(accountname, "remoteport"):
|
if not usetunnel:
|
||||||
port = config.getint(accountname, "remoteport")
|
host = self.repos.gethost()
|
||||||
ssl = config.getdefaultboolean(accountname, "ssl", 0)
|
user = self.repos.getuser()
|
||||||
usetunnel = config.has_option(accountname, "preauthtunnel")
|
port = self.repos.getport()
|
||||||
reference = '""'
|
ssl = self.repos.getssl()
|
||||||
if config.has_option(accountname, "reference"):
|
reference = self.repos.getreference()
|
||||||
reference = config.get(accountname, "reference")
|
|
||||||
server = None
|
server = None
|
||||||
password = None
|
password = None
|
||||||
if accountname in passwordhash:
|
|
||||||
password = passwordhash[accountname]
|
if repository.getname() in passwordhash:
|
||||||
|
password = passwordhash[repository.getname()]
|
||||||
|
|
||||||
# Connect to the remote server.
|
# Connect to the remote server.
|
||||||
if usetunnel:
|
if usetunnel:
|
||||||
IMAPServer.__init__(self, config, accountname,
|
IMAPServer.__init__(self, self.config, self.repos.getname(),
|
||||||
tunnel = config.get(accountname, "preauthtunnel"),
|
tunnel = usetunnel,
|
||||||
reference = reference,
|
reference = reference,
|
||||||
maxconnections = config.getint(accountname, "maxconnections"))
|
maxconnections = self.repos.getmaxconnections())
|
||||||
else:
|
else:
|
||||||
if not password:
|
if not password:
|
||||||
if config.has_option(accountname, 'remotepass'):
|
password = self.repos.getpassword()
|
||||||
password = config.get(accountname, 'remotepass')
|
IMAPServer.__init__(self, self.config, self.repos.getname(),
|
||||||
elif config.has_option(accountname, 'remotepassfile'):
|
|
||||||
passfile = open(os.path.expanduser(config.get(accountname, "remotepassfile")))
|
|
||||||
password = passfile.readline().strip()
|
|
||||||
passfile.close()
|
|
||||||
IMAPServer.__init__(self, config, accountname,
|
|
||||||
user, password, host, port, ssl,
|
user, password, host, port, ssl,
|
||||||
config.getdefaultint(accountname, "maxconnections", 1),
|
self.repos.getmaxconnections(),
|
||||||
reference = reference)
|
reference = reference)
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# OfflineIMAP initialization code
|
# OfflineIMAP initialization code
|
||||||
# Copyright (C) 2002 John Goerzen
|
# Copyright (C) 2002, 2003 John Goerzen
|
||||||
# <jgoerzen@complete.org>
|
# <jgoerzen@complete.org>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
@ -16,13 +16,14 @@
|
|||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version, syncmaster
|
from offlineimap import imaplib, imapserver, repository, folder, mbnames, threadutil, version, syncmaster, accounts
|
||||||
from offlineimap.localeval import LocalEval
|
from offlineimap.localeval import LocalEval
|
||||||
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
|
from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
|
||||||
from offlineimap.ui import UIBase
|
from offlineimap.ui import UIBase
|
||||||
import re, os, os.path, offlineimap, sys, fcntl
|
import re, os, os.path, offlineimap, sys, fcntl
|
||||||
from offlineimap.CustomConfig import CustomConfigParser
|
from offlineimap.CustomConfig import CustomConfigParser
|
||||||
from threading import *
|
from threading import *
|
||||||
|
import threading
|
||||||
from getopt import getopt
|
from getopt import getopt
|
||||||
|
|
||||||
lockfd = None
|
lockfd = None
|
||||||
@ -78,18 +79,26 @@ def startup(versionno):
|
|||||||
ui.add_debug(debugtype.strip())
|
ui.add_debug(debugtype.strip())
|
||||||
if debugtype == 'imap':
|
if debugtype == 'imap':
|
||||||
imaplib.Debug = 5
|
imaplib.Debug = 5
|
||||||
|
if debugtype == 'thread':
|
||||||
|
threading._VERBOSE = 1
|
||||||
|
|
||||||
if '-o' in options:
|
if '-o' in options:
|
||||||
for section in config.getaccountlist():
|
# FIXME: maybe need a better
|
||||||
config.remove_option(section, "autorefresh")
|
for section in accounts.getaccountlist(config):
|
||||||
|
config.remove_option('Account ' + section, "autorefresh")
|
||||||
|
|
||||||
lock(config, ui)
|
lock(config, ui)
|
||||||
|
|
||||||
accounts = config.get("general", "accounts")
|
activeaccounts = config.get("general", "accounts")
|
||||||
if '-a' in options:
|
if '-a' in options:
|
||||||
accounts = options['-a']
|
activeaccounts = options['-a']
|
||||||
accounts = accounts.replace(" ", "")
|
activeaccounts = activeaccounts.replace(" ", "")
|
||||||
accounts = accounts.split(",")
|
activeaccounts = activeaccounts.split(",")
|
||||||
|
allaccounts = accounts.AccountHashGenerator(config)
|
||||||
|
|
||||||
|
syncaccounts = {}
|
||||||
|
for account in activeaccounts:
|
||||||
|
syncaccounts[account] = allaccounts[account]
|
||||||
|
|
||||||
server = None
|
server = None
|
||||||
remoterepos = None
|
remoterepos = None
|
||||||
@ -101,18 +110,19 @@ def startup(versionno):
|
|||||||
threadutil.initInstanceLimit("ACCOUNTLIMIT",
|
threadutil.initInstanceLimit("ACCOUNTLIMIT",
|
||||||
config.getdefaultint("general", "maxsyncaccounts", 1))
|
config.getdefaultint("general", "maxsyncaccounts", 1))
|
||||||
|
|
||||||
for account in accounts:
|
for reposname in config.getsectionlist('Repository'):
|
||||||
for instancename in ["FOLDER_" + account, "MSGCOPY_" + account]:
|
for instancename in ["FOLDER_" + reposname,
|
||||||
|
"MSGCOPY_" + reposname]:
|
||||||
if '-1' in options:
|
if '-1' in options:
|
||||||
threadutil.initInstanceLimit(instancename, 1)
|
threadutil.initInstanceLimit(instancename, 1)
|
||||||
else:
|
else:
|
||||||
threadutil.initInstanceLimit(instancename,
|
threadutil.initInstanceLimit(instancename,
|
||||||
config.getdefaultint(account, "maxconnections", 1))
|
config.getdefaultint('Repository ' + reposname, "maxconnections", 1))
|
||||||
|
|
||||||
threadutil.initexitnotify()
|
threadutil.initexitnotify()
|
||||||
t = ExitNotifyThread(target=syncmaster.syncitall,
|
t = ExitNotifyThread(target=syncmaster.syncitall,
|
||||||
name='Sync Runner',
|
name='Sync Runner',
|
||||||
kwargs = {'accounts': accounts,
|
kwargs = {'accounts': syncaccounts,
|
||||||
'config': config})
|
'config': config})
|
||||||
t.setDaemon(1)
|
t.setDaemon(1)
|
||||||
t.start()
|
t.start()
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# Base repository support
|
# Base repository support
|
||||||
# Copyright (C) 2002 John Goerzen
|
# Copyright (C) 2002, 2003 John Goerzen
|
||||||
# <jgoerzen@complete.org>
|
# <jgoerzen@complete.org>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
@ -16,7 +16,71 @@
|
|||||||
# along with this program; if not, write to the Free Software
|
# along with this program; if not, write to the Free Software
|
||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
class BaseRepository:
|
from offlineimap import CustomConfig
|
||||||
|
import os.path
|
||||||
|
|
||||||
|
def LoadRepository(name, account, reqtype):
|
||||||
|
from offlineimap.repository.IMAP import IMAPRepository, MappedIMAPRepository
|
||||||
|
from offlineimap.repository.Maildir import MaildirRepository
|
||||||
|
if reqtype == 'remote':
|
||||||
|
# For now, we don't support Maildirs on the remote side.
|
||||||
|
typemap = {'IMAP': IMAPRepository}
|
||||||
|
elif reqtype == 'local':
|
||||||
|
typemap = {'IMAP': MappedIMAPRepository,
|
||||||
|
'Maildir': MaildirRepository}
|
||||||
|
else:
|
||||||
|
raise ValueError, "Request type %s not supported" % reqtype
|
||||||
|
config = account.getconfig()
|
||||||
|
repostype = config.get('Repository ' + name, 'type').strip()
|
||||||
|
return typemap[repostype](name, account)
|
||||||
|
|
||||||
|
class BaseRepository(CustomConfig.ConfigHelperMixin):
|
||||||
|
def __init__(self, reposname, account):
|
||||||
|
self.account = account
|
||||||
|
self.config = account.getconfig()
|
||||||
|
self.name = reposname
|
||||||
|
self.localeval = account.getlocaleval()
|
||||||
|
self.accountname = self.account.getname()
|
||||||
|
self.uiddir = os.path.join(self.config.getmetadatadir(), 'Repository-' + self.name)
|
||||||
|
if not os.path.exists(self.uiddir):
|
||||||
|
os.mkdir(self.uiddir, 0700)
|
||||||
|
self.mapdir = os.path.join(self.uiddir, 'UIDMapping')
|
||||||
|
if not os.path.exists(self.mapdir):
|
||||||
|
os.mkdir(self.mapdir, 0700)
|
||||||
|
self.uiddir = os.path.join(self.uiddir, 'FolderValidity')
|
||||||
|
if not os.path.exists(self.uiddir):
|
||||||
|
os.mkdir(self.uiddir, 0700)
|
||||||
|
|
||||||
|
def holdordropconnections(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def dropconnections(self):
|
||||||
|
pass
|
||||||
|
|
||||||
|
def getaccount(self):
|
||||||
|
return self.account
|
||||||
|
|
||||||
|
def getname(self):
|
||||||
|
return self.name
|
||||||
|
|
||||||
|
def getuiddir(self):
|
||||||
|
return self.uiddir
|
||||||
|
|
||||||
|
def getmapdir(self):
|
||||||
|
return self.mapdir
|
||||||
|
|
||||||
|
def getaccountname(self):
|
||||||
|
return self.accountname
|
||||||
|
|
||||||
|
def getsection(self):
|
||||||
|
return 'Repository ' + self.name
|
||||||
|
|
||||||
|
def getconfig(self):
|
||||||
|
return self.config
|
||||||
|
|
||||||
|
def getlocaleval(self):
|
||||||
|
return self.account.getlocaleval()
|
||||||
|
|
||||||
def getfolders(self):
|
def getfolders(self):
|
||||||
"""Returns a list of ALL folders on this server."""
|
"""Returns a list of ALL folders on this server."""
|
||||||
return []
|
return []
|
||||||
@ -68,3 +132,14 @@ class BaseRepository:
|
|||||||
# if not key in srchash:
|
# if not key in srchash:
|
||||||
# dest.deletefolder(key)
|
# dest.deletefolder(key)
|
||||||
|
|
||||||
|
##### Keepalive
|
||||||
|
|
||||||
|
def startkeepalive(self):
|
||||||
|
"""The default implementation will do nothing."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
def stopkeepalive(self, abrupt = 0):
|
||||||
|
"""Stop keep alive. If abrupt is 1, stop it but don't bother waiting
|
||||||
|
for the threads to terminate."""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@ -17,38 +17,116 @@
|
|||||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||||
|
|
||||||
from Base import BaseRepository
|
from Base import BaseRepository
|
||||||
from offlineimap import folder, imaputil
|
from offlineimap import folder, imaputil, imapserver
|
||||||
|
from offlineimap.folder.UIDMaps import MappedIMAPFolder
|
||||||
|
from offlineimap.threadutil import ExitNotifyThread
|
||||||
import re, types
|
import re, types
|
||||||
from threading import *
|
from threading import *
|
||||||
|
|
||||||
class IMAPRepository(BaseRepository):
|
class IMAPRepository(BaseRepository):
|
||||||
def __init__(self, config, localeval, accountname, imapserver):
|
def __init__(self, reposname, account):
|
||||||
"""Initialize an IMAPRepository object. Takes an IMAPServer
|
"""Initialize an IMAPRepository object."""
|
||||||
object."""
|
BaseRepository.__init__(self, reposname, account)
|
||||||
self.imapserver = imapserver
|
self.imapserver = imapserver.ConfigedIMAPServer(self)
|
||||||
self.config = config
|
|
||||||
self.accountname = accountname
|
|
||||||
self.folders = None
|
self.folders = None
|
||||||
self.nametrans = lambda foldername: foldername
|
self.nametrans = lambda foldername: foldername
|
||||||
self.folderfilter = lambda foldername: 1
|
self.folderfilter = lambda foldername: 1
|
||||||
self.folderincludes = []
|
self.folderincludes = []
|
||||||
self.foldersort = cmp
|
self.foldersort = cmp
|
||||||
if config.has_option(accountname, 'nametrans'):
|
localeval = self.localeval
|
||||||
self.nametrans = localeval.eval(config.get(accountname, 'nametrans'), {'re': re})
|
if self.config.has_option(self.getsection(), 'nametrans'):
|
||||||
if config.has_option(accountname, 'folderfilter'):
|
self.nametrans = localeval.eval(self.getconf('nametrans'),
|
||||||
self.folderfilter = localeval.eval(config.get(accountname, 'folderfilter'), {'re': re})
|
{'re': re})
|
||||||
if config.has_option(accountname, 'folderincludes'):
|
if self.config.has_option(self.getsection(), 'folderfilter'):
|
||||||
self.folderincludes = localeval.eval(config.get(accountname, 'folderincludes'), {'re': re})
|
self.folderfilter = localeval.eval(self.getconf('folderfilter'),
|
||||||
if config.has_option(accountname, 'foldersort'):
|
{'re': re})
|
||||||
self.foldersort = localeval.eval(config.get(accountname, 'foldersort'), {'re': re})
|
if self.config.has_option(self.getsection(), 'folderincludes'):
|
||||||
|
self.folderincludes = localeval.eval(self.getconf('folderincludes'),
|
||||||
|
{'re': re})
|
||||||
|
if self.config.has_option(self.getsection(), 'foldersort'):
|
||||||
|
self.foldersort = localeval.eval(self.getconf('foldersort'),
|
||||||
|
{'re': re})
|
||||||
|
|
||||||
|
def startkeepalive(self):
|
||||||
|
keepalivetime = self.getkeepalive()
|
||||||
|
if not keepalivetime: return
|
||||||
|
self.kaevent = Event()
|
||||||
|
self.kathread = ExitNotifyThread(target = self.imapserver.keepalive,
|
||||||
|
name = "Keep alive " + self.getname(),
|
||||||
|
args = (keepalivetime, self.kaevent))
|
||||||
|
self.kathread.setDaemon(1)
|
||||||
|
self.kathread.start()
|
||||||
|
|
||||||
|
def stopkeepalive(self, abrupt = 0):
|
||||||
|
if not hasattr(self, 'kaevent'):
|
||||||
|
# Keepalive is not active.
|
||||||
|
return
|
||||||
|
|
||||||
|
self.kaevent.set()
|
||||||
|
if not abrupt:
|
||||||
|
self.kathread.join()
|
||||||
|
del self.kathread
|
||||||
|
del self.kaevent
|
||||||
|
|
||||||
|
def holdordropconnections(self):
|
||||||
|
if not self.getholdconnectionopen():
|
||||||
|
self.dropconnections()
|
||||||
|
|
||||||
|
def dropconnections(self):
|
||||||
|
self.imapserver.close()
|
||||||
|
|
||||||
|
def getholdconnectionopen(self):
|
||||||
|
return self.getconfboolean("holdconnectionopen", 0)
|
||||||
|
|
||||||
|
def getkeepalive(self):
|
||||||
|
return self.getconfint("keepalive", 0)
|
||||||
|
|
||||||
def getsep(self):
|
def getsep(self):
|
||||||
return self.imapserver.delim
|
return self.imapserver.delim
|
||||||
|
|
||||||
|
def gethost(self):
|
||||||
|
return self.getconf('remotehost')
|
||||||
|
|
||||||
|
def getuser(self):
|
||||||
|
return self.getconf('remoteuser')
|
||||||
|
|
||||||
|
def getport(self):
|
||||||
|
return self.getconfint('remoteport', None)
|
||||||
|
|
||||||
|
def getssl(self):
|
||||||
|
return self.getconfboolean('ssl', 0)
|
||||||
|
|
||||||
|
def getpreauthtunnel(self):
|
||||||
|
return self.getconf('preauthtunnel', None)
|
||||||
|
|
||||||
|
def getreference(self):
|
||||||
|
return self.getconf('reference', '""')
|
||||||
|
|
||||||
|
def getmaxconnections(self):
|
||||||
|
return self.getconfint('maxconnections', 1)
|
||||||
|
|
||||||
|
def getexpunge(self):
|
||||||
|
return self.getconfboolean('expunge', 1)
|
||||||
|
|
||||||
|
def getpassword(self):
|
||||||
|
password = self.getconf('remotepass', None)
|
||||||
|
if password != None:
|
||||||
|
return password
|
||||||
|
passfile = self.getconf('remotepassfile', None)
|
||||||
|
if passfile != None:
|
||||||
|
fd = open(os.path.expanduser(passfile))
|
||||||
|
password = passfile.readline().strip()
|
||||||
|
passfile.close()
|
||||||
|
return password
|
||||||
|
return None
|
||||||
|
|
||||||
def getfolder(self, foldername):
|
def getfolder(self, foldername):
|
||||||
return folder.IMAP.IMAPFolder(self.imapserver, foldername,
|
return self.getfoldertype()(self.imapserver, foldername,
|
||||||
self.nametrans(foldername),
|
self.nametrans(foldername),
|
||||||
accountname, self)
|
self.accountname, self)
|
||||||
|
|
||||||
|
def getfoldertype(self):
|
||||||
|
return folder.IMAP.IMAPFolder
|
||||||
|
|
||||||
def getfolders(self):
|
def getfolders(self):
|
||||||
if self.folders != None:
|
if self.folders != None:
|
||||||
@ -60,7 +138,8 @@ class IMAPRepository(BaseRepository):
|
|||||||
finally:
|
finally:
|
||||||
self.imapserver.releaseconnection(imapobj)
|
self.imapserver.releaseconnection(imapobj)
|
||||||
for string in listresult:
|
for string in listresult:
|
||||||
if type(string) == types.StringType and string == '':
|
if string == None or \
|
||||||
|
(type(string) == types.StringType and string == ''):
|
||||||
# Bug in imaplib: empty strings in results from
|
# Bug in imaplib: empty strings in results from
|
||||||
# literals.
|
# literals.
|
||||||
continue
|
continue
|
||||||
@ -71,13 +150,31 @@ class IMAPRepository(BaseRepository):
|
|||||||
foldername = imaputil.dequote(name)
|
foldername = imaputil.dequote(name)
|
||||||
if not self.folderfilter(foldername):
|
if not self.folderfilter(foldername):
|
||||||
continue
|
continue
|
||||||
retval.append(folder.IMAP.IMAPFolder(self.imapserver, foldername,
|
retval.append(self.getfoldertype()(self.imapserver, foldername,
|
||||||
self.nametrans(foldername),
|
self.nametrans(foldername),
|
||||||
self.accountname, self))
|
self.accountname, self))
|
||||||
for foldername in self.folderincludes:
|
for foldername in self.folderincludes:
|
||||||
retval.append(folder.IMAP.IMAPFolder(self.imapserver, foldername,
|
retval.append(self.getfoldertype()(self.imapserver, foldername,
|
||||||
self.nametrans(foldername),
|
self.nametrans(foldername),
|
||||||
self.accountname, self))
|
self.accountname, self))
|
||||||
retval.sort(lambda x, y: self.foldersort(x.getvisiblename(), y.getvisiblename()))
|
retval.sort(lambda x, y: self.foldersort(x.getvisiblename(), y.getvisiblename()))
|
||||||
self.folders = retval
|
self.folders = retval
|
||||||
return retval
|
return retval
|
||||||
|
|
||||||
|
def makefolder(self, foldername):
|
||||||
|
#if self.getreference() != '""':
|
||||||
|
# newname = self.getreference() + self.getsep() + foldername
|
||||||
|
#else:
|
||||||
|
# newname = foldername
|
||||||
|
newname = foldername
|
||||||
|
imapobj = self.imapserver.acquireconnection()
|
||||||
|
try:
|
||||||
|
result = imapobj.create(newname)
|
||||||
|
if result[0] != 'OK':
|
||||||
|
raise RuntimeError, "Repository %s could not create folder %s: %s" % (self.getname(), foldername, str(result))
|
||||||
|
finally:
|
||||||
|
self.imapserver.releaseconnection(imapobj)
|
||||||
|
|
||||||
|
class MappedIMAPRepository(IMAPRepository):
|
||||||
|
def getfoldertype(self):
|
||||||
|
return MappedIMAPFolder
|
||||||
|
@ -18,18 +18,22 @@
|
|||||||
|
|
||||||
from Base import BaseRepository
|
from Base import BaseRepository
|
||||||
from offlineimap import folder
|
from offlineimap import folder
|
||||||
import os
|
import os, re
|
||||||
|
|
||||||
class LocalStatusRepository(BaseRepository):
|
class LocalStatusRepository(BaseRepository):
|
||||||
def __init__(self, directory, accountname):
|
def __init__(self, reposname, account):
|
||||||
self.directory = directory
|
BaseRepository.__init__(self, reposname, account)
|
||||||
|
self.directory = os.path.join(account.getaccountmeta(), 'LocalStatus')
|
||||||
|
if not os.path.exists(self.directory):
|
||||||
|
os.mkdir(self.directory, 0700)
|
||||||
self.folders = None
|
self.folders = None
|
||||||
self.accountname = accountname
|
|
||||||
|
|
||||||
def getsep(self):
|
def getsep(self):
|
||||||
return '.'
|
return '.'
|
||||||
|
|
||||||
def getfolderfilename(self, foldername):
|
def getfolderfilename(self, foldername):
|
||||||
|
foldername = re.sub('/\.$', '/dot', foldername)
|
||||||
|
foldername = re.sub('^\.$', 'dot', foldername)
|
||||||
return os.path.join(self.directory, foldername)
|
return os.path.join(self.directory, foldername)
|
||||||
|
|
||||||
def makefolder(self, foldername):
|
def makefolder(self, foldername):
|
||||||
|
@ -23,25 +23,24 @@ from mailbox import Maildir
|
|||||||
import os
|
import os
|
||||||
|
|
||||||
class MaildirRepository(BaseRepository):
|
class MaildirRepository(BaseRepository):
|
||||||
def __init__(self, root, accountname, config):
|
def __init__(self, reposname, account):
|
||||||
"""Initialize a MaildirRepository object. Takes a path name
|
"""Initialize a MaildirRepository object. Takes a path name
|
||||||
to the directory holding all the Maildir directories."""
|
to the directory holding all the Maildir directories."""
|
||||||
|
BaseRepository.__init__(self, reposname, account)
|
||||||
|
|
||||||
self.root = root
|
self.root = self.getlocalroot()
|
||||||
self.folders = None
|
self.folders = None
|
||||||
self.accountname = accountname
|
|
||||||
self.config = config
|
|
||||||
self.ui = UIBase.getglobalui()
|
self.ui = UIBase.getglobalui()
|
||||||
self.debug("MaildirRepository initialized, sep is " + repr(self.getsep()))
|
self.debug("MaildirRepository initialized, sep is " + repr(self.getsep()))
|
||||||
|
|
||||||
|
def getlocalroot(self):
|
||||||
|
return os.path.expanduser(self.getconf('localfolders'))
|
||||||
|
|
||||||
def debug(self, msg):
|
def debug(self, msg):
|
||||||
self.ui.debug('maildir', msg)
|
self.ui.debug('maildir', msg)
|
||||||
|
|
||||||
def getsep(self):
|
def getsep(self):
|
||||||
if self.config.has_option(self.accountname, 'sep'):
|
return self.getconf('sep', '.').strip()
|
||||||
return self.config.get(self.accountname, 'sep').strip()
|
|
||||||
else:
|
|
||||||
return '.'
|
|
||||||
|
|
||||||
def makefolder(self, foldername):
|
def makefolder(self, foldername):
|
||||||
self.debug("makefolder called with arg " + repr(foldername))
|
self.debug("makefolder called with arg " + repr(foldername))
|
||||||
@ -65,7 +64,8 @@ class MaildirRepository(BaseRepository):
|
|||||||
# makedirs will fail because the higher-up dir already exists.
|
# makedirs will fail because the higher-up dir already exists.
|
||||||
# So, check to see if this is indeed the case.
|
# So, check to see if this is indeed the case.
|
||||||
|
|
||||||
if self.getsep() == '/' and os.path.isdir(foldername):
|
if (self.getsep() == '/' or self.getconfboolean('existsok', 0)) \
|
||||||
|
and os.path.isdir(foldername):
|
||||||
self.debug("makefolder: %s already is a directory" % foldername)
|
self.debug("makefolder: %s already is a directory" % foldername)
|
||||||
# Already exists. Sanity-check that it's not a Maildir.
|
# Already exists. Sanity-check that it's not a Maildir.
|
||||||
for subdir in ['cur', 'new', 'tmp']:
|
for subdir in ['cur', 'new', 'tmp']:
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
# OfflineIMAP synchronization master code
|
# OfflineIMAP synchronization master code
|
||||||
# Copyright (C) 2002 John Goerzen
|
# Copyright (C) 2002, 2003 John Goerzen
|
||||||
# <jgoerzen@complete.org>
|
# <jgoerzen@complete.org>
|
||||||
#
|
#
|
||||||
# This program is free software; you can redistribute it and/or modify
|
# This program is free software; you can redistribute it and/or modify
|
||||||
@ -25,21 +25,16 @@ import re, os, os.path, offlineimap, sys
|
|||||||
from ConfigParser import ConfigParser
|
from ConfigParser import ConfigParser
|
||||||
from threading import *
|
from threading import *
|
||||||
|
|
||||||
def syncaccount(threads, config, accountname):
|
def syncaccount(config, accountname):
|
||||||
account = SyncableAccount(config, accountname)
|
account = SyncableAccount(config, accountname)
|
||||||
thread = InstanceLimitedThread(instancename = 'ACCOUNTLIMIT',
|
thread = InstanceLimitedThread(instancename = 'ACCOUNTLIMIT',
|
||||||
target = account.syncrunner,
|
target = account.syncrunner,
|
||||||
name = "Account sync %s" % accountname)
|
name = "Account sync %s" % accountname)
|
||||||
thread.setDaemon(1)
|
thread.setDaemon(1)
|
||||||
thread.start()
|
thread.start()
|
||||||
threads.add(thread)
|
|
||||||
|
|
||||||
def syncitall(accounts, config):
|
def syncitall(accounts, config):
|
||||||
currentThread().setExitMessage('SYNC_WITH_TIMER_TERMINATE')
|
|
||||||
ui = UIBase.getglobalui()
|
ui = UIBase.getglobalui()
|
||||||
threads = threadutil.threadlist()
|
|
||||||
mbnames.init(config, accounts)
|
mbnames.init(config, accounts)
|
||||||
for accountname in accounts:
|
for accountname in accounts:
|
||||||
syncaccount(threads, config, accountname)
|
syncaccount(config, accountname)
|
||||||
# Wait for the threads to finish.
|
|
||||||
threads.reset()
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
# Copyright (C) 2002 John Goerzen
|
# Copyright (C) 2002, 2003 John Goerzen
|
||||||
# Thread support module
|
# Thread support module
|
||||||
# <jgoerzen@complete.org>
|
# <jgoerzen@complete.org>
|
||||||
#
|
#
|
||||||
@ -113,12 +113,14 @@ def exitnotifymonitorloop(callback):
|
|||||||
global exitcondition, exitthreads
|
global exitcondition, exitthreads
|
||||||
while 1: # Loop forever.
|
while 1: # Loop forever.
|
||||||
exitcondition.acquire()
|
exitcondition.acquire()
|
||||||
while not len(exitthreads):
|
try:
|
||||||
exitcondition.wait(1)
|
while not len(exitthreads):
|
||||||
|
exitcondition.wait(1)
|
||||||
|
|
||||||
while len(exitthreads):
|
while len(exitthreads):
|
||||||
callback(exitthreads.pop(0)) # Pull off in order added!
|
callback(exitthreads.pop(0)) # Pull off in order added!
|
||||||
exitcondition.release()
|
finally:
|
||||||
|
exitcondition.release()
|
||||||
|
|
||||||
def threadexited(thread):
|
def threadexited(thread):
|
||||||
"""Called when a thread exits."""
|
"""Called when a thread exits."""
|
||||||
@ -132,11 +134,6 @@ def threadexited(thread):
|
|||||||
ui.threadException(thread) # Expected to terminate
|
ui.threadException(thread) # Expected to terminate
|
||||||
sys.exit(100) # Just in case...
|
sys.exit(100) # Just in case...
|
||||||
os._exit(100)
|
os._exit(100)
|
||||||
elif thread.getExitMessage() == 'SYNC_WITH_TIMER_TERMINATE':
|
|
||||||
ui.terminate()
|
|
||||||
# Just in case...
|
|
||||||
sys.exit(100)
|
|
||||||
os._exit(100)
|
|
||||||
else:
|
else:
|
||||||
ui.threadExited(thread)
|
ui.threadExited(thread)
|
||||||
|
|
||||||
|
@ -155,7 +155,7 @@ class UIBase:
|
|||||||
if hostname == None:
|
if hostname == None:
|
||||||
hostname = ''
|
hostname = ''
|
||||||
if port != None:
|
if port != None:
|
||||||
port = ":%d" % port
|
port = ":%s" % str(port)
|
||||||
displaystr = ' to %s%s.' % (hostname, port)
|
displaystr = ' to %s%s.' % (hostname, port)
|
||||||
if hostname == '' and port == None:
|
if hostname == '' and port == None:
|
||||||
displaystr = '.'
|
displaystr = '.'
|
||||||
@ -182,9 +182,9 @@ class UIBase:
|
|||||||
s.getnicename(srcrepos),
|
s.getnicename(srcrepos),
|
||||||
s.getnicename(destrepos)))
|
s.getnicename(destrepos)))
|
||||||
|
|
||||||
def validityproblem(s, folder):
|
def validityproblem(s, folder, saved, new):
|
||||||
s.warn("UID validity problem for folder %s; skipping it" % \
|
s.warn("UID validity problem for folder %s (saved %d; got %d); skipping it" % \
|
||||||
folder.getname())
|
(folder.getname(), saved, new))
|
||||||
|
|
||||||
def loadmessagelist(s, repos, folder):
|
def loadmessagelist(s, repos, folder):
|
||||||
if s.verbose > 0:
|
if s.verbose > 0:
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
productname = 'OfflineIMAP'
|
productname = 'OfflineIMAP'
|
||||||
versionstr = "3.99.10"
|
versionstr = "3.99.12"
|
||||||
revno = long('$Rev: 367 $'[6:-2])
|
revno = long('$Rev: 439 $'[6:-2])
|
||||||
revstr = "Rev %d" % revno
|
revstr = "Rev %d" % revno
|
||||||
datestr = '$Date: 2003-04-16 09:23:45 -0500 (Wed, 16 Apr 2003) $'
|
datestr = '$Date: 2003-04-17 16:16:00 -0500 (Thu, 17 Apr 2003) $'
|
||||||
|
|
||||||
versionlist = versionstr.split(".")
|
versionlist = versionstr.split(".")
|
||||||
major = versionlist[0]
|
major = versionlist[0]
|
||||||
|
Loading…
Reference in New Issue
Block a user