]>
code.delx.au - offlineimap/blob - offlineimap/ui/UIBase.py
f4f0b6e684df5c33096295ed1fa6a0e14b4beeff
2 # Copyright (C) 2002 John Goerzen
3 # <jgoerzen@complete.org>
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.
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.
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 import offlineimap
.version
20 import re
, time
, sys
, traceback
, threading
, thread
21 from StringIO
import StringIO
23 debugtypes
= {'imap': 'IMAP protocol debugging',
24 'maildir': 'Maildir repository debugging',
25 'thread': 'Threading debugging'}
28 def setglobalui(newui
):
36 def __init__(s
, config
, verbose
= 0):
45 ################################################## UTILS
47 """Generic tool called when no other works."""
52 """Log it to disk. Returns true if it wrote something; false
55 s
.logfile
.write("%s: %s\n" % (threading
.currentThread().getName(),
60 def setlogfd(s
, logfd
):
62 logfd
.write("This is %s %s\n" % \
63 (offlineimap
.version
.productname
,
64 offlineimap
.version
.versionstr
))
65 logfd
.write("Python: %s\n" % sys
.version
)
66 logfd
.write("Platform: %s\n" % sys
.platform
)
67 logfd
.write("Args: %s\n" % sys
.argv
)
70 """Display a message."""
71 raise NotImplementedError
73 def warn(s
, msg
, minor
= 0):
75 s
._msg
("warning: " + msg
)
77 s
._msg
("WARNING: " + msg
)
79 def registerthread(s
, account
):
80 """Provides a hint to UIs about which account this particular
81 thread is processing."""
82 if s
.threadaccounts
.has_key(threading
.currentThread()):
83 raise ValueError, "Thread %s already registered (old %s, new %s)" %\
84 (threading
.currentThread().getName(),
85 s
.getthreadaccount(s
), account
)
86 s
.threadaccounts
[threading
.currentThread()] = account
88 def unregisterthread(s
, thr
):
89 """Recognizes a thread has exited."""
90 if s
.threadaccounts
.has_key(thr
):
91 del s
.threadaccounts
[thr
]
93 def getthreadaccount(s
, thr
= None):
95 thr
= threading
.currentThread()
96 if s
.threadaccounts
.has_key(thr
):
97 return s
.threadaccounts
[thr
]
100 def debug(s
, debugtype
, msg
):
101 thisthread
= threading
.currentThread()
102 if s
.debugmessages
.has_key(thisthread
):
103 s
.debugmessages
[thisthread
].append("%s: %s" % (debugtype
, msg
))
105 s
.debugmessages
[thisthread
] = ["%s: %s" % (debugtype
, msg
)]
107 while len(s
.debugmessages
[thisthread
]) > s
.debugmsglen
:
108 s
.debugmessages
[thisthread
] = s
.debugmessages
[thisthread
][1:]
110 if debugtype
in s
.debuglist
:
111 if not s
._log
("DEBUG[%s]: %s" % (debugtype
, msg
)):
112 s
._display
("DEBUG[%s]: %s" % (debugtype
, msg
))
114 def add_debug(s
, debugtype
):
116 if debugtype
in debugtypes
:
117 if not debugtype
in s
.debuglist
:
118 s
.debuglist
.append(debugtype
)
119 s
.debugging(debugtype
)
121 s
.invaliddebug(debugtype
)
123 def debugging(s
, debugtype
):
125 s
._msg
("Now debugging for %s: %s" % (debugtype
, debugtypes
[debugtype
]))
127 def invaliddebug(s
, debugtype
):
128 s
.warn("Invalid debug type: %s" % debugtype
)
131 raise Exception, "Another OfflineIMAP is running with the same metadatadir; exiting."
133 def getnicename(s
, object):
134 prelimname
= str(object.__class
__).split('.')[-1]
135 # Strip off extra stuff.
136 return re
.sub('(Folder|Repository)', '', prelimname
)
139 """Returns true if this UI object is usable in the current
140 environment. For instance, an X GUI would return true if it's
141 being run in X with a valid DISPLAY setting, and false otherwise."""
144 ################################################## INPUT
146 def getpass(s
, accountname
, config
, errmsg
= None):
147 raise NotImplementedError
149 def folderlist(s
, list):
150 return ', '.join(["%s[%s]" % (s
.getnicename(x
), x
.getname()) for x
in list])
152 ################################################## WARNINGS
153 def msgtoreadonly(s
, destfolder
, uid
, content
, flags
):
154 if not (s
.config
.has_option('general', 'ignore-readonly') and s
.config
.getboolean("general", "ignore-readonly")):
155 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." % \
156 (uid
, s
.getnicename(destfolder
), destfolder
.getname()))
158 def flagstoreadonly(s
, destfolder
, uidlist
, flags
):
159 if not (s
.config
.has_option('general', 'ignore-readonly') and s
.config
.getboolean("general", "ignore-readonly")):
160 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." % \
161 (str(uidlist
), s
.getnicename(destfolder
), destfolder
.getname()))
163 def deletereadonly(s
, destfolder
, uidlist
):
164 if not (s
.config
.has_option('general', 'ignore-readonly') and s
.config
.getboolean("general", "ignore-readonly")):
165 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." % \
166 (str(uidlist
), s
.getnicename(destfolder
), destfolder
.getname()))
168 ################################################## MESSAGES
171 """Called when the UI starts. Must be called before any other UI
172 call except isusable(). Displays the copyright banner. This is
173 where the UI should do its setup -- TK, for instance, would
174 create the application window here."""
176 s
._msg
(offlineimap
.version
.banner
)
178 def connecting(s
, hostname
, port
):
184 port
= ":%s" % str(port
)
185 displaystr
= ' to %s%s.' % (hostname
, port
)
186 if hostname
== '' and port
== None:
188 s
._msg
("Establishing connection" + displaystr
)
190 def acct(s
, accountname
):
192 s
._msg
("***** Processing account %s" % accountname
)
194 def acctdone(s
, accountname
):
196 s
._msg
("***** Finished processing account " + accountname
)
198 def syncfolders(s
, srcrepos
, destrepos
):
200 s
._msg
("Copying folder structure between %s and %s" % \
201 (s
.getnicename(srcrepos
), s
.getnicename(destrepos
)))
203 ############################## Folder syncing
204 def syncingfolder(s
, srcrepos
, srcfolder
, destrepos
, destfolder
):
205 """Called when a folder sync operation is started."""
207 s
._msg
("Syncing %s: %s -> %s" % (srcfolder
.getname(),
208 s
.getnicename(srcrepos
),
209 s
.getnicename(destrepos
)))
211 def skippingfolder(s
, folder
):
212 """Called when a folder sync operation is started."""
214 s
._msg
("Skipping %s (not changed)" % folder
.getname())
216 def validityproblem(s
, folder
):
217 s
.warn("UID validity problem for folder %s (repo %s) (saved %d; got %d); skipping it" % \
218 (folder
.getname(), folder
.getrepository().getname(),
219 folder
.getsaveduidvalidity(), folder
.getuidvalidity()))
221 def ignorefolder(s
, foldername
, here
, there
):
222 s
.warn("Folder %s disappeared from %s; skipping it" % \
223 (foldername
, there
.getname()))
225 def loadmessagelist(s
, repos
, folder
):
227 s
._msg
("Loading message list for %s[%s]" % (s
.getnicename(repos
),
230 def messagelistloaded(s
, repos
, folder
, count
):
232 s
._msg
("Message list for %s[%s] loaded: %d messages" % \
233 (s
.getnicename(repos
), folder
.getname(), count
))
235 ############################## Message syncing
237 def syncingmessages(s
, sr
, sf
, dr
, df
):
239 s
._msg
("Syncing messages %s[%s] -> %s[%s]" % (s
.getnicename(sr
),
244 def copyingmessage(s
, uid
, src
, destlist
):
246 ds
= s
.folderlist(destlist
)
247 s
._msg
("Copy message %d %s[%s] -> %s" % (uid
, s
.getnicename(src
),
250 def deletingmessage(s
, uid
, destlist
):
252 ds
= s
.folderlist(destlist
)
253 s
._msg
("Deleting message %d in %s" % (uid
, ds
))
255 def deletingmessages(s
, uidlist
, destlist
):
257 ds
= s
.folderlist(destlist
)
258 s
._msg
("Deleting %d messages (%s) in %s" % \
260 ", ".join([str(u
) for u
in uidlist
]),
263 def addingflags(s
, uidlist
, flags
, destlist
):
265 ds
= s
.folderlist(destlist
)
266 s
._msg
("Adding flags %s to %d messages on %s" % \
267 (", ".join(flags
), len(uidlist
), ds
))
269 def deletingflags(s
, uidlist
, flags
, destlist
):
271 ds
= s
.folderlist(destlist
)
272 s
._msg
("Deleting flags %s to %d messages on %s" % \
273 (", ".join(flags
), len(uidlist
), ds
))
275 ################################################## Threads
277 def getThreadDebugLog(s
, thread
):
278 if s
.debugmessages
.has_key(thread
):
279 message
= "\nLast %d debug messages logged for %s prior to exception:\n"\
280 % (len(s
.debugmessages
[thread
]), thread
.getName())
281 message
+= "\n".join(s
.debugmessages
[thread
])
283 message
= "\nNo debug messages were logged for %s." % \
287 def delThreadDebugLog(s
, thread
):
288 if s
.debugmessages
.has_key(thread
):
289 del s
.debugmessages
[thread
]
291 def getThreadExceptionString(s
, thread
):
292 message
= "Thread '%s' terminated with exception:\n%s" % \
293 (thread
.getName(), thread
.getExitStackTrace())
294 message
+= "\n" + s
.getThreadDebugLog(thread
)
297 def threadException(s
, thread
):
298 """Called when a thread has terminated with an exception.
299 The argument is the ExitNotifyThread that has so terminated."""
300 s
._msg
(s
.getThreadExceptionString(thread
))
301 s
.delThreadDebugLog(thread
)
304 def getMainExceptionString(s
):
306 traceback
.print_exc(file = sbuf
)
307 return "Main program terminated with exception:\n" + \
308 sbuf
.getvalue() + "\n" + \
309 s
.getThreadDebugLog(threading
.currentThread())
311 def mainException(s
):
312 s
._msg
(s
.getMainExceptionString())
314 def terminate(s
, exitstatus
= 0, errortitle
= None, errormsg
= None):
315 """Called to terminate the application."""
317 if errortitle
<> None:
318 sys
.stderr
.write('ERROR: %s\n\n%s\n'%(errortitle
, errormsg
))
320 sys
.stderr
.write('%s\n' % errormsg
)
323 def threadExited(s
, thread
):
324 """Called when a thread has exited normally. Many UIs will
326 s
.delThreadDebugLog(thread
)
327 s
.unregisterthread(thread
)
329 ################################################## Other
331 def sleep(s
, sleepsecs
):
332 """This function does not actually output anything, but handles
333 the overall sleep, dealing with updates as necessary. It will,
334 however, call sleeping() which DOES output something.
336 Returns 0 if timeout expired, 1 if there is a request to cancel
337 the timer, and 2 if there is a request to abort the program."""
340 while sleepsecs
> 0 and not abortsleep
:
341 abortsleep
= s
.sleeping(1, sleepsecs
)
343 s
.sleeping(0, 0) # Done sleeping.
346 def sleeping(s
, sleepsecs
, remainingsecs
):
347 """Sleep for sleepsecs, remainingsecs to go.
348 If sleepsecs is 0, indicates we're done sleeping.
350 Return 0 for normal sleep, or 1 to indicate a request
351 to sync immediately."""
352 s
._msg
("Next refresh in %d seconds" % remainingsecs
)
354 time
.sleep(sleepsecs
)