File Coverage

blib/lib/CPAN/Access/AdHoc/Archive/Null.pm
Criterion Covered Total %
statement 110 117 94.0
branch 20 38 52.6
condition 6 12 50.0
subroutine 20 20 100.0
pod 8 8 100.0
total 164 195 84.1


line stmt bran cond sub pod time code
1             package CPAN::Access::AdHoc::Archive::Null;
2              
3 6     6   6580 use 5.008;
  6         23  
  6         260  
4              
5 6     6   37 use strict;
  6         12  
  6         194  
6 6     6   32 use warnings;
  6         13  
  6         238  
7              
8 6     6   37 use base qw{ CPAN::Access::AdHoc::Archive };
  6         13  
  6         634  
9              
10 6     6   40 use CPAN::Access::AdHoc::Util qw{ :carp __guess_media_type };
  6         11  
  6         985  
11 6     6   36 use File::Path 2.07 ();
  6         148  
  6         142  
12 6     6   34 use File::Spec ();
  6         14  
  6         99  
13 6     6   32 use HTTP::Date ();
  6         9  
  6         151  
14 6     6   39 use IO::File ();
  6         19  
  6         95  
15 6     6   32 use IO::Uncompress::Bunzip2 ();
  6         21  
  6         93  
16 6     6   29 use IO::Uncompress::Gunzip ();
  6         17  
  6         10511  
