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