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