#!/usr/bin/perl

# Simple Photo Gallery 1.5
# 
# Authors:
#   Dan Fraser <dfraser@capybara.org> (primary)
#   Andrew Kilpatrick <andrew@andrewkilpatrick.org>
#
# This software is Public Domain.
# Use it however you wish.

# required modules/pragma
use CGI;
use CGI::Carp qw( fatalsToBrowser);
use URI::Escape;
use Image::Info qw( image_info );
use strict;

# configuration variables
#########################

my $page_header = ".header.html"; # page header html
my $page_footer = ".footer.html"; # page footer html
my $thumbs = ".thumbs"; # thumbnail directory name
my $lowres = ".lowres"; # low res directory name
my $descriptions = ".descriptions"; # descriptions file
my $startatlast = ".startatlast"; # begin thumbs with the last page
my $display_filename = 1; # display filename if no description?
my $page_size = 20; # number of thumbnails per page
my $number_columns = 5; # how many columns of thumbnails
my $custom_sort_enable = "YES"; # uses a custom sort algorithm
$ENV{PATH} .= ':/usr/bin';

############################
# end configuration section

my $version = "1.5";

# we set a cookie for each gallery to hold the last visited date 
# and the last page
# .startatlast overrides the last page cookie behaviour

my $query;

$query = new CGI;

# if d is empty, we're showing the gallery index
# otherwise, if id is empty, we're viewing thumbnails
# otherwise, we're viewing a single picture
if (!defined $query->param('d')) {
	show_gallery_index();
} elsif ($query->param('id')) {
  display_single_image($query);
} else {
  # viewing a page of thumbnails
  display_thumbnail_page($query);
}

exit(0);

sub display_thumbnail_page() {
  my $dir = $query->param('d');
  my $safe_dir = uri_escape($dir);
  cleanDir($dir);
  my @images = get_images();
  my $query = shift;
  my $number_of_pages = int(@images / $page_size)+1;
	if (@images % $page_size == 0) {
	  $number_of_pages--;
	}
  my $current_page_number = $query->param('pg');

  if(!defined($current_page_number)) {
    $current_page_number = 0;
  }
  

  print $query->header();
  display_header();

  # allow entry to the last page
  if($current_page_number eq "last") {
    $current_page_number = $number_of_pages;
  }

  # disallow silly values
  if ($current_page_number > $number_of_pages-1) {
    $current_page_number = $number_of_pages - 1;
  }
  if ($current_page_number < 0) {
    $current_page_number = 0;
  }
  my $next_page;
  my $previous_page;
  my $image_offset = $current_page_number * $page_size;

  # figure out the next and previous page numbers

  $next_page = ($current_page_number+1) % $number_of_pages;
  $previous_page = abs(($current_page_number-1) % $number_of_pages);

  # check if images need to be created
#  makeThumbnails($safe_dir);
  
  
  # print the image tags and links
  my $current_image = $image_offset;
  print "<div align=\"center\">";
  print "<table border=\"0\"><tr>\n";
  while ($current_image < $image_offset+$page_size and $current_image < @images) {
   makeThumbnail($dir, $images[$current_image]);
   print "<td><a href=\"index.cgi?d=$safe_dir&id=$images[$current_image]\"><img src=\"$safe_dir/$thumbs/$images[$current_image]\" border=\"0\"></a></td>\n";
    print "</tr><tr>\n" if ((($current_image-$image_offset+1) % $number_columns) == 0 and $current_image+1 < $image_offset+$page_size);
    $current_image++;
  }
  print "</tr></table>";

  # print the page navigator
  print "<br>";
  print "<table><tr><td>";
  if ($current_page_number == 0) {
    print "Previous ";
  } else {
    print "<a href=\"index.cgi?d=$safe_dir&pg=$previous_page\">Previous</a> ";
  }
  print "</td><td align=\"center\">";
  for (my $i = 0; $i < $number_of_pages; $i++) {
    if ($current_page_number == $i) {
      print "<b>$i</b> ";
    } else {
      print "<a href=\"index.cgi?d=$safe_dir&pg=$i\">$i</a> ";
    }
  }
  print "</td><td>";
  if ($current_page_number == $number_of_pages-1) {
    print "Next";
  } else {
    
    print "<a href=\"index.cgi?d=$safe_dir&pg=$next_page\">Next</a>";
  }
  print "</td></tr></table>";
  print "</div>";
  print "<p align=\"center\"><a href=\"index.cgi\">Back to Gallery Index</a></p>";
  display_footer();
}

