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