File Coverage

blib/lib/Plack/App/Storage/Abstract.pm
Criterion Covered Total %
statement 50 52 96.1
branch 7 10 70.0
condition 5 10 50.0
subroutine 14 14 100.0
pod 2 2 100.0
total 78 88 88.6


line stmt bran cond sub pod time code
1             package Plack::App::Storage::Abstract;
2             $Plack::App::Storage::Abstract::VERSION = '0.002';
3 1     1   226100 use v5.14;
  1         5  
4 1     1   8 use warnings;
  1         2  
  1         69  
5              
6 1     1   790 use Storage::Abstract;
  1         450577  
  1         28  
7 1     1   446 use Plack::MIME;
  1         719  
  1         33  
8 1     1   386 use HTTP::Date;
  1         3651  
  1         55  
9 1     1   5 use Try::Tiny;
  1         1  
  1         43  
10 1     1   4 use Scalar::Util qw(blessed);
  1         4  
  1         30  
11 1     1   3 use parent 'Plack::Component';
  1         2  
  1         5  
12              
13 1     1   9826 use Plack::Util::Accessor qw(encoding storage storage_config);
  1         382  
  1         6  
14              
15             sub new
16             {
17 1     1 1 204706 my ($class, @args) = @_;
18 1         17 my $self = $class->SUPER::new(@args);
19              
20 1 50       65 $self->storage(Storage::Abstract->new(%{$self->storage_config}))
  1         65  
21             unless $self->storage;
22              
23 1   50     80690 $self->encoding($self->encoding // 'utf-8');
24              
25 1         15 return $self;
26             }
27              
28             sub call
29             {
30 9     9 1 49023 my ($self, $env) = @_;
31 9         27 my $path = $env->{PATH_INFO};
32 9         27 my $fh;
33             my %info;
34 9         0 my $e;
35              
36             try {
37 9     9   458 $fh = $self->storage->retrieve($path, \%info);
38             }
39             catch {
40 4     4   4786 $e = $_;
41 9         80 };
42              
43 9 100       1949 if ($e) {
44 4 100 66     395 if (blessed $e && $e->isa('Storage::Abstract::X::NotFound')) {
    50 33        
45 2         8 return $self->_error_code(404);
46             }
47             elsif (blessed $e && $e->isa('Storage::Abstract::X::PathError')) {
48 2         10 return $self->_error_code(403);
49             }
50             else {
51             # StorageError or HandleError or unblessed error
52 0         0 $env->{'psgi.errors'}->print("$e");
53 0         0 return $self->_error_code(500);
54             }
55             }
56              
57 5   50     38 my $content_type = Plack::MIME->mime_type($path) || 'text/plain';
58 5 50       71 if ($content_type =~ m{^text/}) {
59 5         23 $content_type .= "; charset=" . $self->encoding;
60             }
61              
62             return [
63             200,
64             [
65             'Content-Type' => $content_type,
66             'Content-Length' => $info{size},
67 5         51 'Last-Modified' => HTTP::Date::time2str($info{mtime}),
68             ],
69             $fh
70             ];
71             }
72              
73             sub _error_code
74             {
75 4     4   12 my ($self, $code) = @_;
76              
77 4         21 my %text = (
78             400 => 'Bad Request',
79             403 => 'Forbidden',
80             404 => 'Not Found',
81             );
82              
83             return [
84             $code,
85             [
86             'Content-Type' => 'text/plain',
87             'Content-Length' => length $text{$code}
88             ],
89 4         78 [$text{$code}]
90             ];
91             }
92              
93             1;
94              
95             __END__
96              
97             =head1 NAME
98              
99             Plack::App::Storage::Abstract - Serve files with Storage::Abstract
100              
101             =head1 SYNOPSIS
102              
103             use Plack::App::Storage::Abstract;
104              
105             my $app1 = Plack::App::Storage::Abstract->new(
106             storage_config => {
107             driver => 'directory',
108             directory => '/some/dir',
109             },
110             )->to_app;
111              
112             =head1 DESCRIPTION
113              
114             This plack application serves files through L<Storage::Abstract>. It is similar
115             to L<Plack::App::File>, but gives better control over file storage.
116              
117             =head1 CONFIGURATION
118              
119             =head2 storage
120              
121             The constructed C<Storage::Abstract> object. If not present, will be
122             constructed from L</storage_config>.
123              
124             =head2 storage_config
125              
126             A hash reference with keys to be passed to L<Storage::Abstract/new>. Required,
127             but may be skipped if L</storage> is passed instead.
128              
129             =head2 encoding
130              
131             Encoding used for text MIME types. Default C<utf-8>.
132              
133             =head1 CAVEATS
134              
135             =head2 Handling errors
136              
137             On error producing a C<500> page, stringified exception will be written to PSGI error stream.
138              
139             =head1 SEE ALSO
140              
141             L<Plack::App::File>
142              
143             L<Storage::Abstract>
144              
145             =head1 AUTHOR
146              
147             Bartosz Jarzyna E<lt>bbrtj.pro@gmail.comE<gt>
148              
149             =head1 COPYRIGHT AND LICENSE
150              
151             Copyright (C) 2024 by Bartosz Jarzyna
152              
153             This library is free software; you can redistribute it and/or modify
154             it under the same terms as Perl itself.
155