|
#!/usr/bin/perl ## Variables # global use vars qw($camroot $sudo $umount $cam $tmp $mounted $in_alert $cancel_xfer $winbox %choice @rows $dest_dir $picbox $pbshell %entry $path $total %action %size $dialog $text $prog $pid $done $first %opt $start_time $last_etr); # local to main() my ($jhead, $defpath, $win, $button, $vbox, $hbox, $group, $result, @pics, $n, $cols, $c, $row, $mount); ## Configuration $sudo = '/usr/local/bin/sudo'; $mount = '/sbin/mount'; ($umount = $mount) =~ s/mount$/umount/; $jhead = '/usr/local/bin/jhead'; ## Defaults $camroot = '/camera'; $cam = '100olymp'; $defpath = '/export/tmp/pics/%Y-%m-%d-%H:%M'; ## Includes use strict; use Gtk '-init'; use Gtk::Gdk::Pixbuf; use File::Path; use File::Copy; use File::Basename; use POSIX 'strftime'; use Data::Dumper; use Getopt::Std; ## Options getopts('hvr:c:p:', \%opt); die <<EOF if $opt{h} || @ARGV; usage: @{[ basename $0 ]} [-v] [-r camroot] [-c camera] [-p outpath] -v: verbose mode -r: set the root mountpoint of the camera fs; default: $camroot -c: set the camera dir name; default: $cam this means the default input dir is $camroot/dcim/$cam -p: set the output path which will be passed through strftime; default is $defpath EOF $camroot = $opt{r} if defined $opt{r}; $cam = $opt{c} if defined $opt{c}; $defpath = $opt{p} if defined $opt{p}; ## Camera Setup $| = 1; unless (-d "$camroot/dcim") { print "$camroot not mounted; trying to mount with sudo: " if $opt{v}; system($sudo, $mount, $camroot); if ($? >> 8) { print "failed: $!\n" if $opt{v}; exit(1); } print "succeded\n" if $opt{v}; $mounted = 1; } chdir("$camroot/dcim/$cam") || die "couldn't cd to '$camroot/dcim/$cam': $!\n"; opendir(DOT, '.'); @pics = grep(/\.jpg$/i, readdir(DOT)); closedir(DOT); die "no pics found in '$camroot/dcim/$cam'; quitting\n" unless @pics; ## Temp Dir $tmp = sprintf('/tmp/%s-%x', basename($0), rand(0xffffffff)); mkpath([$tmp]) || die "couldn't make tmpdir '$tmp': $!\n"; ## State Setup $in_alert = 0; $cancel_xfer = 0; ## GUI Setup sub TRUE { Gtk->true } sub FALSE { Gtk->false } $win = new Gtk::Window; $win->set_policy(FALSE, FALSE, TRUE); $win->signal_connect('delete_event', \&quit); $win->signal_connect('destroy_event', \&quit); $win->set_title("Camera Image Viewer"); $winbox = Gtk::VBox->new(FALSE, 0); $pbshell = Gtk::VBox->new(FALSE, 0); $winbox->add($pbshell); &draw_pics; $path = Gtk::Entry->new(80); $path->set_text(strftime($defpath, localtime)); $winbox->add($path); $winbox->add(Gtk::HSeparator->new); $button = Gtk::Button->new_with_label('Run'); $button->signal_connect('clicked', \&run); $winbox->add($button); $win->add($winbox); $win->show_all; Gtk->main; ## Subroutines sub quit { rmtree([$tmp]); if ($mounted) { print "unmounting $camroot: " if $opt{v}; chdir('/'); system($sudo, $umount, $camroot); if ($opt{v}) { if ($? >> 8) { print "failed: $!\n"; } else { print "succeeded\n"; } } } Gtk->main_quit; } sub run { $total = 0; %action = (); my $del; %action = (); for (sort keys %choice) { if ($choice{$_}{Copy}->get_active) { $action{$_} = 'copy'; } elsif ($choice{$_}{Move}->get_active) { $action{$_} = 'move'; } elsif ($choice{$_}{Delete}->get_active) { print qq(delete "$_"\n) if $opt{v}; if (unlink($_)) { $del = 1; } else { &alert("Couldn't delete '$camroot/dcim/$cam/$_': $!"); } next; } else { ## Leave next; } $size{$_} = -s; $total += $size{$_}; } unless (%action) { &redraw_pics if $del; return; } $first = 1; $dest_dir = $path->get_text; mkpath([$dest_dir]); $start_time = time; $last_etr = 0; unless ($pid = fork) { for (sort keys %action) { if ($action{$_} eq 'move') { print qq(move "$_" -> "$dest_dir/$_"\n) if $opt{v}; move($_, "$dest_dir/$_", 131072); ## 128k buffer } elsif ($action{$_} eq 'copy') { print qq(copy "$_" -> "$dest_dir/$_"\n) if $opt{v}; copy($_, "$dest_dir/$_", 131072); ## 128k buffer } else { print "unknown action '$action{$_}' for '$_'" if $opt{v}; last; } } Gtk->_exit(0); } Gtk->timeout_add(250, \&xfer_callback); } sub xfer_callback { my ($current, $bytes, $button, $sum, $etr); if ($cancel_xfer) { $cancel_xfer = 0; kill(15, $pid); $dialog->destroy; return; } for (sort keys %action) { $bytes = -s "$dest_dir/$_"; if ($bytes == $size{$_}) { $done += $bytes; delete $action{$_}; } else { $current = $_; last; } } $sum = $done + $bytes; return TRUE unless $sum; if ($first) { $dialog = new Gtk::Dialog; $dialog->set_modal(TRUE); $button = Gtk::Button->new_with_label('Cancel'); $button->signal_connect('clicked', sub { $cancel_xfer = 1 }); $button->signal_connect('delete_event', \&Gtk::true); $dialog->action_area->pack_start($button, FALSE, FALSE, 3); $text = new Gtk::Label; $dialog->vbox->pack_start($text, FALSE, FALSE, 5); $dialog->vbox->add(Gtk::HSeparator->new); $prog = new Gtk::ProgressBar; $dialog->vbox->add($prog); $dialog->show_all; $first = 0; } if (defined $current) { $etr = ($total - $sum) * (time - $start_time) / $sum; if ($last_etr && $etr > $last_etr) { $etr = $last_etr; } else { $last_etr = $etr; } $etr = ($etr > 60 * 60 * 24) ? '???' : strftime('%T', gmtime($etr)); $text->set_text("Current: $action{$current}: $current\n" . "Estimated Remaining: $etr"); $prog->update($sum / $total); return TRUE; } else { $dialog->destroy; &redraw_pics; return FALSE; } } sub alert { my ($mesg, $text, $func) = @_; my ($dialog, $button); $text = 'OK' unless length $text; $func ||= sub { }; $in_alert = 1; $dialog = new Gtk::Dialog; $dialog->set_modal(TRUE); $button = Gtk::Button->new_with_label(" $text "); $button->signal_connect('clicked', \&alert_func, $func); $button->signal_connect('delete_event', \&Gtk::true); $dialog->action_area->pack_start($button, FALSE, FALSE, 3); $dialog->vbox->pack_start( Gtk::Label->new("\n $mesg \n"), FALSE, FALSE, 5); $dialog->show_all; } sub alert_func { my ($self, $func) = @_; $self->get_parent_window->destroy; &$func; $in_alert = 0; } sub draw_pics { my (@pics, $n, $cols, $c, $pic, $button); $picbox = Gtk::VBox->new(FALSE, 0); opendir(DOT, '.'); @pics = sort grep(/\.jpg$/, readdir(DOT)); closedir(DOT); $n = @pics; $cols = int((3+sqrt(9+352*$n))/22+0.5); $c = 0; @rows = (); %choice = (); for $pic (@pics) { $hbox = Gtk::HBox->new(FALSE, 0); $vbox = Gtk::VBox->new(FALSE, 0); $group = Gtk::RadioButton->new_with_label('Leave'); $vbox->add($group); for (qw(Copy Move Delete)) { $button = Gtk::RadioButton->new_with_label_from_widget($group, $_); $vbox->add($button); $choice{$pic}{$_} = $button; } $hbox->add($vbox); unless (-e "$tmp/$pic") { open(JHEAD, '-|') || exec($jhead, '-st', "$tmp/$pic", $pic); $result = <JHEAD>; close(JHEAD); next if $? >> 8 || $result !~ /^Created:/; } $button = new Gtk::Button; $button->set_relief(2); $button->signal_connect('clicked', \&preview, $pic); $button->add(Gtk::Pixmap->new(Gtk::Gdk::Pixbuf->new_from_file( "$tmp/$pic")->render_pixmap_and_mask(1), undef)); $hbox->add($button); $entry{$pic} = $hbox; if ($c++ == 0) { $row = Gtk::HBox->new(FALSE, 0); push(@rows, $row); $picbox->add($row); $picbox->add(Gtk::HSeparator->new); } $row->pack_start($entry{$pic}, FALSE, FALSE, 0); $row->pack_start(Gtk::VSeparator->new, FALSE, FALSE, 0); $c %= $cols; } $picbox->show_all; $pbshell->add($picbox); } sub redraw_pics { $pbshell->remove($picbox); &draw_pics; } sub preview { my ($self, $pic) = @_; my ($prev, $pwin, $scroll); $pwin = new Gtk::Window; $pwin->set_policy(FALSE, TRUE, FALSE); $pwin->set_title("Camera Image Viewer: Preview '$pic'"); $pwin->set_default_size(640, 480); $scroll = new Gtk::ScrolledWindow; $scroll->add_with_viewport(Gtk::Pixmap->new(Gtk::Gdk::Pixbuf->new_from_file( $pic)->render_pixmap_and_mask(1), undef)); $pwin->add($scroll); $pwin->show_all; } |