Update CPAN.pm to 1.93_51
[p5sagit/p5-mst-13.2.git] / lib / CPAN / Index.pm
1 package CPAN::Index;
2 use strict;
3 use vars qw($LAST_TIME $DATE_OF_02 $DATE_OF_03 $HAVE_REANIMATED $VERSION);
4 $VERSION = "1.93";
5 @CPAN::Index::ISA = qw(CPAN::Debug);
6 $LAST_TIME ||= 0;
7 $DATE_OF_03 ||= 0;
8 # use constant PROTOCOL => "2.0"; # outcommented to avoid warning on upgrade from 1.57
9 sub PROTOCOL { 2.0 }
10
11 #-> sub CPAN::Index::force_reload ;
12 sub force_reload {
13     my($class) = @_;
14     $CPAN::Index::LAST_TIME = 0;
15     $class->reload(1);
16 }
17
18 #-> sub CPAN::Index::reload ;
19 sub reload {
20     my($self,$force) = @_;
21     my $time = time;
22
23     # XXX check if a newer one is available. (We currently read it
24     # from time to time)
25     for ($CPAN::Config->{index_expire}) {
26         $_ = 0.001 unless $_ && $_ > 0.001;
27     }
28     unless (1 || $CPAN::Have_warned->{readmetadatacache}++) {
29         # debug here when CPAN doesn't seem to read the Metadata
30         require Carp;
31         Carp::cluck("META-PROTOCOL[$CPAN::META->{PROTOCOL}]");
32     }
33     unless ($CPAN::META->{PROTOCOL}) {
34         $self->read_metadata_cache;
35         $CPAN::META->{PROTOCOL} ||= "1.0";
36     }
37     if ( $CPAN::META->{PROTOCOL} < PROTOCOL  ) {
38         # warn "Setting last_time to 0";
39         $LAST_TIME = 0; # No warning necessary
40     }
41     if ($LAST_TIME + $CPAN::Config->{index_expire}*86400 > $time
42         and ! $force) {
43         # called too often
44         # CPAN->debug("LAST_TIME[$LAST_TIME]index_expire[$CPAN::Config->{index_expire}]time[$time]");
45     } elsif (0) {
46         # IFF we are developing, it helps to wipe out the memory
47         # between reloads, otherwise it is not what a user expects.
48         undef $CPAN::META; # Neue Gruendlichkeit since v1.52(r1.274)
49         $CPAN::META = CPAN->new;
50     } else {
51         my($debug,$t2);
52         local $LAST_TIME = $time;
53         local $CPAN::META->{PROTOCOL} = PROTOCOL;
54
55         my $needshort = $^O eq "dos";
56
57         $self->rd_authindex($self
58                           ->reload_x(
59                                      "authors/01mailrc.txt.gz",
60                                      $needshort ?
61                                      File::Spec->catfile('authors', '01mailrc.gz') :
62                                      File::Spec->catfile('authors', '01mailrc.txt.gz'),
63                                      $force));
64         $t2 = time;
65         $debug = "timing reading 01[".($t2 - $time)."]";
66         $time = $t2;
67         return if $CPAN::Signal; # this is sometimes lengthy
68         $self->rd_modpacks($self
69                          ->reload_x(
70                                     "modules/02packages.details.txt.gz",
71                                     $needshort ?
72                                     File::Spec->catfile('modules', '02packag.gz') :
73                                     File::Spec->catfile('modules', '02packages.details.txt.gz'),
74                                     $force));
75         $t2 = time;
76         $debug .= "02[".($t2 - $time)."]";
77         $time = $t2;
78         return if $CPAN::Signal; # this is sometimes lengthy
79         $self->rd_modlist($self
80                         ->reload_x(
81                                    "modules/03modlist.data.gz",
82                                    $needshort ?
83                                    File::Spec->catfile('modules', '03mlist.gz') :
84                                    File::Spec->catfile('modules', '03modlist.data.gz'),
85                                    $force));
86         $self->write_metadata_cache;
87         $t2 = time;
88         $debug .= "03[".($t2 - $time)."]";
89         $time = $t2;
90         CPAN->debug($debug) if $CPAN::DEBUG;
91     }
92     if ($CPAN::Config->{build_dir_reuse}) {
93         $self->reanimate_build_dir;
94     }
95     if (CPAN::_sqlite_running()) {
96         $CPAN::SQLite->reload(time => $time, force => $force)
97             if not $LAST_TIME;
98     }
99     $LAST_TIME = $time;
100     $CPAN::META->{PROTOCOL} = PROTOCOL;
101 }
102
103 #-> sub CPAN::Index::reanimate_build_dir ;
104 sub reanimate_build_dir {
105     my($self) = @_;
106     unless ($CPAN::META->has_inst($CPAN::Config->{yaml_module}||"YAML")) {
107         return;
108     }
109     return if $HAVE_REANIMATED++;
110     my $d = $CPAN::Config->{build_dir};
111     my $dh = DirHandle->new;
112     opendir $dh, $d or return; # does not exist
113     my $dirent;
114     my $i = 0;
115     my $painted = 0;
116     my $restored = 0;
117     my @candidates = map { $_->[0] }
118         sort { $b->[1] <=> $a->[1] }
119             map { [ $_, -M File::Spec->catfile($d,$_) ] }
120                 grep {/\.yml$/} readdir $dh;
121     unless (@candidates) {
122         $CPAN::Frontend->myprint("Build_dir empty, nothing to restore\n");
123         return;
124     }
125     $CPAN::Frontend->myprint
126         (sprintf("Going to read %d yaml file%s from %s/\n",
127                  scalar @candidates,
128                  @candidates==1 ? "" : "s",
129                  $CPAN::Config->{build_dir}
130                 ));
131     my $start = CPAN::FTP::_mytime();
132   DISTRO: for $i (0..$#candidates) {
133         my $dirent = $candidates[$i];
134         my $y = eval {CPAN->_yaml_loadfile(File::Spec->catfile($d,$dirent))};
135         if ($@) {
136             warn "Error while parsing file '$dirent'; error: '$@'";
137             next DISTRO;
138         }
139         my $c = $y->[0];
140         if ($c && CPAN->_perl_fingerprint($c->{perl})) {
141             my $key = $c->{distribution}{ID};
142             for my $k (keys %{$c->{distribution}}) {
143                 if ($c->{distribution}{$k}
144                     && ref $c->{distribution}{$k}
145                     && UNIVERSAL::isa($c->{distribution}{$k},"CPAN::Distrostatus")) {
146                     $c->{distribution}{$k}{COMMANDID} = $i - @candidates;
147                 }
148             }
149
150             #we tried to restore only if element already
151             #exists; but then we do not work with metadata
152             #turned off.
153             my $do
154                 = $CPAN::META->{readwrite}{'CPAN::Distribution'}{$key}
155                     = $c->{distribution};
156             for my $skipper (qw(
157                                 badtestcnt
158                                 configure_requires_later
159                                 configure_requires_later_for
160                                 force_update
161                                 later
162                                 later_for
163                                 notest
164                                 should_report
165                                 sponsored_mods
166                                 prefs
167                                 negative_prefs_cache
168                                )) {
169                 delete $do->{$skipper};
170             }
171             if ($do->tested_ok_but_not_installed) {
172                 $CPAN::META->is_tested($do->{build_dir},$do->{make_test}{TIME});
173             }
174             $restored++;
175         }
176         $i++;
177         while (($painted/76) < ($i/@candidates)) {
178             $CPAN::Frontend->myprint(".");
179             $painted++;
180         }
181     }
182     my $took = CPAN::FTP::_mytime() - $start;
183     $CPAN::Frontend->myprint(sprintf(
184                                      "DONE\nRestored the state of %s (in %.4f secs)\n",
185                                      $restored || "none",
186                                      $took,
187                                     ));
188 }
189
190
191 #-> sub CPAN::Index::reload_x ;
192 sub reload_x {
193     my($cl,$wanted,$localname,$force) = @_;
194     $force |= 2; # means we're dealing with an index here
195     CPAN::HandleConfig->load; # we should guarantee loading wherever
196                               # we rely on Config XXX
197     $localname ||= $wanted;
198     my $abs_wanted = File::Spec->catfile($CPAN::Config->{'keep_source_where'},
199                                          $localname);
200     if (
201         -f $abs_wanted &&
202         -M $abs_wanted < $CPAN::Config->{'index_expire'} &&
203         !($force & 1)
204        ) {
205         my $s = $CPAN::Config->{'index_expire'} == 1 ? "" : "s";
206         $cl->debug(qq{$abs_wanted younger than $CPAN::Config->{'index_expire'} }.
207                    qq{day$s. I\'ll use that.});
208         return $abs_wanted;
209     } else {
210         $force |= 1; # means we're quite serious about it.
211     }
212     return CPAN::FTP->localize($wanted,$abs_wanted,$force);
213 }
214
215 #-> sub CPAN::Index::rd_authindex ;
216 sub rd_authindex {
217     my($cl, $index_target) = @_;
218     return unless defined $index_target;
219     return if CPAN::_sqlite_running();
220     my @lines;
221     $CPAN::Frontend->myprint("Going to read '$index_target'\n");
222     local(*FH);
223     tie *FH, 'CPAN::Tarzip', $index_target;
224     local($/) = "\n";
225     local($_);
226     push @lines, split /\012/ while <FH>;
227     my $i = 0;
228     my $painted = 0;
229     foreach (@lines) {
230         my($userid,$fullname,$email) =
231             m/alias\s+(\S+)\s+\"([^\"\<]*)\s+\<(.*)\>\"/;
232         $fullname ||= $email;
233         if ($userid && $fullname && $email) {
234             my $userobj = $CPAN::META->instance('CPAN::Author',$userid);
235             $userobj->set('FULLNAME' => $fullname, 'EMAIL' => $email);
236         } else {
237             CPAN->debug(sprintf "line[%s]", $_) if $CPAN::DEBUG;
238         }
239         $i++;
240         while (($painted/76) < ($i/@lines)) {
241             $CPAN::Frontend->myprint(".");
242             $painted++;
243         }
244         return if $CPAN::Signal;
245     }
246     $CPAN::Frontend->myprint("DONE\n");
247 }
248
249 sub userid {
250   my($self,$dist) = @_;
251   $dist = $self->{'id'} unless defined $dist;
252   my($ret) = $dist =~ m|(?:\w/\w\w/)?([^/]+)/|;
253   $ret;
254 }
255
256 #-> sub CPAN::Index::rd_modpacks ;
257 sub rd_modpacks {
258     my($self, $index_target) = @_;
259     return unless defined $index_target;
260     return if CPAN::_sqlite_running();
261     $CPAN::Frontend->myprint("Going to read '$index_target'\n");
262     my $fh = CPAN::Tarzip->TIEHANDLE($index_target);
263     local $_;
264     CPAN->debug(sprintf "start[%d]", time) if $CPAN::DEBUG;
265     my $slurp = "";
266     my $chunk;
267     while (my $bytes = $fh->READ(\$chunk,8192)) {
268         $slurp.=$chunk;
269     }
270     my @lines = split /\012/, $slurp;
271     CPAN->debug(sprintf "end[%d]", time) if $CPAN::DEBUG;
272     undef $fh;
273     # read header
274     my($line_count,$last_updated);
275     while (@lines) {
276         my $shift = shift(@lines);
277         last if $shift =~ /^\s*$/;
278         $shift =~ /^Line-Count:\s+(\d+)/ and $line_count = $1;
279         $shift =~ /^Last-Updated:\s+(.+)/ and $last_updated = $1;
280     }
281     CPAN->debug("line_count[$line_count]last_updated[$last_updated]") if $CPAN::DEBUG;
282     if (not defined $line_count) {
283
284         $CPAN::Frontend->mywarn(qq{Warning: Your $index_target does not contain a Line-Count header.
285 Please check the validity of the index file by comparing it to more
286 than one CPAN mirror. I'll continue but problems seem likely to
287 happen.\a
288 });
289
290         $CPAN::Frontend->mysleep(5);
291     } elsif ($line_count != scalar @lines) {
292
293         $CPAN::Frontend->mywarn(sprintf qq{Warning: Your %s
294 contains a Line-Count header of %d but I see %d lines there. Please
295 check the validity of the index file by comparing it to more than one
296 CPAN mirror. I'll continue but problems seem likely to happen.\a\n},
297 $index_target, $line_count, scalar(@lines));
298
299     }
300     if (not defined $last_updated) {
301
302         $CPAN::Frontend->mywarn(qq{Warning: Your $index_target does not contain a Last-Updated header.
303 Please check the validity of the index file by comparing it to more
304 than one CPAN mirror. I'll continue but problems seem likely to
305 happen.\a
306 });
307
308         $CPAN::Frontend->mysleep(5);
309     } else {
310
311         $CPAN::Frontend
312             ->myprint(sprintf qq{  Database was generated on %s\n},
313                       $last_updated);
314         $DATE_OF_02 = $last_updated;
315
316         my $age = time;
317         if ($CPAN::META->has_inst('HTTP::Date')) {
318             require HTTP::Date;
319             $age -= HTTP::Date::str2time($last_updated);
320         } else {
321             $CPAN::Frontend->mywarn("  HTTP::Date not available\n");
322             require Time::Local;
323             my(@d) = $last_updated =~ / (\d+) (\w+) (\d+) (\d+):(\d+):(\d+) /;
324             $d[1] = index("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec", $d[1])/4;
325             $age -= $d[1]>=0 ? Time::Local::timegm(@d[5,4,3,0,1,2]) : 0;
326         }
327         $age /= 3600*24;
328         if ($age > 30) {
329
330             $CPAN::Frontend
331                 ->mywarn(sprintf
332                          qq{Warning: This index file is %d days old.
333   Please check the host you chose as your CPAN mirror for staleness.
334   I'll continue but problems seem likely to happen.\a\n},
335                          $age);
336
337         } elsif ($age < -1) {
338
339             $CPAN::Frontend
340                 ->mywarn(sprintf
341                          qq{Warning: Your system date is %d days behind this index file!
342   System time:          %s
343   Timestamp index file: %s
344   Please fix your system time, problems with the make command expected.\n},
345                          -$age,
346                          scalar gmtime,
347                          $DATE_OF_02,
348                         );
349
350         }
351     }
352
353
354     # A necessity since we have metadata_cache: delete what isn't
355     # there anymore
356     my $secondtime = $CPAN::META->exists("CPAN::Module","CPAN");
357     CPAN->debug("secondtime[$secondtime]") if $CPAN::DEBUG;
358     my(%exists);
359     my $i = 0;
360     my $painted = 0;
361     foreach (@lines) {
362         # before 1.56 we split into 3 and discarded the rest. From
363         # 1.57 we assign remaining text to $comment thus allowing to
364         # influence isa_perl
365         my($mod,$version,$dist,$comment) = split " ", $_, 4;
366         unless ($mod && defined $version && $dist) {
367             $CPAN::Frontend->mywarn("Could not split line[$_]\n");
368             next;
369         }
370         my($bundle,$id,$userid);
371
372         if ($mod eq 'CPAN' &&
373             ! (
374             CPAN::Queue->exists('Bundle::CPAN') ||
375             CPAN::Queue->exists('CPAN')
376             )
377         ) {
378             local($^W)= 0;
379             if ($version > $CPAN::VERSION) {
380                 $CPAN::Frontend->mywarn(qq{
381   New CPAN.pm version (v$version) available.
382   [Currently running version is v$CPAN::VERSION]
383   You might want to try
384     install CPAN
385     reload cpan
386   to both upgrade CPAN.pm and run the new version without leaving
387   the current session.
388
389 }); #});
390                 $CPAN::Frontend->mysleep(2);
391                 $CPAN::Frontend->myprint(qq{\n});
392             }
393             last if $CPAN::Signal;
394         } elsif ($mod =~ /^Bundle::(.*)/) {
395             $bundle = $1;
396         }
397
398         if ($bundle) {
399             $id =  $CPAN::META->instance('CPAN::Bundle',$mod);
400             # Let's make it a module too, because bundles have so much
401             # in common with modules.
402
403             # Changed in 1.57_63: seems like memory bloat now without
404             # any value, so commented out
405
406             # $CPAN::META->instance('CPAN::Module',$mod);
407
408         } else {
409
410             # instantiate a module object
411             $id = $CPAN::META->instance('CPAN::Module',$mod);
412
413         }
414
415         # Although CPAN prohibits same name with different version the
416         # indexer may have changed the version for the same distro
417         # since the last time ("Force Reindexing" feature)
418         if ($id->cpan_file ne $dist
419             ||
420             $id->cpan_version ne $version
421            ) {
422             $userid = $id->userid || $self->userid($dist);
423             $id->set(
424                      'CPAN_USERID' => $userid,
425                      'CPAN_VERSION' => $version,
426                      'CPAN_FILE' => $dist,
427                     );
428         }
429
430         # instantiate a distribution object
431         if ($CPAN::META->exists('CPAN::Distribution',$dist)) {
432         # we do not need CONTAINSMODS unless we do something with
433         # this dist, so we better produce it on demand.
434
435         ## my $obj = $CPAN::META->instance(
436         ##                                 'CPAN::Distribution' => $dist
437         ##                                );
438         ## $obj->{CONTAINSMODS}{$mod} = undef; # experimental
439         } else {
440             $CPAN::META->instance(
441                                   'CPAN::Distribution' => $dist
442                                  )->set(
443                                         'CPAN_USERID' => $userid,
444                                         'CPAN_COMMENT' => $comment,
445                                        );
446         }
447         if ($secondtime) {
448             for my $name ($mod,$dist) {
449                 # $self->debug("exists name[$name]") if $CPAN::DEBUG;
450                 $exists{$name} = undef;
451             }
452         }
453         $i++;
454         while (($painted/76) < ($i/@lines)) {
455             $CPAN::Frontend->myprint(".");
456             $painted++;
457         }
458         return if $CPAN::Signal;
459     }
460     $CPAN::Frontend->myprint("DONE\n");
461     if ($secondtime) {
462         for my $class (qw(CPAN::Module CPAN::Bundle CPAN::Distribution)) {
463             for my $o ($CPAN::META->all_objects($class)) {
464                 next if exists $exists{$o->{ID}};
465                 $CPAN::META->delete($class,$o->{ID});
466                 # CPAN->debug("deleting ID[$o->{ID}] in class[$class]")
467                 #     if $CPAN::DEBUG;
468             }
469         }
470     }
471 }
472
473 #-> sub CPAN::Index::rd_modlist ;
474 sub rd_modlist {
475     my($cl,$index_target) = @_;
476     return unless defined $index_target;
477     return if CPAN::_sqlite_running();
478     $CPAN::Frontend->myprint("Going to read '$index_target'\n");
479     my $fh = CPAN::Tarzip->TIEHANDLE($index_target);
480     local $_;
481     my $slurp = "";
482     my $chunk;
483     while (my $bytes = $fh->READ(\$chunk,8192)) {
484         $slurp.=$chunk;
485     }
486     my @eval2 = split /\012/, $slurp;
487
488     while (@eval2) {
489         my $shift = shift(@eval2);
490         if ($shift =~ /^Date:\s+(.*)/) {
491             if ($DATE_OF_03 eq $1) {
492                 $CPAN::Frontend->myprint("Unchanged.\n");
493                 return;
494             }
495             ($DATE_OF_03) = $1;
496         }
497         last if $shift =~ /^\s*$/;
498     }
499     push @eval2, q{CPAN::Modulelist->data;};
500     local($^W) = 0;
501     my($compmt) = Safe->new("CPAN::Safe1");
502     my($eval2) = join("\n", @eval2);
503     CPAN->debug(sprintf "length of eval2[%d]", length $eval2) if $CPAN::DEBUG;
504     my $ret = $compmt->reval($eval2);
505     Carp::confess($@) if $@;
506     return if $CPAN::Signal;
507     my $i = 0;
508     my $until = keys(%$ret);
509     my $painted = 0;
510     CPAN->debug(sprintf "until[%d]", $until) if $CPAN::DEBUG;
511     for (keys %$ret) {
512         my $obj = $CPAN::META->instance("CPAN::Module",$_);
513         delete $ret->{$_}{modid}; # not needed here, maybe elsewhere
514         $obj->set(%{$ret->{$_}});
515         $i++;
516         while (($painted/76) < ($i/$until)) {
517             $CPAN::Frontend->myprint(".");
518             $painted++;
519         }
520         return if $CPAN::Signal;
521     }
522     $CPAN::Frontend->myprint("DONE\n");
523 }
524
525 #-> sub CPAN::Index::write_metadata_cache ;
526 sub write_metadata_cache {
527     my($self) = @_;
528     return unless $CPAN::Config->{'cache_metadata'};
529     return if CPAN::_sqlite_running();
530     return unless $CPAN::META->has_usable("Storable");
531     my $cache;
532     foreach my $k (qw(CPAN::Bundle CPAN::Author CPAN::Module
533                       CPAN::Distribution)) {
534         $cache->{$k} = $CPAN::META->{readonly}{$k}; # unsafe meta access, ok
535     }
536     my $metadata_file = File::Spec->catfile($CPAN::Config->{cpan_home},"Metadata");
537     $cache->{last_time} = $LAST_TIME;
538     $cache->{DATE_OF_02} = $DATE_OF_02;
539     $cache->{PROTOCOL} = PROTOCOL;
540     $CPAN::Frontend->myprint("Going to write $metadata_file\n");
541     eval { Storable::nstore($cache, $metadata_file) };
542     $CPAN::Frontend->mywarn($@) if $@; # ?? missing "\n" after $@ in mywarn ??
543 }
544
545 #-> sub CPAN::Index::read_metadata_cache ;
546 sub read_metadata_cache {
547     my($self) = @_;
548     return unless $CPAN::Config->{'cache_metadata'};
549     return if CPAN::_sqlite_running();
550     return unless $CPAN::META->has_usable("Storable");
551     my $metadata_file = File::Spec->catfile($CPAN::Config->{cpan_home},"Metadata");
552     return unless -r $metadata_file and -f $metadata_file;
553     $CPAN::Frontend->myprint("Going to read '$metadata_file'\n");
554     my $cache;
555     eval { $cache = Storable::retrieve($metadata_file) };
556     $CPAN::Frontend->mywarn($@) if $@; # ?? missing "\n" after $@ in mywarn ??
557     if (!$cache || !UNIVERSAL::isa($cache, 'HASH')) {
558         $LAST_TIME = 0;
559         return;
560     }
561     if (exists $cache->{PROTOCOL}) {
562         if (PROTOCOL > $cache->{PROTOCOL}) {
563             $CPAN::Frontend->mywarn(sprintf("Ignoring Metadata cache written ".
564                                             "with protocol v%s, requiring v%s\n",
565                                             $cache->{PROTOCOL},
566                                             PROTOCOL)
567                                    );
568             return;
569         }
570     } else {
571         $CPAN::Frontend->mywarn("Ignoring Metadata cache written ".
572                                 "with protocol v1.0\n");
573         return;
574     }
575     my $clcnt = 0;
576     my $idcnt = 0;
577     while(my($class,$v) = each %$cache) {
578         next unless $class =~ /^CPAN::/;
579         $CPAN::META->{readonly}{$class} = $v; # unsafe meta access, ok
580         while (my($id,$ro) = each %$v) {
581             $CPAN::META->{readwrite}{$class}{$id} ||=
582                 $class->new(ID=>$id, RO=>$ro);
583             $idcnt++;
584         }
585         $clcnt++;
586     }
587     unless ($clcnt) { # sanity check
588         $CPAN::Frontend->myprint("Warning: Found no data in $metadata_file\n");
589         return;
590     }
591     if ($idcnt < 1000) {
592         $CPAN::Frontend->myprint("Warning: Found only $idcnt objects ".
593                                  "in $metadata_file\n");
594         return;
595     }
596     $CPAN::META->{PROTOCOL} ||=
597         $cache->{PROTOCOL}; # reading does not up or downgrade, but it
598                             # does initialize to some protocol
599     $LAST_TIME = $cache->{last_time};
600     $DATE_OF_02 = $cache->{DATE_OF_02};
601     $CPAN::Frontend->myprint("  Database was generated on $DATE_OF_02\n")
602         if defined $DATE_OF_02; # An old cache may not contain DATE_OF_02
603     return;
604 }
605
606 1;