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