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