1 # Copyright (C) 2003 John Goerzen
2 # <jgoerzen@complete.org>
4 # This program is free software; you can redistribute it and/or modify
5 # it under the terms of the GNU General Public License as published by
6 # the Free Software Foundation; version 2 of the License.
8 # This program is distributed in the hope that it will be useful,
9 # but WITHOUT ANY WARRANTY; without even the implied warranty of
10 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 # GNU General Public License for more details.
13 # You should have received a copy of the GNU General Public License
14 # along with this program; if not, write to the Free Software
15 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 from offlineimap
import imapserver
, repository
, threadutil
, mbnames
18 from offlineimap
.ui
import UIBase
19 from offlineimap
.threadutil
import InstanceLimitedThread
, ExitNotifyThread
20 from threading
import Event
26 def __init__(self
, config
, name
):
29 self
.metadatadir
= config
.getmetadatadir()
30 self
.localeval
= config
.getlocaleval()
31 self
.server
= imapserver
.ConfigedIMAPServer(config
, self
.name
)
32 self
.ui
= UIBase
.getglobalui()
33 if self
.config
.has_option(self
.name
, 'autorefresh'):
34 self
.refreshperiod
= self
.config
.getint(self
.name
, 'autorefresh')
36 self
.refreshperiod
= None
37 self
.hold
= self
.config
.has_option(self
.name
, 'holdconnectionopen') \
38 and self
.config
.getboolean(self
.name
, 'holdconnectionopen')
39 if self
.config
.has_option(self
.name
, 'keepalive'):
40 self
.keepalive
= self
.config
.getint(self
.name
, 'keepalive')
44 def getconf(self
, option
, default
= None):
46 return self
.config
.get(self
.name
, option
)
48 return self
.config
.getdefault(self
.name
, option
,
52 """Sleep handler. Returns same value as UIBase.sleep:
53 0 if timeout expired, 1 if there was a request to cancel the timer,
54 and 2 if there is a request to abort the program.
56 Also, returns 100 if configured to not sleep at all."""
58 if not self
.refreshperiod
:
60 refreshperiod
= self
.refreshperiod
* 60
63 kathread
= ExitNotifyThread(target
= self
.server
.keepalive
,
64 name
= "Keep alive " + self
.name
,
65 args
= (self
.keepalive
, kaevent
))
68 sleepresult
= self
.ui
.sleep(refreshperiod
)
70 # Cancel keep-alive, but don't bother terminating threads
75 # Cancel keep-alive and wait for thread to terminate.
81 class AccountSynchronizationMixin
:
83 self
.ui
.registerthread(self
.name
)
84 self
.ui
.acct(self
.name
)
85 if not self
.refreshperiod
:
87 self
.ui
.acctdone(self
.name
)
92 looping
= self
.sleeper() != 2
93 self
.ui
.acctdone(self
.name
)
96 # We don't need an account lock because syncitall() goes through
97 # each account once, then waits for all to finish.
99 accountmetadata
= os
.path
.join(self
.metadatadir
, self
.name
)
100 if not os
.path
.exists(accountmetadata
):
101 os
.mkdir(accountmetadata
, 0700)
103 remoterepos
= repository
.IMAP
.IMAPRepository(self
.config
,
108 # Connect to the Maildirs.
109 localrepos
= repository
.Maildir
.MaildirRepository(os
.path
.expanduser(self
.config
.get(self
.name
, "localfolders")), self
.name
, self
.config
)
111 # Connect to the local cache.
112 statusrepos
= repository
.LocalStatus
.LocalStatusRepository(accountmetadata
, self
.name
)
114 self
.ui
.syncfolders(remoterepos
, localrepos
)
115 remoterepos
.syncfoldersto(localrepos
)
118 for remotefolder
in remoterepos
.getfolders():
119 thread
= InstanceLimitedThread(\
120 instancename
= 'FOLDER_' + self
.name
,
122 name
= "Folder sync %s[%s]" % \
123 (self
.name
, remotefolder
.getvisiblename()),
124 args
= (self
.name
, remoterepos
, remotefolder
, localrepos
,
128 folderthreads
.append(thread
)
129 threadutil
.threadsreset(folderthreads
)
136 class SyncableAccount(Account
, AccountSynchronizationMixin
):
139 def syncfolder(accountname
, remoterepos
, remotefolder
, localrepos
,
142 ui
= UIBase
.getglobalui()
143 ui
.registerthread(accountname
)
145 localfolder
= localrepos
.\
146 getfolder(remotefolder
.getvisiblename().\
147 replace(remoterepos
.getsep(), localrepos
.getsep()))
148 # Write the mailboxes
149 mbnames
.add(accountname
, localfolder
.getvisiblename())
151 ui
.syncingfolder(remoterepos
, remotefolder
, localrepos
, localfolder
)
152 ui
.loadmessagelist(localrepos
, localfolder
)
153 localfolder
.cachemessagelist()
154 ui
.messagelistloaded(localrepos
, localfolder
, len(localfolder
.getmessagelist().keys()))
157 # Load status folder.
158 statusfolder
= statusrepos
.getfolder(remotefolder
.getvisiblename().\
159 replace(remoterepos
.getsep(),
160 statusrepos
.getsep()))
161 if localfolder
.getuidvalidity() == None:
162 # This is a new folder, so delete the status cache to be sure
163 # we don't have a conflict.
164 statusfolder
.deletemessagelist()
166 statusfolder
.cachemessagelist()
169 # If either the local or the status folder has messages and
170 # there is a UID validity problem, warn and abort.
171 # If there are no messages, UW IMAPd loses UIDVALIDITY.
172 # But we don't really need it if both local folders are empty.
173 # So, in that case, save it off.
174 if (len(localfolder
.getmessagelist()) or \
175 len(statusfolder
.getmessagelist())) and \
176 not localfolder
.isuidvalidityok(remotefolder
):
177 ui
.validityproblem(remotefolder
)
180 localfolder
.saveuidvalidity(remotefolder
.getuidvalidity())
182 # Load remote folder.
183 ui
.loadmessagelist(remoterepos
, remotefolder
)
184 remotefolder
.cachemessagelist()
185 ui
.messagelistloaded(remoterepos
, remotefolder
,
186 len(remotefolder
.getmessagelist().keys()))
191 if not statusfolder
.isnewfolder():
192 # Delete local copies of remote messages. This way,
193 # if a message's flag is modified locally but it has been
194 # deleted remotely, we'll delete it locally. Otherwise, we
195 # try to modify a deleted message's flags! This step
196 # need only be taken if a statusfolder is present; otherwise,
197 # there is no action taken *to* the remote repository.
199 remotefolder
.syncmessagesto_delete(localfolder
, [localfolder
,
201 ui
.syncingmessages(localrepos
, localfolder
, remoterepos
, remotefolder
)
202 localfolder
.syncmessagesto(statusfolder
, [remotefolder
, statusfolder
])
204 # Synchronize remote changes.
205 ui
.syncingmessages(remoterepos
, remotefolder
, localrepos
, localfolder
)
206 remotefolder
.syncmessagesto(localfolder
, [localfolder
, statusfolder
])
208 # Make sure the status folder is up-to-date.
209 ui
.syncingmessages(localrepos
, localfolder
, statusrepos
, statusfolder
)
210 localfolder
.syncmessagesto(statusfolder
)