17              
18             our $VERSION = '0.000_18';
19              
20             my %decode = (
21             gzip => sub {
22             my ( $content ) = @_;
23             my $rslt;
24             IO::Uncompress::Gunzip::gunzip( $content, \$rslt );
25             return $rslt;
26             },
27             'x-bzip2' => sub {
28             my ( $content ) = @_;
29             my $rslt;
30             IO::Uncompress::Bunzip2::bunzip2( $content, \$rslt );
31             return $rslt;
32             },
33             );
34              
35              
36             sub new {
37 14     14 1 2099 my ( $class, %arg ) = @_;
38              
39 14   33     125 my $self = bless {}, ref $class || $class;
40 14         84 my $attr = $self->__attr();
41              
42 14 50 33     69 ref $arg{content}
43             or defined $arg{path}
44             or $arg{path} = $arg{content};
45              
46 14         32 my $mtime = delete $arg{mtime};
47              
48 14 50       50 if ( defined( my $content = delete $arg{content} ) ) {
49              
50 14 100       68 if ( my $encoding = delete $arg{encoding} ) {
    50          
    50          
51 9 50       35 $decode{$encoding}
52             or __wail( "Unsupported encoding '$encoding'" );
53 9         31 $content = $decode{$encoding}->( $content );
54             } elsif ( ! ref $content ) {
55 0         0 local $/ = undef; # Slurp mode
56 0 0       0 open my $fh, '<', $content
57             or __wail( "Unable to open $content: $!" );
58 0         0 my @stat = stat $fh;
59 0         0 $content = <$fh>;
60 0         0 close $fh;
61             @stat
62 0 0       0 and $mtime = $stat[9];
63             } elsif ( 'SCALAR' eq ref $content ) {
64 5         7 $content = ${ $content };
  5         12  
65             }
66              
67 14         30 my ( $base_dir, $file_name );
68 14 50       48 if ( $arg{path} ) {
69 14         296 ( undef, $base_dir, $file_name ) =
70             File::Spec->splitpath( $arg{path} );
71 14         52 $base_dir =~ s{ \A authors/id/
72             ([^/]) / ( \1 [^/] ) / \2 [^/]* / }{}smx;
73 14         78 $file_name =~ s/ [.] (?: gz | bz2 ) \z //smx;
74             } else {
75 0         0 ( $base_dir, $file_name ) = ( '', 'unknown' );
76             }
77              
78 14         42 $attr->{base_dir} = $base_dir;
79 14         81 $attr->{contents}{$file_name} = {
80             content => $content,
81             mtime => $mtime,
82             };
83              
84 14         96 $self->archive( undef );
85              
86             }
87              
88 14         81 $self->mtime( $mtime );
89 14         84 $self->path( delete $arg{path} );
90              
91 14         125 return $self;
92             }
93              
94             sub base_directory {
95 5     5 1 549 my ( $self ) = @_;
96 5         18 my $attr = $self->__attr();
97              
98 5         102 return $attr->{base_dir};
99             }
100              
101             sub extract {
102 1     1 1 8 my ( $self ) = @_;
103 1         6 my $attr = $self->__attr();
104              
105 1 50       5 my @dirs = grep { defined $_ and '' ne $_ } File::Spec->splitdir(
  2         14  
106             $self->base_directory() );
107 1         2 my $where;
108 1         3 foreach my $dir ( @dirs ) {
109 1 50       4 $where = defined $where ? File::Spec->catdir( $where, $dir ) :
110             $dir;
111 1 50 33     75 -d $where
112             or mkdir $where
113             or __wail( "Unable to mkdir $where: $!" );
114             }
115              
116 1         3 foreach my $name ( keys %{ $attr->{contents} } ) {
  1         4  
117 1         11 my $path = File::Spec->catfile( $where, $name );
118 1 50       14 my $fh = IO::File->new( $path, '>' )
119             or __wail( "Unable to open $path for output: $!" );
120 1         156 print { $fh } $attr->{contents}{$name}{content};
  1         14  
121 1         43 close $fh;
122 1         3 my $mtime = $attr->{contents}{$name}{mtime};
123 1         27 utime $mtime, $mtime, $path;
124             }
125              
126 1         4 return $self;
127             }
128              
129             sub get_item_content {
130 14     14 1 44 my ( $self, $file ) = @_;
131 14         44 my $attr = $self->__attr();
132              
133 12         46 defined $file
134 14 100       45 or ( $file ) = keys %{ $attr->{contents} };
135              
136 14         68 return $attr->{contents}{$file}{content};
137             }
138              
139             sub get_item_mtime {
140 1     1 1 3 my ( $self, $file ) = @_;
141 1         6 my $attr = $self->__attr();
142              
143 1         5 defined $file
144 1 50       6 or ( $file ) = keys %{ $attr->{contents} };
145              
146 1         5 return $attr->{contents}{$file}{mtime};
147             }
148              
149             {
150              
151             my %handled = map { $_ => 1 } qw{ application/octet-stream };
152              
153             sub __handle_http_response {
154 27     27   47 my ( $class, $rslt ) = @_;
155              
156 27         91 my $content_type = $rslt->header( 'Content-Type' );
157              
158 27 100 100     1079 $handled{ $content_type }
159             or $content_type =~ m{ \A text/ }smx
160             or return;
161              
162 14         76 return $class->new(
163             content => \( scalar $rslt->content() ),
164             encoding => scalar $rslt->header( 'Content-Encoding' ),
165             mtime => HTTP::Date::str2time(
166             scalar $rslt->header( 'Last-Modified' ) ),
167             path => scalar $rslt->header( 'Content-Location' ),
168             );
169             }
170              
171             }
172              
173             sub item_present {
174 1     1 1 2 my ( $self, $item ) = @_;
175 1         5 my $attr = $self->__attr();
176              
177 1         7 return defined $attr->{contents}{$item};
178             }
179              
180             sub list_contents {
181 3     3 1 22 my ( $self ) = @_;
182 3         9 my $attr = $self->__attr();
183              
184 3         6 return ( sort keys %{ $attr->{contents} } );
  3         20  
185             }
186              
187             {
188             my %known_encoding = (
189             # The null encoder does a binmode() on its file handle because I
190             # believe that is equivalent to what happens with the
191             # IO::Compress::* packages - i.e. they compress bytes, not
192             # characters.
193             '' => sub {
194             my ( $fn, $content ) = @_;
195             open my $fh, '>', $fn or __wail( "Open $fn failed: $!" );
196             binmode $fh;
197             print { $fh } $content;
198             close $fh;
199             return;
200             },
201             'gzip' => sub {
202             my ( $fn, $content ) = @_;
203             require IO::Compress::Gzip;
204             IO::Compress::Gzip::gzip( \$content, $fn, AutoClose => 1 )
205             or __wail("gzip $fn failed: $IO::Compress::Gzip::GzipError"
206             );
207             return;
208             },
209             'x-bzip2' => sub {
210             my ( $fn, $content ) = @_;
211             require IO::Compress::Bzip2;
212             IO::Compress::Bzip2::bzip2( \$content, $fn )
213             or __wail("bzip2 $fn failed: $IO::Compress::Bzip2::Bzip2Error"
214             );
215             return;
216             },
217             );
218              
219             sub write : method { ## no critic (ProhibitBuiltInHomonyms)
220 1     1 1 7 my ( $self, $fn ) = @_;
221 1         6 my $attr = $self->__attr();
222              
223 1         3 my ( $file ) = keys %{ $attr->{contents} };
  1         4  
224 1 50       7 if ( ! defined $fn ) {
225 1         5 $fn = ( File::Spec->splitpath( $self->path() ) )[2];
226             }
227 1         11 my $resp = HTTP::Response->new();
228 1         99 __guess_media_type( $resp, $fn );
229 1         4 my $encoding = $resp->header( 'Content-Encoding' );
230 1 50       35 defined $encoding
231             or $encoding = '';
232 1 50       5 my $code = $known_encoding{$encoding}
233             or __wail( "Encoding $encoding not supported" );
234 1         5 $code->( $fn, $attr->{contents}{$file}{content} );
235 1         8 return $self;
236             }
237             }
238              
239             1;
240              
241             __END__