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