File Coverage

lib/Yandex/Disk.pm
Criterion Covered Total %
statement 59 210 28.1
branch 2 76 2.6
condition 1 31 3.2
subroutine 15 28 53.5
pod 7 10 70.0
total 84 355 23.6


line stmt bran cond sub pod time code
1             package Yandex::Disk;
2              
3 2     2   48038 use 5.008001;
  2         7  
4 2     2   7 use strict;
  2         3  
  2         32  
5 2     2   7 use warnings;
  2         7  
  2         46  
6 2     2   365 use utf8;
  2         14  
  2         7  
7 2     2   268 use Yandex::Disk::Public;
  2         7  
  2         43  
8 2     2   9 use Carp qw/croak carp/;
  2         4  
  2         79  
9 2     2   807 use LWP::UserAgent;
  2         66507  
  2         66  
10 2     2   915 use JSON::XS;
  2         6821  
  2         99  
11 2     2   13 use File::Basename;
  2         3  
  2         99  
12 2     2   10 use URI::Escape;
  2         4  
  2         76  
13 2     2   619 use Encode;
  2         14191  
  2         164  
14 2     2   990 use IO::Socket::SSL;
  2         125429  
  2         42  
15              
16             our $VERSION = '0.02';
17              
18             my $WAIT_RETRY = 20;
19             my $BUFF_SIZE = 8192;
20              
21             sub new {
22 2     2 0 1189 my $class = shift;
23 2         7 my %opt = @_;
24 2         5 my $self = {};
25 2   33     11 $self->{token} = $opt{-token} || croak "Specify -token param";
26 2         16 my $ua = LWP::UserAgent->new;
27 2         4067 $ua->agent("Yandex::Disk perl module");
28 2         120 $ua->default_header('Accept' => 'application/json');
29 2         81 $ua->default_header('Content-Type' => 'application/json');
30 2         79 $ua->default_header('Connection' => 'keep-alive');
31 2         81 $ua->default_header('Authorization' => 'OAuth ' . $self->{token});
32 2         76 $self->{ua} = $ua;
33 2         4 $self->{public_info} = {};
34 2         11 return bless $self, $class;
35             }
36              
37              
38             sub getDiskInfo {
39 1     1 1 573 my $self = shift;
40 1         2 my $url = 'https://cloud-api.yandex.net/v1/disk/';
41 1         3 my $res = $self->__request($url, "GET");
42 1 50       4 if ($res->is_success) {
43 0         0 return __fromJson($res->decoded_content);
44             }
45             else {
46 1         11 croak "Cant execute request to $url: " . $res->status_line;
47             }
48             }
49              
50             sub uploadFile {
51 0     0 1 0 my $self = shift;
52 0         0 my %opt = @_;
53 0         0 my $overwrite = $opt{-overwrite};
54 0   0     0 my $file = $opt{-file} || croak "Specify -file param";
55 0   0     0 my $remote_path = $opt{-remote_path} || croak "Specify -remote_path param";
56 0 0       0 $overwrite = 1 if not defined $overwrite;
57              
58 0 0       0 if (not -f $file) {
59 0         0 croak "File $file not exists";
60             }
61              
62 0 0       0 $overwrite = $overwrite ? 'true' : 'false';
63              
64             #Delete end slash
65 0         0 $remote_path =~ s/^\/|\/$//g;
66 0         0 $remote_path = sprintf("/%s/%s", $remote_path, basename($file));
67              
68 0         0 my $param = "?path=" . uri_escape($remote_path) . "&overwrite=$overwrite";
69 0         0 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/upload' . $param, "GET");
70 0         0 my $url_to_upload;
71 0         0 my $code = $res->code;
72 0 0       0 if ($code eq '409') {
    0          
73 0         0 croak "Folder $remote_path not exists";
74             }
75             elsif ($code eq '200') {
76 0         0 $url_to_upload = __fromJson($res->decoded_content)->{href};
77             }
78             else {
79 0         0 croak "Cant uploadFile: " . $res->status_line;
80             }
81            
82 0         0 my $upl_code = __upload_file($url_to_upload, $file);
83 0 0       0 if ($upl_code ne '201') {
84 0         0 croak "Cant upload file. Code: $code";
85             }
86 0         0 return 1;
87             }
88              
89             sub createFolder {
90 0     0 1 0 my $self = shift;
91 0         0 my %opt = @_;
92 0   0     0 my $path = $opt{-path} || croak "Specify -path param";
93 0         0 my $recursive = $opt{-recursive};
94              
95 0 0       0 if ($recursive) {
96 0         0 my @half_path;
97 0         0 for my $l_path (split /\//, $path) {
98 0         0 push @half_path, $l_path;
99 0         0 $self->createFolder( -path => join('/', @half_path) );
100             }
101             }
102              
103 0         0 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/?path=' . uri_escape($path), "PUT");
104 0         0 my $code = $res->code;
105 0 0       0 if ($code eq '409') {
    0          
106             #Папка или существует или не создана родительская папка
107 0         0 my $json_res = __fromJson($res->decoded_content);
108 0 0       0 if ($json_res->{error} eq 'DiskPathPointsToExistentDirectoryError') {
109 0         0 return 1;
110             }
111 0         0 croak $json_res->{description};
112             }
113             elsif ($code ne '201') {
114 0         0 croak "Cant create folder $path. Error: " . $res->status_line;
115             }
116              
117 0         0 return 1;
118             }
119              
120             sub deleteResource {
121 0     0 1 0 my $self = shift;
122 0         0 my %opt = @_;
123 0   0     0 my $path = $opt{-path} || croak "Specify -path param";
124 0         0 my $wait = $opt{-wait};
125 0         0 my $permanently = $opt{-permanently};
126              
127 0 0       0 $permanently = $permanently ? 'true' : 'false';
128              
129 0         0 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources?path=' . uri_escape($path), "DELETE");
130 0         0 my $code = $res->code;
131 0 0       0 if ($code eq '204') {
    0          
132             #Free folder
133 0         0 return 1;
134             }
135             elsif ($code eq '202') {
136 0 0       0 if ($wait) {
137 0         0 my $href = __fromJson($res->decoded_content)->{href};
138 0 0       0 $self->__waitResponse($href, $WAIT_RETRY) or croak "Timeout to wait response. Try increase $WAIT_RETRY variable";
139             }
140 0         0 return 1;
141             }
142             else {
143 0         0 croak "Cant delete $path. Error: " . $res->status_line;
144             }
145             }
146              
147             sub downloadFile {
148 0     0 1 0 my $self = shift;
149 0         0 my %opt = @_;
150 0   0     0 my $path = $opt{-path} || croak "Specify -path param";
151 0   0     0 my $file = $opt{-file} || croak "Specify -file param";
152              
153 0         0 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/download?path=' . uri_escape($path), "GET");
154 0         0 my $code = $res->code;
155 0 0       0 if ($code ne '200') {
156 0         0 croak "Error on request file $path: " . $res->status_line;
157             }
158 0         0 my $download_url = __fromJson($res->decoded_content)->{href};
159              
160 0         0 $self->__download($download_url, $file);
161 0         0 return 1;
162             }
163              
164             sub emptyTrash {
165 0     0 1 0 my $self = shift;
166 0         0 my %opt = @_;
167 0   0     0 my $path = $opt{-path} || '';
168 0         0 my $wait = $opt{-wait};
169              
170 0 0       0 my $param = $path ? '?path=' . uri_escape($path) : '';
171 0         0 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/trash/resources/' . $path, 'DELETE');
172 0         0 my $code = $res->code;
173 0 0       0 if ($code eq '204') {
    0          
174 0         0 return 1;
175             }
176             elsif ($code eq '202') {
177 0 0       0 if ($wait) {
178 0         0 my $href = __fromJson($res->decoded_content)->{href};
179 0 0       0 $self->__waitResponse($href, $WAIT_RETRY) or croak "Timeout to wait response. Try increase $WAIT_RETRY variable";
180             }
181 0         0 return 1;
182             }
183             else {
184 0 0       0 $path = "by path $path" if $path;
185 0         0 croak "Cant empty trash$path. Error: " . $res->status_line;
186             }
187             }
188              
189             sub listFiles {
190 0     0 1 0 my $self = shift;
191 0         0 my %opt = @_;
192 0   0     0 my $path = $opt{-path} || croak "Specify -path param";
193 0   0     0 my $limit = $opt{-limit} || 999999;
194 0         0 my $offset = $opt{-offset};
195 0 0       0 $offset = 0 if not $offset;
196              
197 0         0 my $param = '?path=' . uri_escape($path) . "&limit=$limit&offset=$offset&fields=_embedded.items";
198 0         0 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources' . $param, 'GET');
199 0         0 my $code = $res->code;
200 0 0       0 if ($code ne '200') {
201 0         0 croak "Error on listFiles. Error: " . $res->status_line;
202             }
203 0         0 my $json_res = __fromJson($res->decoded_content);
204              
205 0         0 return $json_res->{_embedded}->{items};
206             }
207              
208             sub public {
209 0     0 0 0 my $self = shift;
210 0         0 return Yandex::Disk::Public->new( -token => $self->{token} );
211             }
212              
213             sub __download {
214 0     0   0 my ($self, $url, $fname) = @_;
215 0         0 my $ua = $self->{ua};
216              
217 0 0       0 open my $FL, ">$fname" or croak "Cant open $fname to write $!";
218 0         0 binmode $FL;
219 0     0   0 my $res = $ua->get($url, ':read_size_hint' => $BUFF_SIZE, ':content_cb' => sub {print $FL $_[0];});
  0         0  
220 0         0 close $FL;
221 0 0       0 if ($res->code eq '200') {
222 0         0 return 1;
223             }
224 0         0 croak "Cant download file $url to $fname. Error: " . $res->status_line;
225             }
226              
227             sub __waitResponse {
228             #Дожидается ответа о статусе операции
229 0     0   0 my ($self, $url, $retry) = @_;
230              
231 0         0 while ($retry > 0) {
232 0         0 my $res = $self->__request($url, "GET");
233 0         0 my $code = $res->code;
234 0 0 0     0 if ($code eq '200' && __fromJson($res->decoded_content)->{status} eq 'success') {
235 0         0 return 1;
236             }
237 0         0 sleep 1;
238 0         0 $retry--;
239             }
240 0         0 return;
241             }
242              
243              
244             sub __upload_file {
245             #Buffered chunked upload file
246 0     0   0 my ($url, $file) = @_;
247              
248 0         0 my $u1 = URI->new($url);
249              
250             # $IO::Socket::SSL::DEBUG = 3;
251 0         0 my $host = $u1->host;
252 0         0 my $port = $u1->port;
253 0         0 my $path = $u1->path;
254              
255 0 0       0 my $sock = IO::Socket::SSL->new(
256             PeerAddr => $host,
257             PeerPort => $port,
258             Proto => 'tcp',
259             ) or croak "Cant connect to $host:$port";
260 0         0 binmode $sock;
261 0         0 $sock->autoflush(1);
262              
263 0         0 $sock->print("PUT $path HTTP/1.1\n");
264 0         0 $sock->print("HOST: $host\n");
265 0         0 $sock->print("Connection: close\n");
266 0         0 $sock->print("Content-Type: application/json\n");
267 0         0 $sock->print("Transfer-Encoding: chunked\n");
268 0         0 $sock->print("\n");
269              
270 0 0       0 open my $FH, "<$file" or croak "Cant open $file $!";
271 0         0 binmode $FH;
272 0         0 my $filebuf;
273 0         0 while (my $bytes = read($FH, $filebuf, $BUFF_SIZE)) {
274 0         0 my $hex = sprintf("%X", $bytes);
275 0 0       0 $sock->print($hex) or croak "Cant print to socket";
276 0 0       0 $sock->print("\r\n") or croak "Cant print to socket";
277              
278 0 0       0 $sock->print($filebuf) or croak "Cant print to socket";
279 0 0       0 $sock->print("\r\n") or croak "Cant print to socket";
280             }
281 0         0 close $FH;
282              
283 0 0       0 $sock->print("0\r\n") or croak "Cant print to socket";
284 0 0       0 $sock->print("\r\n") or croak "Cant print to socket";
285            
286 0         0 my @answer = $sock->getlines();
287 0         0 $sock->close();
288              
289 0         0 my ($code) = $answer[0] =~ /(\d{3})/;
290              
291 0         0 return $code;
292             }
293              
294              
295             sub __request {
296 2     2   74 my ($self, $url, $type, $param) = @_;
297 2 50       9 $param = {} if not $param;
298              
299 2         11 my $ua = $self->{ua};
300 2         17 my $req = HTTP::Request->new($type => $url);
301 2         11657 my $res = $ua->request($req);
302            
303 2         3376 return $res;
304             }
305              
306             sub __fromJson {
307 0 0   0     my $string = ref($_[0]) ? $_[1] : $_[0];
308 0           my $res = JSON::XS::decode_json($string);
309 0           return $res;
310             }
311              
312              
313             sub errstr {
314 0     0 0   return shift->{errstr};
315             }
316            
317             1;
318              
319             __END__