]> git.donarmstrong.com Git - infobot.git/blob - src/Modules/Debian.pl
* Try updating Debian.pl again
[infobot.git] / src / Modules / Debian.pl
1 #
2 #   Debian.pl: Frontend to debian contents and packages files
3 #      Author: dms
4 #     Version: v0.8 (20000918)
5 #     Created: 20000106
6 #
7
8
9 # XXX Add uploader field support
10
11 package Debian;
12
13 use strict;
14 no strict 'refs'; # FIXME: dstats aborts if set
15
16 my $announce    = 0;
17 my $defaultdist = 'sid';
18 my $refresh = &::getChanConfDefault('debianRefreshInterval', 7, $::chan) * 60 * 60 * 24;
19 my $debug       = 0;
20 my $debian_dir  = $::bot_state_dir . 'debian';
21 my $country     = 'us'; # well .config it yourself then. ;-)
22 my $protocol    = 'http';
23
24 # format: "alias=real".
25 my %dists       = (
26         'unstable'      => 'sid',
27         'testing'       => 'lenny',
28         'stable'        => 'etch',
29         'experimental'  => 'experimental',
30         'oldstable'     => 'sarge',
31         'incoming'      => 'incoming',
32 );
33
34 my %archived_dists = (
35     woody  => 'woody',
36     potato => 'potato',
37     hamm   => 'hamm',
38     buzz   => 'buzz',
39     bo     => 'bo',
40     rex    => 'rex',
41     slink  => 'slink',
42 );
43
44 my %archiveurlcontents = (
45         "Contents-##DIST-i386.gz" =>
46                 "$protocol://debian.crosslink.net/debian-archive".
47                 "/dists/##DIST/Contents-i386.gz",
48 );
49
50 my %archiveurlpackages = (
51         "Packages-##DIST-main-i386.gz" =>
52                 "$protocol://debian.crosslink.net/debian-archive".
53                 "/dists/##DIST/main/binary-i386/Packages.gz",
54         "Packages-##DIST-contrib-i386.gz" =>
55                 "$protocol://debian.crosslink.net/debian-archive".
56                 "/dists/##DIST/contrib/binary-i386/Packages.gz",
57         "Packages-##DIST-non-free-i386.gz" =>
58                 "$protocol://debian.crosslink.net/debian-archive".
59                 "/dists/##DIST/non-free/binary-i386/Packages.gz",
60 );
61     
62
63
64
65 my %urlcontents = (
66         "Contents-##DIST-i386.gz" =>
67                 "$protocol://debian.usc.edu".
68                 "/dists/##DIST/Contents-i386.gz",
69         "Contents-##DIST-i386-non-US.gz" =>
70                 "$protocol://non-us.debian.org".
71                 "/debian-non-US/dists/##DIST/non-US/Contents-i386.gz",
72 );
73
74 my %urlpackages = (
75         "Packages-##DIST-main-i386.gz" =>
76                 "$protocol://debian.usc.edu".
77                 "/dists/##DIST/main/binary-i386/Packages.gz",
78         "Packages-##DIST-contrib-i386.gz" =>
79                 "$protocol://debian.usc.edu".
80                 "/dists/##DIST/contrib/binary-i386/Packages.gz",
81         "Packages-##DIST-non-free-i386.gz" =>
82                 "$protocol://debian.usc.edu".
83                 "/dists/##DIST/non-free/binary-i386/Packages.gz",
84
85 #       "Packages-##DIST-non-US-main-i386.gz" =>
86 #               "$protocol://non-us.debian.org".
87 #               "/debian-non-US/dists/##DIST/non-US/main/binary-i386/Packages.gz",
88 #       "Packages-##DIST-non-US-contrib-i386.gz" =>
89 #               "$protocol://non-us.debian.org".
90 #               "/debian-non-US/dists/##DIST/non-US/contrib/binary-i386/Packages.gz",
91 #       "Packages-##DIST-non-US-non-free-i386.gz" =>
92 #               "$protocol://non-us.debian.org".
93 #               "/debian-non-US/dists/##DIST/non-US/non-free/binary-i386/Packages.gz",
94 );
95
96 #####################
97 ### COMMON FUNCTION....
98 #######################
99
100 ####
101 # Usage: &DebianDownload($dist, %hash);
102 sub DebianDownload {
103     my ($dist, %urls)   = @_;
104     my $bad     = 0;
105     my $good    = 0;
106
107     if (! -d $debian_dir) {
108         &::status("Debian: creating debian dir.");
109         mkdir($debian_dir, 0755);
110     }
111
112     # fe dists.
113     # Download the files.
114     my $file;
115     foreach $file (keys %urls) {
116         my $url = $urls{$file};
117         $url  =~ s/##DIST/$dist/g;
118         $file =~ s/##DIST/$dist/g;
119         my $update = 0;
120
121         if ( -f $file ) {
122             my $last_refresh = (stat $file)[9];
123             $update++ if (time() - $last_refresh > $refresh);
124         } else {
125             $update++;
126         }
127
128         next unless ($update);
129
130         &::DEBUG("announce == $announce.") if ($debug);
131         if ($good + $bad == 0 and !$announce) {
132             &::status("Debian: Downloading files for '$dist'.");
133             &::msg($::who, "Updating debian files... please wait.");
134             $announce++;
135         }
136
137         if (exists $::debian{$url}) {
138             &::DEBUG("2: ".(time - $::debian{$url})." <= $refresh") if ($debug);
139             next if (time() - $::debian{$url} <= $refresh);
140             &::DEBUG("stale for url $url; updating!") if ($debug);
141         }
142
143         if ($url =~ /^ftp:\/\/(.*?)\/(\S+)\/(\S+)$/) {
144             my ($host,$path,$thisfile) = ($1,$2,$3);
145
146             if (!&::ftpGet($host,$path,$thisfile,$file)) {
147                 &::WARN("deb: down: $file == BAD.");
148                 $bad++;
149                 next;
150             }
151
152         } elsif ($url =~ /^http:\/\/\S+\/\S+$/) {
153
154             if (!&::getURLAsFile($url,$file)) {
155                 &::WARN("deb: down: http: $file == BAD.");
156                 $bad++;
157                 next;
158             }
159
160         } else {
161             &::ERROR("Debian: invalid format of url => ($url).");
162             $bad++;
163             next;
164         }
165
166         if (! -f $file) {
167             &::WARN("deb: down: http: !file");
168             $bad++;
169             next;
170         }
171
172 #       my $exit = system("/bin/gzip -t $file");
173 #       if ($exit) {
174 #           &::WARN("deb: $file is corrupted ($exit) :/");
175 #           unlink $file;
176 #           next;
177 #       }
178
179         &::DEBUG("deb: download: good.") if ($debug);
180         $good++;
181     }
182
183     # ok... lets just run this.
184     &::miscCheck() if (&::whatInterface() =~ /IRC/);
185
186     if ($good) {
187         &generateIndex($dist);
188         return 1;
189     } else {
190         return -1 unless ($bad);        # no download.
191         &::DEBUG("DD: !good and bad($bad). :(");
192         return 0;
193     }
194 }
195
196 ###########################
197 # DEBIAN CONTENTS SEARCH FUNCTIONS.
198 ########
199
200 ####
201 # Usage: &searchContents($query);
202 sub searchContents {
203     my ($dist, $query)  = &getDistroFromStr($_[0]);
204     &::status("Debian: Contents search for '$query' in '$dist'.");
205     my $dccsend = 0;
206
207     $dccsend++          if ($query =~ s/^dcc\s+//i);
208
209     $query =~ s/\\([\^\$])/$1/g;        # hrm?
210     $query =~ s/^\s+|\s+$//g;
211
212     if (!&::validExec($query)) {
213         &::msg($::who, 'search string looks fuzzy.');
214         return;
215     }
216
217     my %urls = fixDist($dist,'contents');
218     if ($dist eq 'incoming') {          # nothing yet.
219         &::DEBUG('sC: dist = "incoming". no contents yet.');
220         return;
221     } else {
222         # download contents file.
223         &::DEBUG('deb: download 1.') if ($debug);
224         if (!&DebianDownload($dist, %urls)) {
225             &::WARN('Debian: could not download files.');
226         }
227     }
228
229     # start of search.
230     my $start_time = &::timeget();
231
232     my $found   = 0;
233     my $front   = 0;
234     my %contents;
235     my $grepRE;
236     ### TODO: search properly if /usr/bin/blah is done.
237     if ($query =~ s/\$$//) {
238         &::DEBUG("deb: search-regex found.") if ($debug);
239         $grepRE = "$query\[ \t]";
240     } elsif ($query =~ s/^\^//) {
241         &::DEBUG("deb: front marker regex found.") if ($debug);
242         $front = 1;
243         $grepRE = $query;
244     } else {
245         $grepRE = "$query*\[ \t]";
246     }
247
248     # fix up grepRE for "*".
249     $grepRE =~ s/\*/.*/g;
250
251     my @files;
252     foreach (keys %urls) {
253         next unless ( -f $_ );
254         push(@files, $_);
255     }
256
257     if (!scalar @files) {
258         &::ERROR("sC: no files?");
259         &::msg($::who, "failed.");
260         return;
261     }
262
263     my $files = join(' ', @files);
264
265     my $regex   = $query;
266     $regex      =~ s/\./\\./g;
267     $regex      =~ s/\*/\\S*/g;
268     $regex      =~ s/\?/./g;
269
270     open(IN,"zegrep -h '$grepRE' $files |");
271     # wonderful abuse of if, last, next, return, and, unless ;)
272     while (<IN>) {
273         last if ($found > 100);
274
275         next unless (/^\.?\/?(.*?)[\t\s]+(\S+)\n$/);
276         my ($file,$package) = ("/".$1,$2);
277
278         if ($query =~ /[\/\*\\]/) {
279             next unless (eval { $file =~ /$regex/ });
280             return unless &checkEval($@);
281         } else {
282             my ($basename) = $file =~ /^.*\/(.*)$/;
283             next unless (eval { $basename =~ /$regex/ });
284             return unless &checkEval($@);
285         }
286         next if ($query !~ /\.\d\.gz/ and $file =~ /\/man\//);
287         next if ($front and eval { $file !~ /^\/$query/ });
288         return unless &checkEval($@);
289
290         $contents{$package}{$file} = 1;
291         $found++;
292     }
293     close IN;
294
295     my $pkg;
296
297     ### send results with dcc.
298     if ($dccsend) {
299         if (exists $::dcc{'SEND'}{$::who}) {
300             &::msg($::who, "DCC already active!");
301             return;
302         }
303
304         if (!scalar %contents) {
305             &::msg($::who,"search returned no results.");
306             return;
307         }
308
309         my $file = "$::param{tempDir}/$::who.txt";
310         if (!open OUT, ">$file") {
311             &::ERROR("Debian: cannot write file for dcc send.");
312             return;
313         }
314
315         foreach $pkg (keys %contents) {
316             foreach (keys %{ $contents{$pkg} }) {
317                 # TODO: correct padding.
318                 print OUT "$_\t\t\t$pkg\n";
319             }
320         }
321         close OUT;
322
323         &::shmWrite($::shm, "DCC SEND $::who $file");
324
325         return;
326     }
327
328     &::status("Debian: $found contents results found.");
329
330     my @list;
331     foreach $pkg (keys %contents) {
332         my @tmplist = &::fixFileList(keys %{ $contents{$pkg} });
333         my @sublist = sort { length $a <=> length $b } @tmplist;
334
335         pop @sublist while (scalar @sublist > 3);
336
337         $pkg =~ s/\,/\037\,\037/g;      # underline ','.
338         push(@list, "(". join(', ',@sublist) .") in $pkg");
339     }
340     # sort the total list from shortest to longest...
341     @list = sort { length $a <=> length $b } @list;
342
343     # show how long it took.
344     my $delta_time = &::timedelta($start_time);
345     &::status(sprintf("Debian: %.02f sec to complete query.", $delta_time)) if ($delta_time > 0);
346
347     my $prefix = "Debian Search of '$query' ";
348     if (scalar @list) { # @list.
349         &::performStrictReply( &::formListReply(0, $prefix, @list) );
350         return;
351     }
352
353     # !@list.
354     &::DEBUG("deb: ok, !\@list, searching desc for '$query'.") if ($debug);
355     @list = &searchDesc($query);
356
357     if (!scalar @list) {
358         my $prefix = "Debian Package/File/Desc Search of '$query' ";
359         &::performStrictReply( &::formListReply(0, $prefix, ) );
360
361     } elsif (scalar @list == 1) {       # list = 1.
362         &::DEBUG("deb: list == 1; showing package info of '$list[0]'.");
363         &infoPackages("info", $list[0]);
364
365     } else {                            # list > 1.
366         my $prefix = "Debian Desc Search of '$query' ";
367         &::performStrictReply( &::formListReply(0, $prefix, @list) );
368     }
369 }
370
371 ####
372 # Usage: &searchAuthor($query);
373 sub searchAuthor {
374     my ($dist, $query)  = &getDistroFromStr($_[0]);
375     &::DEBUG("deb: searchAuthor: dist => '$dist', query => '$query'.") if ($debug);
376     $query =~ s/^\s+|\s+$//g;
377
378     # start of search.
379     my $start_time = &::timeget();
380     &::status("Debian: starting author search.");
381
382     my %urls = fixDist($dist,'packages');
383     my $files;
384     my ($bad,$good) = (0,0);
385     foreach (keys %urls) {
386         if (! -f $_ ) {
387             $bad++;
388             next;
389         }
390
391         $good++;
392         $files .= " ".$_;
393     }
394
395     &::DEBUG("deb: good = $good, bad = $bad...") if ($debug);
396
397     if ($good == 0 and $bad != 0) {
398         &::DEBUG("deb: download 2.");
399
400         if (!&DebianDownload($dist, %urls)) {
401             &::ERROR("Debian(sA): could not download files.");
402             return;
403         }
404     }
405
406     my (%maint, %pkg, $package);
407     open(IN,"zegrep -h '^Package|^Maintainer' $files |");
408     while (<IN>) {
409         if (/^Package: (\S+)$/) {
410             $package = $1;
411
412         } elsif (/^Maintainer: (.*) \<(\S+)\>$/) {
413             my($name,$email) = ($1,$2);
414             if ($package eq "") {
415                 &::DEBUG("deb: sA: package == NULL.");
416                 next;
417             }
418             $maint{$name}{$email} = 1;
419             $pkg{$name}{$package} = 1;
420             $package = "";
421
422         } else {
423             chop;
424             &::WARN("debian: invalid line: '$_' (1).");
425         }
426     }
427     close IN;
428
429     my %hash;
430     # TODO: can we use 'map' here?
431     foreach (grep /\Q$query\E/i, keys %maint) {
432         $hash{$_} = 1;
433     }
434
435     # TODO: should we only search email if '@' is used?
436     if (scalar keys %hash < 15) {
437         my $name;
438
439         foreach $name (keys %maint) {
440             my $email;
441
442             foreach $email (keys %{ $maint{$name} }) {
443                 next unless ($email =~ /\Q$query\E/i);
444                 next if (exists $hash{$name});
445                 $hash{$name} = 1;
446             }
447         }
448     }
449
450     my @list = keys %hash;
451     if (scalar @list != 1) {
452         my $prefix = "Debian Author Search of '$query' ";
453         &::performStrictReply( &::formListReply(0, $prefix, @list) );
454         return 1;
455     }
456
457     &::DEBUG("deb: showing all packages by '$list[0]'...") if ($debug);
458
459     my @pkg = sort keys %{ $pkg{$list[0]} };
460
461     # show how long it took.
462     my $delta_time = &::timedelta($start_time);
463     &::status(sprintf("Debian: %.02f sec to complete query.", $delta_time)) if ($delta_time > 0);
464
465     my $email   = join(', ', keys %{ $maint{$list[0]} });
466     my $prefix  = "Debian Packages by $list[0] \002<\002$email\002>\002 ";
467     &::performStrictReply( &::formListReply(0, $prefix, @pkg) );
468 }
469
470 ####
471 # Usage: &searchDesc($query);
472 sub searchDesc {
473     my ($dist, $query)  = &getDistroFromStr($_[0]);
474     &::DEBUG("deb: searchDesc: dist => '$dist', query => '$query'.") if ($debug);
475     $query =~ s/^\s+|\s+$//g;
476
477     # start of search.
478     my $start_time = &::timeget();
479     &::status("Debian: starting desc search.");
480
481     my $files;
482     my ($bad,$good) = (0,0);
483     my %urls = fixDist($dist,'packages');
484
485     # XXX This should be abstracted elsewhere.
486     foreach (keys %urls) {
487         if (! -f $_ ) {
488             $bad++;
489             next;
490         }
491
492         $good++;
493         $files .= " $_";
494     }
495
496     &::DEBUG("deb(2): good = $good, bad = $bad...") if ($debug);
497
498     if ($good == 0 and $bad != 0) {
499         &::DEBUG("deb: download 2c.") if ($debug);
500
501         if (!&DebianDownload($dist, %urls)) {
502             &::ERROR("deb: sD: could not download files.");
503             return;
504         }
505     }
506
507     my $regex   = $query;
508     $regex      =~ s/\./\\./g;
509     $regex      =~ s/\*/\\S*/g;
510     $regex      =~ s/\?/./g;
511
512     my (%desc, $package);
513     open(IN,"zegrep -h '^Package|^Description' $files |");
514     while (<IN>) {
515         if (/^Package: (\S+)$/) {
516             $package = $1;
517         } elsif (/^Description: (.*)$/) {
518             my $desc = $1;
519             next unless (eval { $desc =~ /$regex/i });
520             return unless &checkEval($@);
521
522             if ($package eq "") {
523                 &::WARN("sD: package == NULL?");
524                 next;
525             }
526
527             $desc{$package} = $desc;
528             $package = "";
529
530         } else {
531             chop;
532             &::WARN("debian: invalid line: '$_'. (2)");
533         }
534     }
535     close IN;
536
537     # show how long it took.
538     my $delta_time = &::timedelta($start_time);
539     &::status(sprintf("Debian: %.02f sec to complete query.", $delta_time)) if ($delta_time > 0);
540
541     return keys %desc;
542 }
543
544 ####
545 # Usage: &generateIncoming();
546 sub generateIncoming {
547     my $pkgfile  = $debian_dir."/Packages-incoming";
548     my $idxfile  = $pkgfile.".idx";
549     my $stale    = 0;
550     $stale++ if (&::isStale($pkgfile.".gz", $refresh));
551     $stale++ if (&::isStale($idxfile, $refresh));
552     &::DEBUG("deb: gI: stale => '$stale'.") if ($debug);
553     return 0 unless ($stale);
554
555     ### STATIC URL.
556     my %ftp = &::ftpList("llug.sep.bnl.gov", "/pub/debian/Incoming/");
557
558     if (!open PKG, ">$pkgfile") {
559         &::ERROR("cannot write to pkg $pkgfile.");
560         return 0;
561     }
562     if (!open IDX, ">$idxfile") {
563         &::ERROR("cannot write to idx $idxfile.");
564         return 0;
565     }
566
567     print IDX "*$pkgfile.gz\n";
568     my $file;
569     foreach $file (sort keys %ftp) {
570         next unless ($file =~ /deb$/);
571
572         if ($file =~ /^(\S+)\_(\S+)\_(\S+)\.deb$/) {
573             print IDX "$1\n";
574             print PKG "Package: $1\n";
575             print PKG "Version: $2\n";
576             print PKG "Architecture: ", (defined $4) ? $4 : "all", "\n";
577         }
578         print PKG "Filename: $file\n";
579         print PKG "Size: $ftp{$file}\n";
580         print PKG "\n";
581     }
582     close IDX;
583     close PKG;
584
585     system("gzip -9fv $pkgfile");       # lame fix.
586
587     &::status("Debian: generateIncoming() complete.");
588 }
589
590
591 ##############################
592 # DEBIAN PACKAGE INFO FUNCTIONS.
593 #########
594
595 # Usage: &getPackageInfo($query,$file);
596 sub getPackageInfo {
597     my ($package, $file) = @_;
598
599     if (! -f $file) {
600         &::status("gPI: file $file does not exist?");
601         return 'NULL';
602     }
603
604     my $found = 0;
605     my (%pkg, $pkg);
606
607     open(IN, "/bin/zcat $file 2>&1 |");
608
609     my $done = 0;
610     while (!eof IN) {
611         $_ = <IN>;
612
613         next if (/^ \S+/);      # package long description.
614
615         # package line.
616         if (/^Package: (.*)\n$/) {
617             $pkg = $1;
618             if ($pkg =~ /^\Q$package\E$/i) {
619                 $found++;       # we can use pkg{'package'} instead.
620                 $pkg{'package'} = $pkg;
621             }
622
623             next;
624         }
625
626         if ($found) {
627             chop;
628
629             if (/^Version: (.*)$/) {
630                 $pkg{'version'}         = $1;
631             } elsif (/^Priority: (.*)$/) {
632                 $pkg{'priority'}        = $1;
633             } elsif (/^Section: (.*)$/) {
634                 $pkg{'section'}         = $1;
635             } elsif (/^Size: (.*)$/) {
636                 $pkg{'size'}            = $1;
637             } elsif (/^Installed-Size: (.*)$/i) {
638                 $pkg{'installed'}       = $1;
639             } elsif (/^Description: (.*)$/) {
640                 $pkg{'desc'}            = $1;
641             } elsif (/^Filename: (.*)$/) {
642                 $pkg{'find'}            = $1;
643             } elsif (/^Pre-Depends: (.*)$/) {
644                 $pkg{'depends'}         = "pre-depends on $1";
645             } elsif (/^Depends: (.*)$/) {
646                 if (exists $pkg{'depends'}) {
647                     $pkg{'depends'} .= "; depends on $1";
648                 } else {
649                     $pkg{'depends'} = "depends on $1";
650                 }
651             } elsif (/^Maintainer: (.*)$/) {
652                 $pkg{'maint'} = $1;
653             } elsif (/^Provides: (.*)$/) {
654                 $pkg{'provides'} = $1;
655             } elsif (/^Suggests: (.*)$/) {
656                 $pkg{'suggests'} = $1;
657             } elsif (/^Conflicts: (.*)$/) {
658                 $pkg{'conflicts'} = $1;
659             }
660
661 ###         &::DEBUG("=> '$_'.");
662         }
663
664         # blank line.
665         if (/^$/) {
666             undef $pkg;
667             last if ($found);
668             next;
669         }
670
671         next if (defined $pkg);
672     }
673
674     close IN;
675
676     %pkg;
677 }
678
679 # Usage: &infoPackages($query,$package);
680 sub infoPackages {
681     my ($query,$dist,$package) = ($_[0], &getDistroFromStr($_[1]));
682
683     &::status("Debian: Searching for package '$package' in '$dist'.");
684
685     # download packages file.
686     # hrm...
687     my %urls = &fixDist($dist,'packages');
688     if ($dist ne "incoming") {
689         &::DEBUG("deb: download 3.") if ($debug);
690
691         if (!&DebianDownload($dist, %urls)) {   # no good download.
692             &::WARN("Debian(iP): could not download ANY files.");
693         }
694     }
695
696     # check if the package is valid.
697     my $incoming = 0;
698     my @files = &validPackage($package, $dist);
699     if (!scalar @files) {
700         &::status("Debian: no valid package found; checking incoming.");
701         @files = &validPackage($package, "incoming");
702
703         if (scalar @files) {
704             &::status("Debian: cool, it exists in incoming.");
705             $incoming++;
706         } else {
707             &::msg($::who, "Package '$package' does not exist.");
708             return 0;
709         }
710     }
711
712     if (scalar @files > 1) {
713         &::WARN("same package in more than one file; random.");
714         &::DEBUG("THIS SHOULD BE FIXED SOMEHOW!!!");
715         $files[0] = &::getRandom(@files);
716     }
717
718     if (! -f $files[0]) {
719         &::WARN("files[0] ($files[0]) doesn't exist.");
720         &::msg($::who, "FIXME: $files[0] does not exist?");
721         return 'NULL';
722     }
723
724     ### TODO: if specific package is requested, note down that a version
725     ###         exists in incoming.
726
727     my $found = 0;
728     my $file = $files[0];
729     my ($pkg);
730
731     ### TODO: use fe, dump to a hash. if only one version of the package
732     ###         exists. do as normal otherwise list all versions.
733     if (! -f $file) {
734         &::ERROR("D:iP: file '$file' DOES NOT EXIST!!! should never happen.");
735         return 0;
736     }
737     my %pkg = &getPackageInfo($package, $file);
738
739     $query = "info" if ($query eq "dinfo");
740
741     # 'fm'-like output.
742     if ($query eq "info") {
743         if (scalar keys %pkg <= 5) {
744             &::DEBUG("deb: running debianCheck() due to problems (".scalar(keys %pkg).").");
745             &debianCheck();
746             &::DEBUG("deb: end of debianCheck()");
747
748             &::msg($::who,"Debian: Package appears to exist but I could not retrieve info about it...");
749             return;
750         }
751
752         $pkg{'info'}  = "\002(\002". $pkg{'desc'} ."\002)\002";
753         $pkg{'info'} .= ", section ".$pkg{'section'};
754         $pkg{'info'} .= ", is ".$pkg{'priority'};
755 #       $pkg{'info'} .= ". Version: \002$pkg{'version'}\002";
756         $pkg{'info'} .= ". Version: \002$pkg{'version'}\002 ($dist)";
757         $pkg{'info'} .= ", Packaged size: \002". int($pkg{'size'}/1024) ."\002 kB";
758         $pkg{'info'} .= ", Installed size: \002$pkg{'installed'}\002 kB";
759
760         if ($incoming) {
761             &::status("iP: info requested and pkg is in incoming, too.");
762             my %incpkg = &getPackageInfo($query, $debian_dir ."/Packages-incoming");
763
764             if (scalar keys %incpkg) {
765                 $pkg{'info'} .= ". Is in incoming ($incpkg{'file'}).";
766             } else {
767                 &::ERROR("iP: pkg $query is in incoming but we couldn't get any info?");
768             }
769         }
770     }
771
772     if ($dist eq "incoming") {
773         $pkg{'info'} .= "Version: \002$pkg{'version'}\002";
774         $pkg{'info'} .= ", Packaged size: \002". int($pkg{'size'}/1024) ."\002 kB";
775         $pkg{'info'} .= ", is in incoming!!!";
776     }
777
778     if (!exists $pkg{$query}) {
779         if ($query eq "suggests") {
780             $pkg{$query} = "has no suggestions";
781         } elsif ($query eq "conflicts") {
782             $pkg{$query} = "does not conflict with any other package";
783         } elsif ($query eq "depends") {
784             $pkg{$query} = "does not depend on anything";
785         } elsif ($query eq "maint") {
786             $pkg{$query} = "has no maintainer";
787         } else {
788             $pkg{$query} = "has nothing about $query";
789         }
790     }
791
792     &::performStrictReply("$package: $pkg{$query}");
793 }
794
795 # Usage: &infoStats($dist);
796 sub infoStats {
797     my ($dist)  = @_;
798     $dist       = &getDistro($dist);
799     return unless (defined $dist);
800
801     &::DEBUG("deb: infoS: dist => '$dist'.");
802
803     # download packages file if needed.
804     my %urls = &fixDist($dist,'packages');
805     &::DEBUG("deb: download 4.");
806     if (!&DebianDownload($dist, %urls)) {
807         &::WARN("Debian(iS): could not download ANY files.");
808         &::msg($::who, "Debian(iS): internal error.");
809         return;
810     }
811
812     my %stats;
813     my %total = (count => 0, maint => 0, isize => 0, csize => 0);
814     my $file;
815     foreach $file (keys %urls) {
816         &::DEBUG("deb: file => '$file'.");
817         if (exists $stats{$file}{'count'}) {
818             &::DEBUG("deb: hrm... duplicate open with $file???");
819             next;
820         }
821
822         open(IN, "zcat $file 2>&1 |");
823
824         if (! -e "$file") {
825             &::DEBUG("deb: iS: $file does not exist.");
826             next;
827         }
828
829         while (!eof IN) {
830             $_ = <IN>;
831
832             next if (/^ \S+/);  # package long description.
833
834             if (/^Package: (.*)\n$/) {          # counter.
835                 $stats{$file}{'count'}++;
836                 $total{'count'}++;
837             } elsif (/^Maintainer: .* <(\S+)>$/) {
838                 $stats{$file}{'maint'}{$1}++;
839                 $total{'maint'}{$1}++;
840             } elsif (/^Size: (.*)$/) {          # compressed size.
841                 $stats{$file}{'csize'}  += $1;
842                 $total{'csize'}         += $1;
843             } elsif (/^i.*size: (.*)$/i) {      # installed size.
844                 $stats{$file}{'isize'}  += $1;
845                 $total{'isize'}         += $1;
846             }
847
848 ###         &::DEBUG("=> '$_'.");
849         }
850         close IN;
851     }
852
853     ### TODO: don't count ppl with multiple email addresses.
854
855     &::performStrictReply(
856         "Debian Distro Stats on $dist... ".
857         "\002$total{'count'}\002 packages, ".
858         "\002".scalar(keys %{ $total{'maint'} })."\002 maintainers, ".
859         "\002". int($total{'isize'}/1024)."\002 MB installed size, ".
860         "\002". int($total{'csize'}/1024/1024)."\002 MB compressed size."
861     );
862
863 ### TODO: do individual stats? if so, we need _another_ arg.
864 #    foreach $file (keys %stats) {
865 #       foreach (keys %{ $stats{$file} }) {
866 #           &::DEBUG("  '$file' '$_' '$stats{$file}{$_}'.");
867 #       }
868 #    }
869
870     return;
871 }
872
873 ###
874 # HELPER FUNCTIONS FOR INFOPACKAGES...
875 ###
876
877 # Usage: &generateIndex();
878 sub generateIndex {
879     my (@dists) = @_;
880     &::DEBUG("D: generateIndex($dists[0]) called! ".join(':',caller(),));
881     if (!scalar @dists or $dists[0] eq '') {
882         &::ERROR("gI: no dists to generate index.");
883         return 1;
884     }
885
886     foreach (@dists) {
887         my $dist = &getDistro($_); # incase the alias is returned, possible?
888         my $idx  = $debian_dir."/Packages-$dist.idx";
889         my %urls = fixDist($_,'packages');
890
891         # TODO: check if any of the Packages file have been updated then
892         #       regenerate it, even if it's not stale.
893         # TODO: also, regenerate the index if the packages file is newer
894         #       than the index.
895         next unless (&::isStale($idx, $refresh));
896
897         if (/^incoming$/i) {
898             &::DEBUG("deb: gIndex: calling generateIncoming()!");
899             &generateIncoming();
900             next;
901         }
902
903 #       if (/^sarge$/i) {
904 #           &::DEBUG("deb: Copying old index of sarge to -old");
905 #           system("cp $idx $idx-old");
906 #       }
907
908         &::DEBUG("deb: gIndex: calling DebianDownload($dist, ...).") if ($debug);
909         &DebianDownload($dist, &fixDist($dist,'packages') );
910
911         &::status("Debian: generating index for '$dist'.");
912         if (!open OUT, ">$idx") {
913             &::ERROR("cannot write to $idx.");
914             return 0;
915         }
916
917         my $packages;
918         foreach $packages (keys %urls) {
919             if (! -e $packages) {
920                 &::ERROR("gIndex: '$packages' does not exist?");
921                 next;
922             }
923
924             print OUT "*$packages\n";
925             open(IN,"zcat $packages |");
926
927             while (<IN>) {
928                 next unless (/^Package: (.*)\n$/);
929                 print OUT $1."\n";
930             }
931             close IN;
932         }
933         close OUT;
934     }
935
936     return 1;
937 }
938
939 # Usage: &validPackage($package, $dist);
940 sub validPackage {
941     my ($package,$dist) = @_;
942     my @files;
943     my $file;
944
945     ### this majorly sucks, we need some standard in place.
946     # why is this needed... need to investigate later.
947     my $olddist = $dist;
948     $dist = &getDistro($dist);
949
950     &::DEBUG("deb: validPackage($package, $dist) called.") if ($debug);
951
952     my $error = 0;
953     while (!open IN, $debian_dir."/Packages-$dist.idx") {
954         if ($error) {
955             &::ERROR("Packages-$dist.idx does not exist (#1).");
956             return;
957         }
958
959         &generateIndex($dist);
960
961         $error++;
962     }
963
964     my $count = 0;
965     while (<IN>) {
966         if (/^\*(.*)\n$/) {
967             $file = $1;
968             next;
969         }
970
971         if (/^\Q$package\E\n$/) {
972             push(@files,$file);
973         }
974         $count++;
975     }
976     close IN;
977
978     &::VERB("vP: scanned $count items in index.",2);
979
980     return @files;
981 }
982
983 sub searchPackage {
984     my ($dist, $query) = &getDistroFromStr($_[0]);
985     my $file    = $debian_dir."/Packages-$dist.idx";
986     my $warn    = ($query =~ tr/A-Z/a-z/) ? 1 : 0;
987     my $error   = 0;
988     my @files;
989
990     &::status("Debian: Search package matching '$query' in '$dist'.");
991     unlink $file if ( -z $file );
992
993     while (!open IN, $file) {
994         if ($dist eq "incoming") {
995             &::DEBUG("deb: sP: dist == incoming; calling gI().");
996             &generateIncoming();
997         }
998
999         if ($error) {
1000             &::ERROR("could not generate index ($file)!");
1001             return;
1002         }
1003
1004         $error++;
1005         &::DEBUG("deb: should we be doing this?");
1006         &generateIndex(($dist));
1007     }
1008
1009     while (<IN>) {
1010         chop;
1011
1012         if (/^\*(.*)$/) {
1013             $file = $1;
1014
1015             if (&::isStale($file, $refresh)) {
1016                 &::DEBUG("deb: STALE $file! regen.") if ($debug);
1017                 &generateIndex(($dist));
1018 ###             @files = searchPackage("$query $dist");
1019                 &::DEBUG("deb: EVIL HACK HACK HACK.") if ($debug);
1020                 last;
1021             }
1022
1023             next;
1024         }
1025
1026         if (/\Q$query\E/) {
1027             push(@files,$_);
1028         }
1029     }
1030     close IN;
1031
1032     if (scalar @files and $warn) {
1033         &::msg($::who, "searching for package name should be fully lowercase!");
1034     }
1035
1036     return @files;
1037 }
1038
1039 sub getDistro {
1040     my $dist = $_[0];
1041
1042     if (!defined $dist or $dist eq "") {
1043         &::DEBUG("deb: gD: dist == NULL; dist = defaultdist.");
1044         $dist = $defaultdist;
1045     }
1046
1047     if (exists $dists{$dist}) {
1048         &::VERB("gD: returning dists{$dist} ($dists{$dist})",2);
1049         return $dists{$dist};
1050
1051     }
1052     elsif (exists $archived_dists{$dist}){
1053         &::VERB("gD: returning archivedists{$dist} ($archived_dists{$dist})",2);
1054         return $archived_dists{$dist};
1055     }
1056     else {
1057         if (!grep(/^\Q$dist\E$/i, %dists) and !grep(/^\Q$dist\E$/i, %archived_dists)) {
1058             &::msg($::who, "invalid dist '$dist'.");
1059             return;
1060         }
1061
1062         &::VERB("gD: returning $dist (no change or conversion)",2);
1063         return $dist;
1064     }
1065 }
1066
1067 sub getDistroFromStr {
1068     my ($str) = @_;
1069     my $dists   = join '|', %dists, %archived_dists;
1070     my $dist    = $defaultdist;
1071
1072     if ($str =~ s/\s+($dists)$//i) {
1073         $dist = &getDistro(lc $1);
1074         $str =~ s/\\+$//;
1075     }
1076     $str =~ s/\\([\$\^])/$1/g;
1077
1078     return($dist,$str);
1079 }
1080
1081 sub fixDist {
1082     my ($dist, $type) = @_;
1083     my %new;
1084     my ($key,$val);
1085     my %dist_urls;
1086     
1087     if (exists $archived_dists{$dist}){
1088         if ($type eq 'contents'){
1089             %dist_urls = %archiveurlcontents;
1090         }
1091         else {
1092             %dist_urls = %archiveurlpackages;
1093         }  
1094     }
1095     else {
1096         if ($type eq 'contents'){
1097             %dist_urls = %urlcontents;
1098         }
1099         else {
1100             %dist_urls = %urlpackages;
1101         }
1102     }
1103        
1104     while (($key,$val) = each %dist_urls) {
1105         $key =~ s/##DIST/$dist/;
1106         $val =~ s/##DIST/$dist/;
1107         ### TODO: what should we do if the sar wasn't done.
1108         $new{$debian_dir."/".$key} = $val;
1109     }
1110
1111     return %new;
1112 }
1113
1114 sub DebianFind {
1115     # HACK! HACK! HACK!
1116     my ($str) = @_;
1117     my ($dist, $query) = &getDistroFromStr($str);
1118     my @results = sort &searchPackage($str);
1119
1120     if (!scalar @results) {
1121         &::Forker("Debian", sub { &searchContents($str); } );
1122     } elsif (scalar @results == 1) {
1123         &::status("searchPackage returned one result; getting info of package instead!");
1124         &::Forker("Debian", sub { &infoPackages("info", "$results[0] $dist"); } );
1125     } else {
1126         my $prefix = "Debian Package Listing of '$query' ";
1127         &::performStrictReply( &::formListReply(0, $prefix, @results) );
1128     }
1129 }
1130
1131 sub debianCheck {
1132     my $error   = 0;
1133
1134     &::status("debianCheck() called.");
1135
1136     ### TODO: remove the following loop (check if dir exists before)
1137     while (1) {
1138         last if (opendir(DEBIAN, $debian_dir));
1139
1140         if ($error) {
1141             &::ERROR("dC: cannot opendir debian.");
1142             return;
1143         }
1144
1145         mkdir $debian_dir, 0755;
1146         $error++;
1147     }
1148
1149     my $retval = 0;
1150     my $file;
1151     while (defined($file = readdir DEBIAN)) {
1152         next unless ($file =~ /(gz|bz2)$/);
1153
1154         # TODO: add bzip2 support (debian doesn't do .bz2 anyway)
1155         my $exit = system("/bin/gzip -t '$debian_dir/$file'");
1156         next unless ($exit);
1157         &::DEBUG("deb: hmr... => ".(time() - (stat($debian_dir/$file))[8])."'.");
1158         next unless (time() - (stat($file))[8] > 3600);
1159
1160         #&::DEBUG("deb: dC: exit => '$exit'.");
1161         &::WARN("dC: '$debian_dir/$file' corrupted? deleting!");
1162         unlink $debian_dir."/".$file;
1163         $retval++;
1164     }
1165
1166     return $retval;
1167 }
1168
1169 sub checkEval {
1170     my($str)    = @_;
1171
1172     if ($str) {
1173         &::WARN("cE: $str");
1174         return 0;
1175     } else {
1176         return 1;
1177     }
1178 }
1179
1180 sub searchDescFE {
1181 #    &::DEBUG("deb: FE called for searchDesc");
1182     my ($query) = @_;
1183     my @list = &searchDesc($query);
1184
1185     if (!scalar @list) {
1186         my $prefix = "Debian Desc Search of '$query' ";
1187         &::performStrictReply( &::formListReply(0, $prefix, ) );
1188     } elsif (scalar @list == 1) {       # list = 1.
1189         &::DEBUG("deb: list == 1; showing package info of '$list[0]'.");
1190         &infoPackages("info", $list[0]);
1191     } else {                            # list > 1.
1192         my $prefix = "Debian Desc Search of '$query' ";
1193         &::performStrictReply( &::formListReply(0, $prefix, @list) );
1194     }
1195 }
1196
1197 1;