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