]> code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/ui/UIBase.py
467f46775a88470a3c251ab501fc70b0a4533ab4
[offlineimap] / offlineimap / head / offlineimap / ui / UIBase.py
1 # UI base class
2 # Copyright (C) 2002 John Goerzen
3 # <jgoerzen@complete.org>
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; version 2 of the License.
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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
18 import offlineimap.version
19 import re, time, sys, traceback, threading, thread
20 from StringIO import StringIO
21
22 debugtypes = {'imap': 'IMAP protocol debugging',
23 'maildir': 'Maildir repository debugging'}
24
25 globalui = None
26 def setglobalui(newui):
27 global globalui
28 globalui = newui
29 def getglobalui():
30 global globalui
31 return globalui
32
33 class UIBase:
34 def __init__(s, config, verbose = 0):
35 s.verbose = verbose
36 s.config = config
37 s.debuglist = []
38 s.debugmessages = {}
39 s.debugmsglen = 50
40 s.threadaccounts = {}
41
42 ################################################## UTILS
43 def _msg(s, msg):
44 """Generic tool called when no other works."""
45 raise NotImplementedError
46
47 def warn(s, msg, minor = 0):
48 if minor:
49 s._msg("warning: " + msg)
50 else:
51 s._msg("WARNING: " + msg)
52
53 def registerthread(s, account):
54 """Provides a hint to UIs about which account this particular
55 thread is processing."""
56 if s.threadaccounts.has_key(threading.currentThread()):
57 raise ValueError, "Thread %s already registered (old %s, new %s)" %\
58 (threading.currentThread().getName(),
59 s.getthreadaccount(s), account)
60 s.threadaccounts[threading.currentThread()] = account
61
62 def unregisterthread(s, thr):
63 """Recognizes a thread has exited."""
64 if s.threadaccounts.has_key(thr):
65 del s.threadaccounts[thr]
66
67 def getthreadaccount(s, thr = None):
68 if not thr:
69 thr = threading.currentThread()
70 if s.threadaccounts.has_key(thr):
71 return s.threadaccounts[thr]
72 return '*Control'
73
74 def debug(s, debugtype, msg):
75 thisthread = threading.currentThread()
76 if s.debugmessages.has_key(thisthread):
77 s.debugmessages[thisthread].append("%s: %s" % (debugtype, msg))
78 else:
79 s.debugmessages[thisthread] = ["%s: %s" % (debugtype, msg)]
80
81 while len(s.debugmessages[thisthread]) > s.debugmsglen:
82 s.debugmessages[thisthread] = s.debugmessages[thisthread][1:]
83
84 if debugtype in s.debuglist:
85 s._msg("DEBUG[%s]: %s" % (debugtype, msg))
86
87 def add_debug(s, debugtype):
88 global debugtypes
89 if debugtype in debugtypes:
90 if not debugtype in s.debuglist:
91 s.debuglist.append(debugtype)
92 s.debugging(debugtype)
93 else:
94 s.invaliddebug(debugtype)
95
96 def debugging(s, debugtype):
97 global debugtypes
98 s._msg("Now debugging for %s: %s" % (debugtype, debugtypes[debugtype]))
99
100 def invaliddebug(s, debugtype):
101 s.warn("Invalid debug type: %s" % debugtype)
102
103 def locked(s):
104 s.warn("Another OfflineIMAP is running with the same metadatadir; exiting.")
105
106 def getnicename(s, object):
107 prelimname = str(object.__class__).split('.')[-1]
108 # Strip off extra stuff.
109 return re.sub('(Folder|Repository)', '', prelimname)
110
111 def isusable(s):
112 """Returns true if this UI object is usable in the current
113 environment. For instance, an X GUI would return true if it's
114 being run in X with a valid DISPLAY setting, and false otherwise."""
115 return 1
116
117 ################################################## INPUT
118
119 def getpass(s, accountname, config, errmsg = None):
120 raise NotImplementedError
121
122 def folderlist(s, list):
123 return ', '.join(["%s[%s]" % (s.getnicename(x), x.getname()) for x in list])
124
125 ################################################## WARNINGS
126 def msgtoreadonly(s, destfolder, uid, content, flags):
127 if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
128 s.warn("Attempted to synchronize message %d to folder %s[%s], but that folder is read-only. The message will not be copied to that folder." % \
129 (uid, s.getnicename(destfolder), destfolder.getname()))
130
131 def flagstoreadonly(s, destfolder, uidlist, flags):
132 if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
133 s.warn("Attempted to modify flags for messages %s in folder %s[%s], but that folder is read-only. No flags have been modified for that message." % \
134 (str(uidlist), s.getnicename(destfolder), destfolder.getname()))
135
136 def deletereadonly(s, destfolder, uidlist):
137 if not (config.has_option('general', 'ignore-readonly') and config.getboolean("general", "ignore-readonly")):
138 s.warn("Attempted to delete messages %s in folder %s[%s], but that folder is read-only. No messages have been deleted in that folder." % \
139 (str(uidlist), s.getnicename(destfolder), destfolder.getname()))
140
141 ################################################## MESSAGES
142
143 def init_banner(s):
144 """Called when the UI starts. Must be called before any other UI
145 call except isusable(). Displays the copyright banner. This is
146 where the UI should do its setup -- TK, for instance, would
147 create the application window here."""
148 if s.verbose >= 0:
149 s._msg(offlineimap.version.banner)
150
151 def connecting(s, hostname, port):
152 if s.verbose < 0:
153 return
154 if hostname == None:
155 hostname = ''
156 if port != None:
157 port = ":%d" % port
158 displaystr = ' to %s%s.' % (hostname, port)
159 if hostname == '' and port == None:
160 displaystr = '.'
161 s._msg("Establishing connection" + displaystr)
162
163 def acct(s, accountname):
164 if s.verbose >= 0:
165 s._msg("***** Processing account %s" % accountname)
166
167 def acctdone(s, accountname):
168 if s.verbose >= 0:
169 s._msg("***** Finished processing account " + accountname)
170
171 def syncfolders(s, srcrepos, destrepos):
172 if s.verbose >= 0:
173 s._msg("Copying folder structure from %s to %s" % \
174 (s.getnicename(srcrepos), s.getnicename(destrepos)))
175
176 ############################## Folder syncing
177 def syncingfolder(s, srcrepos, srcfolder, destrepos, destfolder):
178 """Called when a folder sync operation is started."""
179 if s.verbose >= 0:
180 s._msg("Syncing %s: %s -> %s" % (srcfolder.getname(),
181 s.getnicename(srcrepos),
182 s.getnicename(destrepos)))
183
184 def validityproblem(s, folder):
185 s.warn("UID validity problem for folder %s; skipping it" % \
186 folder.getname())
187
188 def loadmessagelist(s, repos, folder):
189 if s.verbose > 0:
190 s._msg("Loading message list for %s[%s]" % (s.getnicename(repos),
191 folder.getname()))
192
193 def messagelistloaded(s, repos, folder, count):
194 if s.verbose > 0:
195 s._msg("Message list for %s[%s] loaded: %d messages" % \
196 (s.getnicename(repos), folder.getname(), count))
197
198 ############################## Message syncing
199
200 def syncingmessages(s, sr, sf, dr, df):
201 if s.verbose > 0:
202 s._msg("Syncing messages %s[%s] -> %s[%s]" % (s.getnicename(sr),
203 sf.getname(),
204 s.getnicename(dr),
205 df.getname()))
206
207 def copyingmessage(s, uid, src, destlist):
208 if s.verbose >= 0:
209 ds = s.folderlist(destlist)
210 s._msg("Copy message %d %s[%s] -> %s" % (uid, s.getnicename(src),
211 src.getname(), ds))
212
213 def deletingmessage(s, uid, destlist):
214 if s.verbose >= 0:
215 ds = s.folderlist(destlist)
216 s._msg("Deleting message %d in %s" % (uid, ds))
217
218 def deletingmessages(s, uidlist, destlist):
219 if s.verbose >= 0:
220 ds = s.folderlist(destlist)
221 s._msg("Deleting %d messages (%s) in %s" % \
222 (len(uidlist),
223 ", ".join([str(u) for u in uidlist]),
224 ds))
225
226 def addingflags(s, uidlist, flags, destlist):
227 if s.verbose >= 0:
228 ds = s.folderlist(destlist)
229 s._msg("Adding flags %s to %d messages on %s" % \
230 (", ".join(flags), len(uidlist), ds))
231
232 def deletingflags(s, uidlist, flags, destlist):
233 if s.verbose >= 0:
234 ds = s.folderlist(destlist)
235 s._msg("Deleting flags %s to %d messages on %s" % \
236 (", ".join(flags), len(uidlist), ds))
237
238 ################################################## Threads
239
240 def getThreadDebugLog(s, thread):
241 if s.debugmessages.has_key(thread):
242 message = "\nLast %d debug messages logged for %s prior to exception:\n"\
243 % (len(s.debugmessages[thread]), thread.getName())
244 message += "\n".join(s.debugmessages[thread])
245 else:
246 message = "\nNo debug messages were logged for %s." % \
247 thread.getName()
248 return message
249
250 def delThreadDebugLog(s, thread):
251 if s.debugmessages.has_key(thread):
252 del s.debugmessages[thread]
253
254 def getThreadExceptionString(s, thread):
255 message = "Thread '%s' terminated with exception:\n%s" % \
256 (thread.getName(), thread.getExitStackTrace())
257 message += "\n" + s.getThreadDebugLog(thread)
258 return message
259
260 def threadException(s, thread):
261 """Called when a thread has terminated with an exception.
262 The argument is the ExitNotifyThread that has so terminated."""
263 s._msg(s.getThreadExceptionString(thread))
264 s.delThreadDebugLog(thread)
265 s.terminate(100)
266
267 def getMainExceptionString(s):
268 sbuf = StringIO()
269 traceback.print_exc(file = sbuf)
270 return "Main program terminated with exception:\n" + \
271 sbuf.getvalue() + "\n" + \
272 s.getThreadDebugLog(threading.currentThread())
273
274 def mainException(s):
275 s._msg(s.getMainExceptionString())
276
277 def terminate(s, exitstatus = 0):
278 """Called to terminate the application."""
279 sys.exit(exitstatus)
280
281 def threadExited(s, thread):
282 """Called when a thread has exited normally. Many UIs will
283 just ignore this."""
284 s.delThreadDebugLog(thread)
285 s.unregisterthread(thread)
286
287 ################################################## Other
288
289 def sleep(s, sleepsecs):
290 """This function does not actually output anything, but handles
291 the overall sleep, dealing with updates as necessary. It will,
292 however, call sleeping() which DOES output something.
293
294 Returns 0 if timeout expired, 1 if there is a request to cancel
295 the timer, and 2 if there is a request to abort the program."""
296
297 abortsleep = 0
298 while sleepsecs > 0 and not abortsleep:
299 abortsleep = s.sleeping(1, sleepsecs)
300 sleepsecs -= 1
301 s.sleeping(0, 0) # Done sleeping.
302 return abortsleep
303
304 def sleeping(s, sleepsecs, remainingsecs):
305 """Sleep for sleepsecs, remainingsecs to go.
306 If sleepsecs is 0, indicates we're done sleeping.
307
308 Return 0 for normal sleep, or 1 to indicate a request
309 to sync immediately."""
310 s._msg("Next refresh in %d seconds" % remainingsecs)
311 if sleepsecs > 0:
312 time.sleep(sleepsecs)
313 return 0