diff --git a/lib/LDV/Filebin.pm b/lib/LDV/Filebin.pm new file mode 100644 index 0000000..7eb5742 --- /dev/null +++ b/lib/LDV/Filebin.pm @@ -0,0 +1,180 @@ +package LDV::Filebin; + +use strict; +use warnings; +use utf8; + +use Mojo::Base 'Mojolicious::Controller'; +use Mojo::Asset::File; + +use File::MimeInfo::Magic qw(mimetype); +use Imager; + +sub _file_path { + my ($self, $fname, $fext, $full) = @_; + + my $path = sprintf "/files/%s.%s", $fname, $fext; + return $full ? $self->app->home->rel_file("public/$path") : $path; +} + +sub create { + my ($self) = @_; + + $self->access_allowed($self->app->config->{filebin}->{access}) + or return; + + $self->render; +} + +sub view { + my ($self) = @_; + my $time = $self->stash('time') || 0; + + eval { + my $now = time(); + my $file = $self->app->db->select('filebin', '*', {created => $time, expire => {'>=' => $now}})->hash + or die "file not found\n"; + $file->{path} = $self->_file_path($time, $file->{fext}); + + $self->stash({file => $file}); 1; + $self->render; 1; + } or do { + chomp $@; + $self->flash({error => $@}); + $self->app->log->error($@); + $self->redirect_to('/filebin'); + }; + + $self->rendered; +} + +sub save { + my ($self) = @_; + + $self->access_allowed($self->app->config->{filebin}->{access}) + or return; + + eval { + my $time = time(); + my $conf = $self->app->config->{filebin}; + + die "request too large\n" + if $self->req->is_limit_exceeded; + + my $expire = $self->req->param('expire') || 7; # days + my $upload = $self->req->upload('file'); + die "no file uploaded\n" unless ($upload and $upload->size > 0); + die "file too large\n" if ($upload->size > $conf->{file_maxsize}); + + { # hack: don't use memory backend + my $path = POSIX::tmpnam; + $upload->move_to($path); + $upload->asset(Mojo::Asset::File->new(path => $path)); + $upload->asset->cleanup(1); # rearm self-destruction + } + + my $file = { + created => $time, expire => $time + $expire * 86400, + ftype => 'b', # default -- generic binary + fsize => $upload->size, + fname => $upload->filename, + fmime => mimetype($upload->asset->path), + fext => '', + res_w => 0, + res_h => 0, + }; + + if ($file->{fname} =~ m<\.([a-z0-9]{1,5})$>oi) { + $file->{fext} = lc($1); + } + if ($file->{fmime} =~ m{^image/}o) { + my ($max_w, $max_h) = ($conf->{image_maxres} =~ m/(\d+)x(\d+)/oi); + Imager->set_file_limits( + width => $max_w, height => $max_h, + bytes => $conf->{image_maxmem}, + ); + # query image + my $image = Imager->new(file => $upload->asset->path) + or die(Imager->errstr() . "\n"); + $file->{ftype} = 'i'; + $file->{fext} = $image->tags(name => 'i_format'), + $file->{res_w} = $image->getwidth, + $file->{res_h} = $image->getheight, + # make thumb + my ($th_w, $th_h) = ($conf->{thumbs_size} =~ m/(\d+)x(\d+)/oi); + my $thumb = $image->scale(xpixels => $th_w, ypixels => $th_h, type => 'min'); + my $path = $self->_file_path("${time}_th", $conf->{thumbs_ext}, 'fullpath'); + $thumb->write(file => $path) + or die $thumb->errstr; + } elsif ($file->{fmime} =~ m{^video/}o) { + $file->{ftype} = 'v'; + } elsif ($file->{fmime} =~ m{^audio/}o) { + $file->{ftype} = 'a'; + } elsif ($file->{fmime} =~ m{^text/}o) { + $file->{ftype} = 't'; + } + + $upload->move_to($self->_file_path($time, $file->{fext}, 'fullpath')); + $self->app->db->insert('filebin', $file); + + $self->redirect_to("/filebin/$time"); + } or do { + chomp $@; + $self->app->log->error($@); + $@ =~ s/\s+ at \s+ .+ \s+ line \s+ \d+//oxi; + $self->flash({error => $@}); + $self->redirect_to('/filebin'); + }; + + $self->rendered; +} + +sub latest { + my ($self) = @_; + + eval { + my $now = time(); + my @files = $self->app->db->select('filebin', '*', {expire => {'>=' => $now}}, {-desc => 'created'})->hashes; + splice(@files, 15); # first 15 elements + foreach my $file (@files) { + $file->{thumb} = $self->_file_path($file->{created}, $file->{fext}); + $file->{url} = sprintf '/filebin/%d', $file->{created}; + } + $self->stash(files => \@files); + $self->render; + } or do { + chomp $@; + $self->flash({error => $@}); + $self->app->log->error($@); + $self->redirect_to('/filebin'); + }; +} + +sub prune { + my ($self) = @_; + + $self->access_allowed($self->app->config->{filebin}->{access}) + or return; + + eval { + my $now = time(); + my $conf = $self->app->config->{filebin}; + my @files = $self->app->db->select('filebin', '*', {expire => {'<=' => $now}})->hashes; + foreach my $file (@files) { + unlink $self->_file_path($file->{created}, $file->{fext}, 1); + if ($file->{ftype} eq 'i') { + unlink $self->_file_path("$file->{created}_tn", $conf->{thumbs_ext}, 1); + } + $self->app->db->delete('filebin', {id => $file->{id}}); + } 1; + } or do { + chomp $@; + $self->flash({error => $@}); + $self->app->log->error($@); + }; + + $self->redirect_to('/filebin'); + $self->rendered; +} + +1;