File Coverage

blib/lib/HTML/KhatGallery/Core.pm
Criterion Covered Total %
statement 39 702 5.5
branch 0 202 0.0
condition 0 71 0.0
subroutine 13 58 22.4
pod 44 44 100.0
total 96 1077 8.9


line stmt bran cond sub pod time code
1             package HTML::KhatGallery::Core;
2             our $VERSION = '0.2405'; # VERSION
3 3     3   13976 use strict;
  3         4  
  3         72  
4 3     3   11 use warnings;
  3         5  
  3         85  
5              
6             =head1 NAME
7              
8             HTML::KhatGallery::Core - the core methods for HTML::KhatGallery
9              
10             =head1 VERSION
11              
12             version 0.2405
13              
14             =head1 SYNOPSIS
15              
16             # implicitly
17             use HTML::KhatGallery qw(HTML::KhatGallery::Core HTML::KhatGallery::Plugin::MyPlugin ...);
18              
19             # or explicitly
20             require HTML::KhatGallery;
21              
22             @plugins = qw(HTML::KhatGallery::Core HTML::KhatGallery::Plugin::MyPlugin ...);
23             HTML::KhatGallery->import(@plugins);
24             HTML::KhatGallery->run(%args);
25              
26              
27             =head1 DESCRIPTION
28              
29             HTML::KhatGallery is a photo-gallery generator.
30              
31             HTML::KhatGallery::Core provides the core functionality of the system.
32             Other functions can be added or overridden by plugin modules.
33              
34             =cut
35              
36 3     3   1095 use POSIX qw(ceil);
  3         14194  
  3         11  
37 3     3   3090 use File::Basename;
  3         9  
  3         189  
38 3     3   16 use File::Spec;
  3         3  
  3         75  
39 3     3   13 use Cwd qw(realpath);
  3         3  
  3         100  
40 3     3   1050 use File::stat;
  3         16361  
  3         9  
41 3     3   1082 use YAML qw(Dump LoadFile);
  3         15057  
  3         127  
42 3     3   3789 use Image::ExifTool;
  3         130744  
  3         1497  
