1#! /usr/bin/env python 2 3"""Tkinter-based GUI for websucker. 4 5Easy use: type or paste source URL and destination directory in 6their respective text boxes, click GO or hit return, and presto. 7""" 8 9from Tkinter import * 10import websucker 11import os 12import threading 13import Queue 14import time 15 16VERBOSE = 2 17 18 19try: 20 class Canceled(Exception): 21 "Exception used to cancel run()." 22except (NameError, TypeError): 23 Canceled = __name__ + ".Canceled" 24 25 26class 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 87class 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 196class 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 236def main(): 237 AppArray().mainloop() 238 239if __name__ == '__main__': 240 main() 241