|
| 1 | +#! /usr/bin/env python |
| 2 | + |
| 3 | +"""GUI interface to webchecker. |
| 4 | +
|
| 5 | +This works as a Grail applet too! E.g. |
| 6 | +
|
| 7 | + <APPLET CODE=wcgui.py NAME=CheckerWindow> |
| 8 | + <PARAM NAME=roots VALUE="http://<yourfavoritehost>"> |
| 9 | + </APPLET> |
| 10 | +
|
| 11 | +Checkpoints are not (yet?) supported. |
| 12 | +
|
| 13 | +User interface: |
| 14 | +
|
| 15 | +When the 'Go' checkbutton is checked, web pages will be checked, one |
| 16 | +at a time in the Tk 'idle' time. The other checkbuttons determine |
| 17 | +whether the corresponding output panel is shown. There are six |
| 18 | +panels: |
| 19 | +
|
| 20 | +Log -- raw output from the checker (-v, -q affect this) |
| 21 | +To check -- local links discovered but not yet checked |
| 22 | +Off site -- links discovered that point off site |
| 23 | +Checked -- local links that have been checked |
| 24 | +Bad links -- links that failed upon checking |
| 25 | +Details -- details about one URL; double click on a URL in any of |
| 26 | + the aboce list panels (not in Log) will show that URL |
| 27 | +
|
| 28 | +XXX There ought to be a list of pages known to contain at least one |
| 29 | +bad link. |
| 30 | +
|
| 31 | +Use your window manager's Close command to quit. |
| 32 | +
|
| 33 | +Command line options: |
| 34 | +
|
| 35 | +-m bytes -- skip HTML pages larger than this size (default %(MAXPAGE)d) |
| 36 | +-n -- reports only, no checking (use with -R) |
| 37 | +-q -- quiet operation (also suppresses external links report) |
| 38 | +-v -- verbose operation; repeating -v will increase verbosity |
| 39 | +-x -- check external links (during report phase) |
| 40 | +
|
| 41 | +Command line arguments: |
| 42 | +
|
| 43 | +rooturl -- URL to start checking |
| 44 | + (default %(DEFROOT)s) |
| 45 | +
|
| 46 | +XXX The command line options should all be GUI accessible. |
| 47 | +
|
| 48 | +XXX The roots should be a list and there should be a GUI way to add a |
| 49 | +root. |
| 50 | +
|
| 51 | +XXX The alphabetizing of the lists makes the display very jumpy. |
| 52 | +
|
| 53 | +XXX The multipanel user interface is bogus. |
| 54 | +
|
| 55 | +""" |
| 56 | + |
| 57 | +# ' Emacs bait |
| 58 | + |
| 59 | + |
| 60 | +import sys |
| 61 | +import getopt |
| 62 | +import string |
| 63 | +from Tkinter import * |
| 64 | +import tktools |
| 65 | +import webchecker |
| 66 | +import random |
| 67 | + |
| 68 | + |
| 69 | +def main(): |
| 70 | + checkext = 0 |
| 71 | + try: |
| 72 | + opts, args = getopt.getopt(sys.argv[1:], 'm:qvx') |
| 73 | + except getopt.error, msg: |
| 74 | + sys.stdout = sys.stderr |
| 75 | + print msg |
| 76 | + print __doc__%vars(webchecker) |
| 77 | + sys.exit(2) |
| 78 | + for o, a in opts: |
| 79 | + if o == '-m': |
| 80 | + webchecker.maxpage = string.atoi(a) |
| 81 | + if o == '-q': |
| 82 | + webchecker.verbose = 0 |
| 83 | + if o == '-v': |
| 84 | + webchecker.verbose = webchecker.verbose + 1 |
| 85 | + if o == '-x': |
| 86 | + checkext = 1 |
| 87 | + root = Tk(className='Webchecker') |
| 88 | + root.protocol("WM_DELETE_WINDOW", root.quit) |
| 89 | + c = CheckerWindow(root, checkext) |
| 90 | + for arg in args or [webchecker.DEFROOT]: |
| 91 | + c.addroot(arg) |
| 92 | + root.mainloop() |
| 93 | + |
| 94 | + |
| 95 | +class CheckerWindow(webchecker.Checker): |
| 96 | + |
| 97 | + def __init__(self, parent, checkext=0, roots=None): |
| 98 | + self.__parent = parent |
| 99 | + self.__checkext = checkext |
| 100 | + self.__controls = Frame(parent) |
| 101 | + self.__controls.pack(side=TOP, fill=X) |
| 102 | + self.__govar = IntVar() |
| 103 | + self.__go = Checkbutton(self.__controls, text="GO", |
| 104 | + variable=self.__govar, |
| 105 | + command=self.go) |
| 106 | + self.__go.pack(side=LEFT) |
| 107 | + self.__status = Label(parent, text="Status: initial", anchor=W) |
| 108 | + self.__status.pack(side=TOP, fill=X) |
| 109 | + self.__checking = Label(parent, text="Checking: none", anchor=W) |
| 110 | + self.__checking.pack(side=TOP, fill=X) |
| 111 | + self.__mp = mp = MultiPanel(parent) |
| 112 | + sys.stdout = self.__log = LogPanel(mp, "Log") |
| 113 | + self.__todo = ListPanel(mp, "To check", self.showinfo) |
| 114 | + self.__ext = ListPanel(mp, "Off site", self.showinfo) |
| 115 | + self.__done = ListPanel(mp, "Checked", self.showinfo) |
| 116 | + self.__bad = ListPanel(mp, "Bad links", self.showinfo) |
| 117 | + self.__details = LogPanel(mp, "Details") |
| 118 | + self.__extodo = [] |
| 119 | + webchecker.Checker.__init__(self) |
| 120 | + if roots: |
| 121 | + roots = string.split(roots) |
| 122 | + for root in roots: |
| 123 | + self.addroot(root) |
| 124 | + |
| 125 | + def go(self): |
| 126 | + if self.__govar.get(): |
| 127 | + self.__parent.after_idle(self.dosomething) |
| 128 | + else: |
| 129 | + self.__checking.config(text="Checking: none") |
| 130 | + |
| 131 | + def dosomething(self): |
| 132 | + if self.todo: |
| 133 | + i = random.randint(0, len(self.todo)-1) |
| 134 | + url = self.__todo.items[i] |
| 135 | + self.__checking.config(text="Checking: "+url) |
| 136 | + self.__parent.update() |
| 137 | + self.dopage(url) |
| 138 | + elif self.__checkext and self.__extodo: |
| 139 | + # XXX Should have an indication of these in the todo window... |
| 140 | + i = random.randint(0, len(self.__extodo)-1) |
| 141 | + url = self.__extodo[i] |
| 142 | + del self.__extodo[i] |
| 143 | + self.__checking.config(text="Checking: "+url) |
| 144 | + self.__parent.update() |
| 145 | + self.checkextpage(url) |
| 146 | + else: |
| 147 | + self.__govar.set(0) |
| 148 | + self.go() |
| 149 | + |
| 150 | + def showinfo(self, url): |
| 151 | + d = self.__details |
| 152 | + d.clear() |
| 153 | + d.write("URL: %s\n" % url) |
| 154 | + if self.bad.has_key(url): |
| 155 | + d.write("Error: %s\n" % str(self.bad[url])) |
| 156 | + if self.done.has_key(url): |
| 157 | + d.write("Status: done\n") |
| 158 | + o = self.done[url] |
| 159 | + elif self.todo.has_key(url): |
| 160 | + d.write("Status: todo\n") |
| 161 | + o = self.todo[url] |
| 162 | + elif self.ext.has_key(url): |
| 163 | + d.write("Status: ext\n") |
| 164 | + o = self.ext[url] |
| 165 | + else: |
| 166 | + d.write("Status: unknown (!)\n") |
| 167 | + o = [] |
| 168 | + if url in self.roots: |
| 169 | + d.write("This is a root URL\n") |
| 170 | + for source, rawlink in o: |
| 171 | + d.write("Origin: %s" % source) |
| 172 | + if rawlink != url: |
| 173 | + d.write(" (%s)" % rawlink) |
| 174 | + d.write("\n") |
| 175 | + self.__mp.showpanel("Details") |
| 176 | + |
| 177 | + def newstatus(self): |
| 178 | + self.__status.config(text="Status: "+self.status()[1:-1]) |
| 179 | + self.__parent.update() |
| 180 | + |
| 181 | + def setbad(self, url, msg): |
| 182 | + webchecker.Checker.setbad(self, url, msg) |
| 183 | + self.__bad.insert(url) |
| 184 | + self.newstatus() |
| 185 | + |
| 186 | + def setgood(self, url): |
| 187 | + webchecker.Checker.setgood(self, url) |
| 188 | + self.__bad.remove(url) |
| 189 | + self.newstatus() |
| 190 | + |
| 191 | + def newextlink(self, url, origin): |
| 192 | + webchecker.Checker.newextlink(self, url, origin) |
| 193 | + self.__extodo.append(url) |
| 194 | + self.__ext.insert(url) |
| 195 | + self.newstatus() |
| 196 | + |
| 197 | + def newintlink(self, url, origin): |
| 198 | + webchecker.Checker.newintlink(self, url, origin) |
| 199 | + if self.done.has_key(url): |
| 200 | + self.__done.insert(url) |
| 201 | + elif self.todo.has_key(url): |
| 202 | + self.__todo.insert(url) |
| 203 | + self.newstatus() |
| 204 | + |
| 205 | + def markdone(self, url): |
| 206 | + webchecker.Checker.markdone(self, url) |
| 207 | + self.__done.insert(url) |
| 208 | + self.__todo.remove(url) |
| 209 | + self.newstatus() |
| 210 | + |
| 211 | + |
| 212 | +class ListPanel: |
| 213 | + |
| 214 | + def __init__(self, mp, name, showinfo=None): |
| 215 | + self.mp = mp |
| 216 | + self.name = name |
| 217 | + self.showinfo = showinfo |
| 218 | + self.panel = mp.addpanel(name) |
| 219 | + self.list, self.frame = tktools.make_list_box( |
| 220 | + self.panel, width=60, height=5) |
| 221 | + self.list.config(exportselection=0) |
| 222 | + if showinfo: |
| 223 | + self.list.bind('<Double-Button-1>', self.event) |
| 224 | + self.items = [] |
| 225 | + |
| 226 | + def event(self, dummy): |
| 227 | + l = self.list.curselection() |
| 228 | + if not l: return |
| 229 | + self.showinfo(self.list.get(string.atoi(l[0]))) |
| 230 | + |
| 231 | + def insert(self, url): |
| 232 | + if url not in self.items: |
| 233 | + if not self.items: |
| 234 | + self.mp.showpanel(self.name) |
| 235 | + # (I tried sorting alphabetically, but the display is too jumpy) |
| 236 | + i = len(self.items) |
| 237 | + self.list.insert(i, url) |
| 238 | + self.list.select_clear(0, END) |
| 239 | + self.list.select_set(i) |
| 240 | + self.list.yview(i) |
| 241 | + self.items.insert(i, url) |
| 242 | + |
| 243 | + def remove(self, url): |
| 244 | + try: |
| 245 | + i = self.items.index(url) |
| 246 | + except (ValueError, IndexError): |
| 247 | + pass |
| 248 | + else: |
| 249 | + self.list.delete(i) |
| 250 | + self.list.select_clear(i) |
| 251 | + del self.items[i] |
| 252 | + if not self.items: |
| 253 | + self.mp.hidepanel(self.name) |
| 254 | + |
| 255 | + |
| 256 | +class LogPanel: |
| 257 | + |
| 258 | + def __init__(self, mp, name): |
| 259 | + self.mp = mp |
| 260 | + self.name = name |
| 261 | + self.panel = mp.addpanel(name) |
| 262 | + self.text, self.frame = tktools.make_text_box(self.panel, height=10) |
| 263 | + self.text.config(wrap=NONE) |
| 264 | + |
| 265 | + def clear(self): |
| 266 | + self.text.delete("1.0", END) |
| 267 | + self.text.yview("1.0") |
| 268 | + |
| 269 | + def write(self, s): |
| 270 | + self.text.insert(END, s) |
| 271 | + self.text.yview(END) |
| 272 | + self.panel.update() |
| 273 | + |
| 274 | + |
| 275 | +class MultiPanel: |
| 276 | + |
| 277 | + def __init__(self, parent): |
| 278 | + self.parent = parent |
| 279 | + self.frame = Frame(self.parent) |
| 280 | + self.frame.pack(expand=1, fill=BOTH) |
| 281 | + self.topframe = Frame(self.frame, borderwidth=2, relief=RAISED) |
| 282 | + self.topframe.pack(fill=X) |
| 283 | + self.botframe = Frame(self.frame) |
| 284 | + self.botframe.pack(expand=1, fill=BOTH) |
| 285 | + self.panelnames = [] |
| 286 | + self.panels = {} |
| 287 | + |
| 288 | + def addpanel(self, name, on=0): |
| 289 | + v = StringVar() |
| 290 | + if on: |
| 291 | + v.set(name) |
| 292 | + else: |
| 293 | + v.set("") |
| 294 | + check = Checkbutton(self.topframe, text=name, |
| 295 | + offvalue="", onvalue=name, variable=v, |
| 296 | + command=self.checkpanel) |
| 297 | + check.pack(side=LEFT) |
| 298 | + panel = Frame(self.botframe) |
| 299 | + label = Label(panel, text=name, borderwidth=2, relief=RAISED, anchor=W) |
| 300 | + label.pack(side=TOP, fill=X) |
| 301 | + t = v, check, panel |
| 302 | + self.panelnames.append(name) |
| 303 | + self.panels[name] = t |
| 304 | + if on: |
| 305 | + panel.pack(expand=1, fill=BOTH) |
| 306 | + return panel |
| 307 | + |
| 308 | + def showpanel(self, name): |
| 309 | + v, check, panel = self.panels[name] |
| 310 | + v.set(name) |
| 311 | + panel.pack(expand=1, fill=BOTH) |
| 312 | + |
| 313 | + def hidepanel(self, name): |
| 314 | + v, check, panel = self.panels[name] |
| 315 | + v.set("") |
| 316 | + panel.pack_forget() |
| 317 | + |
| 318 | + def checkpanel(self): |
| 319 | + for name in self.panelnames: |
| 320 | + v, check, panel = self.panels[name] |
| 321 | + panel.pack_forget() |
| 322 | + for name in self.panelnames: |
| 323 | + v, check, panel = self.panels[name] |
| 324 | + if v.get(): |
| 325 | + panel.pack(expand=1, fill=BOTH) |
| 326 | + |
| 327 | + |
| 328 | +if __name__ == '__main__': |
| 329 | + main() |
0 commit comments