43              
44             =head1 CLASS METHODS
45              
46             =head2 run
47              
48             HTML::KhatGallery->run(%args);
49              
50             C is the only method you should need to use from outside
51             this module; other methods are called internally by this one.
52              
53             This method orchestrates all the work; it creates a new object,
54             and applies all the actions.
55              
56             Arguments:
57              
58             =over
59              
60             =item B
61              
62             The name of the captions file; which is in the same directory
63             as the images which it describes. This file is in L format.
64             For example:
65              
66             ---
67             index.html: this is the caption for the album as a whole
68             image1.png: this is the caption for image1.png
69             image2.jpg: I like the second image
70              
71             (default: captions.yml)
72              
73             =item B
74              
75             Instead of generating files, clean up the thumbnail directories to
76             remove thumbnails and image HTML pages for images which are no
77             longer there.
78              
79             =item B
80              
81             Set the level of debugging output. The higher the level, the more verbose.
82             (developer only)
83             (default: 0)
84              
85             =item B
86              
87             Regular expression to match the directories we are interested in.
88             Hidden directories and the thumbnail directory will never be included.
89              
90             =item B
91              
92             Force the re-generation of all the HTML files even if they already
93             exist. If false (the default) then a given HTML file will only be
94             created if there is a change in that particular directory.
95              
96             =item B
97              
98             Force the re-generation of the thumbnail images even if they already
99             exist. If false (the default) then a given (thumbnail) image file will
100             only be created if it doesn't already exist.
101              
102             =item B
103              
104             Regular expression determining what filenames should be interpreted
105             as images.
106              
107             =item B
108              
109             Array reference containing formats for meta-data from the images.
110             Field names are surrounded by % characters. For example:
111              
112             meta => ['Date: %DateTime%', '%Comment%'],
113              
114             If an image doesn't have that particular field, the data for that field is not
115             shown. All the meta-data is placed after any caption the image has.
116              
117             =item B
118              
119             Template for HTML pages. The default template is this:
120              
121            
122             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
123            
124            
125             <!--kg_title-->
126            
127            
128            
129            
130            
131            
132              
133             This can be a string or a filename.
134              
135             =item B
136              
137             The number of images to display per index page.
138              
139             =item B
140              
141             The name of the directory where thumbnails and image-pages are put.
142             It is a subdirectory below the directory where its images are.
143             (default: tn)
144              
145             =item B
146              
147             The size of the thumbnails. This doesn't actually define the dimensions
148             of the thumbnails, but their area. This gives better-quality thumbnails.
149             (default:100x100)
150              
151             =item B
152              
153             The directory to look for images in; this will be searched for images and
154             sub-directories. If this is not given, the current directory is used.
155              
156             =item B
157              
158             The directory to create galleries in; HTML and thumbnails will be created
159             there. If this is not given, it is the same as B.
160              
161             =item B
162              
163             The URL of the top images directory; if the top_out_dir isn't the
164             same as the top_dir, then we need to know this in order
165             to link to the images in the images directory.
166              
167             =item B
168              
169             Print informational messages.
170              
171             =back
172              
173             =cut
174             sub run {
175 0     0 1   my $class = shift;
176 0           my %args = (
177             parent=>'',
178             @_
179             );
180              
181 0           my $self = $class->new(%args);
182 0           $self->init();
183             print "Processing directory $self->{top_dir}\n"
184 0 0         if $self->{verbose};
185              
186 0           $self->do_dir_actions('');
187             } # run
188              
189             =head1 OBJECT METHODS
190              
191             Only of interest to developers and those wishing to write plugins.
192              
193             =head2 new
194              
195             Make a new object. See L for the arguments.
196             This method should not be overridden by plugin writers; use L
197             instead.
198              
199             =cut
200              
201             sub new {
202 0     0 1   my $class = shift;
203 0   0       my $self = bless ({@_}, ref ($class) || $class);
204              
205 0           return ($self);
206             } # new
207              
208             =head2 init
209              
210             Do some initialization of the object after it's created.
211             See L for the arguments.
212             Set up defaults for things which haven't been defined.
213              
214             Plugin writers should override this method rather than L
215             if they want to do some initialization for their plugin.
216              
217             =cut
218              
219             sub init {
220 0     0 1   my $self = shift;
221              
222             # some defaults
223 0   0       $self->{per_page} ||= 16;
224 0   0       $self->{thumbdir} ||= 'tn';
225 0   0       $self->{captions_file} ||= 'captions.yml';
226 0   0       $self->{thumb_geom} ||= '100x100';
227 0   0       $self->{force_html} ||= 0;
228 0   0       $self->{force_images} ||= 0;
229              
230 0   0       $self->{debug_level} ||= 0;
231             # if there's no top dir, make it the current one
232 0 0         if (!defined $self->{top_dir})
233             {
234 0           $self->{top_dir} = '.';
235             }
236 0           $self->{top_dir} = File::Spec->rel2abs($self->{top_dir});
237 0           $self->{top_base} = basename($self->{top_dir});
238              
239             # top_out_dir
240 0 0         if (!defined $self->{top_out_dir})
241             {
242 0           $self->{top_out_dir} = $self->{top_dir};
243             }
244 0           $self->{top_out_dir} = File::Spec->rel2abs($self->{top_out_dir});
245 0           $self->{top_out_base} = basename($self->{top_out_dir});
246              
247             # trim top_url if it has a trailing slash
248 0 0         if (defined $self->{top_url})
249             {
250 0           $self->{top_url} =~ s!/$!!;
251             }
252             else
253             {
254 0           $self->{top_url} = '';
255             }
256              
257             # calculate width and height of thumbnail display
258 0           $self->{thumb_geom} =~ /(\d+)x(\d+)/;
259 0           $self->{thumb_width} = $1;
260 0           $self->{thumb_height} = $2;
261 0           $self->{pixelcount} = $self->{thumb_width} * $self->{thumb_height};
262              
263 0 0         if (!defined $self->{dir_actions})
264             {
265 0           $self->{dir_actions} = [qw(init_settings
266             read_captions
267             read_dir
268             read_out_dir
269             filter_images
270             sort_images
271             filter_dirs
272             sort_dirs
273             make_index_page
274             process_images
275             process_subdirs
276             tidy_up
277             )];
278             }
279 0 0         if (!defined $self->{clean_actions})
280             {
281 0           $self->{clean_actions} = [qw(init_settings
282             read_dir
283             filter_images
284             filter_dirs
285             clean_thumb_dir
286             process_subdirs
287             tidy_up
288             )];
289             }
290              
291 0 0         if (!defined $self->{image_actions})
292             {
293 0           $self->{image_actions} = [qw(init_image_settings
294             make_thumbnail
295             make_image_page
296             image_tidy_up
297             )];
298             }
299              
300 0 0         if (!defined $self->{image_match})
301             {
302 0           my @img_ext = map {"\.$_\$"}
  0            
303             qw(jpg jpeg png gif tif tiff pcx xwd xpm xbm);
304 0           my $img_re = join('|', @img_ext);
305 0           $self->{image_match} = qr/$img_re/i;
306             }
307              
308 0 0         if (!defined $self->{page_template})
309             {
310 0           $self->{page_template} = <
311            
312             "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
313            
314            
315             <!--kg_title-->
316            
317            
318            
319            
320            
321            
322             EOT
323             }
324              
325 0           return ($self);
326             } # init
327              
328             =head2 do_dir_actions
329              
330             $self->do_dir_actions($dir);
331              
332             Do all the actions in the $self->{dir_actions} list, for the
333             given directory. If cleaning, do the actions in the 'clean_actions'
334             list instead.
335             If the dir is empty, this is taken to be the directory given in
336             $self->{top_dir}, the top-level directory.
337              
338             =cut
339             sub do_dir_actions {
340 0     0 1   my $self = shift;
341 0           my $dir = shift;
342              
343 0           my %state = ();
344 0           $state{stop} = 0;
345 0           $state{dir} = $dir;
346              
347 3     3   28 no strict qw(subs refs);
  3         5  
  3         276  
348             my @actions = ($self->{clean}
349 0           ? @{$self->{clean_actions}}
350 0 0         : @{$self->{dir_actions}});
  0            
351 0           while (@actions)
352             {
353 0           my $action = shift @actions;
354 0 0         last if $state{stop};
355 0           $state{action} = $action;
356 0           $self->debug(2, "action: $action");
357 0           $self->$action(\%state);
358             }
359 3     3   18 use strict qw(subs refs);
  3         4  
  3         254  
360 0           1;
361             } # do_dir_actions
362              
363             =head2 do_image_actions
364              
365             $self->do_image_actions(\%dir_state, @images);
366              
367             Do all the actions in the $self->{image_actions} list, for the
368             given images.
369              
370             =cut
371             sub do_image_actions {
372 0     0 1   my $self = shift;
373 0           my $dir_state = shift;
374 0           my @images = @_;
375              
376 0           my %images_state = ();
377              
378 3     3   16 no strict qw(subs refs);
  3         5  
  3         327  
379 0           for (my $i = 0; $i < @images; $i++)
380             {
381 0           %images_state = ();
382 0           $images_state{stop} = 0;
383 0           $images_state{images} = \@images;
384 0           $images_state{num} = $i;
385 0           $images_state{cur_img} = $images[$i];
386             # pop off each action as we go;
387             # that way it's possible for an action to
388             # manipulate the actions array
389 0           @{$images_state{image_actions}} = @{$self->{image_actions}};
  0            
  0            
390 0           while (@{$images_state{image_actions}})
  0            
391             {
392 0           my $action = shift @{$images_state{image_actions}};
  0            
393 0 0         last if $images_state{stop};
394 0           $images_state{action} = $action;
395 0           $self->debug(2, "image_action: $action");
396 0           $self->$action($dir_state,
397             \%images_state);
398             }
399             }
400 3     3   21 use strict qw(subs refs);
  3         6  
  3         14809  
401 0           1;
402             } # do_image_actions
403              
404             =head1 Dir Action Methods
405              
406             Methods implementing directory-related actions. All such actions
407             expect a reference to a state hash, and generally will update either
408             that hash or the object itself, or both, in the course of their
409             running.
410              
411             =head2 init_settings
412              
413             Initialize various settings that need to be set before everything
414             else.
415              
416             This is not the same as "init", because this is the start of
417             the dir_actions sequence; we do it for each directory (or sub-directory)
418             we traverse.
419              
420             =cut
421             sub init_settings {
422 0     0 1   my $self = shift;
423 0           my $dir_state = shift;
424              
425             $dir_state->{abs_dir} = File::Spec->catdir(
426 0           realpath($self->{top_dir}), $dir_state->{dir});
427             $dir_state->{abs_out_dir} = File::Spec->catdir(
428 0           realpath($self->{top_out_dir}), $dir_state->{dir});
429 0           my @path = File::Spec->splitdir($dir_state->{abs_dir});
430 0 0         if ($dir_state->{dir})
431             {
432 0           $dir_state->{dirbase} = pop @path;
433 0           $dir_state->{parent} = pop @path;
434 0           $dir_state->{dir_url} = $self->{top_url} . '/' . $dir_state->{dir};
435             }
436             else # first dir
437             {
438 0           $dir_state->{dirbase} = pop @path;
439 0           $dir_state->{parent} = '';
440 0           $dir_state->{dir_url} = $self->{top_url};
441             }
442             # thumbnail dir for this directory
443             $dir_state->{abs_thumbdir} = File::Spec->catdir($dir_state->{abs_out_dir},
444 0           $self->{thumbdir});
445              
446             # reset the per-directory redo_html flag
447 0           $dir_state->{redo_html} = 0;
448              
449             } # init_settings
450              
451             =head2 read_captions
452              
453             Set the $dir_state->{captions} hash to contain all the
454             captions for this directory (if they exist)
455              
456             =cut
457             sub read_captions {
458 0     0 1   my $self = shift;
459 0           my $dir_state = shift;
460              
461             my $captions_file = File::Spec->catfile($dir_state->{abs_dir},
462 0           $self->{captions_file});
463 0 0         if (!-f $captions_file)
464             {
465             $captions_file = File::Spec->catfile($dir_state->{abs_out_dir},
466 0           $self->{captions_file});
467             }
468 0 0         if (-f $captions_file)
469             {
470 0           $dir_state->{captions} = {};
471 0           $dir_state->{captions} = LoadFile($captions_file);
472             }
473             } # read_captions
474              
475             =head2 read_dir
476              
477             Read the $dir_state->{dir} directory. Sets $dir_state->{subdirs}, and
478             $dir_state->{files} with the relative subdirs, and other files.
479              
480             =cut
481             sub read_dir {
482 0     0 1   my $self = shift;
483 0           my $dir_state = shift;
484              
485 0           my $dh;
486 0 0         opendir($dh, $dir_state->{abs_dir}) or die "Can't opendir $dir_state->{abs_dir}: $!";
487 0           my @subdirs = ();
488 0           my @files = ();
489 0           while (my $fn = readdir($dh))
490             {
491 0           my $abs_fn = File::Spec->catfile($dir_state->{abs_dir}, $fn);
492 0 0 0       if ($fn =~ /^\./ or $fn eq $self->{thumbdir})
    0          
    0          
493             {
494             # skip
495             }
496             elsif (-d $abs_fn)
497             {
498 0           push @subdirs, $fn;
499             }
500             # ignore any html files
501             elsif ($fn =~ /\.html$/)
502             {
503             }
504             else
505             {
506 0           push @files, $fn;
507             }
508             }
509 0           closedir($dh);
510              
511 0           $dir_state->{subdirs} = \@subdirs;
512 0           $dir_state->{files} = \@files;
513             } # read_dir
514              
515             =head2 read_out_dir
516              
517             Read the $dir_state->{dir} directory in the output tree.
518             Sets $dir_state->{index_files} with the index*.html files.
519              
520             =cut
521             sub read_out_dir {
522 0     0 1   my $self = shift;
523 0           my $dir_state = shift;
524              
525 0           my @index_files = ();
526 0 0         if (-d $dir_state->{abs_out_dir})
527             {
528 0           my $dh;
529 0 0         opendir($dh, $dir_state->{abs_out_dir}) or die "Can't opendir $dir_state->{abs_out_dir}: $!";
530 0           while (my $fn = readdir($dh))
531             {
532 0           my $abs_fn = File::Spec->catfile($dir_state->{abs_out_dir}, $fn);
533 0 0 0       if ($fn =~ /^\./ or $fn eq $self->{thumbdir})
    0          
534             {
535             # skip
536             }
537             # remember the index files
538             elsif ($fn =~ /index.*\.html$/)
539             {
540 0           push @index_files, $fn;
541             }
542             }
543 0           closedir($dh);
544             }
545              
546 0           $dir_state->{index_files} = \@index_files;
547             } # read_out_dir
548              
549             =head2 filter_images
550              
551             Sets $dir_state->{files} to contain only image files that
552             we are interested in.
553              
554             =cut
555             sub filter_images {
556 0     0 1   my $self = shift;
557 0           my $dir_state = shift;
558              
559 0 0 0       if ($self->{image_match}
560 0           and @{$dir_state->{files}})
561             {
562 0           my $img_match = $self->{image_match};
563             my @images = grep {
564 0           /$img_match/
565 0           } @{$dir_state->{files}};
  0            
566 0           $dir_state->{files} = \@images;
567             }
568             } # filter_images
569              
570             =head2 sort_images
571              
572             Sorts the $dir_state->{files} array.
573              
574             =cut
575             sub sort_images {
576 0     0 1   my $self = shift;
577 0           my $dir_state = shift;
578              
579 0 0         if (@{$dir_state->{files}})
  0            
580             {
581 0           my @images = sort @{$dir_state->{files}};
  0            
582 0           $dir_state->{files} = \@images;
583             }
584             } # sort_images
585              
586             =head2 filter_dirs
587              
588             Sets $dir_state->{subdirs} to contain only directories that
589             we are interested in.
590              
591             =cut
592             sub filter_dirs {
593 0     0 1   my $self = shift;
594 0           my $dir_state = shift;
595              
596 0 0 0       if ($self->{dir_match}
597 0           and @{$dir_state->{subdirs}})
598             {
599 0           my $dir_match = $self->{dir_match};
600             my @dirs = grep {
601 0           /$dir_match/
602 0           } @{$dir_state->{subdirs}};
  0            
603 0           $dir_state->{subdirs} = \@dirs;
604             }
605             } # filter_dirs
606              
607             =head2 sort_dirs
608              
609             Sorts the $dir_state->{subdirs} array.
610              
611             =cut
612             sub sort_dirs {
613 0     0 1   my $self = shift;
614 0           my $dir_state = shift;
615              
616 0 0         if (@{$dir_state->{subdirs}})
  0            
617             {
618 0           my @dirs = sort @{$dir_state->{subdirs}};
  0            
619 0           $dir_state->{subdirs} = \@dirs;
620             }
621             } # sort_dirs
622              
623             =head2 make_index_page
624              
625             Make the index page(s) for this directory.
626              
627             =cut
628             sub make_index_page {
629 0     0 1   my $self = shift;
630 0           my $dir_state = shift;
631              
632             # determine the number of pages
633             # To make things easier, always put the subdirs on each index page
634 0           my $num_files = @{$dir_state->{files}};
  0            
635 0           my $pages = ceil($num_files / $self->{per_page});
636             # if there are only subdirs make sure you still make an index
637 0 0 0       if ($pages == 0 and @{$dir_state->{subdirs}})
  0            
638             {
639 0           $pages = 1;
640             }
641 0           $dir_state->{pages} = $pages;
642              
643             # make the output dir if it doesn't exist
644 0 0         if (!-d $dir_state->{abs_out_dir})
645             {
646 0           mkdir $dir_state->{abs_out_dir};
647             }
648              
649             # if we have any new images in this directory, we need to re-make the index
650             # files because we don't know which index file it will appear in,
651             # and we need to re-make the other HTML files because
652             # we need to re-generate the prev/next links
653 0           $dir_state->{redo_html} = $self->index_needs_rebuilding($dir_state);
654              
655             # if forcing HTML, delete the old index pages
656             # just in case we are going to have fewer pages
657             # this time around
658 0 0 0       if ($self->{force_html} or $dir_state->{redo_html})
659             {
660 0           foreach my $if (@{$dir_state->{index_files}})
  0            
661             {
662 0           my $ff = File::Spec->catfile($dir_state->{abs_out_dir}, $if);
663 0           unlink $ff;
664             }
665             }
666              
667 0 0         if ($self->{verbose})
668             {
669             # if the first index is gone, we're rebuilding all of them
670 0           my $first_index
671             = $self->get_index_pagename(dir_state=>$dir_state,
672             page=>1, get_filename=>1);
673 0 0         if (!-f $first_index)
674             {
675 0           print "making $pages indexes\n";
676             }
677             }
678              
679             # for each page
680 0           for (my $page = 1; $page <= $pages; $page++)
681             {
682             # calculate the filename
683 0           my $ifile = $self->get_index_pagename(dir_state=>$dir_state,
684             page=>$page, get_filename=>1);
685 0 0         if (-f $ifile)
686             {
687 0           next;
688             }
689              
690             # figure which files are in this page
691             # Determine number of images to skip
692 0           my @images = ();
693 0 0         if (@{$dir_state->{files}})
  0            
694             {
695 0           my $skip = $self->{per_page} * ($page-1);
696             # index of last entry to include
697 0           my $last = $skip + $self->{per_page};
698 0 0         $last = $num_files if ($last > $num_files);
699 0           $last--; # need the index, not the count
700 0           @images = @{$dir_state->{files}}[$skip .. $last];
  0            
701             }
702              
703 0           my @content = ();
704 0           push @content, $self->start_index_page($dir_state, $page);
705             # add the subdirs
706 0           push @content, $self->make_index_subdirs($dir_state, $page);
707             # add the images
708 0           push @content, $self->make_image_index(dir_state=>$dir_state,
709             page=>$page, images=>\@images);
710 0           push @content, $self->end_index_page($dir_state, $page);
711 0           my $content = join('', @content);
712              
713             # make the head stuff
714 0           my $title = $self->make_index_title($dir_state, $page);
715 0           my $style = $self->make_index_style($dir_state, $page);
716              
717             # put the page content in the template
718 0           my $out = $self->get_template($self->{page_template});
719             # save the content of the template in case we read it
720             # from a file
721 0           $self->{page_template} = $out;
722 0           $out =~ s//$title/;
723 0           $out =~ s//$style/;
724 0           $out =~ s//$content/;
725              
726             # write the page to the file
727 0           my $fh = undef;
728 0 0         open($fh, ">", $ifile) or die "Could not open $ifile for writing: $!";
729 0           print $fh $out;
730 0           close($fh);
731             } # for each page
732             } # make_index_page
733              
734             =head2 clean_thumb_dir
735              
736             Clean unused thumbnails and image-pages from
737             the thumbnail directory of this directory
738              
739             =cut
740             sub clean_thumb_dir {
741 0     0 1   my $self = shift;
742 0           my $dir_state = shift;
743              
744 0           my $dir = File::Spec->catdir($dir_state->{abs_out_dir}, $self->{thumbdir});
745 0           my @pics = @{$dir_state->{files}};
  0            
746 0           $self->debug(2, "cleaning dir: $dir");
747              
748 0 0         return unless -d $dir;
749              
750             # store the pics as a hash to make checking easier
751 0           my %pics_hash = ();
752 0           foreach my $pic ( @pics )
753             {
754 0           $pics_hash{$pic} = 1;
755             }
756              
757             # Read the thumbnail directory
758 0           my $dirh;
759 0           opendir($dirh,$dir);
760 0           my @files = grep(!/^\.{1,2}$/, readdir($dirh));
761 0           closedir($dirh);
762              
763             # Check each file to make sure it's a currently used thumbnail or image_page
764 0           foreach my $file ( @files )
765             {
766 0           my $remove = '';
767 0           my $name = $file;
768 0 0         if ($name =~ s/\.html$//)
    0          
769             {
770             # change the last underscore to a dot
771 0           $name =~ s/_([a-zA-Z0-9]+)$/.$1/;
772             $remove = "unused image page"
773 0 0         unless (exists $pics_hash{$name});
774             }
775             elsif ($name =~ /(.+)\.jpg$/i) {
776             # Thumbnail?
777 0           $name = $1;
778             # change the last underscore to a dot
779 0           $name =~ s/_([a-zA-Z0-9]+)$/.$1/;
780 0           $self->debug(2, "thumb: $name");
781             $remove = "unused thumbnail"
782 0 0         unless (exists $pics_hash{$name});
783             } else {
784 0           $remove = "unknown file";
785             }
786 0 0         if ($remove) {
787 0 0         print "Remove $remove: $file\n" if $self->{verbose};
788 0           my $fullname = File::Spec->catfile($dir, $file);
789 0 0         warn "Couldn't erase [$file]"
790             unless unlink $fullname;
791             }
792             } # for each file
793             } # clean_thumb_dir
794              
795             =head2 process_images
796              
797             Process the images from this directory.
798              
799             =cut
800             sub process_images {
801 0     0 1   my $self = shift;
802 0           my $dir_state = shift;
803              
804 0           $self->do_image_actions($dir_state, @{$dir_state->{files}});
  0            
805             } # process_images
806              
807             =head2 process_subdirs
808              
809             Process the sub-directories of this directory.
810              
811             =cut
812             sub process_subdirs {
813 0     0 1   my $self = shift;
814 0           my $dir_state = shift;
815              
816 0           my @image_dirs = @{$dir_state->{subdirs}};
  0            
817              
818 0           foreach my $subdir (@image_dirs)
819             {
820 0           my $dir = $subdir;
821 0 0         if ($dir_state->{dir})
822             {
823 0           $dir = File::Spec->catdir($dir_state->{dir}, $subdir);
824             }
825 0 0         print "=== $dir ===\n" if $self->{verbose};
826 0           $self->do_dir_actions($dir);
827             }
828             } # process_subdirs
829              
830             =head2 tidy_up
831              
832             Cleanup after processing this directory.
833              
834             =cut
835             sub tidy_up {
836 0     0 1   my $self = shift;
837 0           my $dir_state = shift;
838              
839             } # tidy_up
840              
841             =head1 Image Action Methods
842              
843             Methods implementing per-image actions.
844              
845             =head2 init_image_settings
846              
847             Initialize settings for the current image.
848              
849             =cut
850             sub init_image_settings {
851 0     0 1   my $self = shift;
852 0           my $dir_state = shift;
853 0           my $img_state = shift;
854              
855             $img_state->{abs_img} = File::Spec->catfile($dir_state->{abs_dir},
856 0           $img_state->{cur_img});
857 0           $img_state->{info} = $self->get_image_info($img_state->{abs_img});
858              
859             } # init_image_settings
860              
861             =head2 make_thumbnail
862              
863             Make a thumbnail of the current image.
864             Constant pixel count among generated images based on
865             http://www.chaosreigns.com/code/thumbnail/
866              
867             =cut
868             sub make_thumbnail {
869 0     0 1   my $self = shift;
870 0           my $dir_state = shift;
871 0           my $img_state = shift;
872              
873             my $thumb_file = $self->get_thumbnail_name(
874             dir_state=>$dir_state, image=>$img_state->{cur_img},
875 0           type=>'file');
876 0 0         if (!$self->need_to_generate_image($dir_state, $img_state,
877             check_image=>$thumb_file))
878             {
879 0           return;
880             }
881             # make the thumbnail dir if it doesn't exist
882 0 0         if (!-d $dir_state->{abs_thumbdir})
883             {
884 0           mkdir $dir_state->{abs_thumbdir};
885             }
886              
887 0           my $command = '';
888 0 0         if ($img_state->{cur_img} =~ /\.gif$/)
889             {
890             # in case this is an animated gif, get the first frame only
891             $command = sprintf('convert -geometry "%d@>" %s %s',
892             $self->{pixelcount},
893 0           $img_state->{abs_img}[0],
894             $thumb_file);
895             }
896             else
897             {
898             $command = sprintf('convert -geometry "%d@>" %s %s',
899             $self->{pixelcount},
900             $img_state->{abs_img},
901 0           $thumb_file);
902             }
903 0 0         system($command) == 0
904             or die "$command failed";
905            
906             } # make_thumbnail
907              
908             =head2 make_image_page
909              
910             Make HTML page for current image.
911              
912             =cut
913             sub make_image_page {
914 0     0 1   my $self = shift;
915 0           my $dir_state = shift;
916 0           my $img_state = shift;
917              
918 0           my $img_name = $img_state->{cur_img};
919             my $img_page_file = $self->get_image_pagename(dir_state=>$dir_state,
920             image=>$img_state->{cur_img},
921 0           type=>'file');
922 0 0 0       if (-f $img_page_file
      0        
923             and !$self->{force_html}
924             and !$dir_state->{redo_html})
925             {
926 0           return;
927             }
928             # make the thumbnail dir if it doesn't exist
929 0 0         if (!-d $dir_state->{abs_thumbdir})
930             {
931 0           mkdir $dir_state->{abs_thumbdir};
932             }
933 0           my @content = ();
934 0           push @content, $self->start_image_page($dir_state, $img_state);
935             # add the image itself
936 0           push @content, $self->make_image_content($dir_state, $img_state);
937 0           push @content, $self->end_image_page($dir_state, $img_state);
938 0           my $content = join('', @content);
939              
940             # make the head stuff
941 0           my $title = $self->make_image_title($dir_state, $img_state);
942 0           my $style = $self->make_image_style($dir_state, $img_state);
943              
944             # put the page content in the template
945 0           my $out = $self->get_template($self->{page_template});
946             # save the content of the template in case we read it
947             # from a file
948 0           $self->{page_template} = $out;
949 0           $out =~ s//$title/;
950 0           $out =~ s//$style/;
951 0           $out =~ s//$content/;
952              
953             # write the page to the file
954 0           my $fh = undef;
955 0 0         open($fh, ">", $img_page_file) or die "Could not open $img_page_file for writing: $!";
956 0           print $fh $out;
957 0           close($fh);
958             } # make_image_page
959              
960             =head2 image_tidy_up
961              
962             Clean up after the current image.
963              
964             =cut
965             sub image_tidy_up {
966 0     0 1   my $self = shift;
967 0           my $dir_state = shift;
968 0           my $img_state = shift;
969              
970             } # image_tidy_up
971              
972             =head1 Helper Methods
973              
974             Methods which can be called from within other methods.
975              
976             =head2 start_index_page
977              
978             push @content, $self->start_index_page($dir_state, $page);
979              
980             Create the start-of-page for an index page.
981             This contains page content, not full etc (that's expected
982             to be in the full-page template).
983             It contains the header, link to parent dirs and links to
984             previous and next index-pages, and the album caption.
985              
986             =cut
987             sub start_index_page {
988 0     0 1   my $self = shift;
989 0           my $dir_state = shift;
990 0           my $page = shift;
991              
992 0           my @out = ();
993 0           push @out, "
\n";
994              
995             # Path array contains basenames from the top dir down to the current dir.
996 0           my @path = split(/[\/\\]/, $dir_state->{dir});
997              
998             # Note that what we want is the top_out_base and not the top_base
999             # because if they are not the same (because top_out_dir was set)
1000             # the salient info is the output directory and not the source directory.
1001 0           unshift @path, $self->{top_out_base};
1002              
1003             # we want to create relative links to all the dirs
1004             # above the current one, so work backwards
1005 0           my %uplinks = ();
1006 0           my $uplink = '';
1007 0           foreach my $dn (reverse @path)
1008             {
1009 0           $uplinks{$dn} = $uplink;
1010 0 0 0       if (!$uplink and $page > 1)
1011             {
1012 0           $uplinks{$dn} = "index.html";
1013             }
1014             else
1015             {
1016 0           $uplink .= '../';
1017             }
1018             }
1019 0           my @header = ();
1020 0           foreach my $dn (@path)
1021             {
1022 0           my $pretty = $dn;
1023 0           $pretty =~ s/_/ /g;
1024 0 0         if ($uplinks{$dn})
1025             {
1026 0           push @header, "$pretty";
1027             }
1028             else
1029             {
1030 0           push @header, $pretty;
1031             }
1032             }
1033 0           push @out, '

';

1034 0           push @out, join(' :: ', @header);
1035 0           push @out, "\n";
1036              
1037             # now for the prev, next links
1038 0           push @out, $self->make_index_prev_next($dir_state, $page);
1039              
1040             # and now for the album caption
1041 0 0         if (exists $dir_state->{captions})
1042             {
1043 0           my $index_caption = 'index.html';
1044 0 0 0       if (exists $dir_state->{captions}->{$index_caption}
1045             and defined $dir_state->{captions}->{$index_caption})
1046             {
1047 0           push @out, '
';
1048 0           push @out, $dir_state->{captions}->{$index_caption};
1049 0           push @out, "\n";
1050             }
1051             }
1052              
1053 0           return join('', @out);
1054             } # start_index_page
1055              
1056             =head2 make_index_prev_next
1057              
1058             my $links = $self->start_index_page($dir_state, $page);
1059              
1060             Make the previous next other-index-pages links for the
1061             given index-page. Generally called for the top and bottom
1062             of the index page.
1063              
1064             =cut
1065             sub make_index_prev_next {
1066 0     0 1   my $self = shift;
1067 0           my $dir_state = shift;
1068 0           my $page = shift;
1069              
1070 0           my @out = ();
1071 0 0         if ($dir_state->{pages} > 1)
1072             {
1073 0           push @out, '

';

1074             # prev
1075 0           my $label = '< - prev';
1076 0 0         if ($page > 1)
1077             {
1078 0           my $iurl = $self->get_index_pagename(dir_state=>$dir_state,
1079             page=>$page - 1, get_filename=>0);
1080 0           push @out, "$label ";
1081             }
1082              
1083             # pages, but only if more than two
1084 0 0         if ($dir_state->{pages} > 2)
1085             {
1086 0           for (my $i = 1; $i <= $dir_state->{pages}; $i++)
1087             {
1088 0 0         if ($page == $i)
1089             {
1090 0           push @out, " [$i] ";
1091             }
1092             else
1093             {
1094 0           my $iurl = $self->get_index_pagename(dir_state=>$dir_state,
1095             page=>$i, get_filename=>0);
1096 0           push @out, " $i ";
1097             }
1098             }
1099             }
1100 0           $label = 'next ->';
1101 0 0         if (($page+1) <= $dir_state->{pages})
1102             {
1103 0           my $iurl = $self->get_index_pagename(dir_state=>$dir_state,
1104             page=>$page + 1, get_filename=>0);
1105 0           push @out, " $label";
1106             }
1107 0           push @out, "

\n";
1108             }
1109              
1110 0           return join('', @out);
1111             } # make_index_prev_next
1112              
1113             =head2 end_index_page
1114              
1115             push @content, $self->end_index_page($dir_state, $page);
1116              
1117             Create the end-of-page for an index page.
1118             This contains page content, not full etc (that's expected
1119             to be in the full-page template).
1120              
1121             =cut
1122             sub end_index_page {
1123 0     0 1   my $self = shift;
1124 0           my $dir_state = shift;
1125 0           my $page = shift;
1126              
1127 0           my @out = ();
1128 0           push @out, "\n
\n";
1129 0           push @out, $self->make_index_prev_next($dir_state, $page);
1130 0           push @out, "\n";
1131 0           return join('', @out);
1132             } # end_index_page
1133              
1134             =head2 make_index_subdirs
1135              
1136             push @content, $self->make_index_subdirs($dir_state, $page);
1137              
1138             Create the subdirs section; this contains links to subdirs.
1139              
1140             =cut
1141             sub make_index_subdirs {
1142 0     0 1   my $self = shift;
1143 0           my $dir_state = shift;
1144 0           my $page = shift;
1145              
1146 0           my @out = ();
1147              
1148 0 0         if (@{$dir_state->{subdirs}})
  0            
1149             {
1150 0           push @out, "\n
\n";
1151 0           push @out, "
\n";
1152             # subdirs
1153 0           foreach my $subdir (@{$dir_state->{subdirs}})
  0            
1154             {
1155 0           push @out, <
1156            
1157             $subdir
1158            
1159             EOT
1160             }
1161 0           push @out, "\n";
1162             }
1163 0           return join('', @out);
1164             } # make_index_subdirs
1165              
1166             =head2 make_image_index
1167              
1168             push @content, $self->make_image_index(dir_state=>$dir_state,
1169             page=>$page, images=>\@images);
1170              
1171             Create the images section; this contains links to image-pages, with thumbnails.
1172              
1173             =cut
1174             sub make_image_index {
1175 0     0 1   my $self = shift;
1176 0           my %args = (
1177             @_
1178             );
1179 0           my $dir_state = $args{dir_state};
1180              
1181 0           my @out = ();
1182              
1183 0 0         if (@{$args{images}})
  0            
1184             {
1185 0           push @out, "\n
\n";
1186 0           push @out, "
\n";
1187             # subdirs
1188 0           foreach my $image (@{$args{images}})
  0            
1189             {
1190 0           my $image_link = $self->get_image_pagename(dir_state=>$dir_state,
1191             image=>$image, type=>'parent');
1192 0           my $thumbnail_link = $self->get_thumbnail_name(
1193             dir_state=>$dir_state,
1194             image=>$image, type=>'parent');
1195 0           my $image_name = $self->get_image_pagename(dir_state=>$dir_state,
1196             image=>$image, type=>'pretty');
1197 0           push @out, <
1198            
1199            
1200             $image
1201             $image_name
1202            
1203            
1204             EOT
1205             }
1206 0           push @out, "\n";
1207             }
1208 0           return join('', @out);
1209             } # make_image_index
1210              
1211             =head2 make_index_title
1212              
1213             Make the title for the index page.
1214             This is expected to go inside a <!--kg_title-->
1215             in the page template.
1216              
1217             =cut
1218             sub make_index_title {
1219 0     0 1   my $self = shift;
1220 0           my $dir_state = shift;
1221 0           my $page = shift;
1222              
1223 0           my @out = ();
1224             # title
1225 0           push @out, $dir_state->{dirbase};
1226 0 0         push @out, " ($page)" if $page > 1;
1227 0           return join('', @out);
1228             } # make_index_title
1229              
1230             =head2 make_index_style
1231              
1232             Make the style tags for the index page. This will be put in the
1233             part of the template.
1234              
1235             =cut
1236             sub make_index_style {
1237 0     0 1   my $self = shift;
1238 0           my $dir_state = shift;
1239 0           my $page = shift;
1240              
1241 0           my @out = ();
1242             # style
1243 0           my $thumb_area_width = $self->{thumb_width} * 1.5;
1244             # 1.5 times the thumbnail, plus a fudge-factor for the words underneath
1245 0           my $thumb_area_height = ($self->{thumb_height} * 1.5) + 20;
1246 0           push @out, <
1247            
1273             EOT
1274 0           return join('', @out);
1275             } # make_index_style
1276              
1277             =head2 get_index_pagename
1278              
1279             my $name = self->get_index_pagename(
1280             dir_state=>$dir_state,
1281             page=>$page,
1282             get_filename=>0);
1283              
1284             Get the name of the given index page; either the file name
1285             or the relative URL.
1286              
1287             =cut
1288             sub get_index_pagename {
1289 0     0 1   my $self = shift;
1290 0           my %args = (
1291             get_filename=>0,
1292             @_
1293             );
1294 0           my $dir_state = $args{dir_state};
1295 0           my $page = $args{page};
1296              
1297 0           my $pagename;
1298 0 0         if ($page == 1)
    0          
1299             {
1300 0           $pagename = 'index.html';
1301             }
1302             elsif ($dir_state->{pages} > 9)
1303             {
1304 0           $pagename = sprintf("index%02d.html", $page);
1305             }
1306             else
1307             {
1308 0           $pagename = "index${page}.html";
1309             }
1310            
1311 0 0         if ($args{get_filename})
1312             {
1313 0           return File::Spec->catfile($dir_state->{abs_out_dir}, $pagename);
1314             }
1315             else # get URL
1316             {
1317 0           return $pagename;
1318             }
1319             } # get_index_pagename
1320              
1321             =head2 get_image_pagename
1322              
1323             my $name = self->get_image_pagename(
1324             dir_state=>$dir_state,
1325             image=>$image,
1326             type=>'file');
1327              
1328             Get the name of the image page; either the file name
1329             or the relative URL from above, or the relative URL
1330             from the sibling, or a 'pretty' name suitable for a title.
1331              
1332             The 'type' can be 'file', 'parent', 'sibling' or 'pretty'.
1333              
1334             =cut
1335             sub get_image_pagename {
1336 0     0 1   my $self = shift;
1337 0           my %args = (
1338             type=>'parent',
1339             @_
1340             );
1341 0           my $dir_state = $args{dir_state};
1342 0           my $image = $args{image};
1343            
1344 0           my $thumbdir = $self->{thumbdir};
1345 0           my $img_page = $image;
1346             # change the last dot to underscore
1347 0           $img_page =~ s/\.(\w+)$/_$1/;
1348 0           $img_page .= ".html";
1349 0 0         if ($args{type} eq 'file')
    0          
    0          
    0          
1350             {
1351 0           return File::Spec->catfile($dir_state->{abs_out_dir}, $thumbdir, $img_page);
1352             }
1353             elsif ($args{type} eq 'parent')
1354             {
1355 0           return "${thumbdir}/${img_page}";
1356             }
1357             elsif ($args{type} eq 'sibling')
1358             {
1359 0           return ${img_page};
1360             }
1361             elsif ($args{type} eq 'pretty')
1362             {
1363 0           my $pretty = ${image};
1364 0           $pretty =~ s/\.(\w+)$//;
1365 0           $pretty =~ s/_/ /g;
1366 0           return $pretty;
1367             }
1368 0           return '';
1369             } # get_image_pagename
1370              
1371             =head2 get_thumbnail_name
1372              
1373             my $name = self->get_thumbnail_name(
1374             dir_state=>$dir_state,
1375             image=>$image,
1376             type=>'file');
1377              
1378             Get the name of the image thumbnail file; either the file name
1379             or the relative URL from above, or the relative URL
1380             from the sibling.
1381              
1382             The 'type' can be 'file', 'parent', 'sibling'.
1383              
1384             =cut
1385             sub get_thumbnail_name {
1386 0     0 1   my $self = shift;
1387 0           my %args = (
1388             type=>'parent',
1389             @_
1390             );
1391 0           my $dir_state = $args{dir_state};
1392 0           my $image = $args{image};
1393            
1394 0           my $thumbdir = $self->{thumbdir};
1395 0           my $thumb = $image;
1396             # change the last dot to underscore
1397 0           $thumb =~ s/\.([\w]+)$/_$1/;
1398 0           $thumb .= ".jpg";
1399 0 0         if ($args{type} eq 'file')
    0          
    0          
1400             {
1401 0           return File::Spec->catfile($dir_state->{abs_out_dir}, $thumbdir, $thumb);
1402             }
1403             elsif ($args{type} eq 'parent')
1404             {
1405 0           return "${thumbdir}/${thumb}";
1406             }
1407             elsif ($args{type} eq 'sibling')
1408             {
1409 0           return ${thumb};
1410             }
1411 0           return '';
1412             } # get_thumbnail_name
1413              
1414             =head2 get_caption
1415              
1416             my $name = self->get_caption(
1417             dir_state=>$dir_state,
1418             img_state->$img_state,
1419             image=>$image)
1420              
1421             Get the caption for this image.
1422             This also gets the meta-data if any is required.
1423              
1424             =cut
1425             sub get_caption {
1426 0     0 1   my $self = shift;
1427 0           my %args = (
1428             @_
1429             );
1430 0           my $dir_state = $args{dir_state};
1431 0           my $img_state = $args{img_state};
1432 0           my $image = $args{image};
1433            
1434 0           my @out = ();
1435 0 0         if (exists $dir_state->{captions})
1436             {
1437 0 0 0       if (exists $dir_state->{captions}->{$image}
1438             and defined $dir_state->{captions}->{$image})
1439             {
1440 0           push @out, $dir_state->{captions}->{$image};
1441             }
1442             }
1443 0 0 0       if ($img_state and defined $self->{meta} and @{$self->{meta}})
  0   0        
1444             {
1445             # only add the meta data if it's there
1446 0           foreach my $fieldspec (@{$self->{meta}})
  0            
1447             {
1448 0           $fieldspec =~ /%([\w\s]+)%/;
1449 0           my $field = $1;
1450 0 0 0       if (exists $img_state->{info}->{$field}
      0        
1451             and defined $img_state->{info}->{$field}
1452             and $img_state->{info}->{$field})
1453             {
1454 0           my $val = $fieldspec;
1455 0           my $fieldval = $img_state->{info}->{$field};
1456             # make the fieldval HTML-safe
1457 0           $fieldval =~ s/&/&/g;
1458 0           $fieldval =~ s/
1459 0           $fieldval =~ s/>/>/g;
1460 0           $val =~ s/%${field}%/$fieldval/g;
1461 0           push @out, $val;
1462             }
1463             }
1464             }
1465 0           return join("\n", @out);
1466             } # get_caption
1467              
1468             =head2 get_template
1469              
1470             my $templ = $self->get_template($template);
1471              
1472             Get the given template (read if it's from a file)
1473              
1474             =cut
1475             sub get_template {
1476 0     0 1   my $self = shift;
1477 0           my $template = shift;
1478              
1479 0 0 0       if ($template !~ /\n/
1480             && -r $template)
1481             {
1482 0           local $/ = undef;
1483 0           my $fh;
1484 0 0         open($fh, $template)
1485             or die "Could not open ", $template;
1486 0           $template = <$fh>;
1487 0           close($fh);
1488             }
1489 0           return $template;
1490             } # get_template
1491              
1492             =head2 start_image_page
1493              
1494             push @content, $self->start_image_page($dir_state, $img_state);
1495              
1496             Create the start-of-page for an image page.
1497             This contains page content, not full etc (that's expected
1498             to be in the full-page template).
1499             It contains the header, link to parent dirs and links to
1500             previous and next image-pages.
1501              
1502             =cut
1503             sub start_image_page {
1504 0     0 1   my $self = shift;
1505 0           my $dir_state = shift;
1506 0           my $img_state = shift;
1507              
1508 0           my @out = ();
1509 0           push @out, "
\n";
1510              
1511             # Path array contains basenames from the top dir
1512             # down to the current dir.
1513 0           my @path = split(/[\/\\]/, $dir_state->{dir});
1514 0           unshift @path, $self->{top_out_base};
1515             # we want to create relative links to all the dirs
1516             # including the current one, so work backwards
1517 0           my %uplinks = ();
1518 0           my $uplink = '';
1519 0           foreach my $dn (reverse @path)
1520             {
1521 0           $uplink .= '../';
1522 0           $uplinks{$dn} = $uplink;
1523             }
1524 0           my @breadcrumb = ();
1525 0           foreach my $dn (@path)
1526             {
1527 0 0         if ($uplinks{$dn})
1528             {
1529 0           push @breadcrumb, "$dn";
1530             }
1531             else
1532             {
1533 0           push @breadcrumb, $dn;
1534             }
1535             }
1536 0           push @out, '

';

1537 0           push @out, $img_state->{cur_img};
1538 0           push @out, "\n";
1539 0           push @out, '
1540 0           push @out, join(' > ', @breadcrumb);
1541 0           push @out, "

\n";
1542              
1543             # now for the prev, next links
1544 0           push @out, $self->make_image_prev_next(dir_state=>$dir_state,
1545             img_state=>$img_state);
1546              
1547 0           return join('', @out);
1548             } # start_image_page
1549              
1550             =head2 end_image_page
1551              
1552             push @content, $self->end_image_page($dir_state, $img_state);
1553              
1554             Create the end-of-page for an image page.
1555             This contains page content, not full etc (that's expected
1556             to be in the full-page template).
1557              
1558             =cut
1559             sub end_image_page {
1560 0     0 1   my $self = shift;
1561 0           my $dir_state = shift;
1562 0           my $img_state = shift;
1563              
1564 0           my @out = ();
1565              
1566             # now for the prev, next links
1567 0           push @out, $self->make_image_prev_next(dir_state=>$dir_state,
1568             img_state=>$img_state,
1569             use_thumb=>1);
1570 0           push @out, "\n\n";
1571              
1572 0           return join('', @out);
1573             } # end_image_page
1574              
1575             =head2 make_image_prev_next
1576              
1577             my $links = $self->make_image_prev_next(
1578             dir_state=>$dir_state,
1579             img_state=>$img_state);
1580              
1581             Make the previous next other-image-pages links for the
1582             given image-page. Generally called for the top and bottom
1583             of the image page.
1584              
1585             =cut
1586             sub make_image_prev_next {
1587 0     0 1   my $self = shift;
1588 0           my %args = (
1589             use_thumb=>0,
1590             @_
1591             );
1592 0           my $dir_state = $args{dir_state};
1593 0           my $img_state = $args{img_state};
1594              
1595 0           my $img_num = $img_state->{num};
1596 0           my @out = ();
1597 0 0         if ($dir_state->{files} > 1)
1598             {
1599 0           push @out, '
';
1600             # prev
1601 0           push @out, "";
1602 0           my $label = '< - prev';
1603 0           my $iurl;
1604             my $turl;
1605 0 0         if ($img_num > 0)
1606             {
1607             $iurl = $self->get_image_pagename(dir_state=>$dir_state,
1608 0           image=>$img_state->{images}->[$img_num - 1],
1609             type=>'sibling');
1610             $turl = $self->get_thumbnail_name(dir_state=>$dir_state,
1611 0           image=>$img_state->{images}->[$img_num - 1],
1612             type=>'sibling');
1613             }
1614             else
1615             {
1616             # loop to the last image
1617             $iurl = $self->get_image_pagename(dir_state=>$dir_state,
1618 0           image=>$img_state->{images}->[$#{$img_state->{images}}],
  0            
1619             type=>'sibling');
1620             $turl = $self->get_thumbnail_name(dir_state=>$dir_state,
1621 0           image=>$img_state->{images}->[$#{$img_state->{images}}],
  0            
1622             type=>'sibling');
1623             }
1624 0           push @out, "$label ";
1625 0 0         if ($args{use_thumb})
1626             {
1627 0           push @out, "\"$label\"/ ";
1628             }
1629 0           push @out, "";
1630              
1631 0           push @out, "";
1632 0           $label = 'next ->';
1633 0 0         if (($img_num+1) < @{$img_state->{images}})
  0            
1634             {
1635             $iurl = $self->get_image_pagename(dir_state=>$dir_state,
1636 0           image=>$img_state->{images}->[$img_num + 1],
1637             type=>'sibling');
1638             $turl = $self->get_thumbnail_name(dir_state=>$dir_state,
1639 0           image=>$img_state->{images}->[$img_num + 1],
1640             type=>'sibling');
1641             }
1642             else
1643             {
1644             # loop to the first image
1645             $iurl = $self->get_image_pagename(dir_state=>$dir_state,
1646 0           image=>$img_state->{images}->[0],
1647             type=>'sibling');
1648             $turl = $self->get_thumbnail_name(dir_state=>$dir_state,
1649 0           image=>$img_state->{images}->[0],
1650             type=>'sibling');
1651             }
1652 0 0         if ($args{use_thumb})
1653             {
1654 0           push @out, "\"$label\"/ ";
1655             }
1656 0           push @out, " $label";
1657 0           push @out, "";
1658 0           push @out, "\n";
1659             }
1660              
1661 0           return join('', @out);
1662             } # make_image_prev_next
1663              
1664             =head2 make_image_content
1665              
1666             Make the content of the image page, the image itself.
1667              
1668             =cut
1669             sub make_image_content {
1670 0     0 1   my $self = shift;
1671 0           my $dir_state = shift;
1672 0           my $img_state = shift;
1673              
1674 0           my $img_name = $img_state->{cur_img};
1675 0           my $caption = $self->get_caption(dir_state=>$dir_state,
1676             img_state=>$img_state,
1677             image=>$img_name);
1678 0           my $img_url = "../$img_name";
1679 0 0         if ($self->{top_dir} ne $self->{top_out_dir})
1680             {
1681 0           $img_url = $dir_state->{dir_url} . '/' . $img_name;
1682             }
1683 0           my @out = ();
1684 0           push @out, "
\n";
1685 0           my $width = $img_state->{info}->{ImageWidth};
1686 0           my $height = $img_state->{info}->{ImageHeight};
1687 0           push @out, "\"$img_name\"\n";
1688 0           push @out, "

$caption

\n";
1689 0           push @out, "\n";
1690 0           return join('', @out);
1691             } # make_image_content
1692              
1693             =head2 make_image_title
1694              
1695             Make the title for the image page.
1696             This is expected to go inside a <!--kg_title-->
1697             in the page template.
1698              
1699             =cut
1700             sub make_image_title {
1701 0     0 1   my $self = shift;
1702 0           my $dir_state = shift;
1703 0           my $img_state = shift;
1704              
1705 0           my @out = ();
1706             # title
1707 0           push @out, $img_state->{cur_img};
1708 0           return join('', @out);
1709             } # make_image_title
1710              
1711             =head2 make_image_style
1712              
1713             Make the style tags for the image page. This will be put in the
1714             part of the template.
1715              
1716             =cut
1717             sub make_image_style {
1718 0     0 1   my $self = shift;
1719 0           my $dir_state = shift;
1720 0           my $img_state = shift;
1721              
1722 0           my @out = ();
1723             # style
1724 0           push @out, <
1725            
1740             EOT
1741 0           return join('', @out);
1742             } # make_image_style
1743              
1744             =head2 need_to_generate_image
1745              
1746             Check if a thumbnail needs to be made (or rebuilt).
1747              
1748             =cut
1749             sub need_to_generate_image {
1750 0     0 1   my $self = shift;
1751 0           my $dir_state = shift;
1752 0           my $img_state = shift;
1753 0           my %args = @_;
1754              
1755 0 0 0       if (!-f $args{check_image} or $self->{force_images})
1756             {
1757 0           return 1;
1758             }
1759 0           return 0;
1760             } # need_to_generate_image
1761              
1762             =head2 index_needs_rebuilding
1763              
1764             Check to see if there are any new (or deleted) images or directories
1765             in this directory.
1766              
1767             =cut
1768             sub index_needs_rebuilding {
1769 0     0 1   my $self = shift;
1770 0           my $dir_state = shift;
1771              
1772             # ------- Subdirs -------------
1773             # Need to check if any of the subdirs are new or deleted
1774            
1775 0           my @subdirs = @{$dir_state->{subdirs}};
  0            
1776 0           my @dest_subdirs = ();
1777 0           my $dirh;
1778 0           opendir($dirh,$dir_state->{abs_out_dir});
1779 0           while (my $fn = readdir($dirh))
1780             {
1781 0           my $abs_fn = File::Spec->catfile($dir_state->{abs_out_dir}, $fn);
1782 0 0 0       if ($fn =~ /^\./ or $fn eq $self->{thumbdir})
    0          
1783             {
1784             # skip
1785             }
1786             elsif (-d $abs_fn)
1787             {
1788 0           push @dest_subdirs, $fn;
1789             }
1790             }
1791 0           closedir($dirh);
1792              
1793 0           my %destdir_has_src = ();
1794 0           my %srcdir_has_dest = ();
1795             # initialise to false
1796 0           foreach my $sd ( @subdirs )
1797             {
1798 0           $srcdir_has_dest{$sd} = 0;
1799             }
1800             # Are there dest-dirs without src-dirs?
1801 0           foreach my $dsd ( @dest_subdirs )
1802             {
1803 0 0         if (exists $srcdir_has_dest{$dsd})
1804             {
1805 0           $srcdir_has_dest{$dsd} = 1;
1806 0           $destdir_has_src{$dsd} = 1;
1807             }
1808             else
1809             {
1810 0           $self->debug(1, "GONE DIR: $dsd");
1811 0           $destdir_has_src{$dsd} = 0;
1812 0           return 1;
1813             }
1814             }
1815             # Are there src-dirs without dest-dirs?
1816 0           while (my ($key, $dir_exists) = each(%srcdir_has_dest))
1817             {
1818 0 0         if (!$dir_exists)
1819             {
1820 0           $self->debug(1, "NEW DIR: $key");
1821 0           return 1;
1822             }
1823             }
1824              
1825             # --------- Thumbnail Directory ----------
1826 0           my $thumb_dir = File::Spec->catdir($dir_state->{abs_out_dir}, $self->{thumbdir});
1827 0           my @pics = @{$dir_state->{files}};
  0            
1828 0           $self->debug(2, "dir: $thumb_dir");
1829              
1830             # if the thumbnail directory doesn't exist, then either all images
1831             # are new, or we don't have any images in this directory
1832 0 0         if (!-d $thumb_dir)
1833             {
1834 0 0         return (@pics ? 1 : 0);
1835             }
1836              
1837             # Read the thumbnail directory
1838 0           opendir($dirh,$thumb_dir);
1839 0           my @files = grep(!/^\.{1,2}$/, readdir($dirh));
1840 0           closedir($dirh);
1841              
1842             # check whether a picture has a thumbnail, and a thumbnail has a picture
1843 0           my %pic_has_tn = ();
1844 0           my %tn_has_pic = ();
1845              
1846             # initialize to false
1847 0           foreach my $pic ( @pics )
1848             {
1849 0           $pic_has_tn{$pic} = 0;
1850             }
1851              
1852             # Check each file to make sure it's a currently used thumbnail or image_page
1853 0           foreach my $file ( @files )
1854             {
1855 0           my $name = $file;
1856 0 0         if ($name =~ s/\.html$//)
    0          
1857             {
1858             # change the last underscore to a dot
1859 0           $name =~ s/_([a-zA-Z0-9]+)$/.$1/;
1860 0 0         if (exists $pic_has_tn{$name})
1861             {
1862 0           $pic_has_tn{$name} = 1;
1863 0           $tn_has_pic{$name} = 1;
1864             }
1865             else
1866             {
1867 0           $tn_has_pic{$name} = 0;
1868 0           return 1;
1869             }
1870             }
1871             elsif ($name =~ /(.+)\.jpg$/i) {
1872             # Thumbnail?
1873 0           $name = $1;
1874             # change the last underscore to a dot
1875 0           $name =~ s/_([a-zA-Z0-9]+)$/.$1/;
1876 0           $self->debug(2, "thumb: $name");
1877 0 0         if (exists $pic_has_tn{$name})
1878             {
1879 0           $pic_has_tn{$name} = 1;
1880 0           $tn_has_pic{$name} = 1;
1881             }
1882             else
1883             {
1884 0           $tn_has_pic{$name} = 0;
1885 0           return 1;
1886             }
1887             }
1888             } # for each file
1889              
1890             # now check if there are pics without thumbnails
1891 0           while (my ($key, $tn_exists) = each(%pic_has_tn))
1892             {
1893 0 0         if (!$tn_exists)
1894             {
1895 0           return 1;
1896             }
1897             }
1898              
1899 0           return 0;
1900             } # index_needs_rebuilding
1901              
1902             =head2 get_image_info
1903              
1904             Get the image information for an image. Returns a hash of
1905             information.
1906              
1907             %info = $self->get_image_info($image_file);
1908              
1909             =cut
1910             sub get_image_info {
1911 0     0 1   my $self = shift;
1912 0           my $img_file = shift;
1913              
1914 0           my $info = Image::ExifTool::ImageInfo($img_file);
1915             # add the basename
1916 0           my ($basename, $path, $suffix) = fileparse($img_file, qr/\.[^.]*/);
1917 0           $info->{file_basename} = $basename;
1918 0           return $info;
1919             } # get_image_info
1920              
1921             =head2 debug
1922              
1923             $self->debug($level, $message);
1924              
1925             Print a debug message (for debugging).
1926             Checks $self->{'debug_level'} to see if the message should be printed or
1927             not.
1928              
1929             =cut
1930             sub debug {
1931 0     0 1   my $self = shift;
1932 0           my $level = shift;
1933 0           my $message = shift;
1934              
1935 0 0         if ($level <= $self->{'debug_level'})
1936             {
1937 0           my $oh = \*STDERR;
1938 0           print $oh $message, "\n";
1939             }
1940             } # debug
1941              
1942             =head1 Private Methods
1943              
1944             Methods which may or may not be here in future.
1945              
1946             =head2 _whowasi
1947              
1948             For debugging: say who called this
1949              
1950             =cut
1951 0     0     sub _whowasi { (caller(1))[3] . '()' }
1952              
1953             =head1 REQUIRES
1954              
1955             Test::More
1956              
1957             =head1 INSTALLATION
1958              
1959             To install this module, run the following commands:
1960              
1961             perl Build.PL
1962             ./Build
1963             ./Build test
1964             ./Build install
1965              
1966             Or, if you're on a platform (like DOS or Windows) that doesn't like the
1967             "./" notation, you can do this:
1968              
1969             perl Build.PL
1970             perl Build
1971             perl Build test
1972             perl Build install
1973              
1974             In order to install somewhere other than the default, such as
1975             in a directory under your home directory, like "/home/fred/perl"
1976             go
1977              
1978             perl Build.PL --install_base /home/fred/perl
1979              
1980             as the first step instead.
1981              
1982             This will install the files underneath /home/fred/perl.
1983              
1984             You will then need to make sure that you alter the PERL5LIB variable to
1985             find the modules, and the PATH variable to find the script.
1986              
1987             Therefore you will need to change:
1988             your path, to include /home/fred/perl/script (where the script will be)
1989              
1990             PATH=/home/fred/perl/script:${PATH}
1991              
1992             the PERL5LIB variable to add /home/fred/perl/lib
1993              
1994             PERL5LIB=/home/fred/perl/lib:${PERL5LIB}
1995              
1996              
1997             =head1 SEE ALSO
1998              
1999             perl(1).
2000              
2001             =head1 BUGS
2002              
2003             Please report any bugs or feature requests to the author.
2004              
2005             =head1 AUTHOR
2006              
2007             Kathryn Andersen (RUBYKAT)
2008             perlkat AT katspace dot com
2009             http://www.katspace.org/tools
2010              
2011             =head1 COPYRIGHT AND LICENCE
2012              
2013             Copyright (c) 2006 by Kathryn Andersen
2014              
2015             This program is free software; you can redistribute it and/or modify it
2016             under the same terms as Perl itself.
2017              
2018              
2019             =cut
2020              
2021             1; # End of HTML::KhatGallery::Core
2022             __END__