]> code.delx.au - offlineimap/blob - offlineimap/accounts.py
d05928c561b990f153dfb30db59b8febc77cbeec
[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.forgetfolders()
150 remoterepos.forgetfolders()
151 localrepos.holdordropconnections()
152 remoterepos.holdordropconnections()
153 finally:
154 pass
155
156 class SyncableAccount(Account, AccountSynchronizationMixin):
157 pass
158
159 def syncfolder(accountname, remoterepos, remotefolder, localrepos,
160 statusrepos):
161 global mailboxes
162 ui = UIBase.getglobalui()
163 ui.registerthread(accountname)
164 # Load local folder.
165 localfolder = localrepos.\
166 getfolder(remotefolder.getvisiblename().\
167 replace(remoterepos.getsep(), localrepos.getsep()))
168 # Write the mailboxes
169 mbnames.add(accountname, localfolder.getvisiblename())
170 # Load local folder
171 ui.syncingfolder(remoterepos, remotefolder, localrepos, localfolder)
172 ui.loadmessagelist(localrepos, localfolder)
173 localfolder.cachemessagelist()
174 ui.messagelistloaded(localrepos, localfolder, len(localfolder.getmessagelist().keys()))
175
176
177 # Load status folder.
178 statusfolder = statusrepos.getfolder(remotefolder.getvisiblename().\
179 replace(remoterepos.getsep(),
180 statusrepos.getsep()))
181 if localfolder.getuidvalidity() == None:
182 # This is a new folder, so delete the status cache to be sure
183 # we don't have a conflict.
184 statusfolder.deletemessagelist()
185
186 statusfolder.cachemessagelist()
187
188 # If either the local or the status folder has messages and there is a UID
189 # validity problem, warn and abort. If there are no messages, UW IMAPd
190 # loses UIDVALIDITY. But we don't really need it if both local folders are
191 # empty. So, in that case, just save it off.
192 if len(localfolder.getmessagelist()) or len(statusfolder.getmessagelist()):
193 if not localfolder.isuidvalidityok():
194 ui.validityproblem(localfolder)
195 localrepos.restore_atime()
196 return
197 if not remotefolder.isuidvalidityok():
198 ui.validityproblem(remotefolder)
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