File Coverage

blib/lib/Mojolicious/Plugin/StaticShare.pm
Criterion Covered Total %
statement 52 88 59.0
branch 11 46 23.9
condition 6 47 12.7
subroutine 10 16 62.5
pod 1 3 33.3
total 80 200 40.0


line stmt bran cond sub pod time code
1             package Mojolicious::Plugin::StaticShare;
2 2     2   53405 use Mojo::Base 'Mojolicious::Plugin';
  2         153234  
  2         13  
3 2     2   1405 use Mojo::File qw(path);
  2         23925  
  2         95  
4 2     2   383 use Mojolicious::Types;
  2         754  
  2         15  
5 2     2   405 use Mojo::Path;
  2         1469  
  2         12  
6 2     2   57 use Mojo::Util;# qw(decode);
  2         4  
  2         58  
7 2     2   9 use Scalar::Util 'weaken';
  2         4  
  2         3816  
8              
9             my $PKG = __PACKAGE__;
10              
11             has qw(app) => undef, weak => 1;
12             has qw(config);# => undef, weak => 1;
13             has root_url => sub { Mojo::Path->new(shift->config->{root_url})->leading_slash(1)->trailing_slash(1) };
14             has root_dir => sub { Mojo::Path->new(shift->config->{root_dir} // '.')->trailing_slash(1) };
15             has admin_pass => sub { shift->config->{admin_pass} };
16             has access => sub { shift->config->{access} };
17             has public_uploads => sub { !! shift->config->{public_uploads} };
18             has render_dir => sub { shift->config->{render_dir} };
19             has dir_index => sub { shift->config->{dir_index} // [qw(README.md INDEX.md README.pod INDEX.pod)] };
20             has render_pod => sub { shift->config->{render_pod} };
21             has render_markdown => sub { shift->config->{render_markdown} };
22             has markdown_pkg => sub { shift->config->{markdown_pkg} // 'Text::Markdown::Hoedown' };
23             has templates_dir => sub { shift->config->{templates_dir} };
24             has markdown => sub {# parser object
25             __internal__::Markdown->new(shift->markdown_pkg);
26             };
27             has re_markdown => sub { qr{[.]m(?:d(?:own)?|kdn?|arkdown)$}i };
28             has re_pod => sub { qr{[.]p(?:od|m|l)$} };
29             has re_html => sub { qr{[.]html?$} };
30             has mime => sub { Mojolicious::Types->new };
31             has max_upload_size => sub { shift->config->{max_upload_size} };
32             #separate routes store for matching without conflicts
33             has routes => sub { Mojolicious::Routes->new }; # для max_upload_size
34             has routes_names => sub {# тоже для max_upload_size
35             my $self = shift;
36             return [$self." ROOT GET (#1)", $self." PATH GET (#2)", $self." ROOT POST (#3)", $self." PATH POST (#4)"];
37             };
38             has debug => sub { shift->config->{debug} // $ENV{StaticShare_DEBUG} };
39              
40              
41             sub register {# none magic
42 1     1 1 72 my ($self, $app, $args) = @_;
43 1         4 $self->config($args);
44 1         7 $self->app($app);
45            
46 1         9 my $push_class = "$PKG\::Templates";
47 1         5 my $push_path = path(__FILE__)->sibling('StaticShare')->child('static');
48            
49             require Mojolicious::Plugin::StaticShare::Templates
50 1         7 and push @{$app->renderer->classes}, grep($_ eq $push_class, @{$app->renderer->classes}) ? () : $push_class
  1         15  
51 1 50 33     121 and push @{$app->static->paths}, grep($_ eq $push_path, @{$app->static->paths}) ? () : $push_path
  1 50 33     14  
  1 50 50     10  
      0        
      33        
52             unless ($self->render_dir // '') eq 0
53             && ($self->render_markdown // '') eq 0;
54 1 0       16 push @{$app->renderer->paths}, ref $self->templates_dir ? @{$self->templates_dir} : $self->templates_dir
  0 50       0  
  0         0  
55             if $self->templates_dir;
56            
57             # ROUTING 4 routes
58 1         7 my $route = $self->root_url->clone->merge('*pth');#"$args->{root_url}/*pth";
59 1         248 my $r = $app->routes;
60 1         6 my $names = $self->routes_names;
61 1         3 $r->get($self->root_url->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'get', pth=>'', plugin=>$self, )->name($names->[0]);#$PKG
62 1         316 $r->get($route->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'get', plugin=>$self, )->name($names->[1]);#;
63 1         268 $r->post($self->root_url->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'post', pth=>'', plugin=>$self, )->name($names->[2]);#->name("$PKG ROOT POST");
64 1         193 $r->post($route->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'post', plugin=>$self, )->name($names->[3]);#;
65            
66             # only POST uploads
67 1 50 0     242 $self->routes->post($self->root_url->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'post', pth=>'', plugin=>$self, )->name($names->[2])
68             and $self->routes->post($route->to_route)->to(namespace=>$PKG, controller=>"Controller", action=>'post', plugin=>$self, )->name($names->[3])
69             if defined $self->max_upload_size;
70            
71             path($self->config->{root_dir})->make_path
72 1 50 33     8 unless !$self->config->{root_dir} || -e $self->config->{root_dir};
73              
74 1         11 $app->helper(i18n => \&i18n);
75             #~ $app->helper(StaticShareIsAdmin => sub { $self->is_admin(@_) });
76            
77             #POD
78             $self->app->plugin(PODRenderer => {no_perldoc => 1})
79 1 50 0     122 unless $self->app->renderer->helpers->{'pod_to_html'} && ($self->render_pod // '') eq 0 ;
      33        
80            
81             # PATCH EMIT CHUNK
82 1 50       8 $self->_hook_chunk()
83             if defined $self->max_upload_size;
84            
85 1         13 return ($app, $self);
86             }
87              
88             my %loc = (
89             'ru-ru'=>{
90             'Not found'=>"Не найдено",
91             'Error on path'=>"Ошибка в",
92             'Error'=>"Ошибка",
93             'Permission denied'=>"Нет доступа",
94             'Cant open directory'=>"Нет доступа в папку",
95             'Share'=>'Обзор',
96             'Edit'=>'Редактировать',
97             'Index of'=>'Содержание',
98             'Dirs'=>'Папки',
99             'Files'=>'Файлы',
100             'Name'=>'Название файла',
101             'Size'=>'Размер',
102             'Last Modified'=>'Дата изменения',
103             'Up'=>'Выше',
104             'Down'=>'Ниже',
105             'Add uploads'=>'Добавить файлы',
106             'Add dir'=>'Добавить папку',
107             'root'=>"корень",
108             'Uploading'=>'Загружается',
109             'file is too big'=>'слишком большой файл',
110             'path is not directory'=>"нет такого каталога/папки",
111             'file already exists' => "такой файл уже есть",
112             'new dir name'=>"имя новой папки",
113             'Confirm to delete these files'=>"Подтвердите удаление этих файлов",
114             'Confirm to delete these dirs'=>"Подтвердите удаление этих папок",
115             'I AM SURE'=>"ДА",
116             'Save'=> 'Сохранить',
117             'Success saved' => "Успешно сохранено",
118             'you cant delete'=>"Удаление запрещено, обратитесь к админу",
119             'you cant upload'=>"Запрещено, обратитесь к админу",
120             'you cant create dir'=>"Создание папки запрещено, обратитесь к админу",
121             'you cant rename'=>"Переименование запрещено, обратитесь к админу",
122             'you cant edit' => "Редактирование запрещено, обратитесь к админу",
123             },
124             );
125             sub i18n {# helper
126 15     15 0 38928 my ($c, $str, $lang) = @_;
127             #~ $lang //= $c->stash('language');
128 15 50       36 return $str
129             unless $c->stash('language');
130 15         151 my $loc;
131 15         27 for ($c->stash('language')->languages) {
132 15 50       252 return $str
133             if /en/;
134 0 0 0     0 $loc = $loc{$_} || $loc{lc $_} || $loc{lc "$_-$_"}
135             and last;
136             }
137 0 0 0     0 return $loc->{$str} || $loc->{lc $str} || $str
138             if $loc;
139 0         0 return $str;
140             }
141              
142             sub is_admin {# as helper
143 1     1 0 6 my ($self, $c) = @_;
144             return
145 1 50       4 unless my $pass = $self->admin_pass;
146 0           my $sess = $c->session;
147 0 0 0       $sess->{StaticShare}{admin} = 1
148             if $c->param('admin') && $c->param('admin') eq $pass;
149 0   0       return $sess->{StaticShare} && $sess->{StaticShare}{admin};
150             }
151              
152             my $patched;
153             sub _hook_chunk {
154 0     0     my $self = shift;
155            
156 0 0         _patch_emit_chunk() unless $patched++;
157            
158 0           weaken $self;
159             $self->app->hook(after_build_tx => sub {
160 0     0     my ($tx, $app) = @_;
161             $tx->once('chunk'=>sub {
162 0           my ($tx, $chunk) = @_;
163 0 0         return unless $tx->req->method =~ /PUT|POST/;
164 0           my $url = $tx->req->url->to_abs;
165 0           my $match = Mojolicious::Routes::Match->new(root => $self->routes);
166             # Mojolicious::Controller->new
167 0           $match->find(undef, {method => $tx->req->method, path => $tx->req->url->path->to_route,});# websocket => $ws
168 0   0       my $route = $match->endpoint
169             || return;
170            
171             #~ warn "TX CHUNK ROUTE: ", $route->name;# eq $self->routes_names->[1] || $route->name eq $self->routes_names->[3]);#Mojo::Util::dumper($url);, length($chunk)
172 0 0 0       $tx->req->max_message_size($self->max_upload_size)
173             if ($route->name eq $self->routes_names->[2]) || ($route->name eq $self->routes_names->[3])
174             # TODO admin session
175            
176 0           });
177 0           });
178            
179             }
180              
181             sub _patch_emit_chunk {
182            
183             Mojo::Util::monkey_patch 'Mojo::Transaction::HTTP', 'server_read' => sub {
184 0     0     my ($self, $chunk) = @_;
185            
186 0           $self->emit('chunk before req parse'=>$chunk);### только одна строка патча этот эмит
187            
188             # Parse request
189 0           my $req = $self->req;
190 0 0         $req->parse($chunk) unless $req->error;
191            
192 0           $self->emit('chunk'=>$chunk);### только одна строка патча этот эмит
193            
194             # Generate response
195 0 0 0       $self->emit('request') if $req->is_finished && !$self->{handled}++;
196            
197 0     0     };
198            
199             }
200              
201             our $VERSION = '0.073';
202              
203             ##############################################
204             package __internal__::Markdown;
205             sub new {
206 0     0     my $class = shift;
207 0           my $pkg = shift;
208             return
209 0 0         unless eval "require $pkg; 1";#
210             #~ $pkg->import
211             #~ if $pkg->can('import');
212 0 0 0       return $pkg->new()
213             if $pkg->can('new') && $pkg->can('parse');
214             return
215 0 0         unless $pkg->can('markdown');
216 0           bless {pkg=>$pkg} => $class;
217             }
218              
219 2     2   16 sub parse { my $self = shift; no strict 'refs'; ($self->{pkg}.'::markdown')->(@_); }
  2     0   3  
  2         207  
  0            
  0            
220              
221              
222             =pod
223              
224             =encoding utf8
225              
226             Доброго всем
227              
228             =head1 Mojolicious::Plugin::StaticShare
229              
230             ¡ ¡ ¡ ALL GLORY TO GLORIA ! ! !
231              
232             =head1 NAME
233              
234             Mojolicious::Plugin::StaticShare - browse, upload, copy, move, delete, edit, rename static files and dirs.
235              
236             =head1 VERSION
237              
238             0.073
239              
240             =head1 SYNOPSIS
241              
242             # Mojolicious
243             $app->plugin('StaticShare', );
244              
245             # Mojolicious::Lite
246             plugin 'StaticShare', ;
247            
248             # oneliner
249             $ perl -MMojolicious::Lite -E 'plugin("StaticShare", root_url=>"/my/share",)->secrets([rand])->start' daemon
250              
251             L also.
252              
253             =head1 DESCRIPTION
254              
255             This plugin allow to share static files/dirs/markdown and has public and admin functionality:
256              
257             =head2 Public interface
258              
259             Can browse and upload files if name not exists.
260              
261             =head2 Admin interface
262              
263             Can copy, move, delete, rename and edit content of files/dirs.
264              
265             Append param C<< admin= option >> to any url inside B requests (see below).
266              
267             =head1 OPTIONS
268              
269             =head2 root_dir
270              
271             Absolute or relative file system path root directory. Defaults to '.'.
272              
273             root_dir => '/mnt/usb',
274             root_dir => 'foo',
275              
276             =head2 root_url
277              
278             This prefix to url path. Defaults to '/'.
279              
280             root_url => '/', # mean route '/*pth'
281             root_url => '', # mean also route '/*pth'
282             root_url => '/my/share', # mean route '/my/share/*pth'
283              
284             See L.
285              
286             =head2 admin_pass
287              
288             Admin password (be sure https) for admin tasks. None defaults.
289              
290             admin_pass => '$%^!!9nes--', #
291              
292             Signin to admin interface C< https://myhost/my/share/foo/bar?admin=$%^!!9nes-- >
293              
294             =head2 render_dir
295              
296             Template path, format, handler, etc which render directory index. Defaults to builtin things.
297              
298             render_dir => 'foo/dir_index',
299             render_dir => {template => 'foo/my_directory_index', foo=>...},
300             # Disable directory index rendering
301             render_dir => 0,
302              
303             =head3 Usefull stash variables
304              
305             Plugin make any stash variables for rendering: C, C, C, C, C, C, C
306              
307             =head4 pth
308              
309             Path of request exept C option, as L object.
310              
311             =head4 url_path
312              
313             Path of request with C option, as L object.
314              
315             =head4 language
316              
317             Req header AcceptLanguage as L object.
318              
319             =head4 dirs
320              
321             List of scalars dirnames. Not sorted.
322              
323             =head4 files
324              
325             List of hashrefs (C keys) files. Not sorted.
326              
327             =head4 index
328              
329             Filename for markdown or pod rendering in page below the column dirs and column files.
330              
331             =head2 templates_dir
332              
333             String or arrayref strings. Simply C<< push @{$app->renderer->paths}, ; >>. None defaults.
334              
335             Mainly needs for layouting markdown. When you set this option then you can define layout inside markdown/pod files like syntax:
336              
337             % layouts/foo.html.ep
338             # Foo header
339              
340             =head2 render_markdown
341              
342             Same as B but for markdown files. Defaults to builtin things.
343              
344             render_markdown => 'foo/markdown',
345             render_markdown => {template => 'foo/markdown', foo=>...},
346             # Disable markdown rendering
347             render_markdown => 0,
348              
349             =head2 markdown_pkg
350              
351             Module name for render markdown. Must contains sub C or method C. Defaults to L.
352              
353             markdown_pkg => 'Foo::Markup';
354              
355             Does not need to install if C<< render_markdown => 0 >> or never render md files.
356              
357             =head2 render_pod
358              
359             Template path, format, handler, etc which render pod files. Defaults to builtin things.
360              
361             render_pod=>'foo/pod',
362             render_pod => {template => 'foo/pod', layout=>'pod', foo=>...},
363             # Disable pod rendering
364             render_pod => 0,
365              
366             =head2 dir_index
367              
368             Arrayref to match files to include to directory index page. Defaults to C<< [qw(README.md INDEX.md README.pod INDEX.pod)] >>.
369              
370             dir_index => [qw(DIR.md)],
371             dir_index => 0, # disable include markdown to index dir page
372              
373             =head2 public_uploads
374              
375             Boolean to disable/enable uploads for public users. Defaults to undef (disable).
376              
377             public_uploads=>1, # enable
378              
379             =head2 max_upload_size
380              
381             max_upload_size=>0, # unlimited POST
382              
383             Numeric value limiting uploads size for route.
384             WARN-EXPIRIMENTAL patching of L
385             for emit chunk event.
386              
387             See also L, L.
388              
389             =head1 Extended markdown & pod
390              
391             You can place attributes like:
392              
393             =head2 id (# as prefix)
394              
395             =head2 classnames (dot as prefix and separator)
396              
397             =head2 css-style rules (key:value; colon separator and semicolon terminator)
398              
399             to markup elements as below.
400              
401             In markdown:
402              
403             # {#foo123 .class1 .class2 padding: 0 0.5rem;} Header 1
404             {.brown-text} brown paragraph text ...
405              
406             In pod:
407              
408             =head2 {.class1.blue-text border-bottom: 1px dotted;} Header 2
409            
410             {.red-text} red color text...
411              
412             =head1 METHODS
413              
414             L inherits all methods from
415             L and implements the following new ones.
416              
417             =head2 register
418              
419             $plugin->register(Mojolicious->new);
420              
421             Register plugin in L application.
422              
423             =head1 MULTI PLUGIN
424              
425             A possible:
426              
427             # Mojolicious
428             $app->plugin('StaticShare', )
429             ->plugin('StaticShare', ); # and so on ...
430            
431             # Mojolicious::Lite
432             app->config(...)
433             ->plugin('StaticShare', )
434             ->plugin('StaticShare', ) # and so on ...
435             ...
436              
437             =head1 UTF-8
438              
439             Everywhere and everything: module, files, content.
440              
441             =head1 WINDOWS OS
442              
443             It was not tested but I hope you dont worry and have happy.
444              
445             =head1 SEE ALSO
446              
447             L
448              
449             L, L, L.
450              
451             =head1 AUTHOR
452              
453             Михаил Че (Mikhail Che), C<< >>
454              
455             =head1 BUGS / CONTRIBUTING
456              
457             Please report any bugs or feature requests at L. Pull requests also welcome.
458              
459             =head1 COPYRIGHT
460              
461             Copyright 2017+ Mikhail Che.
462              
463             This library is free software; you can redistribute it and/or modify
464             it under the same terms as Perl itself.
465              
466             =cut