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