]>
code.delx.au - offlineimap/blob - offlineimap/head/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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 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'}
27 def setglobalui(newui
):
35 def __init__(s
, config
, verbose
= 0):
43 ################################################## UTILS
45 """Generic tool called when no other works."""
46 raise NotImplementedError
48 def warn(s
, msg
, minor
= 0):
50 s
._msg
("warning: " + msg
)
52 s
._msg
("WARNING: " + msg
)
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
62 def unregisterthread(s
, thr
):
63 """Recognizes a thread has exited."""
64 if s
.threadaccounts
.has_key(thr
):
65 del s
.threadaccounts
[thr
]
67 def getthreadaccount(s
, thr
= None):
69 thr
= threading
.currentThread()
70 if s
.threadaccounts
.has_key(thr
):
71 return s
.threadaccounts
[thr
]
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
))
79 s
.debugmessages
[thisthread
] = ["%s: %s" % (debugtype
, msg
)]
81 while len(s
.debugmessages
[thisthread
]) > s
.debugmsglen
:
82 s
.debugmessages
[thisthread
] = s
.debugmessages
[thisthread
][1:]
84 if debugtype
in s
.debuglist
:
85 s
._msg
("DEBUG[%s]: %s" % (debugtype
, msg
))
87 def add_debug(s
, debugtype
):
89 if debugtype
in debugtypes
:
90 if not debugtype
in s
.debuglist
:
91 s
.debuglist
.append(debugtype
)
92 s
.debugging(debugtype
)
94 s
.invaliddebug(debugtype
)
96 def debugging(s
, debugtype
):
98 s
._msg
("Now debugging for %s: %s" % (debugtype
, debugtypes
[debugtype
]))
100 def invaliddebug(s
, debugtype
):
101 s
.warn("Invalid debug type: %s" % debugtype
)
103 def getnicename(s
, object):
104 prelimname
= str(object.__class
__).split('.')[-1]
105 # Strip off extra stuff.
106 return re
.sub('(Folder|Repository)', '', prelimname
)
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."""
114 ################################################## INPUT
116 def getpass(s
, accountname
, config
, errmsg
= None):
117 raise NotImplementedError
119 def folderlist(s
, list):
120 return ', '.join(["%s[%s]" % (s
.getnicename(x
), x
.getname()) for x
in list])
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()))
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()))
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()))
138 ################################################## MESSAGES
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."""
146 s
._msg
(offlineimap
.version
.banner
)
148 def connecting(s
, hostname
, port
):
155 displaystr
= ' to %s%s.' % (hostname
, port
)
156 if hostname
== '' and port
== None:
158 s
._msg
("Establishing connection" + displaystr
)
160 def acct(s
, accountname
):
162 s
._msg
("***** Processing account %s" % accountname
)
164 def acctdone(s
, accountname
):
166 s
._msg
("***** Finished processing account " + accountname
)
168 def syncfolders(s
, srcrepos
, destrepos
):
170 s
._msg
("Copying folder structure from %s to %s" % \
171 (s
.getnicename(srcrepos
), s
.getnicename(destrepos
)))
173 ############################## Folder syncing
174 def syncingfolder(s
, srcrepos
, srcfolder
, destrepos
, destfolder
):
175 """Called when a folder sync operation is started."""
177 s
._msg
("Syncing %s: %s -> %s" % (srcfolder
.getname(),
178 s
.getnicename(srcrepos
),
179 s
.getnicename(destrepos
)))
181 def validityproblem(s
, folder
):
182 s
.warn("UID validity problem for folder %s; skipping it" % \
185 def loadmessagelist(s
, repos
, folder
):
187 s
._msg
("Loading message list for %s[%s]" % (s
.getnicename(repos
),
190 def messagelistloaded(s
, repos
, folder
, count
):
192 s
._msg
("Message list for %s[%s] loaded: %d messages" % \
193 (s
.getnicename(repos
), folder
.getname(), count
))
195 ############################## Message syncing
197 def syncingmessages(s
, sr
, sf
, dr
, df
):
199 s
._msg
("Syncing messages %s[%s] -> %s[%s]" % (s
.getnicename(sr
),
204 def copyingmessage(s
, uid
, src
, destlist
):
206 ds
= s
.folderlist(destlist
)
207 s
._msg
("Copy message %d %s[%s] -> %s" % (uid
, s
.getnicename(src
),
210 def deletingmessage(s
, uid
, destlist
):
212 ds
= s
.folderlist(destlist
)
213 s
._msg
("Deleting message %d in %s" % (uid
, ds
))
215 def deletingmessages(s
, uidlist
, destlist
):
217 ds
= s
.folderlist(destlist
)
218 s
._msg
("Deleting %d messages (%s) in %s" % \
220 ", ".join([str(u
) for u
in uidlist
]),
223 def addingflags(s
, uid
, flags
, destlist
):
225 ds
= s
.folderlist(destlist
)
226 s
._msg
("Adding flags %s to message %d on %s" % \
227 (", ".join(flags
), uid
, ds
))
229 def deletingflags(s
, uid
, flags
, destlist
):
231 ds
= s
.folderlist(destlist
)
232 s
._msg
("Deleting flags %s to message %d on %s" % \
233 (", ".join(flags
), uid
, ds
))
235 ################################################## Threads
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
])
243 message
= "\nNo debug messages were logged for %s." % \
247 def delThreadDebugLog(s
, thread
):
248 if s
.debugmessages
.has_key(thread
):
249 del s
.debugmessages
[thread
]
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
)
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
)
264 def getMainExceptionString(s
):
266 traceback
.print_exc(file = sbuf
)
267 return "Main program terminated with exception:\n" + \
268 sbuf
.getvalue() + "\n" + \
269 s
.getThreadDebugLog(threading
.currentThread())
271 def mainException(s
):
272 s
._msg
(s
.getMainExceptionString())
274 def terminate(s
, exitstatus
= 0):
275 """Called to terminate the application."""
278 def threadExited(s
, thread
):
279 """Called when a thread has exited normally. Many UIs will
281 s
.delThreadDebugLog(thread
)
282 s
.unregisterthread(thread
)
284 ################################################## Other
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.
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."""
295 while sleepsecs
> 0 and not abortsleep
:
296 abortsleep
= s
.sleeping(1, sleepsecs
)
298 s
.sleeping(0, 0) # Done sleeping.
301 def sleeping(s
, sleepsecs
, remainingsecs
):
302 """Sleep for sleepsecs, remainingsecs to go.
303 If sleepsecs is 0, indicates we're done sleeping.
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
)
309 time
.sleep(sleepsecs
)