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