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