Tools/webchecker/wsgui.py
#! /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()