package Mojolicious::Plugin::StaticShare; use Mojo::Base 'Mojolicious::Plugin'; use Mojo::File qw(path); use Mojolicious::Types; use Mojo::Path; #~ use Mojo::Util qw(decode); my $PKG = __PACKAGE__; has [qw(app config)]; has root_url => sub { Mojo::Path->new(shift->config->{root_url})->leading_slash(1)->trailing_slash(1) }; has root_dir => sub { Mojo::Path->new(shift->config->{root_dir} // '.')->trailing_slash(1) }; has admin_pass => sub { shift->config->{admin_pass} }; has access => sub { shift->config->{access} }; has public_uploads => sub { !! shift->config->{public_uploads} }; has render_dir => sub { shift->config->{render_dir} }; has dir_index => sub { shift->config->{dir_index} // [qw(README.md INDEX.md README.pod INDEX.pod)] }; has render_pod => sub { shift->config->{render_pod} }; has render_markdown => sub { shift->config->{render_markdown} }; has markdown_pkg => sub { shift->config->{markdown_pkg} // 'Text::Markdown::Hoedown' }; has templates_dir => sub { shift->config->{templates_dir} }; has markdown => sub {# parser object __internal__::Markdown->new(shift->markdown_pkg); }; has re_markdown => sub { qr{[.]m(?:d(?:own)?|kdn?|arkdown)$}i }; has re_pod => sub { qr{[.]p(?:od|m|l)$} }; has mime => sub { Mojolicious::Types->new }; sub register {# none magic my ($self, $app, $args) = @_; $self->config($args); $self->app($app); my $push_class = "$PKG\::Templates"; my $push_path = path(__FILE__)->sibling('StaticShare')->child('static'); require Mojolicious::Plugin::StaticShare::Templates and push @{$app->renderer->classes}, grep($_ eq $push_class, @{$app->renderer->classes}) ? () : $push_class and push @{$app->static->paths}, grep($_ eq $push_path, @{$app->static->paths}) ? () : $push_path unless ($self->render_dir // '') eq 0 && ($self->render_markdown // '') eq 0; push @{$app->renderer->paths}, ref $self->templates_dir ? @{$self->templates_dir} : $self->templates_dir if $self->templates_dir; my $route = $self->root_url->clone->merge('*pth');#"$args->{root_url}/*pth"; my $r = $app->routes; $r->get($self->root_url->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'get', pth=>'', plugin=>$self);#->name("$PKG ROOT GET"); $r->post($self->root_url->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'post', pth=>'', plugin=>$self);#->name("$PKG ROOT POST"); $r->get($route->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'get', plugin=>$self );#->name("$PKG GET"); $r->post($route->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'post', plugin=>$self );#->name("$PKG POST"); path($self->config->{root_dir})->make_path unless !$self->config->{root_dir} || -e $self->config->{root_dir}; $app->helper(i18n => \&i18n); #~ $app->helper(StaticShareIsAdmin => sub { $self->is_admin(@_) }); #POD $self->app->plugin(PODRenderer => {no_perldoc => 1}) unless $self->app->renderer->helpers->{'pod_to_html'} && ($self->render_pod // '') eq 0 ; return ($app, $self); } my %loc = ( 'ru-ru'=>{ 'Not found'=>"Не найдено", 'Error on path'=>"Ошибка в", 'Error'=>"Ошибка", 'Permission denied'=>"Нет доступа", 'Cant open directory'=>"Нет доступа в папку", 'Share'=>'Обзор', 'Edit'=>'Редактировать', 'Index of'=>'Содержание', 'Dirs'=>'Папки', 'Files'=>'Файлы', 'Name'=>'Название файла', 'Size'=>'Размер', 'Last Modified'=>'Дата изменения', 'Up'=>'Выше', 'Down'=>'Ниже', 'Add uploads'=>'Добавить файлы', 'Add dir'=>'Добавить папку', 'root'=>"корень", 'Uploading'=>'Загружается', 'file is too big'=>'слишком большой файл', 'path is not directory'=>"нет такого каталога/папки", 'file already exists' => "такой файл уже есть", 'new dir name'=>"имя новой папки", 'Confirm to delete these files'=>"Подтвердите удаление этих файлов", 'Confirm to delete these dirs'=>"Подтвердите удаление этих папок", 'I AM SURE'=>"ДА", 'Save'=> 'Сохранить', 'Success saved' => "Успешно сохранено", }, ); sub i18n {# helper my ($c, $str, $lang) = @_; #~ $lang //= $c->stash('language'); return $str unless $c->stash('language'); my $loc; for ($c->stash('language')->languages) { return $str if /en/; $loc = $loc{$_} || $loc{lc $_} || $loc{lc "$_-$_"} and last; } return $loc->{$str} || $loc->{lc $str} || $str if $loc; return $str; } sub is_admin {# as helper my ($self, $c) = @_; return unless my $pass = $self->admin_pass; my $sess = $c->session; $sess->{StaticShare}{admin} = 1 if $c->param('admin') && $c->param('admin') eq $pass; return $sess->{StaticShare} && $sess->{StaticShare}{admin}; } ############################################## package __internal__::Markdown; sub new { my $class = shift; my $pkg = shift; return unless eval "require $pkg; 1";# #~ $pkg->import #~ if $pkg->can('import'); return $pkg->new() if $pkg->can('new') && $pkg->can('parse'); return unless $pkg->can('markdown'); bless {pkg=>$pkg} => $class; } sub parse { my $self = shift; no strict 'refs'; ($self->{pkg}.'::markdown')->(@_); } our $VERSION = '0.059'; =pod =encoding utf8 Доброго всем =head1 Mojolicious::Plugin::StaticShare ¡ ¡ ¡ ALL GLORY TO GLORIA ! ! ! =head1 NAME Mojolicious::Plugin::StaticShare - browse, upload, copy, move, delete static files and dirs. =head1 VERSION 0.059 =head1 SYNOPSIS # Mojolicious $app->plugin('StaticShare', ); # Mojolicious::Lite plugin 'StaticShare', ; # oneliner $ perl -MMojolicious::Lite -E 'plugin("StaticShare", root_url=>"/my/share",)->secrets([rand])->start' daemon L also. =head1 DESCRIPTION This plugin allow to share static files/dirs/markdown and has public and admin functionality: =head2 Public interface Can browse and upload files if name not exists. =head2 Admin interface Can copy, move, delete, rename and edit content of files/dirs. Append param C<< admin= option >> to any url inside B requests (see below). =head1 OPTIONS =head2 root_dir Absolute or relative file system path root directory. Defaults to '.'. root_dir => '/mnt/usb', root_dir => 'foo', =head2 root_url This prefix to url path. Defaults to '/'. root_url => '/', # mean route '/*pth' root_url => '', # mean also route '/*pth' root_url => '/my/share', # mean route '/my/share/*pth' See L. =head2 admin_pass Admin password (be sure https) for admin tasks. None defaults. admin_pass => '$%^!!9nes--', # Signin to admin interface C< https://myhost/my/share/foo/bar?admin=$%^!!9nes-- > =head2 render_dir Template path, format, handler, etc which render directory index. Defaults to builtin things. render_dir => 'foo/dir_index', render_dir => {template => 'foo/my_directory_index', foo=>...}, # Disable directory index rendering render_dir => 0, =head3 Usefull stash variables C, C, C, C, C, C, C =head4 pth Path of request exept C option, as L object. =head4 url_path Path of request with C option, as L object. =head4 language Req header AcceptLanguage as L object. =head4 dirs List of scalars dirnames. Not sorted. =head4 files List of hashrefs (C keys) files. Not sorted. =head4 index Filename for markdown or pod rendering in page below the column dirs and column files. =head2 templates_dir String or arrayref strings. Simply C<< push @{$app->renderer->paths}, ; >>. None defaults. Mainly needs for layouting markdown. When you set this option then you can define layout inside markdown/pod files like syntax: % layouts/foo.html.ep # Foo header =head2 render_markdown Same as B but for markdown files. Defaults to builtin things. render_markdown => 'foo/markdown', render_markdown => {template => 'foo/markdown', foo=>...}, # Disable markdown rendering render_markdown => 0, =head2 markdown_pkg Module name for render markdown. Must contains sub C or method C. Defaults to L. markdown_pkg => 'Foo::Markup'; Does not need to install if C<< render_markdown => 0 >> or never render md files. =head2 render_pod Template path, format, handler, etc which render pod files. Defaults to builtin things. render_pod=>'foo/pod', render_pod => {template => 'foo/pod', layout=>'pod', foo=>...}, # Disable pod rendering render_pod => 0, =head2 dir_index Arrayref to match files to include to directory index page. Defaults to C<< [qw(README.md INDEX.md README.pod INDEX.pod)] >>. dir_index => [qw(DIR.md)], dir_index => 0, # disable include markdown to index dir page =head2 public_uploads Boolean to disable/enable uploads for public users. Defaults to undef (disable). public_uploads=>1, # enable =head1 Extended markdown & pod You can place attributes like: =head2 id (# as prefix) =head2 classnames (dot as prefix and separator) =head2 css-style rules (key:value; colon separator and semicolon terminator) to markup elements as below. In markdown: # {#foo123 .class1 .class2 padding: 0 0.5rem;} Header 1 {.brown-text} brown paragraph text ... In pod: =head2 {.class1.blue-text border-bottom: 1px dotted;} Header 2 {.red-text} red color text... =head1 METHODS L inherits all methods from L and implements the following new ones. =head2 register $plugin->register(Mojolicious->new); Register plugin in L application. =head1 MULTI PLUGIN A possible: # Mojolicious $app->plugin('StaticShare', ) ->plugin('StaticShare', ); # and so on ... # Mojolicious::Lite app->config(...) ->plugin('StaticShare', ) ->plugin('StaticShare', ) # and so on ... ... =head1 UTF-8 Everywhere and everything: module, files, content. =head1 WINDOWS OS It was not tested but I hope you dont worry and have happy. =head1 SEE ALSO L L, L, L. =head1 AUTHOR Михаил Че (Mikhail Che), C<< >> =head1 BUGS / CONTRIBUTING Please report any bugs or feature requests at L. Pull requests also welcome. =head1 COPYRIGHT Copyright 2017 Mikhail Che. This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut