 |
#!/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;
}
|
 |