sub display_single_image() {

  # get the current directory
  my $dir = $query->param('d');
  my $safe_dir = uri_escape($dir);

    
   
  cleanDir($dir);
  my @images = get_images();
  

  # load the description database
  my %desc;
  if ( -r "$dir/$descriptions" ) {
    open DESC, "$dir/$descriptions";
    my $line;
    while (defined ($line = <DESC>)) {
      chomp($line);
      my ($filename, $description);
      ($filename, $description) = split(/ /,$line,2);
      $desc{$filename} = $description;
    }
  }


  my $image = $query->param('id');
  my $thumbs_page = $query->param('pg');
  my $imgnum;
  for (my $i = 0; $i < @images; $i++) {
    if ($image eq $images[$i]) {
      $imgnum = $i;
      last;
    }
  }

  my $res = $query->param('res');
my $delay;
  if ($delay = $query->param('delay')) {
    print $query->header(-refresh=>"$delay; URL=index.cgi?d=$safe_dir&id=$images[$imgnum+1]&print=1&res=$res&delay=$delay");

  } else {
    print $query->header;
  }
  display_header();


  if (!defined($imgnum)) {
    print "<p>Image not found! Id is probably invalid.</p>";
    print "<a href=\"index.cgi?pg=$thumbs_page\">Back to thumbnails</a>";
    return;
  }
  
  $thumbs_page = int($imgnum / $page_size);  
  makeLowres($dir,$image);
  print "<div align=\"center\">";
  if ($res eq 'high') {

    print "<img src=\"$safe_dir/$image\" border=\"0\"><br />";
  } else {
    if ($imgnum+1 < @images) {
      print "<a href=\"index.cgi?d=$safe_dir&id=$images[$imgnum+1]\">";
    }
    print "<img src=\"$safe_dir/$lowres/$image\" border=\"0\"><br />";
    if ($imgnum+1 < @images) {
      print "</a>";
    }

  }

  # display the image controls
  ############################
  if (!$query->param('print')) {
    if ($res eq 'high') {
      print "<a href=\"index.cgi?d=$safe_dir&id=$image&pg=$thumbs_page\">Preview Size</a>";
    } else {
       print "<a href=\"index.cgi?d=$safe_dir&id=$image&res=high&pg=$thumbs_page\">Full Size</a>";
     }
    print " * <a href=\"index.cgi?d=$safe_dir&id=$image&res=$res&print=1\" target=\"_blank\">Printable</a>";
    print " * <a href=\"index.cgi?d=$safe_dir&id=$image&res=$res&print=1&delay=10\">Start Slideshow</a>";

    if ($desc{$images[$imgnum]}) {
      print "<p align=\"center\"><b>".$desc{$images[$imgnum]}."</b></p>";
    }
   
    # display the navigation
    ##########################
      print '<br /><br /><table width="576" cellpadding="0" cellspacing="0" border="0"><tr>';
      print "<td width=\"33%\" valign=\"top\">";
      if ($imgnum-1 >= 0) {
	  makeThumbnail($dir,$images[$imgnum-1]);
	  print "<a href=\"index.cgi?d=$safe_dir&id=$images[$imgnum-1]\"><img src=\"$safe_dir/$thumbs/$images[$imgnum-1]\"></a><br />Previous";
      }
      print "</td>";
      print "<td align=\"center\">&nbsp;Image #$imgnum of ". ( @images-1) ."&nbsp;<br />";
  }
    my $info = image_info("$dir/$images[$imgnum]");
  if (!defined $info->{error}) {
    print "<small>";
    print "Taken ".$info->{DateTime}."<br />";
    print "Exposure: ".$info->{ExposureTime};
    print " F".eval($info->{FNumber})."<br />";
    print "  Flash: ".$info->{Flash}."<br />";
    print " Focal Length: ".eval($info->{FocalLength})."mm<br />";
    print " ".$info->{Model};
    print "</small>";
   }  
  if (!$query->param('print')) {
      print "</td>";
      print "<td width=\"33%\" align=\"right\" valign=\"top\">";
      if ($imgnum+1 < @images) {
	  makeThumbnail($dir,$images[$imgnum+1]);
	  print "<a href=\"index.cgi?d=$safe_dir&id=$images[$imgnum+1]\"><img src=\"$safe_dir/$thumbs/$images[$imgnum+1]\"></a><br />Next" 
       }
    print "</td>";
    
    print "</tr></table>";


  

    print "<a href=\"index.cgi?d=$safe_dir&pg=$thumbs_page\">Back to thumbnails</a>";
  }

  print "</div>";
  display_footer();
}

