#!/usr/bin/perl 
# camdump -- gphoto2 frontend which just dumps all new photos
#            in specified directory.
# copyright (c) 2002 by Aaron Peromsik <aaron@peromsik.net>.
# released under the terms of the GNU GPL, see COPYING for details.
#
# usage: plug in your camera, then run "camdump tgt_path" to dump in tgt_path
# all images from the camera which don't already exist there. camdump can 
# be executed by hotplug for seamless operation.
#
# progress bar based on gtkperl tutorial.

use gphoto2_perl;
use File::Spec;
use Gtk2;
use strict;

my $tgt_dir;
# Not only a wrong number of arguments, but also giving "-h", "--help",
# or other switches should lead to the help message
if ((scalar @ARGV > 1) or ($ARGV[0] =~ /^\-/)) {
  die "Usage: camdump [ <destination directory> ]\n";
} elsif (scalar @ARGV == 0) {
  $tgt_dir = "/var/spool/camdump";
  my $found = 0;
  my $personalconfig = $ENV{HOME} . "/.camdump";
  if ( -r $personalconfig ) {
    open F, "< $personalconfig";
    while (<F>) {
      if (/^\s*CAMDUMP\s*=\s*(\S*)\s*$/) {
        $tgt_dir = $1;
        $found = 1;
	last;
      }
    }
    close F;
  }
  if ((!$found) and ( -r "/etc/camdump" )) {
    open F, "< /etc/camdump";
    while (<F>) {
      if (/^\s*CAMDUMP\s*=\s*(\S*)\s*$/) {
        $tgt_dir = $1;
	last;
      }
    }
    close F;
  }
} else {
  $tgt_dir = shift;
}

# $tgt_dir must be an absolute path, otherwise camdump does not work
# out of hotplug
if ($tgt_dir !~ m!^/!) {
  $tgt_dir = $ENV{HOME} . "/" . $tgt_dir;
}

set_locale Gtk2;
init Gtk2;

# variables snarfed from gtkperl tutorial
my $true = 1;
my $false = 0;

my $value;
my $signal;

my $window;
my $button;
my $vbox;
my $label;
my $adj;
my $pbar;

# My variables
my %files = ();

my $gui = 0;

my $status_msg = "";
my $num_files = 0;
my $cur_file = 0;
my $num_saved = 0;

my $cam;
my $context;
my $initstatus = -1;
my $cam_model;

sub handle_gtk() {
  if ($gui) {

    if ($num_files > 0) {
      my $fraction = $cur_file / $num_files;
      $pbar->set_fraction($fraction);
      my $pbtext = sprintf ( "%2d%%", 100 * $fraction );
      $pbar->set_text($pbtext);
    }
    $label->set_text($status_msg);
    
    Gtk2->main_iteration while ( Gtk2->events_pending );
  } else {
    print "$status_msg\n";
  }
}

sub try_gui_init() {
  return if (! defined $ENV{DISPLAY});
  $gui = 1;
  $window = new Gtk2::Window( 'toplevel' );
  $signal = $window->signal_connect( 'delete_event', sub { Gtk2::exit( 0 ); } );
  $window->set_border_width( 15 );
  $window->set_position( 'center' );
  
  $vbox = new Gtk2::VBox( $false, 0 );
  $window->add( $vbox );
  
  $label = new Gtk2::Label( $status_msg );
  $vbox->pack_start( $label, $false, $false, 10 );
  $label->show();
  
  $adj = new Gtk2::Adjustment( 0, 1, 100, 0, 0, 0 );
  $pbar = new  Gtk2::ProgressBar( );
  $vbox->pack_start( $pbar, $false, $false, 10 );
  $pbar->set_fraction( 0.0 );
  $pbar->show();
  $vbox->show();

  Gtk2->main_iteration while ( Gtk2->events_pending );
  
  $window->show();
}

sub cam_cleanup() {
  if ($initstatus == 0) {
    gphoto2_perl::gp_camera_exit($cam, $context);
    gphoto2_perl::gp_camera_unref($cam);
  }
  if ($gui) {
    Gtk2::exit(0);
  } else {
    exit (0);
  }
}

sub exit_with_message($) {
  my $errmsg = $_[0];

  if (! $gui) {
    print "$errmsg\n";
    cam_cleanup;
    exit;
  }

  $window->hide;
  my $dlg = new Gtk2::Dialog();
  my $close = new Gtk2::Button( "Dismiss" );
  $dlg->action_area->pack_start( $close, $true, $true, 0 );
  $signal = $close->signal_connect( 'clicked', sub { cam_cleanup; } );
  $signal = $dlg->signal_connect( 'delete_event', sub { cam_cleanup; } );
  $close->show();
  my $errlabel = new Gtk2::Label( $errmsg );
  $dlg->vbox->pack_start( $errlabel, $true, $true, 10 );
  $errlabel->show();
  $dlg->set_title($cam_model) if defined $cam_model;

  $dlg->set_position( 'center' );
  $dlg->show();
  $dlg->set_border_width(15);

  main Gtk2;
}

