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