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