sub read_camera_recur($$$$) {
    my $cam = $_[0];
    my $context = $_[1];
    my $list = $_[2];
    my $folder = $_[3];
    my $num;
    my @subfolders = ();
    my $subfolder;

    # get files
    gphoto2_perl::gp_list_reset($list);
    gphoto2_perl::gp_camera_folder_list_files($cam, $folder,
                                              $list, $context);
    $num = gphoto2_perl::gp_list_count($list);
    my $n = gphoto2_perl::new_string();
    foreach my $i (0 .. $num -1) {
      my $status;
      $status = gphoto2_perl::gp_list_get_name($list, $i, $n);
      next if $status != 0;
      my $shortname = gphoto2_perl::string_value($n);
      $files{$shortname} = $folder;
    }
    
    # get folders
    gphoto2_perl::gp_list_reset($list);
    gphoto2_perl::gp_camera_folder_list_folders($cam, $folder,
                                                $list, $context);
    $num = gphoto2_perl::gp_list_count($list);
    foreach my $i (0 .. $num -1) {
      my $status;
      $status = gphoto2_perl::gp_list_get_name($list, $i, $n);
      next if $status != 0;
      $subfolder = File::Spec->catfile($folder,
                                       gphoto2_perl::string_value($n));
      push @subfolders, $subfolder;
    }

    gphoto2_perl::delete_string($n);
    foreach $subfolder (@subfolders) {
      read_camera_recur($cam, $context, $list, $subfolder);
      handle_gtk();
    }
}

sub read_camera_contents($$) {
    my $cam = $_[0];
    my $context = $_[1];
    my $list = gphoto2_perl::camdump_list_new();

    read_camera_recur($cam, $context, $list, "/");
    
    gphoto2_perl::gp_list_free($list);
}

sub filter_existing_files() {
  foreach my $file (keys %files) {
    my $destpath = File::Spec->catfile($tgt_dir, lc $file);
    if (-e $destpath) {
#      print "$file exists, skipping\n";
      delete $files{$file};
#    } else {
#      print "$file not found, will download.\n";
    }
  }
}

sub save_files($$) {
  my $cam = $_[0];
  my $context = $_[1];
  $num_files = scalar keys %files;
  $cur_file = 0;
  foreach my $file (sort keys %files) {
    my $destpath = File::Spec->catfile($tgt_dir, lc $file);
    my $camfile = gphoto2_perl::camdump_file_new();
    gphoto2_perl::gp_file_ref($camfile);
    $cur_file ++;
    $status_msg = "Saving $file to $tgt_dir ($cur_file / $num_files)";
    handle_gtk();
    my $getstat =
      gphoto2_perl::camdump_camera_file_get($cam, $files{$file}, $file,
                                            $camfile, $context);
    if ($getstat != 0) {
      print "error saving $file to $tgt_dir\n" if ! $gui;
    } else {
      print "saving $destpath\n" if ! $gui;
      my $savestat = gphoto2_perl::gp_file_save($camfile, $destpath);
      $num_saved ++ if $savestat == 0;
    }
    # it would seem more efficient to recycle the same camfile
    # structure for the whole loop, but when doing so all files
    # wind up with the same timestamp. still call gp_file_clean
    # to free memory from the image just downloaded.
    gphoto2_perl::gp_file_clean($camfile);
    gphoto2_perl::gp_file_unref($camfile);
  }
}

# Exit if we are already running, as sometimes hotplug triggers camdump
# twice
my $running = `ps -e 2> /dev/null | grep -c camdump`;
exit 0 if ($running > 1);

$status_msg = "Setting up camera...";
try_gui_init();
handle_gtk();

$cam = gphoto2_perl::camdump_open_camera();
$context = gphoto2_perl::gp_context_new();
$initstatus = gphoto2_perl::gp_camera_init($cam, $context);
if ($initstatus != 0) {
  exit_with_message "Failed to detect camera \n(" .
    gphoto2_perl::gp_result_as_string($initstatus) . ")";
}

my $abil = new gphoto2_perl::CameraAbilities();
my $abilstatus = gphoto2_perl::gp_camera_get_abilities($cam, $abil);
if ($abilstatus == 0) {
  $cam_model = $abil->swig_model_get() . "\n";
  $window->set_title($cam_model) if $gui;
}
undef $abil;

$status_msg = "Checking for new photos...";
handle_gtk();
read_camera_contents($cam, $context);
handle_gtk();
filter_existing_files();
save_files($cam, $context);

if ($cur_file > 0) {
  if ($num_saved == $cur_file) {
    $status_msg = "Saved $cur_file files to $tgt_dir";
  } else {
    $status_msg = "$num_saved files saved to $tgt_dir... " .
      ($cur_file - $num_saved) . " failed.";
  }
} else {
  $status_msg = "No new files for $tgt_dir on camera!";
}


exit_with_message($status_msg);
