|
1 #! /usr/bin/env python |
|
2 |
|
3 """Tkinter-based GUI for websucker. |
|
4 |
|
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 """ |
|
8 |
|
9 from Tkinter import * |
|
10 import websucker |
|
11 import os |
|
12 import threading |
|
13 import Queue |
|
14 import time |
|
15 |
|
16 VERBOSE = 2 |
|
17 |
|
18 |
|
19 try: |
|
20 class Canceled(Exception): |
|
21 "Exception used to cancel run()." |
|
22 except (NameError, TypeError): |
|
23 Canceled = __name__ + ".Canceled" |
|
24 |
|
25 |
|
26 class SuckerThread(websucker.Sucker): |
|
27 |
|
28 stopit = 0 |
|
29 savedir = None |
|
30 rootdir = None |
|
31 |
|
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__), |
|
38 ] |
|
39 |
|
40 def message(self, format, *args): |
|
41 if args: |
|
42 format = format%args |
|
43 ##print format |
|
44 self.msgq.put(format) |
|
45 |
|
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) |
|
58 |
|
59 def savefile(self, text, path): |
|
60 if self.stopit: |
|
61 raise Canceled |
|
62 websucker.Sucker.savefile(self, text, path) |
|
63 |
|
64 def getpage(self, url): |
|
65 if self.stopit: |
|
66 raise Canceled |
|
67 return websucker.Sucker.getpage(self, url) |
|
68 |
|
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 |
|
79 |
|
80 def XXXaddrobot(self, *args): |
|
81 pass |
|
82 |
|
83 def XXXisallowed(self, *args): |
|
84 return 1 |
|
85 |
|
86 |
|
87 class App: |
|
88 |
|
89 sucker = None |
|
90 msgq = None |
|
91 |
|
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) |
|
120 |
|
121 def message(self, text, *args): |
|
122 if args: |
|
123 text = text % args |
|
124 self.status_label.config(text=text) |
|
125 |
|
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) |
|
139 |
|
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() |
|
170 |
|
171 def cancel(self): |
|
172 if self.sucker: |
|
173 self.sucker.stopit = 1 |
|
174 self.message("[canceling...]") |
|
175 |
|
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() |
|
194 |
|
195 |
|
196 class AppArray: |
|
197 |
|
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/") |
|
214 |
|
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) |
|
221 |
|
222 done = 0 |
|
223 |
|
224 def mainloop(self): |
|
225 while not self.done: |
|
226 time.sleep(0.1) |
|
227 self.top.update() |
|
228 |
|
229 def exit(self): |
|
230 for app in self.applist: |
|
231 app.cancel() |
|
232 app.message("[exiting...]") |
|
233 self.done = 1 |
|
234 |
|
235 |
|
236 def main(): |
|
237 AppArray().mainloop() |
|
238 |
|
239 if __name__ == '__main__': |
|
240 main() |