Merged release21-maint changes.
[python/dscho.git] / Tools / webchecker / wsgui.py
blobc301c6fdaf43810ff6de51cd2f19c42a0897da05
1 #! /usr/bin/env python
3 """Tkinter-based GUI for websucker.
5 Easy use: type or paste source URL and destination directory in
6 their respective text boxes, click GO or hit return, and presto.
7 """
9 from Tkinter import *
10 import Tkinter
11 import string
12 import websucker
13 import sys
14 import os
15 import threading
16 import Queue
17 import time
19 VERBOSE = 2
22 try:
23 class Canceled(Exception):
24 "Exception used to cancel run()."
25 except (NameError, TypeError):
26 Canceled = __name__ + ".Canceled"
29 class SuckerThread(websucker.Sucker):
31 stopit = 0
32 savedir = None
33 rootdir = None
35 def __init__(self, msgq):
36 self.msgq = msgq
37 websucker.Sucker.__init__(self)
38 self.setflags(verbose=VERBOSE)
39 self.urlopener.addheaders = [
40 ('User-agent', 'websucker/%s' % websucker.__version__),
43 def message(self, format, *args):
44 if args:
45 format = format%args
46 ##print format
47 self.msgq.put(format)
49 def run1(self, url):
50 try:
51 try:
52 self.reset()
53 self.addroot(url)
54 self.run()
55 except Canceled:
56 self.message("[canceled]")
57 else:
58 self.message("[done]")
59 finally:
60 self.msgq.put(None)
62 def savefile(self, text, path):
63 if self.stopit:
64 raise Canceled
65 websucker.Sucker.savefile(self, text, path)
67 def getpage(self, url):
68 if self.stopit:
69 raise Canceled
70 return websucker.Sucker.getpage(self, url)
72 def savefilename(self, url):
73 path = websucker.Sucker.savefilename(self, url)
74 if self.savedir:
75 n = len(self.rootdir)
76 if path[:n] == self.rootdir:
77 path = path[n:]
78 while path[:1] == os.sep:
79 path = path[1:]
80 path = os.path.join(self.savedir, path)
81 return path
83 def XXXaddrobot(self, *args):
84 pass
86 def XXXisallowed(self, *args):
87 return 1
90 class App:
92 sucker = None
93 msgq = None
95 def __init__(self, top):
96 self.top = top
97 top.columnconfigure(99, weight=1)
98 self.url_label = Label(top, text="URL:")
99 self.url_label.grid(row=0, column=0, sticky='e')
100 self.url_entry = Entry(top, width=60, exportselection=0)
101 self.url_entry.grid(row=0, column=1, sticky='we',
102 columnspan=99)
103 self.url_entry.focus_set()
104 self.url_entry.bind("<Key-Return>", self.go)
105 self.dir_label = Label(top, text="Directory:")
106 self.dir_label.grid(row=1, column=0, sticky='e')
107 self.dir_entry = Entry(top)
108 self.dir_entry.grid(row=1, column=1, sticky='we',
109 columnspan=99)
110 self.go_button = Button(top, text="Go", command=self.go)
111 self.go_button.grid(row=2, column=1, sticky='w')
112 self.cancel_button = Button(top, text="Cancel",
113 command=self.cancel,
114 state=DISABLED)
115 self.cancel_button.grid(row=2, column=2, sticky='w')
116 self.auto_button = Button(top, text="Paste+Go",
117 command=self.auto)
118 self.auto_button.grid(row=2, column=3, sticky='w')
119 self.status_label = Label(top, text="[idle]")
120 self.status_label.grid(row=2, column=4, sticky='w')
121 self.top.update_idletasks()
122 self.top.grid_propagate(0)
124 def message(self, text, *args):
125 if args:
126 text = text % args
127 self.status_label.config(text=text)
129 def check_msgq(self):
130 while not self.msgq.empty():
131 msg = self.msgq.get()
132 if msg is None:
133 self.go_button.configure(state=NORMAL)
134 self.auto_button.configure(state=NORMAL)
135 self.cancel_button.configure(state=DISABLED)
136 if self.sucker:
137 self.sucker.stopit = 0
138 self.top.bell()
139 else:
140 self.message(msg)
141 self.top.after(100, self.check_msgq)
143 def go(self, event=None):
144 if not self.msgq:
145 self.msgq = Queue.Queue(0)
146 self.check_msgq()
147 if not self.sucker:
148 self.sucker = SuckerThread(self.msgq)
149 if self.sucker.stopit:
150 return
151 self.url_entry.selection_range(0, END)
152 url = self.url_entry.get()
153 url = string.strip(url)
154 if not url:
155 self.top.bell()
156 self.message("[Error: No URL entered]")
157 return
158 self.rooturl = url
159 dir = string.strip(self.dir_entry.get())
160 if not dir:
161 self.sucker.savedir = None
162 else:
163 self.sucker.savedir = dir
164 self.sucker.rootdir = os.path.dirname(
165 websucker.Sucker.savefilename(self, url))
166 self.go_button.configure(state=DISABLED)
167 self.auto_button.configure(state=DISABLED)
168 self.cancel_button.configure(state=NORMAL)
169 self.message( '[running...]')
170 self.sucker.stopit = 0
171 t = threading.Thread(target=self.sucker.run1, args=(url,))
172 t.start()
174 def cancel(self):
175 if self.sucker:
176 self.sucker.stopit = 1
177 self.message("[canceling...]")
179 def auto(self):
180 tries = ['PRIMARY', 'CLIPBOARD']
181 text = ""
182 for t in tries:
183 try:
184 text = self.top.selection_get(selection=t)
185 except TclError:
186 continue
187 text = string.strip(text)
188 if text:
189 break
190 if not text:
191 self.top.bell()
192 self.message("[Error: clipboard is empty]")
193 return
194 self.url_entry.delete(0, END)
195 self.url_entry.insert(0, text)
196 self.go()
199 class AppArray:
201 def __init__(self, top=None):
202 if not top:
203 top = Tk()
204 top.title("websucker GUI")
205 top.iconname("wsgui")
206 top.wm_protocol('WM_DELETE_WINDOW', self.exit)
207 self.top = top
208 self.appframe = Frame(self.top)
209 self.appframe.pack(fill='both')
210 self.applist = []
211 self.exit_button = Button(top, text="Exit", command=self.exit)
212 self.exit_button.pack(side=RIGHT)
213 self.new_button = Button(top, text="New", command=self.addsucker)
214 self.new_button.pack(side=LEFT)
215 self.addsucker()
216 ##self.applist[0].url_entry.insert(END, "http://www.python.org/doc/essays/")
218 def addsucker(self):
219 self.top.geometry("")
220 frame = Frame(self.appframe, borderwidth=2, relief=GROOVE)
221 frame.pack(fill='x')
222 app = App(frame)
223 self.applist.append(app)
225 done = 0
227 def mainloop(self):
228 while not self.done:
229 time.sleep(0.1)
230 self.top.update()
232 def exit(self):
233 for app in self.applist:
234 app.cancel()
235 app.message("[exiting...]")
236 self.done = 1
239 def main():
240 AppArray().mainloop()
242 if __name__ == '__main__':
243 main()