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