]> code.delx.au - offlineimap/blob - offlineimap/accounts.py
Update FSF address
[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 return
195 if not remotefolder.isuidvalidityok():
196 ui.validityproblem(remotefolder, remotefolder.getsaveduidvalidity(),
197 remotefolder.getuidvalidity())
198 return
199 else:
200 localfolder.saveuidvalidity()
201 remotefolder.saveuidvalidity()
202
203 # Load remote folder.
204 ui.loadmessagelist(remoterepos, remotefolder)
205 remotefolder.cachemessagelist()
206 ui.messagelistloaded(remoterepos, remotefolder,
207 len(remotefolder.getmessagelist().keys()))
208
209
210 #
211
212 if not statusfolder.isnewfolder():
213 # Delete local copies of remote messages. This way,
214 # if a message's flag is modified locally but it has been
215 # deleted remotely, we'll delete it locally. Otherwise, we
216 # try to modify a deleted message's flags! This step
217 # need only be taken if a statusfolder is present; otherwise,
218 # there is no action taken *to* the remote repository.
219
220 remotefolder.syncmessagesto_delete(localfolder, [localfolder,
221 statusfolder])
222 ui.syncingmessages(localrepos, localfolder, remoterepos, remotefolder)
223 localfolder.syncmessagesto(statusfolder, [remotefolder, statusfolder])
224
225 # Synchronize remote changes.
226 ui.syncingmessages(remoterepos, remotefolder, localrepos, localfolder)
227 remotefolder.syncmessagesto(localfolder, [localfolder, statusfolder])
228
229 # Make sure the status folder is up-to-date.
230 ui.syncingmessages(localrepos, localfolder, statusrepos, statusfolder)
231 localfolder.syncmessagesto(statusfolder)
232 statusfolder.save()
233