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