]>
code.delx.au - offlineimap/blob - offlineimap/head/offlineimap/ui/UIBase.py
467f46775a88470a3c251ab501fc70b0a4533ab4
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; version 2 of the License.
9 # This program is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 # GNU General Public License for more details.
14 # You should have received a copy of the GNU General Public License
15 # along with this program; if not, write to the Free Software
16 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 import offlineimap
.version
19 import re
, time
, sys
, traceback
, threading
, thread
20 from StringIO
import StringIO
22 debugtypes
= {'imap': 'IMAP protocol debugging',
23 'maildir': 'Maildir repository debugging'}
26 def setglobalui(newui
):
34 def __init__(s
, config
, verbose
= 0):
42 ################################################## UTILS
44 """Generic tool called when no other works."""
45 raise NotImplementedError
47 def warn(s
, msg
, minor
= 0):
49 s
._msg
("warning: " + msg
)
51 s
._msg
("WARNING: " + msg
)
53 def registerthread(s
, account
):
54 """Provides a hint to UIs about which account this particular
55 thread is processing."""
56 if s
.threadaccounts
.has_key(threading
.currentThread()):
57 raise ValueError, "Thread %s already registered (old %s, new %s)" %\
58 (threading
.currentThread().getName(),
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
)
104 s
.warn("Another OfflineIMAP is running with the same metadatadir; exiting.")
106 def getnicename(s
, object):
107 prelimname
= str(object.__class
__).split('.')[-1]
108 # Strip off extra stuff.
109 return re
.sub('(Folder|Repository)', '', prelimname
)
112 """Returns true if this UI object is usable in the current
113 environment. For instance, an X GUI would return true if it's
114 being run in X with a valid DISPLAY setting, and false otherwise."""
117 ################################################## INPUT
119 def getpass(s
, accountname
, config
, errmsg
= None):
120 raise NotImplementedError
122 def folderlist(s
, list):
123 return ', '.join(["%s[%s]" % (s
.getnicename(x
), x
.getname()) for x
in list])
125 ################################################## WARNINGS
126 def msgtoreadonly(s
, destfolder
, uid
, content
, flags
):
127 if not (config
.has_option('general', 'ignore-readonly') and config
.getboolean("general", "ignore-readonly")):
128 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." % \
129 (uid
, s
.getnicename(destfolder
), destfolder
.getname()))
131 def flagstoreadonly(s
, destfolder
, uidlist
, flags
):
132 if not (config
.has_option('general', 'ignore-readonly') and config
.getboolean("general", "ignore-readonly")):
133 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." % \
134 (str(uidlist
), s
.getnicename(destfolder
), destfolder
.getname()))
136 def deletereadonly(s
, destfolder
, uidlist
):
137 if not (config
.has_option('general', 'ignore-readonly') and config
.getboolean("general", "ignore-readonly")):
138 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." % \
139 (str(uidlist
), s
.getnicename(destfolder
), destfolder
.getname()))
141 ################################################## MESSAGES
144 """Called when the UI starts. Must be called before any other UI
145 call except isusable(). Displays the copyright banner. This is
146 where the UI should do its setup -- TK, for instance, would
147 create the application window here."""
149 s
._msg
(offlineimap
.version
.banner
)
151 def connecting(s
, hostname
, port
):
158 displaystr
= ' to %s%s.' % (hostname
, port
)
159 if hostname
== '' and port
== None:
161 s
._msg
("Establishing connection" + displaystr
)
163 def acct(s
, accountname
):
165 s
._msg
("***** Processing account %s" % accountname
)
167 def acctdone(s
, accountname
):
169 s
._msg
("***** Finished processing account " + accountname
)
171 def syncfolders(s
, srcrepos
, destrepos
):
173 s
._msg
("Copying folder structure from %s to %s" % \
174 (s
.getnicename(srcrepos
), s
.getnicename(destrepos
)))
176 ############################## Folder syncing
177 def syncingfolder(s
, srcrepos
, srcfolder
, destrepos
, destfolder
):
178 """Called when a folder sync operation is started."""
180 s
._msg
("Syncing %s: %s -> %s" % (srcfolder
.getname(),
181 s
.getnicename(srcrepos
),
182 s
.getnicename(destrepos
)))
184 def validityproblem(s
, folder
):
185 s
.warn("UID validity problem for folder %s; skipping it" % \
188 def loadmessagelist(s
, repos
, folder
):
190 s
._msg
("Loading message list for %s[%s]" % (s
.getnicename(repos
),
193 def messagelistloaded(s
, repos
, folder
, count
):
195 s
._msg
("Message list for %s[%s] loaded: %d messages" % \
196 (s
.getnicename(repos
), folder
.getname(), count
))
198 ############################## Message syncing
200 def syncingmessages(s
, sr
, sf
, dr
, df
):
202 s
._msg
("Syncing messages %s[%s] -> %s[%s]" % (s
.getnicename(sr
),
207 def copyingmessage(s
, uid
, src
, destlist
):
209 ds
= s
.folderlist(destlist
)
210 s
._msg
("Copy message %d %s[%s] -> %s" % (uid
, s
.getnicename(src
),
213 def deletingmessage(s
, uid
, destlist
):
215 ds
= s
.folderlist(destlist
)
216 s
._msg
("Deleting message %d in %s" % (uid
, ds
))
218 def deletingmessages(s
, uidlist
, destlist
):
220 ds
= s
.folderlist(destlist
)
221 s
._msg
("Deleting %d messages (%s) in %s" % \
223 ", ".join([str(u
) for u
in uidlist
]),
226 def addingflags(s
, uidlist
, flags
, destlist
):
228 ds
= s
.folderlist(destlist
)
229 s
._msg
("Adding flags %s to %d messages on %s" % \
230 (", ".join(flags
), len(uidlist
), ds
))
232 def deletingflags(s
, uidlist
, flags
, destlist
):
234 ds
= s
.folderlist(destlist
)
235 s
._msg
("Deleting flags %s to %d messages on %s" % \
236 (", ".join(flags
), len(uidlist
), ds
))
238 ################################################## Threads
240 def getThreadDebugLog(s
, thread
):
241 if s
.debugmessages
.has_key(thread
):
242 message
= "\nLast %d debug messages logged for %s prior to exception:\n"\
243 % (len(s
.debugmessages
[thread
]), thread
.getName())
244 message
+= "\n".join(s
.debugmessages
[thread
])
246 message
= "\nNo debug messages were logged for %s." % \
250 def delThreadDebugLog(s
, thread
):
251 if s
.debugmessages
.has_key(thread
):
252 del s
.debugmessages
[thread
]
254 def getThreadExceptionString(s
, thread
):
255 message
= "Thread '%s' terminated with exception:\n%s" % \
256 (thread
.getName(), thread
.getExitStackTrace())
257 message
+= "\n" + s
.getThreadDebugLog(thread
)
260 def threadException(s
, thread
):
261 """Called when a thread has terminated with an exception.
262 The argument is the ExitNotifyThread that has so terminated."""
263 s
._msg
(s
.getThreadExceptionString(thread
))
264 s
.delThreadDebugLog(thread
)
267 def getMainExceptionString(s
):
269 traceback
.print_exc(file = sbuf
)
270 return "Main program terminated with exception:\n" + \
271 sbuf
.getvalue() + "\n" + \
272 s
.getThreadDebugLog(threading
.currentThread())
274 def mainException(s
):
275 s
._msg
(s
.getMainExceptionString())
277 def terminate(s
, exitstatus
= 0):
278 """Called to terminate the application."""
281 def threadExited(s
, thread
):
282 """Called when a thread has exited normally. Many UIs will
284 s
.delThreadDebugLog(thread
)
285 s
.unregisterthread(thread
)
287 ################################################## Other
289 def sleep(s
, sleepsecs
):
290 """This function does not actually output anything, but handles
291 the overall sleep, dealing with updates as necessary. It will,
292 however, call sleeping() which DOES output something.
294 Returns 0 if timeout expired, 1 if there is a request to cancel
295 the timer, and 2 if there is a request to abort the program."""
298 while sleepsecs
> 0 and not abortsleep
:
299 abortsleep
= s
.sleeping(1, sleepsecs
)
301 s
.sleeping(0, 0) # Done sleeping.
304 def sleeping(s
, sleepsecs
, remainingsecs
):
305 """Sleep for sleepsecs, remainingsecs to go.
306 If sleepsecs is 0, indicates we're done sleeping.
308 Return 0 for normal sleep, or 1 to indicate a request
309 to sync immediately."""
310 s
._msg
("Next refresh in %d seconds" % remainingsecs
)
312 time
.sleep(sleepsecs
)