sub display_header() {
  open HEADER, "$page_header" or die "couldn't open header html file: $!\n";
  my $line;
  while (defined ($line = <HEADER>)) {
    print $line;
  }
  close HEADER;
}

sub show_gallery_index() {
    my $oldCookie = $query->cookie("simple_photo_gallery");
    my ($lastvisit, $oldafter) = split(/ /,$oldCookie);
    if ($oldafter <= time) {
	my $newfor = 60*60*2; # 2 hours
	$lastvisit = $oldafter-$newfor;
	$oldafter = time + $newfor;
     }
    my $newCookie = $query->cookie(-name=>'simple_photo_gallery',
				   -value=>int($lastvisit)." ".int($oldafter));
    print $query->header(-cookie=>$newCookie);
    display_header();
    print CGI::h1("Available Galleries");
    my $dirs = find_dirs();
    print "<table>";
    my $dir;
    my @sortedDirs = sort {uc($a) cmp uc($b)} @$dirs;
    foreach $dir ( @sortedDirs ) {
		my $safe_dir = uri_escape($dir);
		opendir GALLERY, $dir;
		my @images = grep { /\.[jJ][pP][eE]?[gG]$/ } readdir(GALLERY);
		close GALLERY;
		my ($last_modified_time, $total_dir_size);
		my $image;
		my $new = 0;
		foreach $image (@images) {
		  if ( -r "$dir/$image" ) {
		    my ($size, $mtime);
		    ($size, $mtime) = (stat("$dir/$image"))[7,9];
		    if ($mtime > $last_modified_time) {
		      $last_modified_time = $mtime;
		    }
		    if ($last_modified_time > $lastvisit) { 
		     	$new++;
   	        }
		    $total_dir_size += $size;
		  }
		}
		if (@images > 0) {
		  if(-f "$safe_dir/$startatlast") {
		    print "<tr><td><a href=\"index.cgi?d=${safe_dir}&pg=last\">$dir</a></td>";
		  }
		  else {
		    print "<tr><td><a href=\"index.cgi?d=${safe_dir}\">$dir</a></td>";
		  }

		  print "<td>".localtime($last_modified_time)."</td>";
		  my $total_megs = sprintf("%2.1f",($total_dir_size / 1048576));
		  print "<td>".@images." photos, ${total_megs}MB";
		  print " <b>($new new)</b>" if ($new);
		  print "</td></tr>";
		}
	      }
  print "</table>";
  print "<hr /><small><a href=\"http://www.capybara.org/~dfraser/photo_gallery/\">Simple Photo Gallery</a> $version</small>";
}


sub find_dirs() {
	opendir DIR, ".";
	my @files = readdir DIR;
	close DIR;
	my @dirs = grep { $_ ne '.' and $_ ne '..' and -d $_ } @files;
	return \@dirs;
}

sub display_footer() {
  open FOOTER, "$page_footer" or die "couldn't open footer html file: $!\n";
  my $line;
  while (defined ($line = <FOOTER>)) {
    print $line;
  }
  close FOOTER;
}

