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