File Coverage

lib/Yandex/Disk.pm
Criterion Covered Total %
statement 203 235 86.3
branch 39 84 46.4
condition 12 35 34.2
subroutine 28 30 93.3
pod 9 12 75.0
total 291 396 73.4


line stmt bran cond sub pod time code
1             package Yandex::Disk;
2              
3 2     2   57588 use 5.008001;
  2         9  
4 2     2   16 use strict;
  2         3  
  2         51  
5 2     2   12 use warnings;
  2         4  
  2         72  
6 2     2   544 use utf8;
  2         15  
  2         13  
7 2     2   447 use Yandex::Disk::Public;
  2         6  
  2         79  
8 2     2   15 use Carp qw/croak carp/;
  2         4  
  2         109  
9 2     2   1476 use LWP::UserAgent;
  2         90513  
  2         77  
10 2     2   1364 use JSON::XS;
  2         9382  
  2         137  
11 2     2   18 use File::Basename;
  2         5  
  2         139  
12 2     2   15 use URI::Escape;
  2         5  
  2         116  
13 2     2   1214 use Encode;
  2         21742  
  2         164  
14 2     2   1670 use IO::Socket::SSL;
  2         164288  
  2         16  
15              
16             our $VERSION = '0.03';
17              
18             my $WAIT_RETRY = 20;
19             my $BUFF_SIZE = 8192;
20              
21             sub new {
22 3     3 0 1553 my $class = shift;
23 3         15 my %opt = @_;
24 3         22 my $self = {};
25 3   33     20 $self->{token} = $opt{-token} || croak "Specify -token param";
26 3         29 my $ua = LWP::UserAgent->new;
27 3         5700 $ua->agent("Yandex::Disk perl module");
28 3         179 $ua->default_header('Accept' => 'application/json');
29 3         127 $ua->default_header('Content-Type' => 'application/json');
30 3         124 $ua->default_header('Connection' => 'keep-alive');
31 3         127 $ua->default_header('Authorization' => 'OAuth ' . $self->{token});
32 3         118 $self->{ua} = $ua;
33 3         9 $self->{public_info} = {};
34 3         15 return bless $self, $class;
35             }
36              
37              
38             sub getDiskInfo {
39 1     1 1 676 my $self = shift;
40 1         3 my $url = 'https://cloud-api.yandex.net/v1/disk/';
41 1         4 my $res = $self->__request($url, "GET");
42 1 50       8 if ($res->is_success) {
43 1         22 return __fromJson($res->decoded_content);
44             }
45             else {
46 0         0 croak "Cant execute request to $url: " . $res->status_line;
47             }
48             }
49              
50             sub uploadFile {
51 1     1 1 699 my $self = shift;
52 1         5 my %opt = @_;
53 1         10 my $overwrite = $opt{-overwrite};
54 1   33     5 my $file = $opt{-file} || croak "Specify -file param";
55 1   33     3 my $remote_path = $opt{-remote_path} || croak "Specify -remote_path param";
56 1 50       3 $overwrite = 1 if not defined $overwrite;
57              
58 1 50       28 if (not -f $file) {
59 0         0 croak "File $file not exists";
60             }
61              
62 1 50       4 $overwrite = $overwrite ? 'true' : 'false';
63              
64             #Delete end slash
65 1         6 $remote_path =~ s/^\/|\/$//g;
66 1         48 $remote_path = sprintf("/%s/%s", $remote_path, basename($file));
67              
68 1         5 my $param = "?path=" . uri_escape($remote_path) . "&overwrite=$overwrite";
69 1         30 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/upload' . $param, "GET");
70 1         4 my $url_to_upload;
71 1         5 my $code = $res->code;
72 1 50       21 if ($code eq '409') {
    50          
73 0         0 croak "Folder $remote_path not exists";
74             }
75             elsif ($code eq '200') {
76 1         6 $url_to_upload = __fromJson($res->decoded_content)->{href};
77             }
78             else {
79 0         0 croak "Cant uploadFile: " . $res->status_line;
80             }
81            
82 1         7 my $upl_code = __upload_file($url_to_upload, $file);
83 1 50       165 if ($upl_code ne '201') {
84 0         0 croak "Cant upload file. Code: $code";
85             }
86 1         29 return 1;
87             }
88              
89             sub createFolder {
90 3     3 1 2192 my $self = shift;
91 3         13 my %opt = @_;
92 3   33     16 my $path = $opt{-path} || croak "Specify -path param";
93 3         8 my $recursive = $opt{-recursive};
94              
95 3 100       10 if ($recursive) {
96 1         2 my @half_path;
97 1         7 for my $l_path (split /\//, $path) {
98 2         8 push @half_path, $l_path;
99 2         17 $self->createFolder( -path => join('/', @half_path) );
100             }
101             }
102              
103 3         18 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/?path=' . uri_escape($path), "PUT");
104 3         14 my $code = $res->code;
105 3 100       40 if ($code eq '409') {
    50          
106             #Папка или существует или не создана родительская папка
107 2         10 my $json_res = __fromJson($res->decoded_content);
108 2 50       11 if ($json_res->{error} eq 'DiskPathPointsToExistentDirectoryError') {
109 2         32 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 1         16 return 1;
118             }
119              
120             sub deleteResource {
121 1     1 1 2822 my $self = shift;
122 1         10 my %opt = @_;
123 1   33     5 my $path = $opt{-path} || croak "Specify -path param";
124 1         3 my $wait = $opt{-wait};
125 1         2 my $permanently = $opt{-permanently};
126              
127 1 50       5 $permanently = $permanently ? 'true' : 'false';
128              
129 1         5 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources?path=' . uri_escape($path), "DELETE");
130 1         4 my $code = $res->code;
131 1 50       13 if ($code eq '204') {
    0          
132             #Free folder
133 1         17 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 1     1 1 181686 my $self = shift;
149 1         5 my %opt = @_;
150 1   33     6 my $path = $opt{-path} || croak "Specify -path param";
151 1   33     4 my $file = $opt{-file} || croak "Specify -file param";
152              
153 1         7 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/download?path=' . uri_escape($path), "GET");
154 1         10 my $code = $res->code;
155 1 50       22 if ($code ne '200') {
156 0         0 croak "Error on request file $path: " . $res->status_line;
157             }
158 1         8 my $download_url = __fromJson($res->decoded_content)->{href};
159              
160 1         12 $self->__download($download_url, $file);
161 1         22 return 1;
162             }
163              
164             sub emptyTrash {
165 1     1 1 1736 my $self = shift;
166 1         3 my %opt = @_;
167 1   50     7 my $path = $opt{-path} || '';
168 1         3 my $wait = $opt{-wait};
169              
170 1 50       4 my $param = $path ? '?path=' . uri_escape($path) : '';
171 1         5 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/trash/resources/' . $path, 'DELETE');
172 1         4 my $code = $res->code;
173 1 50       13 if ($code eq '204') {
    50          
174 0         0 return 1;
175             }
176             elsif ($code eq '202') {
177 1 50       5 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 1         15 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 1     1 1 687 my $self = shift;
191 1         4 my %opt = @_;
192 1   33     4 my $path = $opt{-path} || croak "Specify -path param";
193 1   50     6 my $limit = $opt{-limit} || 999999;
194 1         2 my $offset = $opt{-offset};
195 1 50       4 $offset = 0 if not $offset;
196              
197 1         8 my $param = '?path=' . uri_escape($path) . "&limit=$limit&offset=$offset&fields=_embedded.items";
198 1         83 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources' . $param, 'GET');
199 1         4 my $code = $res->code;
200 1 50       19 if ($code ne '200') {
201 0         0 croak "Error on listFiles. Error: " . $res->status_line;
202             }
203 1         5 my $json_res = __fromJson($res->decoded_content);
204              
205 1         17 return $json_res->{_embedded}->{items};
206             }
207              
208             sub listAllFiles {
209 1     1 1 675 my $self = shift;
210 1         12 my %opt = @_;
211 1   50     7 my $limit = $opt{-limit} || 999999;
212 1         2 my $media_type = $opt{-media_type};
213 1         2 my $offset = $opt{-offset};
214 1 50       6 $offset = 0 if not $offset;
215              
216 1         4 my $param = "?limit=$limit&offset=$offset";
217 1 50       3 $param .= '&media_type=' . $media_type if $media_type;
218              
219 1         26 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/files' . $param, 'GET');
220 1         4 my $code = $res->code;
221 1 50       10 if ($code ne '200') {
222 0         0 croak "Error on listFiles. Error: " . $res->status_line;
223             }
224 1         4 my $json_res = __fromJson($res->decoded_content);
225              
226 1         6399 return $json_res->{items};
227             }
228              
229             sub lastUploadedFiles {
230 1     1 1 111313 my $self = shift;
231 1         2 my %opt = @_;
232 1   50     8 my $limit = $opt{-limit} || 999999;
233 1         2 my $media_type = $opt{-media_type};
234              
235 1         3 my $param = "?limit=$limit";
236 1 50       3 $param .= '&media_type=' . $media_type if $media_type;
237              
238 1         6 my $res = $self->__request('https://cloud-api.yandex.net/v1/disk/resources/last-uploaded' . $param, 'GET');
239 1         7 my $code = $res->code;
240 1 50       11 if ($code ne '200') {
241 0         0 croak "Error on listFiles. Error: " . $res->status_line;
242             }
243 1         42 my $json_res = __fromJson($res->decoded_content);
244              
245 1         7810 return $json_res->{items};
246             }
247              
248              
249             sub public {
250 1     1 0 652 my $self = shift;
251 1         18 return Yandex::Disk::Public->new( -token => $self->{token} );
252             }
253              
254             sub __download {
255 1     1   5 my ($self, $url, $fname) = @_;
256 1         7 my $ua = $self->{ua};
257              
258 1 50       136 open my $FL, ">$fname" or croak "Cant open $fname to write $!";
259 1         8 binmode $FL;
260 1     3   15 my $res = $ua->get($url, ':read_size_hint' => $BUFF_SIZE, ':content_cb' => sub {print $FL $_[0];});
  3         491636  
261 1         2976 close $FL;
262 1 50       7 if ($res->code eq '200') {
263 1         41 return 1;
264             }
265 0         0 croak "Cant download file $url to $fname. Error: " . $res->status_line;
266             }
267              
268             sub __waitResponse {
269             #Дожидается ответа о статусе операции
270 0     0   0 my ($self, $url, $retry) = @_;
271              
272 0         0 while ($retry > 0) {
273 0         0 my $res = $self->__request($url, "GET");
274 0         0 my $code = $res->code;
275 0 0 0     0 if ($code eq '200' && __fromJson($res->decoded_content)->{status} eq 'success') {
276 0         0 return 1;
277             }
278 0         0 sleep 1;
279 0         0 $retry--;
280             }
281 0         0 return;
282             }
283              
284              
285             sub __upload_file {
286             #Buffered chunked upload file
287 1     1   5 my ($url, $file) = @_;
288              
289 1         12 my $u1 = URI->new($url);
290              
291             # $IO::Socket::SSL::DEBUG = 3;
292 1         158 my $host = $u1->host;
293 1         48 my $port = $u1->port;
294 1         420 my $path = $u1->path;
295              
296 1 50       41 my $sock = IO::Socket::SSL->new(
297             PeerAddr => $host,
298             PeerPort => $port,
299             Proto => 'tcp',
300             ) or croak "Cant connect to $host:$port";
301 1         405899 binmode $sock;
302 1         10 $sock->autoflush(1);
303              
304 1         65 $sock->print("PUT $path HTTP/1.1\n");
305 1         1187 $sock->print("HOST: $host\n");
306 1         196 $sock->print("Connection: close\n");
307 1         149 $sock->print("Content-Type: application/json\n");
308 1         164 $sock->print("Transfer-Encoding: chunked\n");
309 1         153 $sock->print("\n");
310              
311 1 50       188 open my $FH, "<$file" or croak "Cant open $file $!";
312 1         4 binmode $FH;
313 1         2 my $filebuf;
314 1         17 while (my $bytes = read($FH, $filebuf, $BUFF_SIZE)) {
315 2         201 my $hex = sprintf("%X", $bytes);
316 2 50       8 $sock->print($hex) or croak "Cant print to socket";
317 2 50       316 $sock->print("\r\n") or croak "Cant print to socket";
318              
319 2 50       289 $sock->print($filebuf) or croak "Cant print to socket";
320 2 50       602 $sock->print("\r\n") or croak "Cant print to socket";
321             }
322 1         157 close $FH;
323              
324 1 50       5 $sock->print("0\r\n") or croak "Cant print to socket";
325 1 50       147 $sock->print("\r\n") or croak "Cant print to socket";
326            
327 1         149 my @answer = $sock->getlines();
328 1         618440 $sock->close();
329              
330 1         555 my ($code) = $answer[0] =~ /(\d{3})/;
331              
332 1         35 return $code;
333             }
334              
335              
336             sub __request {
337 15     15   395 my ($self, $url, $type) = @_;
338              
339 15         57 my $ua = $self->{ua};
340 15         131 my $req = HTTP::Request->new($type => $url);
341 15         16937 my $res = $ua->request($req);
342            
343 15         177912581 return $res;
344             }
345              
346             sub __fromJson {
347 11 100   11   90003 my $string = ref($_[0]) ? $_[1] : $_[0];
348 11         661011 my $res = JSON::XS::decode_json($string);
349 11         96 return $res;
350             }
351              
352              
353             sub errstr {
354 0     0 0   return shift->{errstr};
355             }
356            
357             1;
358              
359             __END__