Robot saves metadata with unique names in directory "flat" (if it exists).
[tclrobot.git] / robot.tcl
1 #!/usr/bin/tclsh 
2 # $Id: robot.tcl,v 1.23 2001/10/31 08:51:49 adam Exp $
3 #
4 proc RobotFileNext1 {area lead} {
5     # puts "RobotFileNext1 area=$area lead=$lead"
6     if {[catch {set ns [glob ${area}/*]}]} {
7         return {}
8     }
9     foreach n $ns {
10         if {[file isfile $n]} {
11             set off [string last / $n]
12             incr off 2
13             return $lead/[string range $n $off end]
14         }
15     }
16     foreach n $ns {
17         if {[file isdirectory $n]} {
18             set off [string last / $n]
19             incr off 2
20             set sb [RobotFileNext1 $n $lead/[string range $n $off end]]
21             if {[string length $sb]} {
22                 return $sb
23             }
24         }
25     }
26     return {}
27 }
28
29 proc RobotWriteRecord {outf fromurl distance} {
30     puts $outf "<zmbot>"
31     puts $outf "<distance>"
32     puts $outf $distance
33     puts $outf "</distance>"
34     puts $outf "<fromurl>"
35     puts $outf $fromurl
36     puts $outf "</fromurl>"
37     puts $outf "</zmbot>"
38 }
39
40 proc RobotReadRecord {inf fromurlx distancex} {
41     upvar $fromurlx fromurl
42     upvar $distancex distance
43     gets $inf
44     gets $inf
45     set distance [string trim [gets $inf]]
46     # puts "got distance = $distance"
47     gets $inf
48     gets $inf
49     set fromurl [string trim [gets $inf]]
50 }
51
52 proc RobotFileNext {area} {
53     global robotSeq global idletime ns
54
55     # puts "RobotFileNext robotSeq=$robotSeq"
56     if {$robotSeq < 0} {
57         return {}
58     }
59     if {$robotSeq == 0} {
60         if {[catch {set ns [glob ${area}/*]}]} {
61             return {}
62         }
63     }
64     set off [string length $area]
65     incr off
66     set n [lindex $ns $robotSeq]
67     if {![string length $n]} {
68         set robotSeq -1
69         flush stdout
70         puts "Round robin"
71         return wait
72     }
73     incr robotSeq
74     if {[file isfile $n/frobots.txt]} {
75         puts "ok returning http://[string range $n $off end]/robots.txt"
76         return http://[string range $n $off end]/robots.txt
77     } elseif {[file isdirectory $n]} {
78         set sb [RobotFileNext1 $n http://[string range $n $off end]]
79         if {[string length $sb]} {
80             return $sb
81         }
82     }
83     puts "no more work at end of RobotFileNext n=$n"
84     puts "ns=$ns"
85     return {}
86 }
87
88
89 proc RobotFileExist {area host path} {
90     # puts "RobotFileExist begin area=$area host=$host path=$path"
91     set lpath [split $path /]
92     set l [llength $lpath]
93     incr l -1
94     set t [lindex $lpath $l]
95     incr l -1
96     set npath $area/$host[join [lrange $lpath 0 $l] /d]/f$t
97     # puts "RobotFileExist end npath=$npath"
98     return [file exists $npath]
99 }
100
101 proc RobotFileUnlink {area host path} {
102     # puts "RobotFileUnlink begin"
103     # puts "area=$area host=$host path=$path"
104     set lpath [split $path /]
105     set l [llength $lpath]
106     incr l -1
107     set t [lindex $lpath $l]
108     incr l -1
109     set npath $area/$host[join [lrange $lpath 0 $l] /d]/f$t
110     # puts "npath=$npath"
111     set comp [split $npath /]
112     set l [llength $comp]
113     incr l -1
114     if {[catch {exec rm [join $comp /]}]} return
115     incr l -1
116     for {set i $l} {$i > 0} {incr i -1} {
117         set path [join [lrange $comp 0 $i] /]
118         if {![catch {glob $path/*}]} return
119         exec rmdir ./$path
120     }
121     # puts "RobotFileUnlink end"
122 }
123
124 proc RobotFileClose {out} {
125     if [string compare $out stdout] {
126         close $out
127     }
128 }
129
130 proc RobotFileOpen {area host path {mode w}} {
131     set orgPwd [pwd]
132     global workdir
133
134     if {![info exists workdir]} {
135         return stdout
136     }
137     #puts "RobotFileOpen orgPwd=$orgPwd area=$area host=$host path=$path mode=$mode"
138     if {[string compare $orgPwd $workdir]} {
139         puts "ooops. RobotFileOpen failed"
140         puts "workdir = $workdir"
141         puts "pwd = $orgPwd"
142         exit 1
143     }
144     set comp [split $area/$host$path /]
145     set len [llength $comp]
146     incr len -1
147     for {set i 0} {$i < $len} {incr i} {
148         if {$i > 1} {
149             set d "d[lindex $comp $i]" 
150         } else {
151             set d [lindex $comp $i]
152         }
153         if {[catch {cd ./$d}]} {
154             exec mkdir $d
155             cd ./$d
156             if {![string compare $area unvisited] && $i == 1 && $mode == "w"} {
157                 set out [open frobots.txt w]
158                 puts "creating robots.txt in $d"
159                 close $out
160             }
161         }
162     }
163     set d [lindex $comp $len]
164     if {[string length $d]} {
165         if {[file isdirectory $d]} {
166             set out [open $d/f $mode]
167         } else {
168             set out [open f$d $mode]
169         }
170     } else {
171         set out [open f $mode]
172     }
173     cd $orgPwd
174     return $out
175 }
176
177 proc RobotRR {} {
178     global robotSeq robotsRunning
179
180     incr robotsRunning -1
181     while {$robotsRunning} {
182         vwait robotsRunning
183     }
184     set robotSeq 0
185     RobotStart
186 }
187
188 proc RobotRestart {url sock} {
189     global URL robotsRunning
190
191     close $sock
192     after cancel $URL($sock,cancel) 
193
194     foreach v [array names URL $url,*] {
195         unset URL($v)
196     }
197
198     incr robotsRunning -1
199     RobotStart
200 }
201
202 proc RobotStart {} {
203     global URL
204     global robotsRunning robotsMax idletime
205   
206     # puts "RobotStart"
207     while {1} {
208         set url [RobotFileNext unvisited]
209         if {![string length $url]} {
210             return
211         }
212         incr robotsRunning
213         if {[string compare $url wait] == 0} {
214             after $idletime RobotRR
215             return
216         }
217         set r [RobotGetUrl $url {}]
218         if {!$r} {
219             if {$robotsRunning >= $robotsMax} return
220         } else {
221             incr robotsRunning -1
222             if {![RobotFileExist bad $URL($url,hostport) $URL($url,path)]} {
223                 set outf [RobotFileOpen bad $URL($url,hostport) $URL($url,path)]
224                 RobotFileClose $outf
225             }
226             RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)
227         }
228     }
229 }
230
231 proc headSave {url out} {
232     global URL
233     
234     if {[info exists URL($url,head,last-modified)]} {
235         puts $out "<lastmodified>$URL($url,head,last-modified)</lastmodified>"
236     }
237     puts $out {<si>}
238     if {[info exists URL($url,head,date)]} {
239         puts $out " <date>$URL($url,head,date)</date>"
240     }
241     if {[info exists URL($url,head,content-length)]} {
242         puts $out " <by>$URL($url,head,content-length)</by>"
243     }
244     if {[info exists URL($url,head,server)]} {
245         puts $out " <format>$URL($url,head,server)</format>"
246     }
247     puts $out {</si>}
248     puts $out {<publisher>}
249     puts $out " <identifier>$url</identifier>"
250     if {[info exists URL($url,head,content-type)]} {
251         puts $out " <type>$URL($url,head,content-type)</type>"
252     }
253     puts $out {</publisher>}
254 }
255
256 proc RobotHref {url hrefx hostx pathx} {
257     global URL domains debuglevel
258     upvar $hrefx href
259     upvar $hostx host
260     upvar $pathx path
261
262     if {$debuglevel > 1} {
263         puts "Ref input url = $url href=$href"
264     }
265
266     if {[string first { } $href] >= 0} {
267         return 0
268     }
269     if {[string length $href] > 256} {
270         return 0
271     }
272     if {[string first {?} $href] >= 0} {
273         return 0
274     }
275     if {[string first {?} $url] >= 0 && [string first {?} $href] >= 0} {
276         return 0
277     }
278     # get method (if any)
279     if {![regexp {^([^/:]+):(.*)} $href x method hpath]} {
280         set hpath $href
281         set method http
282     } else {
283         if {[string compare $method http]} {
284             return 0
285         }
286     }
287     # get host (if any)
288     if {[regexp {^//([^/]+)([^\#]*)} $hpath x host surl]} {
289         if {![string length $surl]} {
290             set surl /
291         }
292         if {[info exist domains]} {
293             set ok 0
294             foreach domain $domains {
295                 if {[string match $domain $host]} {
296                     set ok 1
297                     break
298                  }
299             }
300             if {!$ok} {
301                 return 0
302             }
303         }
304     } else {
305         regexp {^([^\#]*)} $hpath x surl
306         set host $URL($url,hostport)
307     }
308     if {![string length $surl]} {
309         return 0
310     }
311     if {[string first / $surl]} {
312         # relative path
313         regexp {^([^\#?]*)} $URL($url,path) x dpart
314         set l [string last / $dpart]
315         if {[expr $l >= 0]} {
316             set surl [string range $dpart 0 $l]$surl
317         } else {
318             set surl $dpart/$surl
319         }
320     }
321     set surllist [split $surl /]
322     catch {unset path}
323     set pathl 0
324     foreach c $surllist {
325         switch -- $c {
326             .. {
327                 if {$pathl > 0} {
328                     incr pathl -2
329                     set path [lrange $path 0 $pathl]
330                     incr pathl
331                 }
332             }
333             . {
334
335             }
336             default {
337                 incr pathl
338                 lappend path $c
339             }
340         }
341     }
342     if {$pathl} {
343         set path [join $path /]
344     } else {
345         set path ""
346     }
347     regsub -all {~} $path {%7E} path
348     set href "$method://$host$path"
349
350     if {$debuglevel > 1} {
351         puts "Ref result = $href"
352     }
353     return [checkrule url $href]
354 }
355
356 proc RobotError {url code} {
357     global URL
358
359     puts "Bad URL $url (code $code)"
360     set fromurl {}
361     set distance -1
362     if {[RobotFileExist unvisited $URL($url,hostport) $URL($url,path)]} {
363         set inf [RobotFileOpen unvisited $URL($url,hostport) $URL($url,path) r]
364         RobotReadRecord $inf fromurl distance
365         RobotFileClose $inf
366     }
367     RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)
368     if {![RobotFileExist bad $URL($url,hostport) $URL($url,path)]} {
369         set outf [RobotFileOpen bad $URL($url,hostport) $URL($url,path)]
370         RobotWriteRecord $outf $fromurl $distance
371         RobotFileClose $outf
372     }
373 }
374
375 proc RobotRedirect {url tourl code} {
376     global URL
377
378     puts "Redirecting from $url to $tourl"
379
380     set distance {}
381     set fromurl {}
382     if {[RobotFileExist unvisited $URL($url,hostport) $URL($url,path)]} {
383         set inf [RobotFileOpen unvisited $URL($url,hostport) $URL($url,path) r]
384         RobotReadRecord $inf fromurl distance
385         RobotFileClose $inf
386     }
387     if {![RobotFileExist bad $URL($url,hostport) $URL($url,path)]} {
388         set outf [RobotFileOpen bad $URL($url,hostport) $URL($url,path)]
389         RobotWriteRecord $outf $fromurl $distance
390         RobotFileClose $outf
391     }
392     if {[RobotHref $url tourl host path]} {
393         if {![RobotFileExist visited $host $path]} {
394             if {![RobotFileExist unvisited $host $path]} {
395                 set outf [RobotFileOpen unvisited $host $path]
396                 RobotWriteRecord $outf $fromurl $distance
397                 RobotFileClose $outf
398             }
399         } else {
400             set olddistance {}
401             set inf [RobotFileOpen visited $host $path r]
402             RobotReadRecord $inf oldurl olddistance
403             RobotFileClose $inf
404             if {[string length $olddistance] == 0} {
405                 set olddistance 1000
406             }
407             if {[string length $distance] == 0} {
408                 set distance 1000
409             }
410             puts "distance=$distance olddistance=$olddistance"
411             if {[expr $distance < $olddistance]} {
412                 set outf [RobotFileOpen unvisited $host $path]
413                 RobotWriteRecord $outf $tourl $distance
414                 RobotFileClose $outf
415             }
416         }
417     }
418     if {[catch {RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)}]} {
419         puts "unlink failed"
420         exit 1
421     }
422 }
423
424 proc RobotTextHtml {url out} {
425     global URL maxdistance
426
427     set distance 0
428     if {$maxdistance < 1000 && [info exists URL($url,dist)]} {
429         set distance [expr $URL($url,dist) + 1]
430     }
431     htmlSwitch $URL($url,buf) \
432         title {
433             puts $out "<title>$body</title>"
434         } -nonest meta {
435             puts -nonewline $out "<meta"
436             foreach a [array names parm] {
437                 puts -nonewline $out " $a"
438                 puts -nonewline $out {="}
439                 puts -nonewline $out $parm($a)
440                 puts -nonewline $out {"}
441             }
442             puts $out {></meta>}
443         } body {
444             regsub -all -nocase {<script([^<]|(<!.*>))*</script>} $body {} abody
445             regsub -all {<[^\>]+>} $abody {} nbody
446             puts $out "<documentcontent>"
447             puts $out $nbody
448             puts $out "</documentcontent>"
449         } -nonest a {
450             if {![info exists parm(href)]} {
451                 puts "no href"
452                 continue
453             }
454             if {[expr $distance <= $maxdistance]} {
455                 set href [string trim $parm(href)]
456                 if {![RobotHref $url href host path]} continue
457                 
458                 puts $out "<cr>"
459                 puts $out "<identifier>$href</identifier>"
460                 puts $out "<description>$body</description>"
461                 puts $out "</cr>"
462
463                 if {![RobotFileExist visited $host $path]} {
464                     set olddistance 1000
465                     if {![RobotFileExist bad $host $path]} {
466                         if {[RobotFileExist unvisited $host $path]} {
467                             set inf [RobotFileOpen unvisited $host $path r]
468                             RobotReadRecord $inf oldurl olddistance
469                             RobotFileClose $inf
470                         }
471                     } else {
472                         set olddistance 0
473                     }
474                     if {[string length $olddistance] == 0} {
475                         set olddistance 1000
476                     }
477                     if {[expr $distance < $olddistance]} {
478                         set outf [RobotFileOpen unvisited $host $path]
479                         RobotWriteRecord $outf $url $distance
480                         RobotFileClose $outf
481                     }
482                 } elseif {[string compare $href $url]} {
483                     set inf [RobotFileOpen visited $host $path r]
484                     RobotReadRecord $inf xurl olddistance
485                     close $inf
486                     if {[string length $olddistance] == 0} {
487                         set olddistance 1000
488                     }
489                     if {[expr $distance < $olddistance]} {
490                         puts "OK remarking url=$url href=$href"
491                         puts "olddistance = $olddistance"
492                         puts "newdistance = $distance"
493                         set outf [RobotFileOpen unvisited $host $path]
494                         RobotWriteRecord $outf $url $distance
495                         RobotFileClose $outf
496                     }
497                 }
498             }
499         } -nonest area {
500             if {![info exists parm(href)]} {
501                 puts "no href"
502                 continue
503             }
504             if {[expr $distance <= $maxdistance]} {
505                 set href [string trim $parm(href)]
506                 if {![RobotHref $url href host path]} continue
507                 
508                 puts $out "<cr>"
509                 puts $out "<identifier>$href</identifier>"
510                 puts $out "<description></description>"
511                 puts $out "</cr>"
512
513                 if {![RobotFileExist visited $host $path]} {
514                     set olddistance 1000
515                     if {![RobotFileExist bad $host $path]} {
516                         if {[RobotFileExist unvisited $host $path]} {
517                             set inf [RobotFileOpen unvisited $host $path r]
518                             RobotReadRecord $inf oldurl olddistance
519                             RobotFileClose $inf
520                         }
521                     } else {
522                         set olddistance 0
523                     }
524                     if {[string length $olddistance] == 0} {
525                         set olddistance 1000
526                     }
527                     if {[expr $distance < $olddistance]} {
528                         set outf [RobotFileOpen unvisited $host $path]
529                         RobotWriteRecord $outf $url $distance
530                         RobotFileClose $outf
531                     }
532                 } elseif {[string compare $href $url]} {
533                     set inf [RobotFileOpen visited $host $path r]
534                     RobotReadRecord $inf xurl olddistance
535                     close $inf
536                     if {[string length $olddistance] == 0} {
537                         set olddistance 1000
538                     }
539                     if {[expr $distance < $olddistance]} {
540                         puts "OK remarking url=$url href=$href"
541                         puts "olddistance = $olddistance"
542                         puts "newdistance = $distance"
543                         set outf [RobotFileOpen unvisited $host $path]
544                         RobotWriteRecord $outf $url $distance
545                         RobotFileClose $outf
546                     }
547                 }
548             }
549         }
550 }
551
552 proc RobotsTxt {url} {
553     global agent URL
554
555     RobotsTxt0 URL(URL($url,hostport),robots) $URL($url,buf)
556 }
557
558 proc RobotsTxt0 {v buf} {
559     global URL agent
560     set section 0
561     foreach l [split $buf \n] {
562         if {[regexp {([-A-Za-z]+):[ ]*([^\# ]+)} $l match cmd arg]} {
563             puts "cmd=$cmd arg=$arg"
564             switch -- [string tolower $cmd] {
565                 user-agent {
566                     if {$section} break
567                     set pat [string tolower $arg]*
568                     set section [string match $pat $agent]
569                 }
570                 disallow {
571                     if {$section} {
572                         puts "rule [list 0 $arg]"
573                         lappend $v [list 0 $arg]
574                     }
575                 }
576                 allow {
577                     if {$section} {
578                         puts "rule [list 1 $arg]"
579                         lappend $v [list 1 $arg]
580                     }
581                 }
582             }
583         }
584     }
585 }
586
587 proc RobotTextPlain {url out} {
588     global URL
589
590     puts $out "<documentcontent>"
591     regsub -all {<} $URL($url,buf) {\&lt;} content
592     puts $out $content
593     puts $out "</documentcontent>"
594
595     if {![string compare $URL($url,path) /robots.txt]} {
596         RobotsTxt $url
597     }
598 }
599
600 proc RobotWriteMetadata {url out} {
601     global URL domains
602
603     puts $out "<zmbot>"
604
605     set distance 1000
606     if {[RobotFileExist unvisited $URL($url,hostport) $URL($url,path)]} {
607         set inf [RobotFileOpen unvisited $URL($url,hostport) $URL($url,path) r]
608         RobotReadRecord $inf fromurl distance
609         RobotFileClose $inf
610     }
611     set URL($url,dist) $distance
612     puts $out "<distance>"
613     puts $out "  $distance"
614     puts $out "</distance>"
615     headSave $url $out
616     puts "Parsing $url distance=$distance"
617     switch $URL($url,head,content-type) {
618         text/html {
619             if {[string length $distance]} {
620                 RobotTextHtml $url $out
621             }
622         }
623         text/plain {
624             RobotTextPlain $url $out
625         }
626         application/pdf {
627             set pdff [open test.pdf w]
628             puts -nonewline $pdff $URL($url,buf)
629             close $pdff
630         }
631     }
632     puts $out "</zmbot>"
633 }
634
635 proc Robot200 {url} {
636     global URL domains
637     
638     set out [RobotFileOpen raw $URL($url,hostport) $URL($url,path)]
639     puts -nonewline $out $URL($url,buf)
640     RobotFileClose $out
641
642     if {![checkrule mime $URL($url,head,content-type)]} {
643         RobotError $url mimedeny
644         return
645     }
646     set out [RobotFileOpen visited $URL($url,hostport) $URL($url,path)]
647     RobotWriteMetadata $url $out
648     RobotFileClose $out
649
650     if {[file isdirectory flat]} {
651         regsub -all {/} $URL($url,hostport).$URL($url,path) {.} fname
652         set out [open "flat/$fname" w]
653         RobotWriteMetadata $url $out
654         close $out
655     }
656     RobotFileUnlink unvisited $URL($url,hostport) $URL($url,path)
657 }
658
659 proc RobotReadContent {url sock binary} {
660     global URL
661
662     set buffer [read $sock 16384]
663     set readCount [string length $buffer]
664
665     if {$readCount <= 0} {
666         Robot200 $url
667         RobotRestart $url $sock
668     } elseif {!$binary && [string first \0 $buffer] >= 0} {
669         Robot200 $url
670         RobotRestart $url $sock
671     } else {
672         # puts "Got $readCount bytes"
673         set URL($url,buf) $URL($url,buf)$buffer
674     }
675 }
676
677 proc RobotReadHeader {url sock} {
678     global URL debuglevel
679
680     if {$debuglevel > 1} {
681         puts "HTTP head $url"
682     }
683     if {[catch {set buffer [read $sock 2148]}]} {
684         RobotError $url 404
685         RobotRestart $url $sock
686     }
687     set readCount [string length $buffer]
688     
689     if {$readCount <= 0} {
690         RobotError $url 404
691         RobotRestart $url $sock
692     } else {
693         # puts "Got $readCount bytes"
694         set URL($url,buf) $URL($url,buf)$buffer
695         
696         set n [string first \r\n\r\n $URL($url,buf)]
697         if {$n > 1} {
698             set code 0
699             set version {}
700             set headbuf [string range $URL($url,buf) 0 $n]
701             incr n 4
702             set URL($url,buf) [string range $URL($url,buf) $n end]
703             
704             regexp {^HTTP/([0-9.]+)[ ]+([0-9]+)} $headbuf x version code
705             set lines [split $headbuf \n]
706             foreach line $lines {
707                 if {[regexp {^([^:]+):[ ]+([^;]*)} $line x name value]} {
708                     set URL($url,head,[string tolower $name]) [string trim $value]
709                 }
710             }
711             puts "HTTP CODE $code"
712             set URL($url,state) skip
713             switch $code {
714                 301 {
715                     RobotRedirect $url $URL($url,head,location) 301
716                     RobotRestart $url $sock
717                 }
718                 302 {
719                     RobotRedirect $url $URL($url,head,location) 302
720                     RobotRestart $url $sock
721                 }
722                 200 {
723                     if {![info exists URL($url,head,content-type)]} {
724                         set URL($url,head,content-type) {}
725                     }
726                     set binary 0
727                     switch $URL($url,head,content-type) {
728                         application/pdf {
729                             set binary 1
730                         }
731                     }
732                     fileevent $sock readable [list RobotReadContent $url $sock $binary]
733                 }
734                 default {
735                     RobotError $url $code
736                     RobotRestart $url $sock
737                 }
738             }
739         }
740     }
741 }
742
743 proc RobotSockCancel {url sock} {
744
745     puts "RobotSockCancel sock=$sock url=$url"
746     RobotError $url 401
747     RobotRestart $url $sock
748 }
749
750 proc RobotConnect {url sock} {
751     global URL agent acceptLanguage
752
753     fconfigure $sock -translation {lf crlf} -blocking 0
754     fileevent $sock readable [list RobotReadHeader $url $sock]
755     puts $sock "GET $URL($url,path) HTTP/1.0"
756     puts $sock "Host: $URL($url,host)"
757     puts $sock "User-Agent: $agent"
758     if {[string length $acceptLanguage]} {
759         puts $sock "Accept-Language: $acceptLanguage"
760     }
761     puts $sock ""
762     flush $sock
763     set URL($sock,cancel) [after 30000 [list RobotSockCancel $url $sock]]
764 }
765
766 proc RobotNop {} {
767
768 }
769
770 proc RobotGetUrl {url phost} {
771     global URL robotsRunning
772     flush stdout
773     puts "Retrieve $robotsRunning url=$url"
774     if {![regexp {([^:]+)://([^/]+)(.*)} $url x method hostport path]} {
775         return -1
776     }
777     if {![regexp {([^:]+):([0-9]+)} $hostport x host port]} {
778         set port 80
779         set host $hostport
780     }
781     set URL($url,method) $method
782     set URL($url,host) $host
783     set URL($url,hostport) $hostport
784     set URL($url,path) $path
785     set URL($url,state) head
786     set URL($url,buf) {}
787
788     if {[string compare $path /robots.txt]} {
789         set ok 1
790         if {![info exists URL($hostport,robots)]} {
791             puts "READING robots.txt for host $hostport"
792             if {[RobotFileExist visited $hostport /robots.txt]} {
793                 set inf [RobotFileOpen visited $hostport /robots.txt r]
794                 set buf [read $inf 32768]
795                 close $inf
796             } else {
797                 set buf "User-agent: *\nAllow: /\n"
798             }
799             RobotsTxt0 URL($hostport,robots) $buf
800         }
801         if {[info exists URL($hostport,robots)]} {
802             foreach l $URL($hostport,robots) {
803                 if {[string first [lindex $l 1] $path] == 0} {
804                     set ok [lindex $l 0]
805                     break
806                 }
807             }
808         }
809         if {!$ok} {
810             puts "skipped due to robots.txt"
811             return -1
812         }
813     }
814     if [catch {set sock [socket -async $host $port]}] {
815         return -1
816     }
817     RobotConnect $url $sock
818
819     return 0
820 }
821
822 if {![llength [info commands htmlSwitch]]} {
823     set e [info sharedlibextension]
824     if {[catch {load ./tclrobot$e}]} {
825         load tclrobot$e
826     }
827 }
828
829 set agent "zmbot/0.1"
830 if {![catch {set os [exec uname -s -r]}]} {
831     set agent "$agent ($os)"
832 }
833
834 puts "agent: $agent"
835
836 proc bgerror {m} {
837     global errorInfo
838     puts "BGERROR $m"
839     puts $errorInfo
840 }
841
842 set robotsRunning 0
843 set robotSeq 0
844 set workdir [pwd]
845 set idletime 60000
846 set acceptLanguage {}
847
848 set i 0
849 set l [llength $argv]
850
851 if {$l < 2} {
852     puts {tclrobot: usage:}
853     puts {tclrobot [-j jobs] [-i idle] [-c count] [-d domain] [-r rules] [url ..]}
854     puts " Example: -c 3 -d '*.dk' http://www.indexdata.dk/"
855     exit 1
856 }
857
858 # Rules: allow, deny, url
859 set debuglevel 0
860
861 proc checkrule {type this} {
862     global alrules
863     global debuglevel
864
865     if {$debuglevel > 3} {
866         puts "CHECKRULE $type $this"
867     }
868     if {[info exist alrules]} {
869         foreach l $alrules {
870             if {$debuglevel > 3} {
871                 puts "consider $l"
872             }
873             # consider type
874             if {[lindex $l 1] != $type} continue
875             # consider mask (! negates)
876             set mask [lindex $l 2]
877             if {[string index $mask 0] == "!"} {
878                 set mask [string range $mask 1 end]
879                 if {[string match $mask $this]} continue
880             } else {
881                 if {![string match $mask $this]} continue
882             }
883             # OK, we have a match
884             if {[lindex $l 0] == "allow"} {
885                 if {$debuglevel > 3} {
886                     puts "CHECKRULE MATH OK"
887                 }
888                 return 1
889             } else {
890                 if {$debuglevel > 3} {
891                     puts "CHECKFULE MATCH FAIL"
892                 }
893                 return 0
894             }
895         }
896     }
897     if {$debuglevel > 3} {
898         puts "CHECKRULE MATH OK"
899     }
900     return 1
901 }
902
903
904 proc url {href} {
905     global debuglevel
906
907     if {[RobotHref http://www.indexdata.dk/ href host path]} {
908         if {![RobotFileExist visited $host $path]} {
909             set outf [RobotFileOpen unvisited $host $path]
910             RobotWriteRecord $outf href 0
911             RobotFileClose $outf
912         }
913     }
914 }
915
916 proc deny {type stuff} {
917     global alrules
918
919     lappend alrules [list deny $type $stuff]
920 }
921
922 proc allow {type stuff} {
923     global alrules
924
925     lappend alrules [list allow $type $stuff]
926 }
927
928 proc debug {level} {
929     global debuglevel
930
931     set debuglevel $level
932 }
933
934 # Parse options
935
936 while  {$i < $l} {
937     set arg [lindex $argv $i]
938     switch -glob -- $arg {
939         -j* {
940             set robotsMax [string range $arg 2 end]
941             if {![string length $robotsMax]} {
942                 set robotsMax [lindex $argv [incr i]]
943             }
944         }
945         -c* {
946             set maxdistance [string range $arg 2 end]
947             if {![string length $maxdistance]} {
948                 set maxdistance [lindex $argv [incr i]]
949             }
950         }
951         -d* {
952             set dom [string range $arg 2 end]
953             if {![string length $dom]} {
954                 set dom [lindex $argv [incr i]]
955             }
956             lappend domains $dom
957         }
958         -i* {
959             set idletime [string range $arg 2 end]
960             if {![string length $idletime]} {
961                 set idletime [lindex $argv [incr i]]
962             }
963         }
964         -l* {
965             set acceptLanguage [string range $arg 2 end]
966             if {![string length $acceptLanguage]} {
967                 set acceptLanguage [lindex $argv [incr i]]
968             }
969         }
970         -r* {
971             set rfile [string range $arg 2 end]
972             if {![string length $rfile]} {
973                 set rfile [lindex $argv [incr i]]
974             }
975             source $rfile
976         }
977         default {
978             set href $arg
979             if {[RobotHref http://www.indexdata.dk/ href host path]} {
980                 if {![RobotFileExist visited $host $path]} {
981                     set outf [RobotFileOpen unvisited $host $path]
982                     RobotWriteRecord $outf href 0
983                     RobotFileClose $outf
984                 }
985             }
986         }
987     }
988     incr i
989 }
990
991 if {![info exist domains]} {
992     set domains {*}
993 }
994 if {![info exist maxdistance]} {
995     set maxdistance 50
996 }
997 if {![info exist robotsMax]} {
998     set robotsMax 5
999 }
1000
1001 puts "domains=$domains"
1002 puts "max distance=$maxdistance"
1003 puts "max jobs=$robotsMax"
1004
1005 RobotStart
1006
1007 while {$robotsRunning} {
1008     vwait robotsRunning
1009 }