File Coverage

blib/lib/CPAN/InGit/MirrorTree.pm
Criterion Covered Total %
statement 26 117 22.2
branch 0 38 0.0
condition 0 14 0.0
subroutine 9 17 52.9
pod 4 4 100.0
total 39 190 20.5


line stmt bran cond sub pod time code
1             package CPAN::InGit::MirrorTree;
2             our $VERSION = '0.003'; # VERSION
3             # ABSTRACT: Subclass of ArchiveTree which automatically mirrors files from upstream
4              
5              
6 5     5   38 use Carp;
  5         12  
  5         483  
7 5     5   33 use Scalar::Util 'refaddr', 'blessed';
  5         9  
  5         275  
8 5     5   2531 use POSIX 'strftime';
  5         32539  
  5         75  
9 5     5   8469 use IO::Uncompress::Gunzip qw( gunzip $GunzipError );
  5         17  
  5         960  
10 5     5   2796 use JSON::PP;
  5         56930  
  5         633  
11 5     5   3551 use Time::Piece;
  5         71997  
  5         32  
12 5     5   1326 use Log::Any '$log';
  5         12331  
  5         52  
13 5     5   7899 use Moo;
  5         47028  
  5         31  
14 5     5   9652 use v5.36;
  5         26  
15              
16             extends 'CPAN::InGit::ArchiveTree';
17              
18              
19             has upstream_url => ( is => 'rw', coerce => \&_add_trailing_slash );
20             has upstream_backup_url => ( is => 'rw', lazy => 1, builder => 1, coerce => \&_add_trailing_slash );
21             has autofetch => ( is => 'rw', default => 1 );
22             has package_details_max_age => ( is => 'rw', default => 86400 );
23              
24 0     0     sub _build_upstream_backup_url($self) {
  0            
  0            
25 0 0 0       ($self->upstream_url||'') =~ m{^(https?)://www\.cpan\.org}
26             ? "$1://backpan.perl.org/"
27             : undef;
28             }
29              
30             sub _add_trailing_slash {
31 0     0     my $x= shift;
32 0 0         defined $x? $x =~ s{/?\z}{/}r : $x
33             }
34              
35 0     0     sub _pack_config($self, $config) {
  0            
  0            
  0            
36 0           $config->{upstream_url}= $self->upstream_url;
37 0           $config->{upstream_backup_url}= $self->upstream_backup_url;
38 0           $config->{autofetch}= $self->autofetch;
39 0           $config->{package_details_max_age}= $self->package_details_max_age;
40 0           $self->next::method($config);
41             }
42 0     0     sub _unpack_config($self, $config) {
  0            
  0            
  0            
43 0           $self->next::method($config);
44 0           $self->upstream_url($config->{upstream_url});
45             $self->upstream_backup_url($config->{upstream_backup_url})
46 0 0         if exists $config->{upstream_backup_url};
47 0           $self->autofetch($config->{autofetch});
48 0           $self->package_details_max_age($config->{package_details_max_age});
49             }
50              
51 0     0 1   sub get_path($self, $path) {
  0            
  0            
  0            
52 0           my $ent= $self->next::method($path);
53 0 0         if ($self->autofetch) {
54             # Special case for 02packages.details.txt, load it if missing or if cache is stale
55 0 0 0       if ($path eq 'modules/02packages.details.txt') {
    0          
56 0 0         if ($ent) {
57 0   0       my $blob_last_update= $self->{_blob_last_update}{$ent->[0]->id} // do {
58             # parse it out of the file
59 0           my $head= substr($ent->[0]->content, 0, 10000);
60 0 0         $head =~ /^Last-Updated:\s*(.*)$/m or die "Can't parse 02packages.details.txt";
61 0           (my $date= $1) =~ s/\s+\z//;
62 0           $log->debug("Date in modules/02packages.details.txt is '$date'");
63 0           Time::Piece->strptime($date, "%a, %d %b %Y %H:%M:%S GMT")->epoch
64             };
65 0 0         if ($blob_last_update >= time - $self->package_details_max_age) {
66 0           $log->trace(' 02package.details.txt cache is current');
67             } else {
68 0           $log->trace(' 02package.details.txt cache expired');
69 0           $ent= undef;
70             }
71             }
72 0 0         unless ($ent) {
73 0           $log->debug(" mirror autofetch $path");
74 0           my $blob= $self->add_upstream_package_details;
75 0           $self->clear_package_details; # will lazily rebuild
76 0           $ent= [ $blob, 0100644 ];
77             }
78             }
79             elsif ($path =~ m{^authors/id/(.*)} and !$ent) {
80 0           $log->debug(" mirror autofetch $path");
81 0           my $author_path= $1;
82 0           my $blob= $self->add_upstream_author_file($author_path, undef_if_404 => 1);
83 0 0         $ent= [ $blob, 0100644 ] if $blob;
84             }
85             }
86 0           return $ent;
87             }
88              
89              
90 0     0 1   sub fetch_upstream_file($self, $path, %options) {
  0            
  0            
  0            
  0            
91 0 0         croak "No upstream URL for this tree"
92             unless defined $self->upstream_url;
93 0           my $url= $self->upstream_url . $path;
94 0           my $tx= $self->parent->useragent->get($url);
95 0           $log->debugf(" GET %s -> %s %s", $url, $tx->result->code, $tx->result->message);
96 0 0         unless ($tx->result->is_success) {
97 0 0 0       if ($self->upstream_backup_url && $path =~ m{^authors/id/}) {
98 0           my $url2= $self->upstream_backup_url . $path;
99 0           my $tx2= $self->parent->useragent->get($url2);
100 0           $log->debugf(" GET %s -> %s %s", $url2, $tx2->result->code, $tx2->result->message);
101 0 0         return \$tx2->result->body
102             if $tx2->result->is_success;
103             }
104 0 0 0       return undef if $options{undef_if_404} && $tx->result->code == 404;
105 0           croak "Failed to find file upstream: ".$tx->result->message;
106             }
107 0           return \$tx->result->body;
108             }
109              
110              
111 0     0 1   sub add_upstream_package_details($self, %options) {
  0            
  0            
  0            
112 0 0         my $content_ref= $self->fetch_upstream_file('modules/02packages.details.txt.gz', %options)
113             or return undef;
114             # Unzip the file and store uncompressed, so that 'git diff' works nicely on it.
115 0           my $txt;
116 0 0         gunzip $content_ref => \$txt
117             or croak "gunzip failed: $GunzipError";
118 0           my $blob= Git::Raw::Blob->create($self->git_repo, $txt);
119 0           $self->set_path('modules/02packages.details.txt', $blob);
120 0           $self->{_blob_last_update}{$blob->id}= time;
121 0           return $blob;
122             }
123              
124              
125 0     0 1   sub add_upstream_author_file($self, $author_path, %options) {
  0            
  0            
  0            
  0            
126 0           my $path= "authors/id/$author_path";
127 0 0         my $content_ref= $self->fetch_upstream_file($path, %options)
128             or return undef;
129 0           my $blob= Git::Raw::Blob->create($self->git_repo, $$content_ref);
130 0           $self->set_path($path, $blob);
131 0           return $blob;
132             }
133              
134             1;
135              
136             __END__