Commit 91e07880 authored by Luc Didry's avatar Luc Didry

First commit. Working.

parents
local/*
files/*
lufi.conf
lufi.db
*.swp
script/hypnotoad.pid
EXTRACTFILES=utilities/locales_files.txt
EN=lib/Lufi/I18N/en.po
XGETTEXT=carton exec local/bin/xgettext.pl
CARTON=carton exec
REAL_LUFI=script/application
LUFI=script/lufi
locales:
$(XGETTEXT) -f $(EXTRACTFILES) -o $(EN)
test:
$(CARTON) $(REAL_LUFI) test
clean:
rm -rf lufi.db files/
dev: clean
$(CARTON) morbo $(LUFI) --listen http://0.0.0.0:3000
devlog:
multitail log/development.log
prod:
$(CARTON) hypnotoad $(LUFI)
requires "Mojolicious";
requires "ORLite";
requires "Mojolicious::Plugin::DebugDumperHelper";
requires 'Mojolicious::Plugin::I18N';
requires 'EV';
requires 'Filesys::DiskUsage';
requires 'Switch';
requires 'Locale::Maketext';
requires 'Locale::Maketext::Extract';
This diff is collapsed.
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi;
use Mojo::Base 'Mojolicious';
use LufiDB;
$ENV{MOJO_MAX_WEBSOCKET_SIZE} = 100485760; # 10 * 1024 * 1024 = 10MiB
# This method will run once at server start
sub startup {
my $self = shift;
my $config = $self->plugin('Config' => {
default => {
provisioning => 100,
provis_step => 5,
length => 10,
token_length => 32,
secret => ['hfudsifdsih'],
default_delay => 0,
max_delay => 0
}
});
die "You need to provide a contact information in lufi.conf!" unless (defined($config->{contact}));
# Internationalization
$self->plugin('I18N');
# Debug
$self->plugin('DebugDumperHelper');
$self->secrets($config->{secrets});
# Helpers
$self->helper(
provisioning => sub {
my $c = shift;
# Create some short patterns for provisioning
if (LufiDB::Files->count('WHERE created_at IS NULL') < $c->config('provisioning')) {
for (my $i = 0; $i < $c->config('provis_step'); $i++) {
if (LufiDB->begin) {
my $short;
do {
$short= $c->shortener($c->config('length'));
} while (LufiDB::Files->count('WHERE short = ?', $short));
LufiDB::Files->create(
short => $short
);
LufiDB->commit;
}
}
}
}
);
$self->helper(
get_empty => sub {
my $c = shift;
my @records = LufiDB::Files->select('WHERE created_at IS NULL LIMIT 1');
return $records[0];
}
);
$self->helper(
shortener => sub {
my $c = shift;
my $length = shift;
my @chars = ('a'..'z','A'..'Z','0'..'9', '-', '_');
my $result = '';
foreach (1..$length) {
$result .= $chars[rand scalar(@chars)];
}
return $result;
}
);
$self->helper(
ip => sub {
my $c = shift;
my $proxy = $c->req->headers->header('X-Forwarded-For');
my $ip = ($proxy) ? $proxy : $c->tx->remote_address;
my $remote_port = (defined($c->req->headers->header('X-Remote-Port'))) ? $c->req->headers->header('X-Remote-Port') : $c->tx->remote_port;
return "$ip remote port:$remote_port";
}
);
$self->helper(
default_delay => sub {
my $c = shift;
return $c->config('default_delay') if ($c->config('default_delay') >= 0);
warn "default_delay set to a negative value. Default to 0.";
return 0;
}
);
$self->helper(
is_selected => sub {
my $c = shift;
my $num = shift;
return ($num == $c->default_delay) ? 'selected="selected"' : '';
}
);
# Hooks
$self->hook(
after_dispatch => sub {
shift->provisioning();
}
);
# For the first launch (after, this isn't really useful)
$self->provisioning();
# Create directory if needed
mkdir('files', 0700) unless (-d 'files');
# Default layout
$self->defaults(layout => 'default');
# Router
my $r = $self->routes;
# Page for files uploading
$r->get('/' => sub {
shift->render(template => 'index');
})->name('index');
# Get a file
$r->get('/r/:short')
->to('Files#r')
->name('render');
# List of files (use localstorage, so the server know nothing about files
$r->get('/files' => sub {
shift->render(template => 'files');
})->name('files');
# Get counter informations about a file
$r->post('/c')->
to('Files#get_counter')->
name('counter');
# Get counter informations about a file
$r->get('/d/:short/:token')->
to('Files#delete')->
name('delete');
# About page
$r->get('/about' => sub {
shift->render(template => 'about');
})->name('about');
# Upload files websocket
$r->websocket('/upload')
->to('Files#upload')
->name('upload');
# Get files websocket
$r->websocket('/download/:short')
->to('Files#download')
->name('download');
}
1;
package Lufi::Controller::Files;
use Mojo::Base 'Mojolicious::Controller';
use Mojo::JSON qw(encode_json decode_json true false);
use Mojo::Util qw(slurp spurt encode);
use LufiDB;
use Lufi::File;
use Lufi::Slice;
use File::Spec::Functions;
sub upload {
my $c = shift;
$c->inactivity_timeout(300000);
$c->debug('Client connected');
$c->on(
message => sub {
my ($ws, $text) = @_;
my ($json) = split('XXMOJOXX', $text, 2);
$json = encode 'UTF-8', $json;
$json = decode_json $json;
$c->debug('Got message');
my $f;
if (defined($json->{id})) {
my @records = LufiDB::Files->select('WHERE short = ?', $json->{id});
$f = Lufi::File->new(record => $records[0]);
} else {
$f = Lufi::File->new(
record => $c->get_empty,
created_by => $c->ip,
delete_at_first_view => ($json->{del_at_first_view}) ? 1 : 0,
delete_at_day => $json->{delay},
mediatype => $json->{type},
filename => $json->{name},
filesize => $json->{size},
nbslices => $json->{total},
mod_token => $c->shortener($c->config('token_length'))
);
$f->write;
}
# Create directory
my $dir = catdir('files', $f->short);
mkdir($dir, 0700) unless (-d $dir);
# Create slice file
my $file = catfile($dir, $json->{part}.'.part');
my $s = Lufi::Slice->new(
short => $f->short,
j => $json->{part},
path => $file
);
spurt $text, $file;
push @{$f->slices}, $s;
if (($json->{part} + 1) == $json->{total}) {
$f->complete(1);
$c->provisioning;
}
$f->write;
$ws->send(sprintf('{"success": true, "i": %d, "j": %d, "parts": %d, "short": "%s", "name": "%s", "size": %d, "del_at_first_view": %s, "created_at": %d, "delay": %d, "token": "%s"}', $json->{i}, $json->{part}, $json->{total}, $f->short, $f->filename, $f->filesize, (($f->delete_at_first_view) ? 'true' : 'false'), $f->created_at, $f->delete_at_day, $f->mod_token));
}
);
$c->on(
finish => sub {
$c->debug('Client disconnected');
}
);
}
sub download {
my $c = shift;
my $short = $c->param('short');
$c->inactivity_timeout(300000);
$c->debug('Client connected');
my @records = LufiDB::Files->select('WHERE short = ?', $short);
my $f = Lufi::File->new(record => $records[0]);
$c->on(
message => sub {
my ($ws, $json) = @_;
$json = decode_json $json;
if (defined($json->{part})) {
# Make $num an integer instead of a string
my $num = $json->{part} + 0;
my $e = $f->slices->[$num];
my $text = slurp $e->path;
$c->send($text);
} elsif (defined($json->{ended}) && $json->{ended}) {
$f->counter($f->counter + 1);
$f->last_access_at(time);
if ($f->delete_at_first_view) {
$f->delete;
} else {
$f->write;
}
}
}
);
$c->on(
finish => sub {
$c->debug('Client disconnected');
}
);
}
sub r {
my $c = shift;
my $short = $c->param('short');
my @records = LufiDB::Files->select('WHERE short = ?', $short);
if (scalar @records) {
my $f = Lufi::File->new(record => $records[0]);
my $msg = $c->l('The file has been deleted and is no more available.') if $f->deleted;
return $c->render(
template => 'render',
f => $f,
msg => $msg
);
} else {
return $c->render(
template => 'render',
f => undef,
msg => $c->l('Could not find the file. Are you sure of the URL?')
);
}
}
sub get_counter {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my @records = LufiDB::Files->select('WHERE short = ? AND mod_token = ?', ($short, $token));
if (scalar(@records)) {
return $c->render(
json => {
success => true,
counter => $records[0]->counter
}
);
}
return $c->render(
json => {
success => false,
msg => $c->l('Unable to get counter')
}
);
}
sub delete {
my $c = shift;
my $short = $c->param('short');
my $token = $c->param('token');
my @records = LufiDB::Files->select('WHERE short = ? AND mod_token = ?', ($short, $token));
if (scalar(@records)) {
my $f = Lufi::File->new(record => $records[0]);
my $msg;
if ($f->deleted) {
$msg = $c->l('The file has already been deleted');
} else {
$f->delete;
$msg = $c->l('File deleted');
}
return $c->respond_to(
json => {
success => true,
msg => $msg
},
any => sub {
$c->render(
template => 'msg',
f => $f,
msg => $msg
);
}
);
} else {
my $msg = $c->l('Could not find the file. Are you sure of the URL and the token?');
return $c->respond_to(
json => {
success => false,
msg => $msg
},
any => sub {
$c->render(
template => 'msg',
f => undef,
msg => $msg
);
}
);
}
}
1;
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::File;
use Mojo::Base -base;
use Mojo::Collection;
use LufiDB;
has 'record';
has 'short';
has 'deleted' => 0;
has 'mediatype';
has 'filename';
has 'filesize';
has 'counter' => 0;
has 'delete_at_first_view' => 0;
has 'delete_at_day';
has 'created_at' => sub {
return time;
};
has 'created_by';
has 'last_access_at';
has 'mod_token';
has 'nbslices';
has 'complete' => 0;
has 'slices' => sub {
return Mojo::Collection->new();
};
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if defined $c->record;
return $c;
}
sub write {
my $c = shift;
$c->record->update(
deleted => $c->deleted,
mediatype => $c->mediatype,
filename => $c->filename,
filesize => $c->filesize,
counter => $c->counter,
delete_at_first_view => $c->delete_at_first_view,
delete_at_day => $c->delete_at_day,
created_at => $c->created_at,
created_by => $c->created_by,
last_access_at => $c->last_access_at,
mod_token => $c->mod_token,
nbslices => $c->nbslices,
complete => $c->complete,
);
$c->slices->each(
sub {
my ($e, $num) = @_;
$e->write;
}
);
return $c;
}
sub delete {
my $c = shift;
$c->slices->each(sub {
my ($e, $num) = @_;
unlink $e->path;
});
$c->deleted(1);
$c->write;
return $c;
}
sub _slurp {
my $c = shift;
$c->short($c->record->short);
$c->deleted($c->record->deleted) if defined $c->record->deleted;
$c->mediatype($c->record->mediatype) if defined $c->record->mediatype;
$c->filename($c->record->filename) if defined $c->record->filename;
$c->filesize($c->record->filesize) if defined $c->record->filesize;
$c->counter($c->record->counter) if defined $c->record->counter;
$c->delete_at_first_view($c->record->delete_at_first_view) if defined $c->record->delete_at_first_view;
$c->delete_at_day($c->record->delete_at_day) if defined $c->record->delete_at_day;
$c->created_at($c->record->created_at) if defined $c->record->created_at;
$c->created_by($c->record->created_by) if defined $c->record->created_by;
$c->last_access_at($c->record->last_access_at) if defined $c->record->last_access_at;
$c->mod_token($c->record->mod_token) if defined $c->record->mod_token;
$c->nbslices($c->record->nbslices) if defined $c->record->nbslices;
$c->complete($c->record->complete) if defined $c->record->complete;
my @slices = LufiDB::Slices->select('WHERE short = ? ORDER BY j ASC', $c->short);
for my $s (@slices) {
push @{$c->slices}, Lufi::Slice->new(record => $s);
}
return $c;
}
1;
package Lufi::I18N;
use base 'Locale::Maketext';
use File::Basename qw/dirname/;
use Locale::Maketext::Lexicon {
_auto => 1,
_decode => 1,
_style => 'gettext',
'*' => [Gettext => dirname(__FILE__) . '/I18N/*.po']
};
1;
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=utf8\n"
"Content-Transfer-Encoding: 8bit\n"
#. ($delay)
#. (config('max_delay')
#: templates/index.html.ep:12 templates/index.html.ep:21 templates/index.html.ep:22
msgid "%1 days"
msgstr "%1 days"
#: templates/index.html.ep:5
msgid "1 year"
msgstr ""
#: templates/index.html.ep:21 templates/index.html.ep:4
msgid "24 hours"
msgstr ""
#: templates/layouts/default.html.ep:35
msgid "About"
msgstr ""
#: templates/index.html.ep:44
msgid "Click to open the file browser"
msgstr ""
#: templates/index.html.ep:59
msgid "Copy to clipboard"
msgstr ""
#: lib/Lufi/Controller/Files.pm:188
msgid "Could not find the file. Are you sure of the URL and the token?"
msgstr ""
#: lib/Lufi/Controller/Files.pm:132
msgid "Could not find the file. Are you sure of the URL?"
msgstr ""
#: templates/index.html.ep:35
msgid "Delete at first view?"
msgstr ""
#: templates/index.html.ep:60
msgid "Deletion link"
msgstr ""
#: templates/index.html.ep:58
msgid "Download link"
msgstr ""
#: templates/index.html.ep:41
msgid "Drop files here"
msgstr ""
#: lib/Lufi/Controller/Files.pm:172
msgid "File deleted"
msgstr ""
#: templates/index.html.ep:57
msgid "Hit Enter, then Ctrl+C to copy all the download links"
msgstr ""
#: templates/index.html.ep:56
msgid "Hit Enter, then Ctrl+C to copy the download link"
msgstr ""
#: templates/layouts/default.html.ep:34
msgid "My files"
msgstr ""
#: lib/Lufi/Controller/Files.pm:169
msgid "The file has already been deleted"
msgstr ""
#: lib/Lufi/Controller/Files.pm:122
msgid "The file has been deleted and is no more available."
msgstr ""
#: lib/Lufi/Controller/Files.pm:154
msgid "Unable to get counter"
msgstr ""
#: templates/layouts/default.html.ep:33
msgid "Upload files"
msgstr ""
#: templates/index.html.ep:49
msgid "Uploaded files"
msgstr ""
#: templates/index.html.ep:3
msgid "no time limit"
msgstr ""
#: templates/index.html.ep:42
msgid "or"
msgstr ""
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Lufi::Slice;
use Mojo::Base -base;
use LufiDB;
has 'record';
has 'short';
has 'j';
has 'path';
sub new {
my $c = shift;
$c = $c->SUPER::new(@_);
$c = $c->_slurp if defined $c->record;
return $c;
}
sub write {
my $c = shift;
if (defined $c->record) {
$c->record->update(
short => $c->short,
j => $c->j,
path => $c->path
);
} else {
my $record = LufiDB::Slices->create(
short => $c->short,
j => $c->j,
path => $c->path
);
$c->record($record);
}
return $c;
}
sub _slurp {
my $c = shift;
$c->short($c->record->short) if defined $c->record->short;
$c->j($c->record->j) if defined $c->record->j;
$c->path($c->record->path) if defined $c->record->path;
return $c;
}
1;
# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package LufiDB;
use Mojolicious;
use FindBin qw($Bin);
use File::Spec::Functions;
BEGIN {
my $m = Mojolicious->new;
our $config = $m->plugin('Config' =>
{
file => catfile($Bin, '..' ,'lufi.conf'),
default => {
db_path => 'lufi.db'
}