File Coverage

lib/Nginx/Module/Gallery.pm
Criterion Covered Total %
statement 61 63 96.8
branch n/a
condition n/a
subroutine 21 21 100.0
pod n/a
total 82 84 97.6


line stmt bran cond sub pod time code
1             package Nginx::Module::Gallery;
2              
3 1     1   45295 use strict;
  1         3  
  1         26  
4 1     1   4 use warnings;
  1         1  
  1         26  
5 1     1   4 use utf8;
  1         1  
  1         6  
6 1     1   22 use 5.10.1;
  1         3  
  1         65  
7              
8             =head1 NAME
9              
10             Nginx::Module::Gallery - Gallery perl module for nginx. Like simple file index
11             but thumbnail replace default icon for image.
12              
13             =head1 SYNOPSIS
14              
15             Example of nginx http section:
16              
17             http{
18             ...
19             # Path to Gallery.pm
20             perl_modules /usr/share/perl5/;
21             perl_require Nginx/Module/Gallery.pm;
22             }
23              
24             Example of nginx server section:
25              
26             server {
27             listen 80;
28              
29             server_name gallery.localhost;
30              
31             location / {
32             perl Nginx::Module::Gallery::handler;
33             # Path to image files
34             root /usr/share/images;
35             }
36             }
37              
38             =head1 DESCRIPTION
39              
40             This module not for production servers! But for single user usage.
41             Gallery don`t use nginx event machine, so one nginx worker per connect
42             (typically 8) used for slow create thumbnails!
43              
44             All thumbnails cached on first request. Next show will be more fast.
45              
46             =cut
47              
48             =head1 VARIABLES
49              
50             =cut
51              
52             # Module version
53             our $VERSION = '0.3.0';
54              
55             our %CONFIG;
56              
57             # Fixed thumbnails
58 1     1   5 use constant ICON_FOLDER => '/folder.png';
  1         1  
  1         60  
59 1     1   4 use constant ICON_UPDIR => '/updir.png';
  1         2  
  1         55  
60 1     1   5 use constant ICON_FAVICON => '/favicon.png';
  1         2  
  1         39  
61 1     1   4 use constant ICON_ARCHIVE => '/archive.png';
  1         2  
  1         41  
62              
63             # MIME type of unknown files
64 1     1   4 use constant MIME_UNKNOWN => 'x-unknown/x-unknown';
  1         1  
  1         38  
65              
66             # Buffer size for output archive to client
67 1     1   10 use constant ARCHIVE_BUFFER_SIZE => 4096;
  1         1  
  1         31  
68              
69             # Timeout for index create
70 1     1   3 use constant EVENT_TIMEOUT => 1;
  1         1  
  1         66  
71              
72             # Nginx module is ugly
73             eval{ require nginx; };
74             die $@ if $@;
75              
76 1     1   765 use Mojo::Template;
  1         62164  
  1         11  
77 1     1   875 use MIME::Types;
  1         6050  
  1         45  
78 1     1   11 use File::Spec;
  1         1  
  1         21  
79 1     1   4 use File::Basename;
  1         2  
  1         57  
80 1     1   5 use File::Path qw(make_path);
  1         2  
  1         49  
81 1     1   1196 use File::Temp qw(tempfile);
  1         12809  
  1         69  
82 1     1   9 use File::Find;
  1         1  
  1         77  
83 1     1   4 use Digest::MD5 'md5_hex';
  1         2  
  1         34  
84 1     1   706 use URI::Escape qw(uri_escape);
  1         1160  
  1         53  
85 1     1   408 use Image::Magick;
  0            
  0            
86              
87             # MIME definition objects
88             our $mimetypes = MIME::Types->new;
89             our $mime_unknown = MIME::Type->new(
90             simplified => 'unknown/unknown',
91             type => 'x-unknown/x-unknown'
92             );
93              
94             # Default mime for thumbnails
95             our $mime_png = $mimetypes->mimeTypeOf( 'png' );
96              
97             # Templates
98             our $mt = Mojo::Template->new;
99             $mt->encoding('UTF-8');
100              
101             =head1 HANDLERS
102              
103             =cut
104              
105             =head2 index $r
106              
107             Directory index handler
108              
109             =cut
110              
111             sub index($)
112             {
113             my $r = shift;
114              
115             # Get configuration variables
116             _get_variables($r);
117              
118             # Stop unless GET or HEAD
119             return HTTP_BAD_REQUEST unless grep {$r->request_method eq $_} qw{GET HEAD};
120             # Stop unless dir or file
121             return HTTP_NOT_FOUND unless -f $r->filename or -d _;
122             # Stop if header only
123             return OK if $r->header_only;
124              
125             # show file
126             return show_image($r) if -f _;
127             # show directory index
128             return show_index($r);
129             }
130              
131             =head2 archive $r
132              
133             Online archive response
134              
135             =cut
136              
137             sub archive($)
138             {
139             my $r = shift;
140              
141             # Get configuration variables
142             _get_variables($r);
143              
144             # Stop unless GET or HEAD
145             return HTTP_BAD_REQUEST unless grep {$r->request_method eq $_} qw{GET HEAD};
146             # Stop if header only
147             return OK if $r->header_only;
148              
149             # send archive to client
150             return show_archive($r);
151             }
152              
153             =head1 FUNCTIONS
154              
155             =cut
156              
157             =head2 show_image
158              
159             Send image to client
160              
161             =cut
162              
163             sub show_image($)
164             {
165             my ($r) = @_;
166             $r->send_http_header;
167             $r->sendfile( $r->filename );
168             return OK;
169             }
170              
171             =head2 show_index
172              
173             Send directory index to client. Try do it like event base, but use sleep.
174              
175             =cut
176              
177             sub show_index($)
178             {
179             my ($r) = @_;
180              
181             $r->send_http_header("text/html; charset=utf-8");
182             # Send top of index page
183             $r->sleep(EVENT_TIMEOUT, sub{
184             $_[0]->print( _get_index_top($_[0]->uri) );
185             # Send updir link if need
186             $_[0]->sleep(EVENT_TIMEOUT, sub{
187             $_[0]->print( _get_index_updir($_[0]->uri) );
188             # Send directory archive link
189             $_[0]->sleep(EVENT_TIMEOUT, sub{
190             $_[0]->print( _get_index_archive($_[0]->uri) );
191             $_[0]->flush;
192             # Get directory index
193             $_[0]->sleep(EVENT_TIMEOUT, sub{
194             my $mask =
195             File::Spec->catfile(_escape_path($_[0]->filename), '*');
196             my @index =
197             sort {-d $b cmp -d $a}
198             sort {uc $a cmp uc $b}
199             glob $mask;
200             if( @index ) {
201             $_[0]->variable('gallery_index', join("\n\r", @index));
202             # Send directory index
203             $_[0]->sleep(EVENT_TIMEOUT, \&_make_icon);
204             } else {
205             # Send bottom of index page
206             $_[0]->print( _get_index_bottom() );
207             $_[0]->flush;
208             }
209             return OK;
210             });
211             return OK;
212             });
213             return OK;
214             });
215             return OK;
216             });
217             return OK;
218             }
219              
220             =head2 _make_icon
221              
222             Send index item to client, or init send index bottom.
223              
224             =cut
225              
226             sub _make_icon {
227             my ($r) = @_;
228              
229             my $index = $r->variable('gallery_index');
230             my $url = $r->uri;
231              
232             my @index = split "\n\r", $index;
233             return OK unless @index;
234              
235             my $path = shift @index;
236              
237             $r->print( _get_index_item($url, $path) ) ;
238             $r->flush;
239              
240             if( @index ) {
241             $r->variable('gallery_index', join("\n\r", @index));
242             $r->sleep(EVENT_TIMEOUT, \&_make_icon);
243             return OK;
244             }
245             else {
246             $r->sleep(EVENT_TIMEOUT, sub {
247             my ($r) = @_;
248             $r->print( _get_index_bottom() );
249             $r->flush;
250             return OK;
251             });
252             return OK;
253             }
254              
255             return OK;
256             }
257              
258             =head2 show_archive
259              
260             Sent archive file to client
261              
262             =cut
263              
264             sub show_archive($)
265             {
266             my ($r) = @_;
267              
268             my ($filename, $dir) = File::Basename::fileparse( $r->filename );
269              
270             # Set read buffer size
271             local $/ = ARCHIVE_BUFFER_SIZE;
272              
273             # Get image params
274             open my $pipe1, '-|:raw',
275             '/bin/tar',
276             '--create',
277             '--force-local',
278             '--bzip2',
279             '--exclude-caches-all',
280             '--exclude-vcs',
281             '--directory', $dir,
282             '.'
283             or return HTTP_NOT_FOUND;
284              
285             $r->header_out("Content-Encoding", 'bzip2');
286             $r->send_http_header("application/x-tar");
287              
288             while(my $data = <$pipe1>) {
289             $r->print( $data );
290             }
291             close $pipe1;
292              
293             return OK;
294             }
295              
296             =head2 get_icon_form_cache $path
297              
298             Check icon for image by $path in cache and return it if exists
299              
300             =cut
301              
302             sub get_icon_form_cache($$)
303             {
304             my ($path, $uri) = @_;
305              
306             my ($filename, $dir) = File::Basename::fileparse($path);
307              
308             # Find icon
309             my $mask = File::Spec->catfile(
310             _escape_path( File::Spec->catdir($CONFIG{CACHE_PATH}, $dir) ),
311             sprintf( '%s.*', _get_md5_image( $path ) )
312             );
313             my ($cache_path) = glob $mask;
314              
315             # Icon not found
316             return unless $cache_path;
317              
318             my ($image_width, $image_height, $ext) =
319             $cache_path =~ m{^.*\.(\d+)x(\d+)\.(\w+)$}i;
320              
321             my ($icon_filename, $icon_dir) = File::Basename::fileparse($cache_path);
322              
323             return {
324             href => _escape_url($CONFIG{CACHE_PREFIX}, $uri, $icon_filename),
325             filename => $icon_filename,
326             mime => $mimetypes->mimeTypeOf( $ext ),
327             image => {
328             width => $image_width,
329             height => $image_height,
330             },
331             thumb => 1,
332             cached => 1,
333             };
334             }
335              
336             =head2 update_icon_in_cache $path, $uri, $mime
337              
338             Get $path and $uri of image and make icon for it
339              
340             =cut
341              
342             sub update_icon_in_cache($$;$)
343             {
344             my ($path, $uri, $mime ) = @_;
345              
346             # Get MIME type of original file
347             $mime //= $mimetypes->mimeTypeOf( $path ) || $mime_unknown;
348              
349             my $icon;
350              
351             # Get raw thumbnail data
352             if($mime->subType eq 'vnd.microsoft.icon')
353             {
354             $icon = _get_icon_thumb( $path );
355             }
356             elsif( $mime->mediaType eq 'video' )
357             {
358             $icon = _get_video_thumb( $path );
359             }
360             elsif( $mime->mediaType eq 'image' )
361             {
362             $icon = _get_image_thumb( $path );
363             }
364              
365             return unless $icon;
366              
367             # Save thunbnail
368             $icon = _save_thumb($icon);
369              
370             # Make href on thumbnail
371             $icon->{href} =
372             _escape_url( $CONFIG{CACHE_PREFIX}, $uri, $icon->{filename} );
373              
374             # Cleanup
375             delete $icon->{raw};
376              
377             return wantarray ?%$icon :$icon;
378             }
379              
380             =head1 PRIVATE FUNCTIONS
381              
382             =cut
383              
384             =head2 _get_video_thumb $path
385              
386             Get raw thumbnail data for video file by it`s $path
387              
388             =cut
389              
390             sub _get_video_thumb($)
391             {
392             my ($path) = @_;
393              
394             # Get standart extension
395             my @ext = $mime_png->extensions;
396             my $suffix = $ext[0] || 'png';
397              
398             # Full file read
399             local $/;
400              
401             # Convert to temp thumbnail file
402             my ($fh, $filename) =
403             tempfile( UNLINK => 1, OPEN => 1, SUFFIX => '.'.$suffix );
404             return unless $fh;
405              
406             system '/usr/bin/ffmpegthumbnailer',
407             '-s', $CONFIG{ICON_MAX_DIMENSION},
408             '-q', $CONFIG{ICON_QUALITY_LEVEL},
409             # '-f',
410             '-i', $path,
411             '-o', $filename;
412              
413             # Get image
414             my $raw = <$fh>;
415             close $fh or return;
416             return unless $raw;
417              
418             my $mime = $mime_png || $mime_unknown;
419              
420             my %result = (
421             raw => $raw,
422             mime => $mime,
423             orig => {
424             path => $path,
425             },
426             );
427              
428             return wantarray ?%result :\%result;
429             }
430              
431             =head2 _get_image_thumb $path
432              
433             Get raw thumbnail data for image file by it`s $path
434              
435             =cut
436              
437             sub _get_image_thumb($)
438             {
439             my ($path) = @_;
440              
441             # Get image and attributes
442             my $image = Image::Magick->new;
443             $image->Read($path);
444             my ($image_width, $image_height, $image_size) =
445             $image->Get("width", "height", "filesize");
446              
447             # Save image on disk:
448             # Remove any sequences (for GIF)
449             for (my $x = 1; $image->[$x]; $x++) {
450             undef $image->[$x];
451             }
452             # Remove original comments, EXIF, etc.
453             $image->Strip;
454             # make tumbnail
455             $image->Thumbnail(geometry =>
456             $CONFIG{ICON_MAX_DIMENSION}.'x'.$CONFIG{ICON_MAX_DIMENSION}.'>');
457             # Set colors
458             $image->Quantize(colorspace => 'RGB');
459             # Orient
460             $image->AutoOrient;
461             # Some compression
462             $image->Set(quality => $CONFIG{ICON_COMPRESSION_LEVEL});
463              
464             # Get mime type as icon type
465             my $mime = $mime_png || $mime_unknown;
466              
467             my %result = (
468             mime => $mime,
469             orig => {
470             path => $path,
471             width => $image_width,
472             heigth => $image_height,
473             size => $image_size,
474             },
475             save => sub {
476             my ($cache) = @_;
477             my $msg = $image->Write( $cache );
478             undef $image;
479             warn "$msg" if "$msg";
480             return 1;
481             }
482             );
483              
484             return wantarray ?%result :\%result;
485             }
486              
487             =head2 _get_image_thumb $path
488              
489             Get raw thumbnail data for icon file by it`s $path
490              
491             =cut
492              
493             sub _get_icon_thumb($)
494             {
495             my ($path) = @_;
496              
497             # Show just small icons
498             return unless -s $path < $CONFIG{ICON_MAX_SIZE};
499              
500             # Full file read
501             local $/;
502              
503             # Get image
504             open my $fh, '<:raw', $path or return;
505             my $raw = <$fh>;
506             close $fh or return;
507             return unless $raw;
508              
509             my $mime = $mimetypes->mimeTypeOf( $path ) || $mime_unknown;
510              
511             my %result = (
512             raw => $raw,
513             mime => $mime,
514             orig => {
515             path => $path,
516             },
517             );
518              
519             return wantarray ?%result :\%result;
520             }
521              
522             =head2 _save_thumb $icon
523              
524             Save $icon in cache
525              
526             =cut
527              
528             sub _save_thumb($)
529             {
530             my ($icon) = @_;
531              
532             my ($filename, $dir) = File::Basename::fileparse($icon->{orig}{path});
533              
534             # Create dirs unless exists
535             my $path = File::Spec->catdir($CONFIG{CACHE_PATH}, $dir);
536             unless(-d $path) {
537             my $error;
538             make_path(
539             $path,
540             {
541             mode => oct $CONFIG{CACHE_MODE},
542             error => \$error,
543             }
544             );
545             return if @$error;
546             }
547              
548             my $icon_filename = sprintf( '%s.%dx%d.%s',
549             _get_md5_image( $icon->{orig}{path} ),
550             $icon->{orig}{width},
551             $icon->{orig}{height},
552             $icon->{mime}->subType
553             );
554              
555             # Make path
556             my $cache = File::Spec->catfile(
557             $CONFIG{CACHE_PATH}, $dir, $icon_filename );
558              
559             # Store icon on disk
560             if( $icon->{save} ) {
561             $icon->{save}->( $cache );
562             } else {
563             open my $f, '>:raw', $cache or return;
564             print $f $icon->{raw};
565             close $f;
566             }
567              
568             # Set path and flag
569             $icon->{path} = $cache;
570             $icon->{thumb} = 1;
571             $icon->{cached} = 1;
572             $icon->{filename} = $icon_filename;
573              
574             return $icon;
575             }
576              
577             =head2 _template $name
578              
579             Retrun template my $name
580              
581             =cut
582              
583             sub _template($)
584             {
585             my ($name) = @_;
586              
587             # Return template if loaded
588             our %template;
589             return $template{ $name } if $template{ $name };
590              
591             # Load template
592             my $path = File::Spec->catfile($CONFIG{TEMPLATE_PATH}, $name.'.html.ep');
593             open my $f, '<:utf8', $path or return;
594             local $/;
595             $template{ $name } = <$f>;
596             close $f;
597              
598             return $template{ $name };
599             }
600              
601             =head2 _icon_mime $path
602              
603             Return mime icon for file by $path
604              
605             =cut
606              
607             sub _icon_mime
608             {
609             my ($path) = @_;
610              
611             my ($filename, $dir) = File::Basename::fileparse($path);
612             my ($extension) = $filename =~ m{\.(\w+)$};
613              
614             my $mime = $mimetypes->mimeTypeOf( $path ) || $mime_unknown;
615             my $str = "$mime";
616             my $media = $mime->mediaType;
617             my $sub = $mime->subType;
618             my $full = join '-', $mime =~ m{^(.*?)/(.*)$};
619              
620             my @ext = $mime_png->extensions;
621              
622             my $href = _escape_url(
623             $CONFIG{MIME_PREFIX},
624             sprintf( '%s.%s', $full, ($ext[0] || 'png') ),
625             );
626              
627             return {
628             mime => $mime,
629             href => $href,
630             };
631             }
632              
633             =head2 as_human_size(NUM)
634              
635             converts big numbers to small 1024 = 1K, 1024**2 == 1M, etc
636              
637             =cut
638              
639             sub _as_human_size($)
640             {
641             my ($size, $sign) = (shift, 1);
642              
643             my %result = (
644             original => $size,
645             digit => 0,
646             letter => '',
647             human => 'N/A',
648             byte => '',
649             );
650              
651             {{
652             last unless $size;
653             last unless $size >= 0;
654              
655             my @suffixes = ('', 'K', 'M', 'G', 'T', 'P', 'E');
656             my ($limit, $div) = (1024, 1);
657             for (@suffixes)
658             {
659             if ($size < $limit || $_ eq $suffixes[-1])
660             {
661             $size = $sign * $size / $div;
662             if ($size < 10)
663             {
664             $size = sprintf "%1.2f", $size;
665             }
666             elsif ($size < 50)
667             {
668             $size = sprintf "%1.1f", $size;
669             }
670             else
671             {
672             $size = int($size);
673             }
674             s/(?<=\.\d)0$//, s/\.00?$// for $size;
675             $result{digit} = $size;
676             $result{letter} = $_;
677             $result{byte} = 'B';
678             last;
679             }
680             $div = $limit;
681             $limit *= 1024;
682             }
683             }}
684              
685             $result{human} = $result{digit} . $result{letter} . $result{byte};
686              
687             return ($result{digit}, $result{letter}, $result{byte}, $result{human})
688             if wantarray;
689             return $result{human};
690             }
691              
692             =head2 _get_md5_image $path
693              
694             Return unque MD5 hex string for image file by it`s $path
695              
696             =cut
697              
698             sub _get_md5_image($)
699             {
700             my ($path) = @_;
701             my ($size, $mtime) = ( stat($path) )[7,9];
702             return md5_hex
703             join( ',', $path, $size, $mtime,
704             $CONFIG{ICON_MAX_DIMENSION}, $CONFIG{ICON_COMPRESSION_LEVEL},
705             $CONFIG{ICON_QUALITY_LEVEL}
706             );
707             }
708              
709             =head2 _escape_path $path
710              
711             Return escaped $path
712              
713             =cut
714              
715             sub _escape_path($)
716             {
717             my ($path) = @_;
718             my $escaped = $path;
719             $escaped =~ s{([\s'".?*\(\)\+\}\{\]\[])}{\\$1}g;
720             return $escaped;
721             }
722              
723             =head2 _escape_url @path
724              
725             Return escaped uri for list of @path partitions
726              
727             =cut
728              
729             sub _escape_url(@)
730             {
731             my (@path) = @_;
732             my @dirs;
733             push @dirs, File::Spec->splitdir( $_ ) for @path;
734             $_ = uri_escape $_ for @dirs;
735             return File::Spec->catfile( @dirs );
736             }
737              
738             =head2 _get_variables $r
739              
740             Get configuration variables from request $r
741              
742             =cut
743              
744             sub _get_variables
745             {
746             my ($r) = @_;
747              
748             $CONFIG{$_} //= $r->variable( $_ )
749             for qw(ICON_MAX_DIMENSION ICON_MAX_SIZE ICON_COMPRESSION_LEVEL
750             ICON_QUALITY_LEVEL
751             CACHE_PATH CACHE_MODE CACHE_PREFIX
752             TEMPLATE_PATH
753             ICONS_PREFIX MIME_PREFIX ARCHIVE_PREFIX);
754             return 1;
755             }
756              
757             =head2 _make_title $url
758              
759             Make title from url
760              
761             =cut
762              
763             sub _make_title($)
764             {
765             my ($url) = @_;
766             my @tpath = File::Spec->splitdir( $url );
767             @tpath = grep {$_} @tpath;
768             push @tpath, '/' unless @tpath;
769             return 'Gallery - ' . join ' : ', @tpath;
770             }
771              
772             sub _get_index_top($) {
773             my ($url) = @_;
774              
775             return
776             $mt->render(
777             _template('top'),
778             path => $CONFIG{TEMPLATE_PATH},
779             title => _make_title( $url ),
780             size => $CONFIG{ICON_MAX_DIMENSION},
781             favicon => {
782             icon => {
783             href => _escape_url( $CONFIG{ICONS_PREFIX}, ICON_FAVICON ),
784             },
785             },
786             );
787             }
788              
789             sub _get_index_updir($) {
790             my ($url) = @_;
791              
792             # Add updir for non root directory
793             return '' if $url eq '/';
794              
795             # make link on updir
796             my @updir = File::Spec->splitdir( $url );
797             pop @updir;
798             my $href = _escape_url( File::Spec->catdir( @updir ) );
799              
800             # Send updir icon
801             my %item = (
802             path => File::Spec->updir,
803             filename => File::Spec->updir,
804             href => $href,
805             icon => {
806             href => _escape_url( $CONFIG{ICONS_PREFIX}, ICON_UPDIR ),
807             },
808             class => 'updir',
809             );
810              
811             return $mt->render( _template('item'), item => \%item );
812             }
813              
814             sub _get_index_archive($) {
815             my ($url) = @_;
816              
817             my @dir = File::Spec->splitdir( $url );
818             my $filename = $dir[-1] || 'AllGallery';
819             $filename .= '.tar.bz';
820             my $href = _escape_url(
821             $CONFIG{ARCHIVE_PREFIX},
822             File::Spec->catfile( @dir, $filename )
823             );
824              
825             # Send updir icon
826             my %item = (
827             path => $filename,
828             filename => $filename,
829             href => $href,
830             icon => {
831             href => _escape_url( $CONFIG{ICONS_PREFIX}, ICON_ARCHIVE ),
832             },
833             class => 'archive',
834             );
835              
836             return $mt->render( _template('item'), item => \%item );
837             }
838              
839             sub _get_index_item($$) {
840             my ($url, $path) = @_;
841              
842             # Get filename
843             my ($filename, $dir) = File::Basename::fileparse($path);
844             my ($digit, $letter, $bytes, $human) = _as_human_size( -s $path );
845             my $mime = $mimetypes->mimeTypeOf( $path ) || $mime_unknown;
846              
847             my @href = File::Spec->splitdir( $url );
848             my $href = _escape_url( File::Spec->catfile( @href, $filename ) );
849              
850             # Make item info hash
851             my %item = (
852             path => $path,
853             filename => $filename,
854             href => $href,
855             size => $human,
856             mime => $mime,
857             );
858              
859             # For folders get standart icon
860             if( -d _ )
861             {
862             $item{icon}{href} = _escape_url($CONFIG{ICONS_PREFIX}, ICON_FOLDER);
863              
864             # Remove directory fails
865             delete $item{size};
866             delete $item{mime};
867             }
868             # For images make icons and get some information
869             elsif( $mime->mediaType eq 'image' or $mime->mediaType eq 'video' )
870             {
871             # Load icon from cache
872             my $icon = get_icon_form_cache( $path, $url );
873             # Try to make icon
874             $icon = update_icon_in_cache( $path, $url, $mime ) unless $icon;
875             # Make mime image icon
876             $icon = _icon_mime( $path ) unless $icon;
877              
878             # Save icon and some image information
879             $item{icon} = $icon;
880             $item{image}{width} = $icon->{orig}{width}
881             if defined $icon->{orig}{width};
882             $item{image}{height} = $icon->{orig}{height}
883             if defined $icon->{orig}{height};
884             }
885             # Show mime icon for file
886             else
887             {
888             # Load mime icon
889             $item{icon} = _icon_mime( $path );
890             }
891              
892             return $mt->render( _template('item'), item => \%item );
893             }
894              
895             sub _get_index_bottom() {
896             return $mt->render( _template('bottom') )
897             }
898              
899             1;
900              
901             =head1 AUTHORS
902              
903             Copyright (C) 2012 Dmitry E. Oboukhov ,
904              
905             Copyright (C) 2012 Roman V. Nikolaev
906              
907             =head1 LICENSE
908              
909             This program is free software: you can redistribute it and/or modify it
910             under the terms of the GNU General Public License as published by the Free
911             Software Foundation, either version 3 of the License, or (at your option)
912             any later version.
913              
914             This program is distributed in the hope that it will be useful, but WITHOUT
915             ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
916             FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
917             more details.
918              
919             You should have received a copy of the GNU General Public License along
920             with this program. If not, see .
921              
922             =cut