From 0c147086c0a286b85a1cc6fcb445c7effa7b0049 Mon Sep 17 00:00:00 2001 From: eleksir Date: Tue, 18 May 2021 12:32:46 +0300 Subject: [PATCH] =?UTF-8?q?=D0=9D=D0=B0=D1=87=D0=B0=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=BC=D0=BC=D0=B8=D1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 4 + LICENSE | 127 +++++++ README.md | 62 ++++ TODO | 13 + bin/yad_get_token.pl | 65 ++++ bin/yad_index_repo.pl | 142 ++++++++ bin/yad_refresh_token.pl | 56 +++ bin/yad_show_token.pl | 49 +++ bin/yad_upload_dir.pl | 89 +++++ bootstrap.sh | 5 + cpanfile | 7 + data/sample_config.json | 24 ++ lib/YadProxy.pm | 197 ++++++++++ lib/YadProxy/Conf.pm | 41 +++ lib/YadProxy/Lib.pm | 76 ++++ lib/Yandex/Disk.pm | 631 +++++++++++++++++++++++++++++++++ lib/Yandex/Disk/Public.pm | 164 +++++++++ lib/Yandex/OAuth.pm | 162 +++++++++ lib/Yandex/OAuth/Client.pm | 253 +++++++++++++ test/test_file.txt | 2 + test/yad_file_info.pl | 40 +++ test/yad_info.pl | 32 ++ test/yad_list_all_files.pl | 31 ++ test/yad_list_published.pl | 32 ++ test/yad_mkdir.pl | 44 +++ test/yad_upload_file.pl | 55 +++ test/yadproxy_dump_dircache.pl | 41 +++ yadproxy.psgi | 73 ++++ yadproxy.yaml | 17 + 29 files changed, 2534 insertions(+) create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 TODO create mode 100755 bin/yad_get_token.pl create mode 100755 bin/yad_index_repo.pl create mode 100755 bin/yad_refresh_token.pl create mode 100755 bin/yad_show_token.pl create mode 100755 bin/yad_upload_dir.pl create mode 100644 bootstrap.sh create mode 100644 cpanfile create mode 100644 data/sample_config.json create mode 100644 lib/YadProxy.pm create mode 100644 lib/YadProxy/Conf.pm create mode 100644 lib/YadProxy/Lib.pm create mode 100644 lib/Yandex/Disk.pm create mode 100644 lib/Yandex/Disk/Public.pm create mode 100644 lib/Yandex/OAuth.pm create mode 100644 lib/Yandex/OAuth/Client.pm create mode 100644 test/test_file.txt create mode 100755 test/yad_file_info.pl create mode 100755 test/yad_info.pl create mode 100755 test/yad_list_all_files.pl create mode 100755 test/yad_list_published.pl create mode 100755 test/yad_mkdir.pl create mode 100755 test/yad_upload_file.pl create mode 100755 test/yadproxy_dump_dircache.pl create mode 100644 yadproxy.psgi create mode 100644 yadproxy.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..90426f9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +data/oauth/** +data/config.json +data/fileindex/** +vendor_perl/** diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4ff3f42 --- /dev/null +++ b/LICENSE @@ -0,0 +1,127 @@ + The "Artistic License" + + Preamble + +The intent of this document is to state the conditions under which a +Package may be copied, such that the Copyright Holder maintains some +semblance of artistic control over the development of the package, +while giving the users of the package the right to use and distribute +the Package in a more-or-less customary fashion, plus the right to make +reasonable modifications. + +Definitions: + + "Package" refers to the collection of files distributed by the + Copyright Holder, and derivatives of that collection of files + created through textual modification. + + "Standard Version" refers to such a Package if it has not been + modified, or has been modified in accordance with the wishes + of the Copyright Holder as specified below. + + "Copyright Holder" is whoever is named in the copyright or + copyrights for the package. + + "You" is you, if you're thinking about copying or distributing + this Package. + + "Reasonable copying fee" is whatever you can justify on the + basis of media cost, duplication charges, time of people involved, + and so on. (You will not be required to justify it to the + Copyright Holder, but only to the computing community at large + as a market that must bear the fee.) + + "Freely Available" means that no fee is charged for the item + itself, though there may be fees involved in handling the item. + It also means that recipients of the item may redistribute it + under the same conditions they received it. + +1. You may make and give away verbatim copies of the source form of the +Standard Version of this Package without restriction, provided that you +duplicate all of the original copyright notices and associated disclaimers. + +2. You may apply bug fixes, portability fixes and other modifications +derived from the Public Domain or from the Copyright Holder. A Package +modified in such a way shall still be considered the Standard Version. + +3. You may otherwise modify your copy of this Package in any way, provided +that you insert a prominent notice in each changed file stating how and +when you changed that file, and provided that you do at least ONE of the +following: + + a) place your modifications in the Public Domain or otherwise make them + Freely Available, such as by posting said modifications to Usenet or + an equivalent medium, or placing the modifications on a major archive + site such as uunet.uu.net, or by allowing the Copyright Holder to include + your modifications in the Standard Version of the Package. + + b) use the modified Package only within your corporation or organization. + + c) rename any non-standard executables so the names do not conflict + with standard executables, which must also be provided, and provide + a separate manual page for each non-standard executable that clearly + documents how it differs from the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +4. You may distribute the programs of this Package in object code or +executable form, provided that you do at least ONE of the following: + + a) distribute a Standard Version of the executables and library files, + together with instructions (in the manual page or equivalent) on where + to get the Standard Version. + + b) accompany the distribution with the machine-readable source of + the Package with your modifications. + + c) give non-standard executables non-standard names, and clearly + document the differences in manual pages (or equivalent), together + with instructions on where to get the Standard Version. + + d) make other distribution arrangements with the Copyright Holder. + +5. You may charge a reasonable copying fee for any distribution of this +Package. You may charge any fee you choose for support of this +Package. You may not charge a fee for this Package itself. However, +you may distribute this Package in aggregate with other (possibly +commercial) programs as part of a larger (possibly commercial) software +distribution provided that you do not advertise this Package as a +product of your own. You may embed this Package's interpreter within +an executable of yours (by linking); this shall be construed as a mere +form of aggregation, provided that the complete Standard Version of the +interpreter is so embedded. + +6. The scripts and library files supplied as input to or produced as +output from the programs of this Package do not automatically fall +under the copyright of this Package, but belong to whoever generated +them, and may be sold commercially, and may be aggregated with this +Package. If such scripts or library files are aggregated with this +Package via the so-called "undump" or "unexec" methods of producing a +binary executable image, then distribution of such an image shall +neither be construed as a distribution of this Package nor shall it +fall under the restrictions of Paragraphs 3 and 4, provided that you do +not represent such an executable image as a Standard Version of this +Package. + +7. C subroutines (or comparably compiled subroutines in other +languages) supplied by you and linked into this Package in order to +emulate subroutines and variables of the language defined by this +Package shall not be considered part of this Package, but are the +equivalent of input as in Paragraph 6, provided these subroutines do +not change the language in any way that would cause it to fail the +regression tests for the language. + +8. Aggregation of this Package with a commercial distribution is always +permitted provided that the use of this Package is embedded; that is, +when no overt attempt is made to make this Package's interfaces visible +to the end user of the commercial distribution. Such use shall not be +construed as a distribution of this Package. + +9. The name of the Copyright Holder may not be used to endorse or promote +products derived from this software without specific prior written permission. + +10. THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED +WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE. + + The End diff --git a/README.md b/README.md new file mode 100644 index 0000000..eb988ce --- /dev/null +++ b/README.md @@ -0,0 +1,62 @@ +# YadProxy + +## Что это? + +Своего рода API, делающий запросы в Yandex.Disk и выставляющий наружу листинг публично доступных файлов. + +## Зачем это нужно? + +В моём случае Yandex.Disk хранит файлы пакетов для Slackware. Пакетов мало, поэтому бесплатного тарифа вполне +достаточно. + +## Как это работает? + +При загрузке файлов на диск сохраняется список урлов, по которым эти файлы будут доступны через веб-часть. + +## Как это запустить? + +Для запуска понадобятся: + +* Аккаунт @yandex.ru, через который это всё будет работать +* uwsgi версии 2.0.15 или новее с поддержкой psgi +* perl-5.18 или новее (вплоть до 5.32.x) +* пакеты perl-App-cpanminus и perl-local-lib + +Предполагается, что придётся подтянуть модули из CPAN-а с помощью [cpanm][4]. Для этого есть скрипт **bootstrap.sh** + +Как только всё вышеописанное в арсенале появится, можно переходить к [регистрации][1] приложения. Для работы понадобятся +все привилегии в разделе **Яндекс.Диск REST API**. Полученные ID и Пароль надо вписать в **data/config.json**, пример +конфига **data/sample_config.json**. Также надо убедиться, что параметр "Код подтверждения" либо отсутствует в конфиге, +либо закоментирован. + +Как только дело сделано, можно переходить к получению access_token. Для чего запускаем **bin/yad_get_token.pl** +переходим по ссылке, разрешаем приложению доступы и полученный "Код подтверждения" копи-пастим в конфиг. + +Запускаем **bin/yad_get_token.pl** повторно, чтобы он внёс в локальную базку все необходимые данные. После этого +приложениями можно пользоваться. + +Веб-часть я запускаю как [PSGI][2]-приложение, через [UWSGI][3]. +``` +/usr/bin/uwsgi --yaml /var/lib/yadproxy/yadproxy.yaml +``` + +## Статус пректа + +RC. Пока он *не готов*, хотя в принципе работает. Формально, он выйдет в релиз, как только закончатся пункты из файла +TODO. :) + +Ясное дело, что для серьёзного prodution-а это приложение не годится, во всяком случае в таком виде, как оно есть. +Как минимум необходимо организовать кэширование. Соответственно, корректно выдавать expires со стороны приложения, +чтобы, например, nginx мог без проблем кэшировать контент через proxy_cache. + +## Завендоренные библиотеки + +1. [Yandex::OAuth][5] +2. [Yandex::Disk][6] + +[1]: https://oauth.yandex.ru/ +[2]: https://uwsgi-docs.readthedocs.io/en/latest/Perl.html +[3]: https://github.com/unbit/uwsgi +[4]: https://github.com/miyagawa/cpanminus +[5]: https://metacpan.org/pod/Yandex::OAuth +[6]: https://metacpan.org/pod/Yandex::Disk diff --git a/TODO b/TODO new file mode 100644 index 0000000..70cce3d --- /dev/null +++ b/TODO @@ -0,0 +1,13 @@ +* Реализовать проверку кэша на тухлость (Yandex.Диск зачем-то регулярно + обновляет ссылки, период обновления я пока не выявил). Делать по приходу + клиента. +* Не аплодить файлы, которые уже есть в диске (проверка по длине + md5 или sha). +* Докинуть пример строки запуска uwsgi-сервера. +* Исправить листинг каталогов, чтобы без слеша был 301-й редирект. +* Исправить листинг файлов, чтобы по ссылке на файл отображался локальный урл, + а при переходе на него происходил 302-й редирект. +* Добавить проверки corner-case-ов. +* Описать утилиты. +* Организовать многопоточную загрузку файлов на Yandex.Disk, вероятно, через + AnyEvent::HTTP, т.к. у него есть встроенное огранчение на количество + подключений к одному серверу, либо через CORO::LWP. diff --git a/bin/yad_get_token.pl b/bin/yad_get_token.pl new file mode 100755 index 0000000..59e2550 --- /dev/null +++ b/bin/yad_get_token.pl @@ -0,0 +1,65 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# Надо сменить рабочий каталог до запуска основной логики +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); + +use CHI; +use CHI::Driver::BerkeleyDB; +use Yandex::OAuth; +use YadProxy::Conf qw (LoadConf); + +my $c = LoadConf (); + +my $oauth = Yandex::OAuth->new ( + client_id => $c->{ID}, + client_secret => $c->{'Пароль'}, +); + +unless (defined $c->{'Код подтверждения'}) { + # Первый шаг - получение проверочного кода + + say "Шаг 1.\nСсылка для открывания в браузере: \n" . $oauth->get_code(); + say ''; + say 'Код подтверждения надо записать в config.json в параметр "Код подтверждения"'; + say 'Понадобится запустить этот скрипт ещё раз, чтобы занести refresh_token и access_token в локальную базу для работы yadproxy.'; +} else { + # Второй шаг - получение access-токена + + my $token = $oauth->get_token ( code => $c->{'Код подтверждения'} ); + + my $cache = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $c->{oauth_dir}, + namespace => 'yad_auth' + ); + + # yad обычно выдаёт токен на год, скинем с этого 1 минуту :) чтобы уж точно + $cache->set ('refresh_token', $token->{refresh_token}, 'never'); + $cache->set ('access_token', $token->{access_token}, $token->{expires_in} - 60); + + printf "Шаг 2.\nrefresh_token (%s) и access_token (%s) сохранены в базе.\n\n", $token->{refresh_token}, $token->{access_token}; + say 'Можно пользоваться yadproxy.'; +} + +exit 0; + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/bin/yad_index_repo.pl b/bin/yad_index_repo.pl new file mode 100755 index 0000000..e735d70 --- /dev/null +++ b/bin/yad_index_repo.pl @@ -0,0 +1,142 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use File::Basename qw (basename dirname); +use File::Find qw (find); +use YadProxy::Conf qw (LoadConf); +use YadProxy::Lib qw (GetAccessToken); +use Yandex::Disk; + +my $c = LoadConf (); +my $remote_dir = $c->{remote_dir}; +my $index_dir = $c->{index_dir}; + +my $index_url = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'url' +); + +my $index_size = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'size' +); + +my $index_md5 = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'md5' +); + +my $index_sha256 = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'sha256' +); + +my $index_dirs = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'dirs' +); + +my $index_mtime = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'mtime' +); + +my $disk = Yandex::Disk->new ( -token => GetAccessToken ); + + +if (substr ($remote_dir, -1) eq '/') { + chop $remote_dir; +} + +if (substr ($remote_dir, 0, 1) eq '/') { + $remote_dir = substr $remote_dir, 1; +} + +unless (substr ($remote_dir, 0, 6) eq 'disk:/') { + $remote_dir = sprintf 'disk:/%s/', $remote_dir; +} + +my $list = $disk->listAllFiles ( -path => 'REPO' ); # Note that this will give us first 999999 files +my $pattern = qr /$remote_dir/; + +my %month = ( + '01' => 'Jan', + '02' => 'Feb', + '03' => 'Mar', + '04' => 'Apr', + '05' => 'May', + '06' => 'Jun', + '07' => 'Jul', + '08' => 'Aug', + '09' => 'Sep', + '10' => 'Oct', + '11' => 'Nov', + '12' => 'Dec' +); + +foreach (@{$list}) { + my $filename = $_->{path}; + + if ($filename =~ m/$pattern(.+)/) { + $filename = $1; + + if (defined $filename) { + if (defined $_->{file} && defined $_->{size} && defined $_->{md5} && defined $_->{sha256} && defined $_->{modified}) { + $index_url->set ($filename, $_->{file}, 'never'); + $index_size->set ($filename, $_->{size}, 'never'); + $index_md5->set ($filename, $_->{md5}, 'never'); + $index_sha256->set ($filename, $_->{sha256}, 'never'); + + my $time = sprintf ( + '%s-%s-%s %s:%s', + substr ($_->{modified}, 8, 2), + $month{substr ($_->{modified}, 5, 2)}, + substr ($_->{modified}, 0, 4), + substr ($_->{modified}, 11, 2), + substr ($_->{modified}, 14, 2) + ); + + $index_mtime->set ($filename, $time, 'never'); + my $dir = dirname $filename; + + if (defined $dir && $dir ne '' && $dir ne '.') { + my @dirs = split (/\//, $dir); + + do { + unless (defined $index_dirs->get (join '/', @dirs)) { + $index_dirs->set (join ('/', @dirs), 1, 'never'); + } + } while (pop @dirs); + } + + } else { + warn "Yandex.Диск API вернуло неполные данные для $filename"; + } + } + } +} diff --git a/bin/yad_refresh_token.pl b/bin/yad_refresh_token.pl new file mode 100755 index 0000000..6ed1d78 --- /dev/null +++ b/bin/yad_refresh_token.pl @@ -0,0 +1,56 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# Надо сменить рабочий каталог до запуска основной логики +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); + +use CHI; +use CHI::Driver::BerkeleyDB; +use Yandex::OAuth; +use YadProxy::Conf qw (LoadConf); + +my $c = LoadConf (); + +my $oauth = Yandex::OAuth->new ( + client_id => $c->{ID}, + client_secret => $c->{'Пароль'}, +); + +my $cache = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $c->{oauth_dir}, + namespace => 'yad_auth' +); + +my $refresh_token = $cache->get ('refresh_token'); + +if (defined $refresh_token) { + my $token = $oauth->refresh_token ( refresh_token => $refresh_token ); + # yad обычно выдаёт токен на год, скинем с этого 1 минуту :) чтобы уж точно + $cache->set ('refresh_token', $token->{refresh_token}, 'never'); + $cache->set ('access_token', $token->{access_token}, $token->{expires_in} - 60); +} else { + die "В локальной базе нет refresh token, возможно, для начала следует запустить yad_get_token.pl"; +} + +exit 0; + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/bin/yad_show_token.pl b/bin/yad_show_token.pl new file mode 100755 index 0000000..160f6d5 --- /dev/null +++ b/bin/yad_show_token.pl @@ -0,0 +1,49 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# Надо сменить рабочий каталог до запуска основной логики +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); + +use CHI; +use CHI::Driver::BerkeleyDB; +use YadProxy::Conf qw (LoadConf); + +my $c = LoadConf (); + +my $cache = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $c->{oauth_dir}, + namespace => 'yad_auth' +); + +my $refresh_token = $cache->get ('refresh_token'); +my $access_token = $cache->get ('access_token'); + +if (defined $refresh_token && defined $access_token) { + printf "access_token:\n%s\n\nrefresh_token:\n%s\n", $access_token, $refresh_token; +} else { + say "Что-то пошло не так, токены не нашлись, попробуйте их пере-получить через скрипт yad_get_token.pl"; + exit 1; +} + +exit 0; + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/bin/yad_upload_dir.pl b/bin/yad_upload_dir.pl new file mode 100755 index 0000000..a422fba --- /dev/null +++ b/bin/yad_upload_dir.pl @@ -0,0 +1,89 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use File::Basename qw (basename dirname); +use File::Find qw (find); +use YadProxy::Conf qw (LoadConf); +use YadProxy::Lib qw (GetAccessToken); +use Yandex::Disk; + +my $c = LoadConf (); + +my $disk = Yandex::Disk->new ( -token => GetAccessToken ); +my $remote_dir = $c->{remote_dir}; +my $local_dir = $c->{local_dir}; + +if (substr ($remote_dir, -1) eq '/') { + chop $remote_dir; +} + +if (substr ($remote_dir, 0, 1) eq '/') { + $remote_dir = substr $remote_dir, 1; +} + +if (substr ($local_dir, -1) eq '/') { + chop $local_dir; +} + +# рекурсивненько зааплодим найденные файлы +find ( + { + wanted => \&wanted, + no_chdir => 1 + }, + $local_dir +); + +sub wanted { + my $file = $_; + my %targets; + + if (-f $file) { + print "Uploading file: $file ... "; + my $target = substr $file, length ($local_dir) + 1; + $target = dirname $target; + + if (defined $target && $target ne '' && $target ne '/') { + $target = sprintf '%s/%s', $remote_dir, $target; + } else { + $target = $remote_dir; + } + + unless (defined $targets{$target}) { + unless ($disk->createFolder (-path => $target, -recursive => 1)) { + die "Невозможно создать каталог $c->{remote_dir} на Yandex.Диске"; + } + + $targets{$target} = 1; + } + + $disk->uploadFile ( + -file => $file, + -remote_path => $target, + -overwrite => 1 + ) or warn "Не могу за-аплодить файл в каталог $remote_dir на Yandex.Диске\n"; + + print "done.\n"; + } + + sleep 3; +} diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100644 index 0000000..fe78a4f --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,5 @@ +#!/bin/bash + +cpanm -l vendor_perl --self-contained --force --update --installdeps . + +# vim: set ft=sh noet ai ts=4 sw=4 sts=4: diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..6b1c842 --- /dev/null +++ b/cpanfile @@ -0,0 +1,7 @@ +requires 'File::Basename'; +requires 'CHI'; +requires 'CHI::Driver::BerkeleyDB'; +requires 'List::SomeUtils'; +requires 'Plack::Util'; +requires 'Yandex::Disk'; +requires 'Yandex::OAuth'; diff --git a/data/sample_config.json b/data/sample_config.json new file mode 100644 index 0000000..51e32e7 --- /dev/null +++ b/data/sample_config.json @@ -0,0 +1,24 @@ +{ + # пока что тут лежит только базка с refresh_token и access_token + "oauth_dir" : "data/cache", + + # базовый каталог на yandex.диске, куда складываются за-апложенные файлы + "remote_dir" : "REPO", + + # каталог со списком файлов в yandex.disk + "index_dir" : "data/fileindex", + + # префикс для показа в dirlist-е в web-приложении + # если браузером зайти на http://hostname/$prefix/ , будет показан верхний + # уровень того, что лежит в Yandex.Диске, точнее, в локальном индексе :) + "url_prefix" : "REPO", + + # локальный каталог, для рекурсивного аплоада в yandex.диск + # желательно, абсолютный путь. Можно и относительный, но без . и .. + "local_dir" : "/tmp/REPO", + + # Всё что относится к работе с yandex.api + "ID" : "ID полученный при регистрации прилодения на oauth.yandex.ru", + "Пароль" : "Пароль полученный при регистрации прилодения на oauth.yandex.ru", + "Код подтверждения" : "Циферьки из браузера - код подтверждения", +} diff --git a/lib/YadProxy.pm b/lib/YadProxy.pm new file mode 100644 index 0000000..12e27c4 --- /dev/null +++ b/lib/YadProxy.pm @@ -0,0 +1,197 @@ +package YadProxy; +# Выдаёт листинг каталогов в стиле nginx и редиректы для файлов + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use CHI; +use CHI::Driver::BerkeleyDB; +use File::Basename qw (basename dirname); +use List::SomeUtils qw (any); +use YadProxy::Conf qw (LoadConf); + +use version; our $VERSION = qw (1.0); +use Exporter qw (import); +our @EXPORT_OK = qw (DirList); + +my $c = LoadConf (); +my $index_dir = $c->{index_dir}; +my $url_prefix = $c->{url_prefix}; + + +sub DirList { + my $requested_path = shift; + + if ((! defined $requested_path) || $requested_path eq '') { + $requested_path = '/'; + } else { + while (substr ($requested_path, -1) eq '/') { + chop $requested_path; + } + + if ((! defined $requested_path) || $requested_path eq '') { + $requested_path = '/'; + } + } + + my @output; + my @found; + my $status = '200'; + my $content_type = 'text/plain'; + + my $index_url = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'url' + ); + + # прежде чем строить индекс, проверим-ка мы запрос на вшивость, вдруг, с нас запрашивают файл? + # в этом случае нам надо отдать 302 + if (my $url = $index_url->get ($requested_path)) { + return ('302', 'text/plain', $url); + } + + my $index_size = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'size' + ); + + my $index_md5 = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'md5' + ); + + my $index_sha256 = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'sha256' + ); + + my $index_dirs = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'dirs' + ); + + my $index_mtime = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'mtime' + ); + + my $index_of; + + if ($requested_path eq '/') { + $index_of = $url_prefix; + } else { + $index_of = $url_prefix . '/' . $requested_path; + } + + if (substr ($url_prefix, 0, 1) ne '/') { + $index_of = '/' . $index_of; + } + + push @output, ""; + push @output, sprintf 'Index of %s/', $index_of; + push @output, ""; + push @output, sprintf '

Index of %s/


../', $index_of;
+
+	# попробуем отловить все каталоги, начинающиеся с запрошенного пути
+	foreach my $key (sort ($index_dirs->get_keys ())) {
+		if (
+			(
+				(defined $key) &&
+				(length ($key) > length ($requested_path)) &&
+				(substr ($key, 0, length ($requested_path)) eq $requested_path)
+			) ||
+			$requested_path eq '/'
+		) {
+			my @dirs;
+
+			if ($requested_path eq '/') {
+				@dirs = split /\//, $key;
+			} else {
+				@dirs = split /\//, substr ($key, 1 + length ($requested_path));
+			}
+
+			if (defined $dirs[0] && (! any {$_ eq $dirs[0]} @found)) {
+				# у каталогов нету ctime или mtime, так что поставим 01-Jan-1970 00:00
+				push @found, $dirs[0];
+				my $str = $dirs[0];
+				my $len = 50 - (1 + length ($str));
+
+				if ($len < 0) {
+					$str = substr ($str, 0, 47);
+					$str .= '...';
+					$len = 0;
+				}
+
+				# there is no way to get dir mtime from yandex.disk
+				push @output, sprintf (
+					"%s/% ${len}s 01-Jan-1970 00:00                   -",
+					$dirs[0],
+					$str,
+					''
+				);
+			}
+		}
+	}
+
+	# попробуем отловить все файлы, начинающиеся с запрошенного пути
+	foreach my $key (sort ($index_url->get_keys())) {
+		if (
+			(
+				(defined $key) &&
+				(length ($key) > length ($requested_path)) &&
+				(substr ($key, 0, length ($requested_path)) eq $requested_path)
+			) ||
+			$requested_path eq '/'
+		) {
+			my $str;
+			my @urls;
+
+			if ($requested_path eq '/') {
+				@urls = split /\//, $key;
+			} else {
+				@urls = split /\//, substr ($key, 1 + length ($requested_path));
+			}
+
+			if (defined $urls[0] && (! any {$_ eq $urls[0]} @found)) {
+				push @found, $urls[0];
+				$str = $urls[0];
+			}
+
+			if ($#urls < 1) {
+				my $len = 50 - length ($str);
+
+				if ($len < 0) {
+					$str = substr ($str, 0, 47);
+					$str .= '...';
+					$len = 0;
+				}
+
+				push @output, sprintf (
+					"%s% ${len}s %s % 19s",
+					$index_url->get ($key),
+					$str,
+					'',
+					$index_mtime->get ($key),
+					$index_size->get ($key)
+				);
+			}
+		}
+	}
+
+	push @output, "

"; + push @output, ""; + return ('200', 'text/html', join "\n", @output); +} + +1; + +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/lib/YadProxy/Conf.pm b/lib/YadProxy/Conf.pm new file mode 100644 index 0000000..dfc70c1 --- /dev/null +++ b/lib/YadProxy/Conf.pm @@ -0,0 +1,41 @@ +package YadProxy::Conf; +# loads config + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use JSON::XS; + +use version; our $VERSION = qw (1.0); +use Exporter qw (import); +our @EXPORT_OK = qw (LoadConf); + +sub LoadConf { + my $c = 'data/config.json'; + open my $CH, '<', $c or die "[FATAL] No conf at $c: $OS_ERROR\n"; + binmode $CH; + my $len = (stat $c) [7]; + my $json; + my $readlen = read $CH, $json, $len; + + unless ($readlen) { + close $CH; ## no critic (InputOutput::RequireCheckedSyscalls + die "[FATAL] Unable to read $c: $OS_ERROR\n"; + } + + if ($readlen != $len) { + close $CH; ## no critic (InputOutput::RequireCheckedSyscalls + die "[FATAL] File $c is $len bytes on disk, but we read only $readlen bytes\n"; + } + + close $CH; ## no critic (InputOutput::RequireCheckedSyscalls + my $j = JSON::XS->new->utf8->relaxed; + return $j->decode ($json); +} + +1; + +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/lib/YadProxy/Lib.pm b/lib/YadProxy/Lib.pm new file mode 100644 index 0000000..a192512 --- /dev/null +++ b/lib/YadProxy/Lib.pm @@ -0,0 +1,76 @@ +package YadProxy::Lib; +# Утилитарные функции для работы с ya.disk + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); + +use CHI; +use CHI::Driver::BerkeleyDB; +use Log::Any qw ($log); +use YadProxy::Conf qw (LoadConf); + +use version; our $VERSION = qw (1.0); +use Exporter qw (import); +our @EXPORT_OK = qw (GetAccessToken RefreshAccessToken); + + +my $c = LoadConf (); + +sub GetAccessToken { + my $cache = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $c->{oauth_dir}, + namespace => 'yad_auth' + ); + + my $access_token = $cache->get ('access_token'); + + unless (defined $access_token) { + my $refresh_token = $cache->get ('refresh_token'); + + unless (defined $refresh_token) { + $log->fatal ("No refresh_token found in base, please, register yaproxy application"); + exit 1; + } + + my $token = RefreshAccessToken ($refresh_token); + # yad обычно выдаёт токен на год, скинем с этого 1 минуту :) чтобы уж точно + $cache->set ('refresh_token', $token->{refresh_token}, 'never'); + $cache->set ('access_token', $token->{access_token}, $token->{expires_in} - 60); + $access_token = $token->{access_token}; + } + + return $access_token; +} + +sub RefreshAccessToken { + my $refresh_token = shift; + + my $oauth = Yandex::OAuth->new ( + client_id => $c->{ID}, + client_secret => $c->{'Пароль'}, + ); + + my $token; + my $counter = 0; + + while ((! defined $token->{access_token}) || ($counter < 3)) { + $token = $oauth->refresh_token ( refresh_token => $refresh_token ); + $counter++; + } + + if (defined $token->{access_token}) { + return $token; + } else { + $log->fatal ("[FATAL] Unable to refresh access_token"); + exit 1; + } +} + +1; + +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/lib/Yandex/Disk.pm b/lib/Yandex/Disk.pm new file mode 100644 index 0000000..536b382 --- /dev/null +++ b/lib/Yandex/Disk.pm @@ -0,0 +1,631 @@ +package Yandex::Disk; + +use 5.008001; +use strict; +use warnings; +use utf8; +use Yandex::Disk::Public; +use Carp qw/croak carp/; +use LWP::UserAgent; +use JSON::XS; +use File::Basename; +use URI::Escape; +use Encode; +use IO::Socket::SSL; +use Term::Sk; + +our $VERSION = '0.06'; + +my $WAIT_RETRY = 20; +my $BUFF_SIZE = 2777216; + +sub new { + my $class = shift; + my %opt = @_; + my $self = {}; + $self->{token} = $opt{-token} || croak "Specify -token param"; + + my $ua = LWP::UserAgent->new(timeout => 10); + $ua->agent("Yandex::Disk perl module"); + $ua->default_header('Accept' => 'application/json'); + $ua->default_header('Content-Type' => 'application/json'); + $ua->default_header('Connection' => 'keep-alive'); + $ua->default_header('Authorization' => 'OAuth ' . $self->{token}); + $self->{ua} = $ua; + $self->{public_info} = {}; + return bless $self, $class; +} + + +sub getDiskInfo { + my $self = shift; + my $url = 'https://cloud-api.yandex.net/v1/disk/'; + my $res = $self->__request($url, "GET"); + if ($res->is_success) { + return __fromJson($res->decoded_content); + } + else { + croak "Cant execute request to $url: " . $res->status_line; + } +} + +sub uploadFile { + my $self = shift; + my %opt = @_; + my $overwrite = $opt{-overwrite}; + my $file = $opt{-file} || croak "Specify -file param"; + my $remote_path = $opt{-remote_path} || croak "Specify -remote_path param"; + my $show_progress_bar = $opt{-show_progress_bar}; + + $overwrite = 1 if not defined $overwrite; + + if (not -f $file) { + croak "File $file not exists"; + } + + $overwrite = $overwrite ? 'true' : 'false'; + + #Delete end slash + $remote_path =~ s/^\/|\/$//g; + $remote_path = sprintf("/%s/%s", $remote_path, basename($file)); + + my $param = "?path=" . uri_escape($remote_path) . "&overwrite=$overwrite"; + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/upload' . $param, "GET"); + my $url_to_upload; + my $code = $res->code; + if ($code eq '409') { + croak "Folder $remote_path not exists"; + } + elsif ($code eq '200') { + $url_to_upload = __fromJson($res->decoded_content)->{href}; + } + else { + croak "Cant uploadFile: " . $res->status_line; + } + + # Progress bar if need + my $term; + if ($show_progress_bar) { + $term = __prepareProgressBar(-s $file); + } + + my $upl_code = __upload_file($url_to_upload, $file, $term); + + if ($upl_code ne '201') { + croak "Cant upload file. Code: $code"; + } + return 1; +} + +sub createFolder { + my $self = shift; + my %opt = @_; + my $path = $opt{-path} || croak "Specify -path param"; + my $recursive = $opt{-recursive}; + + if ($recursive) { + my @half_path; + for my $l_path (split /\//, $path) { + push @half_path, $l_path; + $self->createFolder( -path => join('/', @half_path) ); + } + } + + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/?path=' . uri_escape($path), "PUT"); + my $code = $res->code; + if ($code eq '409') { + #Папка или существует или не создана родительская папка + my $json_res = __fromJson($res->decoded_content); + if ($json_res->{error} eq 'DiskPathPointsToExistentDirectoryError') { + return 1; + } + croak $json_res->{description}; + } + elsif ($code ne '201') { + croak "Cant create folder $path. Error: " . $res->status_line; + } + + return 1; +} + +sub deleteResource { + my $self = shift; + my %opt = @_; + my $path = $opt{-path} || croak "Specify -path param"; + my $wait = $opt{-wait}; + my $permanently = $opt{-permanently}; + + $permanently = $permanently ? 'true' : 'false'; + + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources?path=' . uri_escape($path) . "&permanently=$permanently", "DELETE"); + my $code = $res->code; + if ($code eq '204') { + #Free folder + return 1; + } + elsif ($code eq '202') { + if ($wait) { + my $href = __fromJson($res->decoded_content)->{href}; + $self->__waitResponse($href, $WAIT_RETRY) or croak "Timeout to wait response. Try increase $WAIT_RETRY variable"; + } + return 1; + } + else { + croak "Cant delete $path. Error: " . $res->status_line; + } +} + +sub downloadFile { + my $self = shift; + my %opt = @_; + my $path = $opt{-path} || croak "Specify -path param"; + my $file = $opt{-file} || croak "Specify -file param"; + my $show_progress_bar = $opt{-show_progress_bar}; + + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/download?path=' . uri_escape($path), "GET"); + my $code = $res->code; + if ($code ne '200') { + croak "Error on request file $path: " . $res->status_line; + } + + my $download_url = __fromJson($res->decoded_content)->{href}; + + my $term; + if ($show_progress_bar) { + if ($download_url =~ /[&?]fsize=(\d+)/) { + $term = __prepareProgressBar($1); + } + else { + carp "Can't get size of downloaded file. Progress bar will disabled."; + } + } + + $self->__download($download_url, $file, $term); + return 1; +} + +sub emptyTrash { + my $self = shift; + my %opt = @_; + my $path = $opt{-path} || ''; + my $wait = $opt{-wait}; + + my $param = $path ? '?path=' . uri_escape($path) : ''; + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/trash/resources/' . $path, 'DELETE'); + my $code = $res->code; + if ($code eq '204') { + return 1; + } + elsif ($code eq '202') { + if ($wait) { + my $href = __fromJson($res->decoded_content)->{href}; + $self->__waitResponse($href, $WAIT_RETRY) or croak "Timeout to wait response. Try increase $WAIT_RETRY variable"; + } + return 1; + } + else { + $path = "by path $path" if $path; + croak "Cant empty trash$path. Error: " . $res->status_line; + } +} + +sub fileInfo { + my $self = shift; + my %opt = @_; + my $path = $opt{-path} || croak "Specify -path param"; + + my $param = '?path=' . uri_escape($path); + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources' . $param, 'GET'); + + my $code = $res->code; + if ($code ne '200') { + croak "Error on fileInfo. Error: " . $res->status_line; + } + my $json_res = __fromJson($res->{_content}); + return $json_res; +} + +sub listFiles { + my $self = shift; + my %opt = @_; + my $path = $opt{-path} || croak "Specify -path param"; + my $limit = $opt{-limit} || 999999; + my $offset = $opt{-offset}; + $offset = 0 if not $offset; + + my $param = '?path=' . uri_escape($path) . "&limit=$limit&offset=$offset&fields=_embedded.items"; + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources' . $param, 'GET'); + my $code = $res->code; + if ($code ne '200') { + croak "Error on listFiles. Error: " . $res->status_line; + } + my $json_res = __fromJson($res->decoded_content); + + return $json_res->{_embedded}->{items}; +} + +sub listAllFiles { + my $self = shift; + my %opt = @_; + my $limit = $opt{-limit} || 999999; + my $media_type = $opt{-media_type}; + my $offset = $opt{-offset}; + $offset = 0 if not $offset; + + my $param = "?limit=$limit&offset=$offset"; + $param .= '&media_type=' . $media_type if $media_type; + + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/files' . $param, 'GET'); + my $code = $res->code; + if ($code ne '200') { + croak "Error on listFiles. Error: " . $res->status_line; + } + my $json_res = __fromJson($res->decoded_content); + + return $json_res->{items}; +} + +sub lastUploadedFiles { + my $self = shift; + my %opt = @_; + my $limit = $opt{-limit} || 999999; + my $media_type = $opt{-media_type}; + + my $param = "?limit=$limit"; + $param .= '&media_type=' . $media_type if $media_type; + + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/last-uploaded' . $param, 'GET'); + my $code = $res->code; + if ($code ne '200') { + croak "Error on listFiles. Error: " . $res->status_line; + } + my $json_res = __fromJson($res->decoded_content); + + return $json_res->{items}; +} + + +sub public { + my $self = shift; + return Yandex::Disk::Public->new( -token => $self->{token} ); +} + +sub __download { + my ($self, $url, $fname, $term) = @_; + my $ua = $self->{ua}; + + open my $FL, ">$fname" or croak "Cant open $fname to write $!"; + binmode $FL; + my $res = $ua->get($url, ':read_size_hint' => $BUFF_SIZE, ':content_cb' => + sub { + print $FL $_[0]; + $term->up($BUFF_SIZE) if $term; + } + ); + close $FL; + $term->close if $term; + + if ($res->code eq '200') { + return 1; + } + croak "Cant download file $url to $fname. Error: " . $res->status_line; +} + +sub __waitResponse { + #Дожидается ответа о статусе операции + my ($self, $url, $retry) = @_; + + while ($retry > 0) { + my $res = $self->__request($url, "GET"); + my $code = $res->code; + if ($code eq '200' && __fromJson($res->decoded_content)->{status} eq 'success') { + return 1; + } + sleep 1; + $retry--; + } + return; +} + + +sub __upload_file { + #Buffered chunked upload file + my ($url, $file, $term) = @_; + + my $u1 = URI->new($url); + +# $IO::Socket::SSL::DEBUG = 3; + my $host = $u1->host; + my $port = $u1->port; + my $path = $u1->path; + + my $sock = IO::Socket::SSL->new( + PeerAddr => $host, + PeerPort => $port, + Proto => 'tcp', + ) or croak "Cant connect to $host:$port"; + binmode $sock; + $sock->autoflush(1); + + $sock->print("PUT $path HTTP/1.1\n"); + $sock->print("HOST: $host\n"); + $sock->print("Connection: close\n"); + $sock->print("Content-Type: application/json\n"); + $sock->print("Transfer-Encoding: chunked\n"); + $sock->print("\n"); + + open my $FH, "<$file" or croak "Cant open $file $!"; + binmode $FH; + my $filebuf; + while (my $bytes = read($FH, $filebuf, $BUFF_SIZE)) { + my $hex = sprintf("%X", $bytes); + $sock->print($hex) or croak "Cant print to socket"; + $sock->print("\r\n") or croak "Cant print to socket"; + + $sock->print($filebuf) or croak "Cant print to socket"; + $sock->print("\r\n") or croak "Cant print to socket"; + + $term->up($BUFF_SIZE) if $term; + } + close $FH; + + $sock->print("0\r\n") or croak "Cant print to socket"; + $sock->print("\r\n") or croak "Cant print to socket"; + + my @answer = $sock->getlines(); + $sock->close(); + + $term->close if $term; + + my ($code) = $answer[0] =~ /(\d{3})/; + + return $code; +} + + +sub __request { + my ($self, $url, $type) = @_; + + my $ua = $self->{ua}; + my $req = HTTP::Request->new($type => $url); + my $res = $ua->request($req); + + return $res; +} + +sub __fromJson { + my $string = ref($_[0]) ? $_[1] : $_[0]; + my $res = JSON::XS::decode_json($string); + return $res; +} + +sub __prepareProgressBar { + my ($file_size) = @_; + my $format = '%2d Elapsed: %8t %21b %4p %2d (%8c of %11m bytes)'; + my $target = $file_size; + my $term = Term::Sk->new($format, { + freq => 'd', + base => 0, + target => $target, + pdisp => '!', + } + ); + return $term; +} + + +sub errstr { + return shift->{errstr}; +} + +1; + +__END__ +=pod + +=encoding UTF-8 + +=head1 NAME + +B - a simple API for Yandex Disk + +=head1 VERSION + +version 0.06 + +=head1 SYNOPSYS + + use Yandex::Disk; + + my $TOKEN = 'aaaabbbbccc'; #Auth token. You can get token from module L + my $disk = Yandex::Disk->new( -token => $TOKEN ); + + #Get info about disk and print + my $diskinfo = $disk->getDiskInfo(); + p $diskinfo; + + #Upload file from local disk to Yandex disk + $disk->uploadFile( + -overwrite => 1, + -file => 'my_test_upload_file', # Path to file on local disk + -remote_path => "/Upload", # Folder on Yandex Disk + ) or die "Cant upload file"; + + #Download file form Yandex Disk to local disk + $disk->downloadFile( + -path => "/Upload/my_test_upload_file", # Path to file on Yandex Disk + -file => 'my_test_upload_file', # Path to save file on local disk + ) or die "Cant download file"; + + #Create folder on Yandex Disk + $disk->createFolder( + -path => '/Temp/new_folder', # Path to new folder on Yandex Disk + -recursive => 1, # Recursive create folder + + #Share file and get public link + my $public = $disk->public(); + $public->publicFile( + -path => "/Upload/my_test_upload_file", # Path to file, which need share + ) or die "Cant public file"; + my $publicUrl = $public->publicUrl() or die "Cant get public url"; + + + + +=head1 METHODS + +=head2 getDiskInfo() + +Return disk info data as hashref + + my $info = $disk->getDiskInfo(); + + $info: + Example output: + { + max_file_size 1073741824, + revision 14987326771107, + system_folders { + applications "disk:/Приложения", + downloads "disk:/Загрузки/", + facebook "disk:/Социальные сети/Facebook", + google "disk:/Социальные сети/Google+", + instagram "disk:/Социальные сети/Instagram", + mailru "disk:/Социальные сети/Мой Мир", + odnoklassniki "disk:/Социальные сети/Одноклассники", + photostream "disk:/Фотокамера/", + screenshots "disk:/Скриншоты/", + social "disk:/Социальные сети/", + vkontakte "disk:/Социальные сети/ВКонтакте" + }, + total_space 67645734912, + trash_size 0, + used_space 19942927435, + user { + login "login", + uid 123456 + } + } + +=head2 uploadFile(%opt) + +Upload file (-file) to Yandex Disk in folder (-remote_path). Return 1 if success + + $disk->uploadFile(-file => '/root/upload', -remote_path => 'Temp', -overwrite => 0); + Options: + -overwrite => Owervrite file if exists (default: 1) + -remote_path => Path to upload file on disk + -file => Path to local file + -show_progress_bar => Show progress bar upload file + +=head2 createFolder(%opt) + +Create folder on disk + + $disk->createFolder(-path => 'Temp/test', -recursive => 1); + Options: + -path => Path to folder, + -recursive => Recursive create folder (default: 0) + +=head2 deleteResource(%opt) + +Delete file or folder from disk. Return 1 if success + + $disk->deleteResource(-path => 'Temp/test'); + Options: + -path => Path to delete file or folder + -permanently => Do not move to trash, delete permanently (default: 0(moving file to trash)) + -wait => Wait delete resource (defailt: 0) + +=head2 downloadFile(%opt) + +Download file from Yandex Disk to local file. Method overwrites local file if exists. Return 1 if success + + $disk->downloadFile(-path => 'Temp/test', -file => 'test'); + Options: + -path => Path to file on Yandex Disk + -file => Path to local destination + -show_progress_bar => Show progress bar download file + +=head2 emptyTrash(%opt) + +Empty trash. If -path specified, delete -path resource, otherwise - empty all trash. Return 1 if success + + $disk->emptyTrash(-path => 'Temp/test'); #Delete '/Temp/test' from trash + Options: + -path => Path to resource on Yandex Disk to delete from trash + -wait => Wait empty trash (defailt: 0) + + $disk->emptyTrash; #Full empty trash + +=head2 fileInfo(%opt) + +List properties of selected file. Return hashref(keys: "path", "type", "name", "preview", "created", "modified", "md5", "mime_type", "size") + + $disk->fileInfo(-path => 'Temp/test'); + Options: + -path => Path to file on Yandex Disk for which need get info + +=head2 listFiles(%opt) + +List files in folder. Return arrayref to hashref(keys: "path", "type", "name", "preview", "created", "modified", "md5", "mime_type", "size") + + $disk->listFiles(-path => 'Temp/test'); + Options: + -path => Path to resource (folder) on Yandex Disk for which need get info + -limit => Limit max files to output (default: unlimited) + -offset => Offset records from start (default: 0) + +=head2 listAllFiles(%opt) + +List all files on YandexDisk. Return arrayref to hashref(keys: "path", "type", "name", "preview", "created", "modified", "md5", "mime_type", "size") + + $disk->listAllFiles(); + Options: + -media_type => Type of file to return. Afaible types listed below. Multiple types can be specified by using a comma. (default: all types) + -limit => Limit max files to output (default: unlimited) + -offset => Offset records from start (default: 0) + +Media types: +audio, backup, book, compressed, data, development, diskimage, document, encoded, executable, flash, font, image, settings, spreadsheet, text, unknown, video, web + +=head2 lastUploadedFiles(%opt) + +List last uploaded files. Return arrayref to hashref(keys: "path", "type", "name", "preview", "created", "modified", "md5", "mime_type", "size") + + $disk->lastUploadedFiles(); + Options: + -media_type => Type of file to return. Afaible types same as listAllFiles. Multiple types can be specified by using a comma. (default: all types) + -limit => Limit max files to output (default: unlimited) + + + +=head2 Public files + +my $public = $disk->public(); #Create L object + +=head1 CLI/API +Sparrow plugin https://sparrowhub.org/info/yandex-disk + +=head1 DEPENDENCE + +L, L, L, L + +=head1 AUTHORS + +=over 4 + +=item * + +Pavel Andryushin + +=back + +=head1 COPYRIGHT AND LICENSE + +This software is copyright (c) 2017 by Pavel Andryushin. + +This is free software; you can redistribute it and/or modify it under +the same terms as the Perl 5 programming language system itself. + +=cut diff --git a/lib/Yandex/Disk/Public.pm b/lib/Yandex/Disk/Public.pm new file mode 100644 index 0000000..b6fe3fa --- /dev/null +++ b/lib/Yandex/Disk/Public.pm @@ -0,0 +1,164 @@ +# +#=============================================================================== +# +# FILE: Public.pm +# +# DESCRIPTION: +# +# FILES: --- +# BUGS: --- +# NOTES: --- +# AUTHOR: YOUR NAME (), +# ORGANIZATION: +# VERSION: 1.0 +# CREATED: 01.10.2017 12:34:59 +# REVISION: --- +#=============================================================================== +package Yandex::Disk::Public; + +use 5.008001; +use strict; +use warnings; +use utf8; +use URI::Escape; +use Carp 'croak'; + +use base 'Yandex::Disk'; + +#Class for Public actions under Yandex Disk files and folders + +our $VERSION = '0.06'; + +sub publicFile { + my $self = shift; + my %opt = @_; + my $path = $opt{-path} || croak "Specify -path param"; + + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/publish?path=' . uri_escape($path), "PUT"); + my $code = $res->code; + if ($code ne '200') { + croak "Cant public file $path. Error: " . $res->status_line; + } + my $metainfo_href = $self->__fromJson($res->decoded_content)->{href}; + my $res_get_metainfo = $self->__getPublicUrl($metainfo_href); + return $res_get_metainfo; +} + +sub unpublicFile { + my $self = shift; + my %opt = @_; + $self->{public_info} = {}; + my $path = $opt{-path} || croak "Specify -path param"; + + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/unpublish?path=' . uri_escape($path), "PUT"); + my $code = $res->code; + if ($code ne '200') { + croak "Cant unpublic file $path. Error: " . $res->status_line; + } + return 1; +} + +sub listPublished { + my $self = shift; + my %opt = @_; + my $limit = $opt{-limit}; + my $offset = $opt{-offset} || 0; + my $type = $opt{-type}; + + $limit = 999999 if not defined $limit; + + if ($type && ($type ne 'dir' && $type ne 'file')) { + croak "Wrong -type value (dir/file)"; + } + + my $param = "limit=$limit&offset=$offset"; + $param .= "&type=$type" if $type; + + my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/public?' . $param, "GET"); + my $code = $res->code; + if ($code ne '200') { + croak "Cant get listPublished. Error: " . $res->status_line; + } + my $items = $self->__fromJson($res->decoded_content)->{items}; + return $items; +} + +sub __getPublicUrl { + my ($self, $href) = @_; + my $res = $self->__request($href, "GET"); + my $code = $res->code; + if ($code ne '200') { + croak "Cant get public url by href: $href. Error: " . $res->status_line; + } + $self->{public_info} = $self->__fromJson($res->decoded_content); + return 1; +} + +sub publicUrl { + return shift->{public_info}->{public_url}; +} + +sub publicType { + return shift->{public_info}->{type}; +} + +1; + +__END__ +=pod + +=encoding UTF-8 + +=head1 NAME + +B - public and unpublic yandex disk resources(files and folders) + +=head1 VERSION + +version 0.06 + +=head1 SYNOPSIS + use Yandex::Disk; + + my $token = "my token"; + my $disk = Yandex::Disk( -token => $token); #Create Yandex::Disk object + my $public = $disk->public; #Create Yandex::Disk::Public object + + #Public file '/Temp/small_file' + $public->publicFile (-path => '/Temp/small_file'); + +=head1 METHODS + +=head2 publicFile(%opt) + +Make file or folder as public + $public->publicFile ( -path => '/Temp/small_file' ) or die "Cant public file"; #Make file '/Temp/small_file' as public + Options: + -path => Path to resource on yandex disk, where need public + +=head2 unpublicFile(%opt) + +Remove public access to resources + $public->unpublicFile( -path => '/Temp/small_file' ) or die "Cant unpublic file"; + Options: + -path => Path to resource on yandex disk, where need public + +=head2 listPublished(%opt) + +Return array hashref with published files + my $list = $public->listPublished(); + Options: + -limit => Limit max files to output (default: unlimited) + -offset => Offset records from start (default: 0) + -type => dir/file (default: undef (display dirs and files) + +=head2 publicUrl() + +Get public url from published file. Return undef if error + +=head2 publicType() + +Get type of published resource. Retutn undef if error + + +=cut diff --git a/lib/Yandex/OAuth.pm b/lib/Yandex/OAuth.pm new file mode 100644 index 0000000..3ea023d --- /dev/null +++ b/lib/Yandex/OAuth.pm @@ -0,0 +1,162 @@ +=encoding utf-8 + +=head1 NAME + +Yandex::OAuth - module for get access token to Yandex.API + +=head1 SYNOPSIS + + use Yandex::OAuth; + + my $oauth = Yandex::OAuth->new( + client_id => '76df1cf****************fb31d0289', + client_secret => 'e3a2855****************4de3c2afc', + ); + + # return link for open in browser + say $oauth->get_code(); + + # return JSON with access_token + say Dumper $oauth->get_token( code => 3557461 ); + + # return JSON with access_token + say Dumper $oauth->refresh_token( refresh_token => 3557461 ); + +=head1 DESCRIPTION + +Yandex::OAuth is a module for get access token for Yandex.API +See more at https://tech.yandex.ru/oauth/doc/dg/concepts/ya-oauth-intro-docpage/ + + +=cut + +package Yandex::OAuth; +use 5.008001; +use utf8; +use Modern::Perl; +use JSON::XS; +use URI::Escape; + +use LWP::UserAgent; + +use Moo; + +our $VERSION = "0.07"; + +has 'ua' => ( + is => 'ro', + default => sub { + my $ua = LWP::UserAgent->new(); + $ua->agent( 'Yandex::OAuth Perl API Client' ); + $ua->timeout( 120 ); + return $ua; + } +); + +has 'auth_url' => ( + is => 'ro', + default => 'https://oauth.yandex.ru/authorize', + +); + +has 'token_url' => ( + is => 'ro', + default => 'https://oauth.yandex.ru/token', +); + +has 'client_id' => ( + is => 'rw', + required => 1, +); + +has 'client_secret' => ( + is => 'rw', + required => 1, +); + +has 'demo' => ( + is => 'rw' +); + +=head1 METHODS + +=over + +=item B + +return a link for open in browser + + $oauth->get_code(); + +=cut + +sub get_code { + my ( $self, %params ) = @_; + + return $self->auth_url . "?response_type=code&client_id=".$self->client_id . + ( ( defined $params{state} ) ? "&state=" . uri_escape( $params{state} ) : '' ); +} + +=item B + +return a json with access_token or error if code has expired + + $oauth->get_token( code => XXXXXX ); + +=cut + +sub get_token { + my ( $self, %params ) = @_; + + return JSON::XS::decode_json( $self->demo ) if $self->demo; + + my $res = $self->ua->post($self->token_url, { + code => $params{code}, + client_id => $self->client_id, + client_secret => $self->client_secret, + grant_type => 'authorization_code', + }); + + return JSON::XS::decode_json( $res->content ); +} + +=item B + +return a json with access_token or error if refresh_token has already been used + + $oauth->refresh_token( refresh_token => XXXXXX ); + +=cut + +sub refresh_token { + my ( $self, %params ) = @_; + + return JSON::XS::decode_json( $self->demo ) if $self->demo; + + my $res = $self->ua->post($self->token_url, { + refresh_token => $params{refresh_token}, + client_id => $self->client_id, + client_secret => $self->client_secret, + grant_type => 'refresh_token', + }); + + return JSON::XS::decode_json( $res->content ); +} + +1; + +__END__ +=back + +=head1 LICENSE + +Copyright (C) Andrey Kuzmin. + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=head1 AUTHOR + +Andrey Kuzmin Echipsoid@cpan.orgE + +=cut diff --git a/lib/Yandex/OAuth/Client.pm b/lib/Yandex/OAuth/Client.pm new file mode 100644 index 0000000..c282c7e --- /dev/null +++ b/lib/Yandex/OAuth/Client.pm @@ -0,0 +1,253 @@ +=encoding utf-8 + +=head1 NAME + +Yandex::OAuth::Client - base class for any Yandex.API modules + +=head1 SYNOPSIS + + use Yandex::OAuth::Client; + + my $client = Yandex::OAuth::Client->new(token => '************', endpoint => 'https://api-metrika.yandex.ru/'); + + my $client->get(['stat','user_vars'], { + pretty => 1, + # other params specified for resource + }); + # url converting to https://api-metrika.yandex.ru/stat/user_vars/?pretty=1 + +=head1 DESCRIPTION + +Yandex::OAuth::Client is base class for any Yandex.API modules +Contains get, post, put, delete methods for queries to any APIs Yandex. + +Looking for contributors for this and other Yandex.APIs + +=cut + +package Yandex::OAuth::Client; +use 5.008001; +use utf8; +use Modern::Perl; +use Data::Dumper; + +use LWP::UserAgent; +use Storable qw/ dclone /; +use JSON::XS; +use Moo; + +has 'ua' => ( + is => 'ro', + default => sub { + my $ua = LWP::UserAgent->new(); + $ua->agent( 'Yandex::OAuth Perl API Client' ); + $ua->timeout( 120 ); + return $ua; + } +); + +has 'token' => ( + is => 'rw', + required => 1, +); + +has 'endpoint' => ( + is => 'rw', + required => 1, +); + +has 'next_url' => ( + is => 'rw', + default => '', + writer => 'set_next_url', +); + +has 'demo' => ( + is => 'rw' +); + +=head1 CONSTRUCTOR + +=over + +=item B + +Require two arguments: token and endpoint. For module based on Yandex::OAuth::Client +you can override atribute 'endpoint' + + extends 'Yandex::OAuth::Client'; + has '+endpoint' => ( + is => 'ro', + default => 'https://api-metrika.yandex.ru/', + ); + +=back + +=head1 METHODS + +=over + +=item B + +Send GET request. + + $client->get(['stat','load']); + + # or full link. for example, when using "next" link from previous response + + $client->get('https://api-metrika.yandex.ru/stat/load/?pretty=1'); + +=cut + +sub get { + my ($self, $url, $query) = @_; + + return $self->request( 'get', $url, $query ); +} + +=item B + +Send POST request. + + $client->post(['counters'], { + # query params + }, { + # body params + }); + +=cut + +sub post { + my ($self, $url, $query, $body) = @_; + + return $self->request( 'post', $url, $query, $body ); +} + +=item B + +Send PUT request. + + $client->put(['counter', $counter_id], { + # query params + }, { + # body params + }); + +=cut + +sub put { + my ($self, $url, $query, $body) = @_; + + return $self->request( 'put', $url, $query, $body ); +} + +=item B + +Send DELETE request. + + $client->delete(['counter', $counter_id]); + +=cut + +sub delete { + my ($self, $url, $query) = @_; + + return $self->request( 'delete', $url, $query ); +} + +sub request { + my ( $self, $method, $url, $query, $body ) = @_; + + my $req = HTTP::Request->new( uc $method => $self->make_url( $url, $query ) ); + + $self->prepare_request( $req, $body ); + + my $resp = $self->parse_response( $self->ua->request( $req )->content ); + + return $resp; +} + +sub make_url { + my ( $self, $resource, $params ) = @_; + + my @resources = ref $resource ? @$resource : ( $resource ); + + my $url = ''; + if ( $resource =~ /^http/i ) { + $url = $resource; + } + else { + $url = $self->endpoint; + $url =~ s/[\\\/]+$//; + $url .= '/'; + $url .= join '/', @resources; + } + + if ( $params ) { + my $uri = URI->new( $url ); + $uri->query_form( %$params ); + $url = $uri->as_string; + } + + return $url; +} + +sub prepare_request { + my ( $self, $request, $body_param ) = @_; + + my $body = ''; + if ( $body_param ) { + my $params = dclone( $body_param ); + + state $json = JSON::XS->new->utf8; + + $body = $json->encode( $params ); + + $request->content( $body ); + } + $request->header( 'content-type' => 'application/json; charset=UTF-8' ); + $request->header( 'content-length' => length( $body ) ); + + $request->header( 'Authorization' => 'Bearer ' . $self->token ); + + return 1; +} + +sub parse_response { + my ( $self, $response ) = @_; + + return unless $response; + + my $json = JSON::XS::decode_json( $response ); + + my $next = ''; + + if ( defined $json->{links} && defined $json->{links}->{next} ) { + $next = $json->{links}->{next}; + $next =~ s/^http:/https:/i; + + $self->set_next_url( $next ); + } + + return $json; +} + + +1; +__END__ + +=back + +=head1 LICENSE + +Copyright (C) Andrey Kuzmin. + +This library is free software; you can redistribute it and/or modify +it under the same terms as Perl itself. + +=head1 AUTHOR + +Andrey Kuzmin Echipsoid@cpan.orgE + +=cut + diff --git a/test/test_file.txt b/test/test_file.txt new file mode 100644 index 0000000..fbf6ec9 --- /dev/null +++ b/test/test_file.txt @@ -0,0 +1,2 @@ +lorem ipsum и всё такое, ну, вы знаете. +Съешь ещё этих французских булочек... да смотри, не подавись. diff --git a/test/yad_file_info.pl b/test/yad_file_info.pl new file mode 100755 index 0000000..68227d0 --- /dev/null +++ b/test/yad_file_info.pl @@ -0,0 +1,40 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use YadProxy::Conf qw (LoadConf); +use YadProxy::Lib qw (GetAccessToken); +use Yandex::Disk; +use Data::Dumper; + +my $c = LoadConf (); +my $remote_dir = ''; +my $file = 'test_file.txt'; + +if ($c->{remote_dir} && $c->{remote_dir} ne '') { + $remote_dir = $c->{remote_dir}; +} + +my $disk = Yandex::Disk->new ( -token => GetAccessToken ); +say Dumper $disk->fileInfo ( -path => sprintf ("%s/%s", $remote_dir, $file)); + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: \ No newline at end of file diff --git a/test/yad_info.pl b/test/yad_info.pl new file mode 100755 index 0000000..2512a01 --- /dev/null +++ b/test/yad_info.pl @@ -0,0 +1,32 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use YadProxy::Lib qw (GetAccessToken); +use Yandex::Disk; +use Data::Dumper; + +my $disk = Yandex::Disk->new ( -token => GetAccessToken() ); +my $diskinfo = $disk->getDiskInfo(); +say Dumper $diskinfo; + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: \ No newline at end of file diff --git a/test/yad_list_all_files.pl b/test/yad_list_all_files.pl new file mode 100755 index 0000000..b054689 --- /dev/null +++ b/test/yad_list_all_files.pl @@ -0,0 +1,31 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use YadProxy::Lib qw (GetAccessToken); +use Yandex::Disk; +use Data::Dumper; + +my $disk = Yandex::Disk->new ( -token => GetAccessToken ); +say Dumper $disk->listAllFiles (); + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: \ No newline at end of file diff --git a/test/yad_list_published.pl b/test/yad_list_published.pl new file mode 100755 index 0000000..c648578 --- /dev/null +++ b/test/yad_list_published.pl @@ -0,0 +1,32 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use YadProxy::Lib qw (GetAccessToken); +use Yandex::Disk; +use Data::Dumper; + +my $disk = Yandex::Disk->new( -token => GetAccessToken ); +my $public = $disk->public(); +say Dumper $public->listPublished(); + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: \ No newline at end of file diff --git a/test/yad_mkdir.pl b/test/yad_mkdir.pl new file mode 100755 index 0000000..d73664e --- /dev/null +++ b/test/yad_mkdir.pl @@ -0,0 +1,44 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use YadProxy::Conf qw (LoadConf); +use YadProxy::Lib qw (GetAccessToken); +use Yandex::Disk; +use Data::Dumper; + +my $c = LoadConf (); +my $remote_dir = '/'; + +if ($c->{remote_dir} && $c->{remote_dir} ne '') { + $remote_dir = $c->{remote_dir}; + + $disk->createFolder (-path => $remote_dir, -recursive => 1) or do { + die "Невозможно создать каталог $c->{remote_dir} на Yandex.Диске"; + }; +} + +my $disk = Yandex::Disk->new( -token => GetAccessToken ); +$disk->createFolder(-path => 'myYaD/test', -recursive => 1); +say Dumper $disk->listFiles (-path => '/'); + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/test/yad_upload_file.pl b/test/yad_upload_file.pl new file mode 100755 index 0000000..e80bce2 --- /dev/null +++ b/test/yad_upload_file.pl @@ -0,0 +1,55 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use File::Basename; +use YadProxy::Conf qw (LoadConf); +use YadProxy::Lib qw (GetAccessToken); +use Yandex::Disk; +use Data::Dumper; + +my $c = LoadConf (); +my $remote_dir = '/'; +my $file = 'test/test_file.txt'; + +my $disk = Yandex::Disk->new ( -token => GetAccessToken ); + +if ($c->{remote_dir} && $c->{remote_dir} ne '') { + $remote_dir = $c->{remote_dir}; + + $disk->createFolder ( + -path => $remote_dir, + -recursive => 1 + ) or do { + die "Невозможно создать каталог $c->{remote_dir} на Yandex.Диске"; + }; +} + +$disk->uploadFile ( + -file => $file, + -remote_path => $remote_dir, + -overwrite => 1 +) or die "Не могу зааплодить файл в каталог $remote_dir на Yandex.Диске"; + +say Dumper $disk->listFiles (-path =>$remote_dir); + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/test/yadproxy_dump_dircache.pl b/test/yadproxy_dump_dircache.pl new file mode 100755 index 0000000..c7c905b --- /dev/null +++ b/test/yadproxy_dump_dircache.pl @@ -0,0 +1,41 @@ +#!/usr/bin/perl + +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use English qw ( -no_match_vars ); +use version; our $VERSION = qw (1.0); + +my $workdir; + +# сменим рабочий каталог +BEGIN { + use Cwd qw (chdir abs_path); + my @CWD = split /\//xms, abs_path ($PROGRAM_NAME); + if ($#CWD > 1) { $#CWD = $#CWD - 2; } + $workdir = join '/', @CWD; + chdir $workdir; +} + +use lib ("$workdir/lib", "$workdir/vendor_perl", "$workdir/vendor_perl/lib/perl5"); +use CHI; +use CHI::Driver::BerkeleyDB; +use Data::Dumper; +use YadProxy::Conf qw (LoadConf); + +my $c = LoadConf (); +my $index_dir = $c->{index_dir}; + + +my $index_dirs = CHI->new ( + driver => 'BerkeleyDB', + root_dir => $index_dir, + namespace => 'dirs' +); + +say join "\n", $index_dirs->get_keys (); + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/yadproxy.psgi b/yadproxy.psgi new file mode 100644 index 0000000..e3c1668 --- /dev/null +++ b/yadproxy.psgi @@ -0,0 +1,73 @@ +use 5.018; +use strict; +use warnings; +use utf8; +use open qw (:std :utf8); +use version; our $VERSION = qw (1.1.0); + +use lib qw (./lib ./vendor_perl/lib/perl5); +use YadProxy qw (DirList); +use YadProxy::Conf qw (LoadConf); + +my $c = LoadConf (); +my $url_prefix = $c->{url_prefix}; + +if (substr ($url_prefix, -1) eq '/') { + chop $url_prefix; +} + +if (substr ($url_prefix, 0, 1) eq '/') { + $url_prefix = substr $url_prefix, 1; +} + +my $app = sub { + my $env = shift; + + my $msg = "\n404 Not Found\n\n

404 Not Found

\n\n\n"; + my $status = '404'; + my $content = 'text/html'; + + if ($env->{PATH_INFO} =~ m/^\/$url_prefix\/(.*)/) { + my $path = $1; + + ($status, $content, $msg) = DirList ($path); + } + + if ($status eq '302') { + my $message = << "EOM"; + +302 Found + +

302 Found

+ + +EOM + use bytes; + my $length = length $message; + no bytes; + + return [ + $status, + [ + 'Content-Type' => $content, + 'Content-Length' => $length, + 'Location' => $msg + ], + [$message] + ]; + } + + use bytes; + my $length = length $msg; + no bytes; + + return [ + $status, + [ 'Content-Type' => $content, 'Content-Length' => $length ], + [ $msg ], + ]; +}; + + +__END__ +# vim: set ft=perl noet ai ts=4 sw=4 sts=4: diff --git a/yadproxy.yaml b/yadproxy.yaml new file mode 100644 index 0000000..a61e90d --- /dev/null +++ b/yadproxy.yaml @@ -0,0 +1,17 @@ +uwsgi: + http-socket: 127.0.0.1:3081 + socket-protocol: http + so-keepalive: 0 + logformat: %(var.REMOTE_ADDR) %(var.REQUEST_METHOD) %(var.REQUEST_URI) %(status) + processes: 1 + need-app: true + log-reopen: yes + reload-mercy: 5 + buffer-size: 32768 + die-on-term: true + pidfile: /run/yadproxy/yadproxy.pid + logto: /var/log/yadproxy/yadproxy.log + plugins: psgi + chdir: /var/lib/yadproxy + psgi: /var/lib/yadproxy/yadproxy.psgi + perl-local-lib: /var/lib/yadproxy/vendor_perl \ No newline at end of file