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