sub get_images(){
  # get the current directory
  my $dir = $query->param('d');
  opendir DIR,"$dir" or die "couldn't open directory for reading: $!\n";
  my @images = grep { /^[^.]+\.[jJ][pP][eE]?[gG]$/ } readdir(DIR);
  if($custom_sort_enable eq "YES"){
    @images = sort { lc($a) cmp lc($b) } @images;
  }
  else{
    @images = sort @images;
  }
  closedir DIR;
  return @images;
}

sub makeThumbnail($$) {
    my $dir = shift;
    my $image = shift;
    if (-z "$dir/$thumbs/$image") {
	unlink "$dir/$thumbs/$image";
    }
    if (!-r "$dir/$thumbs/$image") {
	#print "[created thumbnail for $image] ";
	system "djpeg '$dir/$image' | pnmscale -pixels 10800 | cjpeg -progressive > '$dir/$thumbs/$image'";
    }
    doRotate($dir);
}

sub makeLowres($$) {
    my $dir = shift;
    my $image = shift;
    if (-z "$dir/$lowres/$image") {
	unlink "$dir/$lowres/$image";
    }
    if (!-r "$dir/$lowres/$image") {
	#	print "[created lowres for $image] ";
	system "djpeg '$dir/$image' | pnmscale -pixels 248832 | cjpeg -progressive > '$dir/$lowres/$image'";    
    }
    doRotate($dir);
}   


sub cleanDir($) {
    my $dir =shift;
    makeDirs($dir);
    cleanOrphans($dir);
    doRotate($dir);
}

sub doRotate($) {
my $dir = shift;
if ( -f "$dir/.rotation" ) {
  my $rotate;
#  print "doing rotations based on $dir/.rotation\n";
  open ROTATE, "$dir/.rotation";
  while (defined ($rotate = <ROTATE>)) {
	chomp $rotate;
	my ($file,$degrees) = split(/ /,$rotate);
	if (! -f "$dir/$thumbs/${file}.orig" ) {
	  #print "rotating thumbnail $dir/$file $degrees degrees.\n";
	  rename "$dir/$thumbs/$file", "$dir/$thumbs/${file}.orig";
	  system("jpegtran -rotate $degrees '$dir/$thumbs/${file}.orig' > '$dir/$thumbs/$file'");
	}
	if (! -f "$dir/$lowres/${file}.orig" ) {
	  #print "rotating lowres $dir/$file $degrees degrees.\n";
	  rename "$dir/$lowres/$file", "$dir/$lowres/${file}.orig";
	  system("jpegtran -rotate $degrees '$dir/$lowres/${file}.orig' > '$dir/$lowres/$file'");
	}
  }
}
}

sub cleanOrphans($) {
	my $dir=shift;
	my $directory;
	foreach $directory ("$dir/$thumbs", "$dir/$lowres") {
	# check dir to see if there are stray files
	#print "removing orphans from $directory directory\n";
	opendir DIR,"$directory" or die "can't open $directory directory: $!\n";
	my @files= readdir DIR;

	   closedir DIR;
	my $file;
	foreach $file ( @files ) {
	# does this file not exist as a hires version
	if ($file !~ /\.orig$/) {
		if(!-r "$dir/$file") {
	#		print "deleting orphan file: $directory/$file\n";
			unlink "$directory/$file";
		}
	}

	
  }
  }
}
    
sub makeDirs($) {
  my $baseDir = shift;
  my $lowresDir = $baseDir."/".$lowres;
  my $thumbsDir = $baseDir."/".$thumbs;
  #print "lowres directory: $lowresDir	thumbs directory: $thumbsDir\n";
  
  if (! -d $lowresDir) {
#	print "creating missing lowres directory: $lowresDir\n";
	mkdir $lowresDir;
  }
  if (! -d $thumbsDir) {
#	print "creating missing thumbnails directory: $thumbsDir\n";
	mkdir $thumbsDir;
  }
}

