]>
code.delx.au - offlineimap/blob - offlineimap/ui/UIBase.py
2 # Copyright (C) 2002 John Goerzen
3 # <jgoerzen@complete.org>
5 # Portions Copyright (C) 2007 David Favro <offlineimap@meta-dynamic.com>
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
21 import offlineimap
.version
22 import re
, time
, sys
, traceback
, threading
, thread
23 from StringIO
import StringIO
25 debugtypes
= {'imap': 'IMAP protocol debugging',
26 'maildir': 'Maildir repository debugging',
27 'thread': 'Threading debugging'}
30 def setglobalui(newui
):
38 def __init__(s
, config
, verbose
= 0):
47 ################################################## UTILS
49 """Generic tool called when no other works."""
54 """Log it to disk. Returns true if it wrote something; false
57 s
.logfile
.write("%s: %s\n" % (threading
.currentThread().getName(),
62 def setlogfd(s
, logfd
):
64 logfd
.write("This is %s %s\n" % \
65 (offlineimap
.version
.productname
,
66 offlineimap
.version
.versionstr
))
67 logfd
.write("Python: %s\n" % sys
.version
)
68 logfd
.write("Platform: %s\n" % sys
.platform
)
69 logfd
.write("Args: %s\n" % sys
.argv
)
72 """Display a message."""
73 raise NotImplementedError
75 def warn(s
, msg
, minor
= 0):
77 s
._msg
("warning: " + msg
)
79 s
._msg
("WARNING: " + msg
)
81 def registerthread(s
, account
):
82 """Provides a hint to UIs about which account this particular
83 thread is processing."""
84 if s
.threadaccounts
.has_key(threading
.currentThread()):
85 raise ValueError, "Thread %s already registered (old %s, new %s)" %\
86 (threading
.currentThread().getName(),
87 s
.getthreadaccount(s
), account
)
88 s
.threadaccounts
[threading
.currentThread()] = account
90 def unregisterthread(s
, thr
):
91 """Recognizes a thread has exited."""
92 if s
.threadaccounts
.has_key(thr
):
93 del s
.threadaccounts
[thr
]
95 def getthreadaccount(s
, thr
= None):
97 thr
= threading
.currentThread()
98 if s
.threadaccounts
.has_key(thr
):
99 return s
.threadaccounts
[thr
]
102 def debug(s
, debugtype
, msg
):
103 thisthread
= threading
.currentThread()
104 if s
.debugmessages
.has_key(thisthread
):
105 s
.debugmessages
[thisthread
].append("%s: %s" % (debugtype
, msg
))
107 s
.debugmessages
[thisthread
] = ["%s: %s" % (debugtype
, msg
)]
109 while len(s
.debugmessages
[thisthread
]) > s
.debugmsglen
:
110 s
.debugmessages
[thisthread
] = s
.debugmessages
[thisthread
][1:]
112 if debugtype
in s
.debuglist
:
113 if not s
._log
("DEBUG[%s]: %s" % (debugtype
, msg
)):
114 s
._display
("DEBUG[%s]: %s" % (debugtype
, msg
))
116 def add_debug(s
, debugtype
):
118 if debugtype
in debugtypes
:
119 if not debugtype
in s
.debuglist
:
120 s
.debuglist
.append(debugtype
)
121 s
.debugging(debugtype
)
123 s
.invaliddebug(debugtype
)
125 def debugging(s
, debugtype
):
127 s
._msg
("Now debugging for %s: %s" % (debugtype
, debugtypes
[debugtype
]))
129 def invaliddebug(s
, debugtype
):
130 s
.warn("Invalid debug type: %s" % debugtype
)
133 raise Exception, "Another OfflineIMAP is running with the same metadatadir; exiting."
135 def getnicename(s
, object):
136 prelimname
= str(object.__class
__).split('.')[-1]
137 # Strip off extra stuff.
138 return re
.sub('(Folder|Repository)', '', prelimname
)
141 """Returns true if this UI object is usable in the current
142 environment. For instance, an X GUI would return true if it's
143 being run in X with a valid DISPLAY setting, and false otherwise."""
146 ################################################## INPUT
148 def getpass(s
, accountname
, config
, errmsg
= None):
149 raise NotImplementedError
151 def folderlist(s
, list):
152 return ', '.join(["%s[%s]" % (s
.getnicename(x
), x
.getname()) for x
in list])
154 ################################################## WARNINGS
155 def msgtoreadonly(s
, destfolder
, uid
, content
, flags
):
156 if not (config
.has_option('general', 'ignore-readonly') and config
.getboolean("general", "ignore-readonly")):
157 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." % \
158 (uid
, s
.getnicename(destfolder
), destfolder
.getname()))
160 def flagstoreadonly(s
, destfolder
, uidlist
, flags
):
161 if not (config
.has_option('general', 'ignore-readonly') and config
.getboolean("general", "ignore-readonly")):
162 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." % \
163 (str(uidlist
), s
.getnicename(destfolder
), destfolder
.getname()))
165 def deletereadonly(s
, destfolder
, uidlist
):
166 if not (config
.has_option('general', 'ignore-readonly') and config
.getboolean("general", "ignore-readonly")):
167 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." % \
168 (str(uidlist
), s
.getnicename(destfolder
), destfolder
.getname()))
170 ################################################## MESSAGES
173 """Called when the UI starts. Must be called before any other UI
174 call except isusable(). Displays the copyright banner. This is
175 where the UI should do its setup -- TK, for instance, would
176 create the application window here."""
178 s
._msg
(offlineimap
.version
.banner
)
180 def connecting(s
, hostname
, port
):
186 port
= ":%s" % str(port
)
187 displaystr
= ' to %s%s.' % (hostname
, port
)
188 if hostname
== '' and port
== None:
190 s
._msg
("Establishing connection" + displaystr
)
192 def acct(s
, accountname
):
194 s
._msg
("***** Processing account %s" % accountname
)
196 def acctdone(s
, accountname
):
198 s
._msg
("***** Finished processing account " + accountname
)
200 def syncfolders(s
, srcrepos
, destrepos
):
202 s
._msg
("Copying folder structure from %s to %s" % \
203 (s
.getnicename(srcrepos
), s
.getnicename(destrepos
)))
205 ############################## Folder syncing
206 def syncingfolder(s
, srcrepos
, srcfolder
, destrepos
, destfolder
):
207 """Called when a folder sync operation is started."""
209 s
._msg
("Syncing %s: %s -> %s" % (srcfolder
.getname(),
210 s
.getnicename(srcrepos
),
211 s
.getnicename(destrepos
)))
213 def validityproblem(s
, folder
):
214 s
.warn("UID validity problem for folder %s (repo %s) (saved %d; got %d); skipping it" % \
215 (folder
.getname(), folder
.getrepository().getname(),
216 folder
.getsaveduidvalidity(), folder
.getuidvalidity()))
218 def loadmessagelist(s
, repos
, folder
):
220 s
._msg
("Loading message list for %s[%s]" % (s
.getnicename(repos
),
223 def messagelistloaded(s
, repos
, folder
, count
):
225 s
._msg
("Message list for %s[%s] loaded: %d messages" % \
226 (s
.getnicename(repos
), folder
.getname(), count
))
228 ############################## Message syncing
230 def syncingmessages(s
, sr
, sf
, dr
, df
):
232 s
._msg
("Syncing messages %s[%s] -> %s[%s]" % (s
.getnicename(sr
),
237 def copyingmessage(s
, uid
, src
, destlist
):
239 ds
= s
.folderlist(destlist
)
240 s
._msg
("Copy message %d %s[%s] -> %s" % (uid
, s
.getnicename(src
),
243 def deletingmessage(s
, uid
, destlist
):
245 ds
= s
.folderlist(destlist
)
246 s
._msg
("Deleting message %d in %s" % (uid
, ds
))
248 def deletingmessages(s
, uidlist
, destlist
):
250 ds
= s
.folderlist(destlist
)
251 s
._msg
("Deleting %d messages (%s) in %s" % \
253 ", ".join([str(u
) for u
in uidlist
]),
256 def addingflags(s
, uidlist
, flags
, destlist
):
258 ds
= s
.folderlist(destlist
)
259 s
._msg
("Adding flags %s to %d messages on %s" % \
260 (", ".join(flags
), len(uidlist
), ds
))
262 def deletingflags(s
, uidlist
, flags
, destlist
):
264 ds
= s
.folderlist(destlist
)
265 s
._msg
("Deleting flags %s to %d messages on %s" % \
266 (", ".join(flags
), len(uidlist
), ds
))
268 ################################################## Threads
270 def getThreadDebugLog(s
, thread
):
271 if s
.debugmessages
.has_key(thread
):
272 message
= "\nLast %d debug messages logged for %s prior to exception:\n"\
273 % (len(s
.debugmessages
[thread
]), thread
.getName())
274 message
+= "\n".join(s
.debugmessages
[thread
])
276 message
= "\nNo debug messages were logged for %s." % \
280 def delThreadDebugLog(s
, thread
):
281 if s
.debugmessages
.has_key(thread
):
282 del s
.debugmessages
[thread
]
284 def getThreadExceptionString(s
, thread
):
285 message
= "Thread '%s' terminated with exception:\n%s" % \
286 (thread
.getName(), thread
.getExitStackTrace())
287 message
+= "\n" + s
.getThreadDebugLog(thread
)
290 def threadException(s
, thread
):
291 """Called when a thread has terminated with an exception.
292 The argument is the ExitNotifyThread that has so terminated."""
293 s
._msg
(s
.getThreadExceptionString(thread
))
294 s
.delThreadDebugLog(thread
)
297 def getMainExceptionString(s
):
299 traceback
.print_exc(file = sbuf
)
300 return "Main program terminated with exception:\n" + \
301 sbuf
.getvalue() + "\n" + \
302 s
.getThreadDebugLog(threading
.currentThread())
304 def mainException(s
):
305 s
._msg
(s
.getMainExceptionString())
307 def terminate(s
, exitstatus
= 0, errortitle
= None, errormsg
= None):
308 """Called to terminate the application."""
310 if errortitle
<> None:
311 sys
.stderr
.write('ERROR: %s\n\n%s\n'%(errortitle
, errormsg
))
313 sys
.stderr
.write('%s\n' % errormsg
)
316 def threadExited(s
, thread
):
317 """Called when a thread has exited normally. Many UIs will
319 s
.delThreadDebugLog(thread
)
320 s
.unregisterthread(thread
)
322 ################################################## Other
324 def sleep(s
, sleepsecs
):
325 """This function does not actually output anything, but handles
326 the overall sleep, dealing with updates as necessary. It will,
327 however, call sleeping() which DOES output something.
329 Returns 0 if timeout expired, 1 if there is a request to cancel
330 the timer, and 2 if there is a request to abort the program."""
333 while sleepsecs
> 0 and not abortsleep
:
334 abortsleep
= s
.sleeping(1, sleepsecs
)
336 s
.sleeping(0, 0) # Done sleeping.
339 def sleeping(s
, sleepsecs
, remainingsecs
):
340 """Sleep for sleepsecs, remainingsecs to go.
341 If sleepsecs is 0, indicates we're done sleeping.
343 Return 0 for normal sleep, or 1 to indicate a request
344 to sync immediately."""
345 s
._msg
("Next refresh in %d seconds" % remainingsecs
)
347 time
.sleep(sleepsecs
)