]>
code.delx.au - offlineimap/blob - offlineimap/ui/UIBase.py
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
22 from Queue
import Empty
24 debugtypes
= {'imap': 'IMAP protocol debugging',
25 'maildir': 'Maildir repository debugging',
26 'thread': 'Threading debugging'}
29 def setglobalui(newui
):
37 def __init__(s
, config
, verbose
= 0):
46 ################################################## UTILS
48 """Generic tool called when no other works."""
53 """Log it to disk. Returns true if it wrote something; false
56 s
.logfile
.write("%s: %s\n" % (threading
.currentThread().getName(),
61 def setlogfd(s
, logfd
):
63 logfd
.write("This is %s %s\n" % \
64 (offlineimap
.version
.productname
,
65 offlineimap
.version
.versionstr
))
66 logfd
.write("Python: %s\n" % sys
.version
)
67 logfd
.write("Platform: %s\n" % sys
.platform
)
68 logfd
.write("Args: %s\n" % sys
.argv
)
71 """Display a message."""
72 raise NotImplementedError
74 def warn(s
, msg
, minor
= 0):
76 s
._msg
("warning: " + msg
)
78 s
._msg
("WARNING: " + msg
)
80 def registerthread(s
, account
):
81 """Provides a hint to UIs about which account this particular
82 thread is processing."""
83 if s
.threadaccounts
.has_key(threading
.currentThread()):
84 raise ValueError, "Thread %s already registered (old %s, new %s)" %\
85 (threading
.currentThread().getName(),
86 s
.getthreadaccount(s
), account
)
87 s
.threadaccounts
[threading
.currentThread()] = account
89 def unregisterthread(s
, thr
):
90 """Recognizes a thread has exited."""
91 if s
.threadaccounts
.has_key(thr
):
92 del s
.threadaccounts
[thr
]
94 def getthreadaccount(s
, thr
= None):
96 thr
= threading
.currentThread()
97 if s
.threadaccounts
.has_key(thr
):
98 return s
.threadaccounts
[thr
]
101 def debug(s
, debugtype
, msg
):
102 thisthread
= threading
.currentThread()
103 if s
.debugmessages
.has_key(thisthread
):
104 s
.debugmessages
[thisthread
].append("%s: %s" % (debugtype
, msg
))
106 s
.debugmessages
[thisthread
] = ["%s: %s" % (debugtype
, msg
)]
108 while len(s
.debugmessages
[thisthread
]) > s
.debugmsglen
:
109 s
.debugmessages
[thisthread
] = s
.debugmessages
[thisthread
][1:]
111 if debugtype
in s
.debuglist
:
112 if not s
._log
("DEBUG[%s]: %s" % (debugtype
, msg
)):
113 s
._display
("DEBUG[%s]: %s" % (debugtype
, msg
))
115 def add_debug(s
, debugtype
):
117 if debugtype
in debugtypes
:
118 if not debugtype
in s
.debuglist
:
119 s
.debuglist
.append(debugtype
)
120 s
.debugging(debugtype
)
122 s
.invaliddebug(debugtype
)
124 def debugging(s
, debugtype
):
126 s
._msg
("Now debugging for %s: %s" % (debugtype
, debugtypes
[debugtype
]))
128 def invaliddebug(s
, debugtype
):
129 s
.warn("Invalid debug type: %s" % debugtype
)
132 raise Exception, "Another OfflineIMAP is running with the same metadatadir; exiting."
134 def getnicename(s
, object):
135 prelimname
= str(object.__class
__).split('.')[-1]
136 # Strip off extra stuff.
137 return re
.sub('(Folder|Repository)', '', prelimname
)
140 """Returns true if this UI object is usable in the current
141 environment. For instance, an X GUI would return true if it's
142 being run in X with a valid DISPLAY setting, and false otherwise."""
145 ################################################## INPUT
147 def getpass(s
, accountname
, config
, errmsg
= None):
148 raise NotImplementedError
150 def folderlist(s
, list):
151 return ', '.join(["%s[%s]" % (s
.getnicename(x
), x
.getname()) for x
in list])
153 ################################################## WARNINGS
154 def msgtoreadonly(s
, destfolder
, uid
, content
, flags
):
155 if not (s
.config
.has_option('general', 'ignore-readonly') and s
.config
.getboolean("general", "ignore-readonly")):
156 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." % \
157 (uid
, s
.getnicename(destfolder
), destfolder
.getname()))
159 def flagstoreadonly(s
, destfolder
, uidlist
, flags
):
160 if not (s
.config
.has_option('general', 'ignore-readonly') and s
.config
.getboolean("general", "ignore-readonly")):
161 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." % \
162 (str(uidlist
), s
.getnicename(destfolder
), destfolder
.getname()))
164 def deletereadonly(s
, destfolder
, uidlist
):
165 if not (s
.config
.has_option('general', 'ignore-readonly') and s
.config
.getboolean("general", "ignore-readonly")):
166 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." % \
167 (str(uidlist
), s
.getnicename(destfolder
), destfolder
.getname()))
169 ################################################## MESSAGES
172 """Called when the UI starts. Must be called before any other UI
173 call except isusable(). Displays the copyright banner. This is
174 where the UI should do its setup -- TK, for instance, would
175 create the application window here."""
177 s
._msg
(offlineimap
.version
.banner
)
179 def connecting(s
, hostname
, port
):
185 port
= ":%s" % str(port
)
186 displaystr
= ' to %s%s.' % (hostname
, port
)
187 if hostname
== '' and port
== None:
189 s
._msg
("Establishing connection" + displaystr
)
191 def acct(s
, accountname
):
193 s
._msg
("***** Processing account %s" % accountname
)
195 def acctdone(s
, accountname
):
197 s
._msg
("***** Finished processing account " + accountname
)
199 def syncfolders(s
, srcrepos
, destrepos
):
201 s
._msg
("Copying folder structure from %s to %s" % \
202 (s
.getnicename(srcrepos
), s
.getnicename(destrepos
)))
204 ############################## Folder syncing
205 def syncingfolder(s
, srcrepos
, srcfolder
, destrepos
, destfolder
):
206 """Called when a folder sync operation is started."""
208 s
._msg
("Syncing %s: %s -> %s" % (srcfolder
.getname(),
209 s
.getnicename(srcrepos
),
210 s
.getnicename(destrepos
)))
212 def skippingfolder(s
, folder
):
213 """Called when a folder sync operation is started."""
215 s
._msg
("Skipping %s (not changed)" % folder
.getname())
217 def validityproblem(s
, folder
):
218 s
.warn("UID validity problem for folder %s (repo %s) (saved %d; got %d); skipping it" % \
219 (folder
.getname(), folder
.getrepository().getname(),
220 folder
.getsaveduidvalidity(), folder
.getuidvalidity()))
222 def loadmessagelist(s
, repos
, folder
):
224 s
._msg
("Loading message list for %s[%s]" % (s
.getnicename(repos
),
227 def messagelistloaded(s
, repos
, folder
, count
):
229 s
._msg
("Message list for %s[%s] loaded: %d messages" % \
230 (s
.getnicename(repos
), folder
.getname(), count
))
232 ############################## Message syncing
234 def syncingmessages(s
, sr
, sf
, dr
, df
):
236 s
._msg
("Syncing messages %s[%s] -> %s[%s]" % (s
.getnicename(sr
),
241 def copyingmessage(s
, uid
, src
, destlist
):
243 ds
= s
.folderlist(destlist
)
244 s
._msg
("Copy message %d %s[%s] -> %s" % (uid
, s
.getnicename(src
),
247 def deletingmessage(s
, uid
, destlist
):
249 ds
= s
.folderlist(destlist
)
250 s
._msg
("Deleting message %d in %s" % (uid
, ds
))
252 def deletingmessages(s
, uidlist
, destlist
):
254 ds
= s
.folderlist(destlist
)
255 s
._msg
("Deleting %d messages (%s) in %s" % \
257 ", ".join([str(u
) for u
in uidlist
]),
260 def addingflags(s
, uidlist
, flags
, destlist
):
262 ds
= s
.folderlist(destlist
)
263 s
._msg
("Adding flags %s to %d messages on %s" % \
264 (", ".join(flags
), len(uidlist
), ds
))
266 def deletingflags(s
, uidlist
, flags
, destlist
):
268 ds
= s
.folderlist(destlist
)
269 s
._msg
("Deleting flags %s to %d messages on %s" % \
270 (", ".join(flags
), len(uidlist
), ds
))
272 ################################################## Threads
274 def getThreadDebugLog(s
, thread
):
275 if s
.debugmessages
.has_key(thread
):
276 message
= "\nLast %d debug messages logged for %s prior to exception:\n"\
277 % (len(s
.debugmessages
[thread
]), thread
.getName())
278 message
+= "\n".join(s
.debugmessages
[thread
])
280 message
= "\nNo debug messages were logged for %s." % \
284 def delThreadDebugLog(s
, thread
):
285 if s
.debugmessages
.has_key(thread
):
286 del s
.debugmessages
[thread
]
288 def getThreadExceptionString(s
, thread
):
289 message
= "Thread '%s' terminated with exception:\n%s" % \
290 (thread
.getName(), thread
.getExitStackTrace())
291 message
+= "\n" + s
.getThreadDebugLog(thread
)
294 def threadException(s
, thread
):
295 """Called when a thread has terminated with an exception.
296 The argument is the ExitNotifyThread that has so terminated."""
297 s
._msg
(s
.getThreadExceptionString(thread
))
298 s
.delThreadDebugLog(thread
)
301 def getMainExceptionString(s
):
303 traceback
.print_exc(file = sbuf
)
304 return "Main program terminated with exception:\n" + \
305 sbuf
.getvalue() + "\n" + \
306 s
.getThreadDebugLog(threading
.currentThread())
308 def mainException(s
):
309 s
._msg
(s
.getMainExceptionString())
311 def terminate(s
, exitstatus
= 0, errortitle
= None, errormsg
= None):
312 """Called to terminate the application."""
314 if errortitle
<> None:
315 sys
.stderr
.write('ERROR: %s\n\n%s\n'%(errortitle
, errormsg
))
317 sys
.stderr
.write('%s\n' % errormsg
)
320 def threadExited(s
, thread
):
321 """Called when a thread has exited normally. Many UIs will
323 s
.delThreadDebugLog(thread
)
324 s
.unregisterthread(thread
)
326 ################################################## Hooks
328 def callhook(s
, msg
):
332 ################################################## Other
334 def sleep(s
, sleepsecs
, siglistener
):
335 """This function does not actually output anything, but handles
336 the overall sleep, dealing with updates as necessary. It will,
337 however, call sleeping() which DOES output something.
339 Returns 0 if timeout expired, 1 if there is a request to cancel
340 the timer, and 2 if there is a request to abort the program."""
343 while sleepsecs
> 0 and not abortsleep
:
345 abortsleep
= siglistener
.get_nowait()
346 # retrieved signal while sleeping: 1 means immediately resynch, 2 means immediately die
349 abortsleep
= s
.sleeping(1, sleepsecs
)
351 s
.sleeping(0, 0) # Done sleeping.
354 def sleeping(s
, sleepsecs
, remainingsecs
):
355 """Sleep for sleepsecs, remainingsecs to go.
356 If sleepsecs is 0, indicates we're done sleeping.
358 Return 0 for normal sleep, or 1 to indicate a request
359 to sync immediately."""
360 s
._msg
("Next refresh in %d seconds" % remainingsecs
)
362 time
.sleep(sleepsecs
)