]> code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/accounts.py
/offlineimap/head: changeset 458
[offlineimap] / offlineimap / head / offlineimap / accounts.py
1 # Copyright (C) 2003 John Goerzen
2 # <jgoerzen@complete.org>
3 #
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; either version 2 of the License, or
7 # (at your option) any later version.
8 #
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18 from offlineimap import repository, threadutil, mbnames, CustomConfig
19 from offlineimap.ui import UIBase
20 from offlineimap.threadutil import InstanceLimitedThread, ExitNotifyThread
21 from threading import Event
22 import os
23
24 def getaccountlist(customconfig):
25 return customconfig.getsectionlist('Account')
26
27 def AccountListGenerator(customconfig):
28 return [Account(customconfig, accountname)
29 for accountname in getaccountlist(customconfig)]
30
31 def AccountHashGenerator(customconfig):
32 retval = {}
33 for item in AccountListGenerator(customconfig):
34 retval[item.getname()] = item
35 return retval
36
37 mailboxes = []
38
39 class Account(CustomConfig.ConfigHelperMixin):
40 def __init__(self, config, name):
41 self.config = config
42 self.name = name
43 self.metadatadir = config.getmetadatadir()
44 self.localeval = config.getlocaleval()
45 self.ui = UIBase.getglobalui()
46 self.refreshperiod = self.getconffloat('autorefresh', 0.0)
47 if self.refreshperiod == 0.0:
48 self.refreshperiod = None
49
50 def getlocaleval(self):
51 return self.localeval
52
53 def getconfig(self):
54 return self.config
55
56 def getname(self):
57 return self.name
58
59 def getsection(self):
60 return 'Account ' + self.getname()
61
62 def sleeper(self):
63 """Sleep handler. Returns same value as UIBase.sleep:
64 0 if timeout expired, 1 if there was a request to cancel the timer,
65 and 2 if there is a request to abort the program.
66
67 Also, returns 100 if configured to not sleep at all."""
68
69 if not self.refreshperiod:
70 return 100
71
72 kaobjs = []
73
74 if hasattr(self, 'localrepos'):
75 kaobjs.append(self.localrepos)
76 if hasattr(self, 'remoterepos'):
77 kaobjs.append(self.remoterepos)
78
79 for item in kaobjs:
80 item.startkeepalive()
81
82 refreshperiod = int(self.refreshperiod * 60)
83 sleepresult = self.ui.sleep(refreshperiod)
84 if sleepresult == 2:
85 # Cancel keep-alive, but don't bother terminating threads
86 for item in kaobjs:
87 item.stopkeepalive(abrupt = 1)
88 return sleepresult
89 else:
90 # Cancel keep-alive and wait for thread to terminate.
91 for item in kaobjs:
92 item.stopkeepalive(abrupt = 0)
93 return sleepresult
94
95 class AccountSynchronizationMixin:
96 def syncrunner(self):
97 self.ui.registerthread(self.name)
98 self.ui.acct(self.name)
99 accountmetadata = self.getaccountmeta()
100 if not os.path.exists(accountmetadata):
101 os.mkdir(accountmetadata, 0700)
102
103 self.remoterepos = repository.Base.LoadRepository(self.getconf('remoterepository'), self, 'remote')
104
105 # Connect to the local repository.
106 self.localrepos = repository.Base.LoadRepository(self.getconf('localrepository'), self, 'local')
107
108 # Connect to the local cache.
109 self.statusrepos = repository.LocalStatus.LocalStatusRepository(self.getconf('localrepository'), self)
110
111 if not self.refreshperiod:
112 self.sync()
113 self.ui.acctdone(self.name)
114 return
115 looping = 1
116 while looping:
117 self.sync()
118 looping = self.sleeper() != 2
119 self.ui.acctdone(self.name)
120
121 def getaccountmeta(self):
122 return os.path.join(self.metadatadir, 'Account-' + self.name)
123
124 def sync(self):
125 # We don't need an account lock because syncitall() goes through
126 # each account once, then waits for all to finish.
127 try:
128 remoterepos = self.remoterepos
129 localrepos = self.localrepos
130 statusrepos = self.statusrepos
131 self.ui.syncfolders(remoterepos, localrepos)
132 remoterepos.syncfoldersto(localrepos)
133
134 folderthreads = []
135 for remotefolder in remoterepos.getfolders():
136 thread = InstanceLimitedThread(\
137 instancename = 'FOLDER_' + self.remoterepos.getname(),
138 target = syncfolder,
139 name = "Folder sync %s[%s]" % \
140 (self.name, remotefolder.getvisiblename()),
141 args = (self.name, remoterepos, remotefolder, localrepos,
142 statusrepos))
143 thread.setDaemon(1)
144 thread.start()
145 folderthreads.append(thread)
146 threadutil.threadsreset(folderthreads)
147 mbnames.write()
148 localrepos.holdordropconnections()
149 remoterepos.holdordropconnections()
150 finally:
151 pass
152
153 class SyncableAccount(Account, AccountSynchronizationMixin):
154 pass
155
156 def syncfolder(accountname, remoterepos, remotefolder, localrepos,
157 statusrepos):
158 global mailboxes
159 ui = UIBase.getglobalui()
160 ui.registerthread(accountname)
161 # Load local folder.
162 localfolder = localrepos.\
163 getfolder(remotefolder.getvisiblename().\
164 replace(remoterepos.getsep(), localrepos.getsep()))
165 # Write the mailboxes
166 mbnames.add(accountname, localfolder.getvisiblename())
167 # Load local folder
168 ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder)
169 ui.loadmessagelist(localrepos, localfolder)
170 localfolder.cachemessagelist()
171 ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys()))
172
173
174 # Load status folder.
175 statusfolder = statusrepos.getfolder(remotefolder.getvisiblename().\
176 replace(remoterepos.getsep(),
177 statusrepos.getsep()))
178 if localfolder.getuidvalidity() == None:
179 # This is a new folder, so delete the status cache to be sure
180 # we don't have a conflict.
181 statusfolder.deletemessagelist()
182
183 statusfolder.cachemessagelist()
184
185 # If either the local or the status folder has messages and there is a UID
186 # validity problem, warn and abort. If there are no messages, UW IMAPd
187 # loses UIDVALIDITY. But we don't really need it if both local folders are
188 # empty. So, in that case, just save it off.
189 if len(localfolder.getmessagelist()) or len(statusfolder.getmessagelist()):
190 if not localfolder.isuidvalidityok():
191 ui.validityproblem(localfolder, localfolder.getsaveduidvalidity(),
192 localfolder.getuidvalidity())
193 return
194 if not remotefolder.isuidvalidityok():
195 ui.validityproblem(remotefolder, remotefolder.getsaveduidvalidity(),
196 remotefolder.getuidvalidity())
197 return
198 else:
199 localfolder.saveuidvalidity()
200 remotefolder.saveuidvalidity()
201
202 # Load remote folder.
203 ui.loadmessagelist(remoterepos, remotefolder)
204 remotefolder.cachemessagelist()
205 ui.messagelistloaded(remoterepos, remotefolder,
206 len(remotefolder.getmessagelist().keys()))
207
208
209 #
210
211 if not statusfolder.isnewfolder():
212 # Delete local copies of remote messages. This way,
213 # if a message's flag is modified locally but it has been
214 # deleted remotely, we'll delete it locally. Otherwise, we
215 # try to modify a deleted message's flags! This step
216 # need only be taken if a statusfolder is present; otherwise,
217 # there is no action taken *to* the remote repository.
218
219 remotefolder.syncmessagesto_delete(localfolder, [localfolder,
220 statusfolder])
221 ui.syncingmessages(localrepos, localfolder, remoterepos, remotefolder)
222 localfolder.syncmessagesto(statusfolder, [remotefolder, statusfolder])
223
224 # Synchronize remote changes.
225 ui.syncingmessages(remoterepos, remotefolder, localrepos, localfolder)
226 remotefolder.syncmessagesto(localfolder, [localfolder, statusfolder])
227
228 # Make sure the status folder is up-to-date.
229 ui.syncingmessages(localrepos, localfolder, statusrepos, statusfolder)
230 localfolder.syncmessagesto(statusfolder)
231 statusfolder.save()
232