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