#! /usr/bin/env python | |
"""Tkinter-based GUI for websucker. | |
Easy use: type or paste source URL and destination directory in | |
their respective text boxes, click GO or hit return, and presto. | |
""" | |
from Tkinter import * | |
import websucker | |
import os | |
import threading | |
import Queue | |
import time | |
VERBOSE = 2 | |
try: | |
class Canceled(Exception): | |
"Exception used to cancel run()." | |
except (NameError, TypeError): | |
Canceled = __name__ + ".Canceled" | |
class SuckerThread(websucker.Sucker): | |
stopit = 0 | |
savedir = None | |
rootdir = None | |
def __init__(self, msgq): | |
self.msgq = msgq | |
websucker.Sucker.__init__(self) | |
self.setflags(verbose=VERBOSE) | |
self.urlopener.addheaders = [ | |
('User-agent', 'websucker/%s' % websucker.__version__), | |
] | |
def message(self, format, *args): | |
if args: | |
format = format%args | |
##print format | |
self.msgq.put(format) | |
def run1(self, url): | |
try: | |
try: | |
self.reset() | |
self.addroot(url) | |
self.run() | |
except Canceled: | |
self.message("[canceled]") | |
else: | |
self.message("[done]") | |
finally: | |
self.msgq.put(None) | |
def savefile(self, text, path): | |
if self.stopit: | |
raise Canceled | |
websucker.Sucker.savefile(self, text, path) | |
def getpage(self, url): | |
if self.stopit: | |
raise Canceled | |
return websucker.Sucker.getpage(self, url) | |
def savefilename(self, url): | |
path = websucker.Sucker.savefilename(self, url) | |
if self.savedir: | |
n = len(self.rootdir) | |
if path[:n] == self.rootdir: | |
path = path[n:] | |
while path[:1] == os.sep: | |
path = path[1:] | |
path = os.path.join(self.savedir, path) | |
return path | |
def XXXaddrobot(self, *args): | |
pass | |
def XXXisallowed(self, *args): | |
return 1 | |
class App: | |
sucker = None | |
msgq = None | |
def __init__(self, top): | |
self.top = top | |
top.columnconfigure(99, weight=1) | |
self.url_label = Label(top, text="URL:") | |
self.url_label.grid(row=0, column=0, sticky='e') | |
self.url_entry = Entry(top, width=60, exportselection=0) | |
self.url_entry.grid(row=0, column=1, sticky='we', | |
columnspan=99) | |
self.url_entry.focus_set() | |
self.url_entry.bind("<Key-Return>", self.go) | |
self.dir_label = Label(top, text="Directory:") | |
self.dir_label.grid(row=1, column=0, sticky='e') | |
self.dir_entry = Entry(top) | |
self.dir_entry.grid(row=1, column=1, sticky='we', | |
columnspan=99) | |
self.go_button = Button(top, text="Go", command=self.go) | |
self.go_button.grid(row=2, column=1, sticky='w') | |
self.cancel_button = Button(top, text="Cancel", | |
command=self.cancel, | |
state=DISABLED) | |
self.cancel_button.grid(row=2, column=2, sticky='w') | |
self.auto_button = Button(top, text="Paste+Go", | |
command=self.auto) | |
self.auto_button.grid(row=2, column=3, sticky='w') | |
self.status_label = Label(top, text="[idle]") | |
self.status_label.grid(row=2, column=4, sticky='w') | |
self.top.update_idletasks() | |
self.top.grid_propagate(0) | |
def message(self, text, *args): | |
if args: | |
text = text % args | |
self.status_label.config(text=text) | |
def check_msgq(self): | |
while not self.msgq.empty(): | |
msg = self.msgq.get() | |
if msg is None: | |
self.go_button.configure(state=NORMAL) | |
self.auto_button.configure(state=NORMAL) | |
self.cancel_button.configure(state=DISABLED) | |
if self.sucker: | |
self.sucker.stopit = 0 | |
self.top.bell() | |
else: | |
self.message(msg) | |
self.top.after(100, self.check_msgq) | |
def go(self, event=None): | |
if not self.msgq: | |
self.msgq = Queue.Queue(0) | |
self.check_msgq() | |
if not self.sucker: | |
self.sucker = SuckerThread(self.msgq) | |
if self.sucker.stopit: | |
return | |
self.url_entry.selection_range(0, END) | |
url = self.url_entry.get() | |
url = url.strip() | |
if not url: | |
self.top.bell() | |
self.message("[Error: No URL entered]") | |
return | |
self.rooturl = url | |
dir = self.dir_entry.get().strip() | |
if not dir: | |
self.sucker.savedir = None | |
else: | |
self.sucker.savedir = dir | |
self.sucker.rootdir = os.path.dirname( | |
websucker.Sucker.savefilename(self.sucker, url)) | |
self.go_button.configure(state=DISABLED) | |
self.auto_button.configure(state=DISABLED) | |
self.cancel_button.configure(state=NORMAL) | |
self.message( '[running...]') | |
self.sucker.stopit = 0 | |
t = threading.Thread(target=self.sucker.run1, args=(url,)) | |
t.start() | |
def cancel(self): | |
if self.sucker: | |
self.sucker.stopit = 1 | |
self.message("[canceling...]") | |
def auto(self): | |
tries = ['PRIMARY', 'CLIPBOARD'] | |
text = "" | |
for t in tries: | |
try: | |
text = self.top.selection_get(selection=t) | |
except TclError: | |
continue | |
text = text.strip() | |
if text: | |
break | |
if not text: | |
self.top.bell() | |
self.message("[Error: clipboard is empty]") | |
return | |
self.url_entry.delete(0, END) | |
self.url_entry.insert(0, text) | |
self.go() | |
class AppArray: | |
def __init__(self, top=None): | |
if not top: | |
top = Tk() | |
top.title("websucker GUI") | |
top.iconname("wsgui") | |
top.wm_protocol('WM_DELETE_WINDOW', self.exit) | |
self.top = top | |
self.appframe = Frame(self.top) | |
self.appframe.pack(fill='both') | |
self.applist = [] | |
self.exit_button = Button(top, text="Exit", command=self.exit) | |
self.exit_button.pack(side=RIGHT) | |
self.new_button = Button(top, text="New", command=self.addsucker) | |
self.new_button.pack(side=LEFT) | |
self.addsucker() | |
##self.applist[0].url_entry.insert(END, "http://www.python.org/doc/essays/") | |
def addsucker(self): | |
self.top.geometry("") | |
frame = Frame(self.appframe, borderwidth=2, relief=GROOVE) | |
frame.pack(fill='x') | |
app = App(frame) | |
self.applist.append(app) | |
done = 0 | |
def mainloop(self): | |
while not self.done: | |
time.sleep(0.1) | |
self.top.update() | |
def exit(self): | |
for app in self.applist: | |
app.cancel() | |
app.message("[exiting...]") | |
self.done = 1 | |
def main(): | |
AppArray().mainloop() | |
if __name__ == '__main__': | |
main() |