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