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