view release on metacpan or search on metacpan
lib/App/MaMGal.pm view on Meta::CPAN
my $method = $AUTOLOAD;
$method =~ s/.*://;
croak "Unknown method $method" unless $method =~ /^make_(without_)?roots$/;
eval {
$self->{maker}->$method(@_);
};
my $e;
if ($e = Exception::Class->caught('App::MaMGal::SystemException')) {
$self->{logger}->log_exception($e);
} elsif ($e = Exception::Class->caught) {
ref $e ? $e->rethrow : die $e;
}
1;
}
1;
lib/App/MaMGal/Base.pm view on Meta::CPAN
#######################################################################################################################
# Utility methods
sub _write_contents_to
{
my $self = shift;
my $code = shift;
my $tmp_name = shift;
my $full_name = shift;
open(OUT, '>', $tmp_name) or App::MaMGal::SystemException->throw(message => '%s: open failed: %s', objects => [$tmp_name, $!]);
print OUT &$code;
close(OUT) or App::MaMGal::SystemException->throw(message => '%s: close failed: %s', objects => [$tmp_name, $!]);
rename($tmp_name, $full_name) or App::MaMGal::SystemException->throw(message => '%s: rename failed from "%s": %s', objects => [$full_name, $tmp_name, $!]);
}
1;
lib/App/MaMGal/Entry/Dir.pm view on Meta::CPAN
{
my $self = shift;
my $was_root = $self->is_root;
my $is_root = $self->{is_root} = shift;
return if $is_root == $was_root;
if ($is_root) {
$self->_write_contents_to(sub {''}, '.mamgal-root');
} else {
unlink($self->child('.mamgal-root')) or App::MaMGal::SystemException->throw(message => '%s: unlink failed: %s', objects => [$self->child(".mamgal-root"), $!]);
}
}
sub is_root
{
my $self = shift;
return $self->{is_root} || 0;
}
sub make
lib/App/MaMGal/Entry/Dir.pm view on Meta::CPAN
{
my $self = shift;
if ($self->is_root) {
my $formatter = $self->tools->{formatter};
$self->_write_contents_to(sub { $formatter->stylesheet }, '.mamgal-style.css');
} else {
# Delete legacy stylesheet files in directories other than root.
# TODO: remove this code a few releases from 1.2
my $path = $self->child('.mamgal-style.css');
return unless -e $path;
unlink($path) or App::MaMGal::SystemException->throw(message => '%s: unlink failed: %s', objects => [$path, $!]);
return 1;
}
}
sub ensure_subdir_exists
{
my $self = shift;
my $basename = shift;
my $dir = $self->child($basename);
mkdir $dir or App::MaMGal::SystemException->throw(message => '%s: mkdir failed: %s', objects => [$dir, $!]) unless -w $dir;
}
# get _picture_ neighbours of given picture
sub neighbours_of_index
{
my $self = shift;
my $idx = shift;
croak "neighbours_of_index must run in array context" unless wantarray;
my @elements = $self->elements;
$idx >= 0 or croak "Pic index must be at least 0";
lib/App/MaMGal/Entry/Dir.pm view on Meta::CPAN
# Return early if the montage is fresh
return if $self->fresher_than_me($montage_path, consider_interesting_only => 1) and $pruned_files == 0;
# Get just a bunch of images, not all of them.
my $montage_count = scalar @images > 36 ? 36 : scalar @images;
# Stack them all together
my $stack = Image::Magick->new;
push @$stack, map {
my $img = Image::Magick->new;
my $rr;
$rr = $img->Read($_->tile_path) and App::MaMGal::SystemException->throw(message => '%s: %s', objects => [$_->tile_path, $rr]);
$img->[0] or $img } @images[0..($montage_count-1)];
my $side = $self->_side_length($montage_count);
my ($m_x, $m_y) = (200, 150);
my ($montage, $r);
# Do the magick, scale and write.
$r = $montage = $stack->Montage(tile => $side.'x'.$side, geometry => $m_x.'x'.$m_y, border => 2);
ref($r) or App::MaMGal::SystemException->throw(message => '%s: montage failed: %s', objects => [$montage_path, $r]);
$r = App::MaMGal::Entry::Picture->scale_into($montage, $m_x, $m_y) and App::MaMGal::SystemException->throw(message => '%s: scaling failed: %s', objects => [$montage_path, $r]);
$r = $montage->Write($montage_path) and App::MaMGal::SystemException->throw(message => '%s: writing montage failed: %s', objects => [$montage_path, $r]);
}
sub _ignorable_name($)
{
my $self = shift;
my $name = shift;
# ignore hidden files
return 1 if substr($_, 0, 1) eq '.';
# TODO: optimize out contants calls, keeping in mind that they are not really constant (eg. tests change them when testing slides/miniatures generation)
return 1 if grep { $_ eq $name } (qw(lost+found index.html .mamgal-index.png .mamgal-style.css), $self->slides_dir, $self->thumbnails_dir, $self->medium_dir);
lib/App/MaMGal/Entry/Dir.pm view on Meta::CPAN
substr($f, 0, length($known_subdirs[1]) + 1) eq $known_subdirs[1].'/' or
substr($f, 0, length($known_subdirs[2]) + 1) eq $known_subdirs[2].'/';
}
my %active = map { $_ => 1 } @$active_files;
my $base = $self->{path_name};
my $pruned_count = 0;
foreach my $dir (@known_subdirs) {
# If the directory is not there, we have nothing to do about it
next unless -d $base.'/'.$dir;
# Read the names from the dir
opendir DIR, $base.'/'.$dir or App::MaMGal::SystemException->throw(message => '%s: opendir failed: %s', objects => ["$base/$dir", $!]);
my @entries = grep { $_ ne '.' and $_ ne '..' } readdir DIR;
closedir DIR or App::MaMGal::SystemException->throw(message => '%s: closedir failed: %s', objects => ["$base/$dir", $!]);
# Delete the files which are not "active"
my $at_start = scalar @entries;
my $deleted = 0;
foreach my $entry (@entries) {
next if $active{$dir.'/'.$entry};
# Before unlinking, update touch self so that if we crash between unlinking and updating thumbnail, it will be done on subsequent invocation.
utime(undef, undef, $base);
unlink($base.'/'.$dir.'/'.$entry) or App::MaMGal::SystemException->throw(message => '%s: unlink failed: %s', objects => ["$base/$dir/$entry", $!]);
$deleted++;
}
$pruned_count += $deleted;
rmdir($base.'/'.$dir) or App::MaMGal::SystemException->throw(message => '%s: rmdir failed: %s', objects => ["$base/$dir", $!]) if $at_start == $deleted;
}
return $pruned_count;
}
sub elements
{
my $self = shift;
# Lookup the cache
return @{$self->{elements}} if exists $self->{elements};
# Get entry factory
my $tools = $self->tools or croak "Tools were not injected";
my $entry_factory = $tools->{entry_factory} or croak "Entry factory required";
ref $entry_factory and $entry_factory->isa('App::MaMGal::EntryFactory') or croak "[$entry_factory] is not an entry factory";
# Read the names from the dir
my $path = $self->{path_name};
opendir DIR, $path or App::MaMGal::SystemException->throw(message => '%s: opendir failed: %s', objects => [$path, $!]);
my @entries = sort { $a cmp $b } grep { ! $self->_ignorable_name($_) } readdir DIR;
closedir DIR or App::MaMGal::SystemException->throw(message => '%s: closedir failed: %s', objects => [$path, $!]);
my $i = 0;
# Instantiate objects and cache them
$self->{elements} = [ map {
$_ = $path.'/'.$_ ;
my $e = $entry_factory->create_entry_for($_);
$e->set_element_index($i++);
$e->set_container($self);
$e
} @entries
lib/App/MaMGal/Entry/Picture.pm view on Meta::CPAN
my $r;
my @ret;
foreach my $miniature (@miniatures) {
my ($subdir, $x, $y, $suffix) = @$miniature;
my $relative_name = $subdir.'/'.$self->{base_name}.($suffix ? $suffix : '');
push @ret, $relative_name;
my $name = $self->{dir_name}.'/'.$relative_name;
next if $self->fresher_than_me($name);
# loading image data deferred until it's necessary
$i = $self->read_image unless defined $i;
$r = $self->scale_into($i, $x, $y) and App::MaMGal::SystemException->throw(message => '%s: scaling failed: %s', objects => [$name, $r]);
$self->container->ensure_subdir_exists($subdir);
$r = $i->Write($name) and App::MaMGal::SystemException->throw(message => '%s: writing failed: %s', objects => [$name, $r]);
}
return @ret;
}
sub is_interesting { 1; }
sub page_path { $_[0]->slides_dir.'/'.$_[0]->{base_name}.'.html' }
sub thumbnail_path { $_[0]->thumbnails_dir.'/'.$_[0]->{base_name} }
sub tile_path { $_[0]->{dir_name}.'/'.$_[0]->thumbnail_path }
# This method does not operate on App::MaMGal::Entry::Picture, but this was the most
lib/App/MaMGal/Entry/Picture/Static.pm view on Meta::CPAN
my $self = shift;
my $i = $self->image_info or return;
return $i->description;
}
sub read_image
{
my $self = shift;
my $i = Image::Magick->new;
my $r;
$r = $i->Read($self->{path_name}) and App::MaMGal::SystemException->throw(message => '%s: reading failed: %s', objects => [$self->{path_name}, $r]);
return $i;
}
sub creation_time
{
my $self = shift;
my $info = $self->image_info or return $self->SUPER::creation_time(@_);
return $info->creation_time || $self->SUPER::creation_time(@_);
}
lib/App/MaMGal/EntryFactory.pm view on Meta::CPAN
}
sub create_entry_for
{
my $self = shift;
my $path_arg = shift or croak "Need path"; # absolute, or relative to CWD
croak "Need 1 arg, got more: [$_[0]]" if @_;
my ($path, $dirname, $basename) = canonicalize_path($path_arg);
my $lstat = lstat($path) or App::MaMGal::SystemException->throw(message => '%s: getting status failed: %s', objects => [$path, $!]);
my $stat = $lstat;
if ($lstat->mode & S_IFLNK) {
$stat = stat($path);
}
my $e;
if (not $stat) {
$e = App::MaMGal::Entry::BrokenSymlink->new($dirname, $basename, $lstat)
} elsif ($stat->mode & S_IFDIR) {
lib/App/MaMGal/ImageInfoFactory.pm view on Meta::CPAN
BEGIN {
if (exists $ENV{MAMGAL_FORCE_IMAGEINFO}) {
$implementation = $ENV{MAMGAL_FORCE_IMAGEINFO};
eval "require $implementation" or die;
} elsif (eval "require App::MaMGal::ImageInfo::ExifTool") {
$implementation = 'App::MaMGal::ImageInfo::ExifTool';
} elsif (eval "require App::MaMGal::ImageInfo::ImageInfo") {
$implementation = 'App::MaMGal::ImageInfo::ImageInfo';
} else {
App::MaMGal::SystemException->throw(message => 'No usable image info library found (looked for "Image::ExifTool" and "Image::Info" in %s).', objects => [join(':', @INC)]);;
}
}
sub init
{
my $self = shift;
my $parser = shift or croak "A Image::EXIF::DateTime::Parser argument is required";
ref $parser and $parser->isa('Image::EXIF::DateTime::Parser') or croak "Arg is not an Image::EXIF::DateTime::Parser , but a [$parser]";
my $logger = shift or croak "A App::MaMGal::Logger argument is required";
ref $logger and $logger->isa('App::MaMGal::Logger') or croak "Arg is not an App::MaMGal::Logger, but a [$logger]";
lib/App/MaMGal/Maker.pm view on Meta::CPAN
sub _make_any
{
my $self = shift;
my $dirs_are_roots = shift;
# TODO: replace with croak after cmdline parsing is added
die "Argument required.\n" unless @_;
my @dirs = map {
my $d = $self->{entry_factory}->create_entry_for($_);
App::MaMGal::SystemException->throw(message => '%s: not a directory.', objects => [$_]) unless $d->isa('App::MaMGal::Entry::Dir');
$d->set_root(1) if $dirs_are_roots;
$d
} @_;
$_->make foreach @dirs;
return 1;
}
1;
lib/App/MaMGal/MplayerWrapper.pm view on Meta::CPAN
sub init
{
my $self = shift;
my $cc = shift or croak 'Arg required: command checker';
$cc->isa('App::MaMGal::CommandChecker') or croak 'Arg must be a CommandChecker';
croak "Just one argument allowed" if @_;
eval {
$self->{tempdir} = tempdir(CLEANUP => 1);
}; if ($@) {
App::MaMGal::SystemException->throw(message => 'Temporary directory creation failed: %s.', objects => [$@]);
}
$self->{cc} = $cc;
}
sub run_mplayer
{
my $self = shift;
my $film_path = shift;
my $dir = $self->{tempdir};
my $pid = fork;
if (not defined $pid) {
App::MaMGal::MplayerWrapper::ExecutionFailureException->throw("Fork failed: $!");
} elsif ($pid == 0) {
# Child
open(STDOUT, ">${dir}/stdout") or App::MaMGal::MplayerWrapper::ExecutionFailureException->throw("Cannot open \"${dir}/stdout\" for writing: $!");
open(STDERR, ">${dir}/stderr") or App::MaMGal::MplayerWrapper::ExecutionFailureException->throw("Cannot open \"${dir}/stderr\" for writing: $!");
my @cmd = ('mplayer', $film_path, '-noautosub', '-nosound', '-vo', "jpeg:quality=100:outdir=${dir}", '-frames', '2');
{ # own scope to prevent a compile-time warning
exec(@cmd);
}
App::MaMGal::MplayerWrapper::ExecutionFailureException->throw("Cannot run mplayer: $!");
} else {
# Parent
waitpid($pid, 0);
App::MaMGal::MplayerWrapper::ExecutionFailureException->throw("Mplayer failed ($?).", $self->_read_messages) if $? != 0;
}
}
sub _read_log
{
my $self = shift;
my $name = shift;
my $dir = $self->{tempdir};
open(F, "<${dir}/$name") or App::MaMGal::MplayerWrapper::ExecutionFailureException->throw("Cannot open \"${dir}/$name\" for reading: $!");
my @ret = <F>;
close(F) or App::MaMGal::MplayerWrapper::ExecutionFailureException->throw("Cannot close \"${dir}/$name\": $!");
chomp @ret;
return \@ret;
}
sub _read_messages
{
my $self = shift;
return map { $_ => $self->_read_log($_) } qw(stdout stderr);
}
sub snapshot
{
my $self = shift;
$self->{available} = $self->{cc}->is_available('mplayer') unless exists $self->{available};
App::MaMGal::MplayerWrapper::NotAvailableException->throw unless $self->{available};
my $film_path = shift or croak "snapshot needs an arg: path to the film";
-r $film_path or App::MaMGal::SystemException->throw(message => '%s: not readable', objects => [$film_path]);
my $dir = $self->{tempdir};
$self->run_mplayer($film_path);
my $img = Image::Magick->new;
if (my $r = $img->Read("${dir}/00000001.jpg")) {
App::MaMGal::MplayerWrapper::ExecutionFailureException->throw(message => "Could not read the snapshot produced by mplayer: $r", $self->_read_messages);
}
$self->cleanup;
return $img;
}
sub cleanup
{
my $self = shift;
my $path = $self->{tempdir};
opendir my $d, $path or App::MaMGal::MplayerWrapper::ExecutionFailureException->throw("Cannot open \"$path\" to clean up after mplayer");
my @files = readdir $d;
closedir $d;
# This assumes that mplayer did not create any directories.
unlink(map($path.'/'.$_, @files));
}
1;
t/100_unit_maker.t view on Meta::CPAN
$ef_dir->set_isa('App::MaMGal::EntryFactory');
my $maker;
lives_ok(sub { $maker = App::MaMGal::Maker->new($ef_dir) }, "maker creation succeeds an entry factory arg");
isa_ok($maker, 'App::MaMGal::Maker');
$self->{maker} = $maker;
}
sub make_roots_dies_without_args : Test(2)
{
my $self = shift;
throws_ok { $self->{maker}->make_roots } qr{^Argument required\.$}, "maker dies on no args";
throws_ok { $self->{maker}->make_without_roots } qr{^Argument required\.$}, "maker dies on no args";
}
package App::MaMGal::Unit::Maker::Normal;
use strict;
use warnings;
use Carp;
use Carp 'verbose';
use base 'App::MaMGal::Unit::Maker::Base';
use Test::More;
t/100_unit_maker.t view on Meta::CPAN
sub set_mock_entry
{
my $self = shift;
my $mock_entry = $self->{mock_entry} = get_mock_entry('App::MaMGal::Entry::SomethinElse');
return sub { $mock_entry };
}
sub make_roots_dies : Test(1)
{
my $self = shift;
throws_ok { $self->{maker}->make_roots('something/whatever') } qr{^%s: not a directory\.$}, "maker dies if thing returned by factory is not a dir object";
}
sub make_without_roots_dies : Test(1)
{
my $self = shift;
throws_ok { $self->{maker}->make_without_roots('something/whatever') } qr{^%s: not a directory\.$}, "maker dies if thing returned by factory is not a dir object";
}
package App::MaMGal::Unit::Maker::Crash;
use strict;
use warnings;
use Carp;
use Carp 'verbose';
use base 'App::MaMGal::Unit::Maker::Base';
t/100_unit_maker.t view on Meta::CPAN
sub set_mock_entry
{
return sub { die 'dying here on purpose' };
}
sub make_roots_dies : Test(1)
{
my $self = shift;
throws_ok { $self->{maker}->make_roots('something/whatever') } qr{^dying here on purpose}, "maker dies if factory call crashes";
}
sub make_without_roots_dies : Test(1)
{
my $self = shift;
throws_ok { $self->{maker}->make_without_roots('something/whatever') } qr{^dying here on purpose}, "maker dies if factory call crashes";
}
package main;
use strict;
use warnings;
use Test::More;
unless (defined caller) {
plan tests =>
App::MaMGal::Unit::Maker::Normal->expected_tests +
t/100_unit_mplayer_wrapper.t view on Meta::CPAN
dies_ok(sub { $w->snapshot() }, "wrapper cannot get a snapshot of undef");
my ($m, $args) = $mccy->next_call;
is($m, 'is_available', 'checker is interrogated on fist wrapper use');
is_deeply($args, [$mccy, 'mplayer'], 'checker is asked about mplayer');
$mccy->clear;
dies_ok(sub { $w->snapshot('td/notthere.mov') }, "wrapper cannot get a snapshot of an inexistant file");
is($mccy->next_call, undef, 'checker not interrogated more than once');
$mccy->clear;
throws_ok(sub { $snap = $w->snapshot('td/c.jpg') }, 'App::MaMGal::MplayerWrapper::ExecutionFailureException', "wrapper cannot survive snapshotting a non-film file");
my $err = $@;
is($mccy->next_call, undef, 'checker not interrogated more than once');
$mccy->clear;
ok($err->message, "invalid file produces some exception message");
ok($err->stdout, "invalid file produces some messages");
cmp_ok(scalar @{$err->stdout}, '>', 0, "there are lines in the stdout file");
is(scalar(grep(/\n$/, @{$err->stdout})), 0, "no newlines in the stdout file");
ok($err->stderr, "invalid file produces some error messages");
cmp_ok(scalar @{$err->stderr}, '>', 0, "there are lines in the stderr file");
is(scalar(grep(/\n$/, @{$err->stderr})), 0, "no newlines in the stderr file");
t/100_unit_mplayer_wrapper.t view on Meta::CPAN
}
{
my $mccn = get_mock_cc(0);
my $w;
lives_ok(sub { $w = App::MaMGal::MplayerWrapper->new($mccn) }, "wrapper can be created with command checker");
is($mccn->next_call, undef, 'checker not interrogated until fist wrapper use');
$mccn->clear;
throws_ok(sub { $w->snapshot() }, 'App::MaMGal::MplayerWrapper::NotAvailableException', "failed because mplayer was not found");
my ($m, $args) = $mccn->next_call;
is($m, 'is_available', 'checker is interrogated on fist wrapper use');
is_deeply($args, [$mccn, 'mplayer'], 'checker is asked about mplayer');
$mccn->clear;
throws_ok(sub { $w->snapshot('td/notthere.mov') }, 'App::MaMGal::MplayerWrapper::NotAvailableException', "failed because mplayer was not found");
is($mccn->next_call, undef, 'checker not interrogated more than once');
$mccn->clear;
throws_ok(sub { $w->snapshot('td/c.jpg') }, 'App::MaMGal::MplayerWrapper::NotAvailableException', "failed because mplayer was not found");
is($mccn->next_call, undef, 'checker not interrogated more than once');
$mccn->clear;
throws_ok(sub { $w->snapshot('td/one_film/m.mov') }, 'App::MaMGal::MplayerWrapper::NotAvailableException', "failed because mplayer was not found");
is($mccn->next_call, undef, 'checker not interrogated more than once');
$mccn->clear;
}