a71b69b02bab3c13ddf1ab2201cafc4f715fff89
[bookreader.git] / plack / lib / Plack / App / BookReader.pm
1 package Plack::App::BookReader;
2 use parent qw(Plack::App::File);
3 use strict;
4 use warnings;
5 use Plack::Util;
6 use HTTP::Date;
7 use Plack::MIME;
8 use DirHandle;
9 use URI::Escape;
10 use Plack::Request;
11 use Data::Dump qw(dump);
12
13 # Stolen from rack/directory.rb
14 my $dir_file = "<tr><td class='name'><a href='%s'>%s</a></td><td class='size'>%s</td><td class='type'>%s</td><td class='mtime'>%s</td></tr>";
15 my $dir_page = <<PAGE;
16 <html><head>
17   <title>%s</title>
18   <meta http-equiv="content-type" content="text/html; charset=utf-8" />
19   <style type='text/css'>
20 table { width:100%%; }
21 .name { text-align:left; }
22 .size, .mtime { text-align:right; }
23 .type { width:11em; }
24 .mtime { width:15em; }
25   </style>
26 </head><body>
27 <h1>%s</h1>
28 <hr />
29 <table>
30   <tr>
31     <th class='name'>Name</th>
32     <th class='size'>Size</th>
33     <th class='type'>Type</th>
34     <th class='mtime'>Last Modified</th>
35   </tr>
36 %s
37 </table>
38 <hr />
39 <code>%s</code>
40 </body></html>
41 PAGE
42
43 sub should_handle {
44     my($self, $file) = @_;
45     return -d $file || -f $file;
46 }
47
48 sub return_dir_redirect {
49     my ($self, $env) = @_;
50     my $uri = Plack::Request->new($env)->uri;
51     return [ 301,
52         [
53             'Location' => $uri . '/',
54             'Content-Type' => 'text/plain',
55             'Content-Length' => 8,
56         ],
57         [ 'Redirect' ],
58     ];
59 }
60
61 sub serve_path {
62     my($self, $env, $dir, $fullpath) = @_;
63
64     if (-f $dir) {
65
66                 if (
67
68         return $self->SUPER::serve_path($env, $dir, $fullpath);
69     }
70
71     my $dir_url = $env->{SCRIPT_NAME} . $env->{PATH_INFO};
72
73     if ($dir_url !~ m{/$}) {
74         return $self->return_dir_redirect($env);
75     }
76
77     my @files = ([ "../", "Parent Directory", '', '', '' ]);
78
79     my $dh = DirHandle->new($dir);
80     my @children;
81     while (defined(my $ent = $dh->read)) {
82         next if $ent eq '.';
83         push @children, $ent;
84     }
85
86         my @page_files;
87
88     for my $basename (sort { $a cmp $b } @children) {
89                 push @page_files, $basename if $basename =~ m/\.jpg$/;
90         my $file = "$dir/$basename";
91         my $url = $dir_url . $basename;
92
93         my $is_dir = -d $file;
94         my @stat = stat _;
95
96
97         $url = join '/', map {uri_escape($_)} split m{/}, $url;
98
99         if ($is_dir) {
100             $basename .= "/";
101             $url      .= "/";
102         }
103
104         my $mime_type = $is_dir ? 'directory' : ( Plack::MIME->mime_type($file) || 'text/plain' );
105         push @files, [ $url, $basename, $stat[7], $mime_type, HTTP::Date::time2str($stat[9]) ];
106     }
107
108     my $path  = Plack::Util::encode_html( $env->{PATH_INFO} );
109     my $files = join "\n", map {
110         my $f = $_;
111         sprintf $dir_file, map Plack::Util::encode_html($_), @$f;
112     } @files;
113
114         my $meta = {
115                 page_urls => [ map { "$dir_url/$_" } sort { $a <=> $b } @page_files ],
116         };
117     my $page  = sprintf $dir_page, $path, $path, $files, dump( $meta );
118
119     return [ 200, ['Content-Type' => 'text/html; charset=utf-8'], [ $page ] ];
120 }
121
122 1;
123
124 __END__
125
126 =head1 NAME
127
128 Plack::App::BookReader - Internet Archive Book Reader with directory index
129
130 =head1 SYNOPSIS
131
132   # app.psgi
133   use Plack::App::BookReader;
134   my $app = Plack::App::BookReader->new({ root => "/path/to/htdocs" })->to_app;
135
136 =head1 DESCRIPTION
137
138 This is a static file server PSGI application with directory index a la Apache's mod_autoindex.
139
140 =head1 CONFIGURATION
141
142 =over 4
143
144 =item root
145
146 Document root directory. Defaults to the current directory.
147
148 =back
149
150 =head1 AUTHOR
151
152 Dobrica Pavlinusic
153 Tatsuhiko Miyagawa (based on L<Plack::App::Directory>
154
155 =head1 SEE ALSO
156
157 L<Plack::App::File>
158
159 =cut
160