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