File Coverage

blib/lib/File/Dropbox.pm
Criterion Covered Total %
statement 73 372 19.6
branch 8 108 7.4
condition 7 59 11.8
subroutine 22 35 62.8
pod 8 8 100.0
total 118 582 20.2


line stmt bran cond sub pod time code
1             package File::Dropbox;
2 12     12   359267 use strict;
  12         31  
  12         431  
3 12     12   63 use warnings;
  12         27  
  12         351  
4 12     12   65 use feature ':5.10';
  12         25  
  12         1675  
5 12     12   71 use base qw{ Tie::Handle Exporter };
  12         27  
  12         16431  
6 12     12   38821 use Symbol;
  12         11714  
  12         860  
7 12     12   12726 use JSON;
  12         241387  
  12         72  
8 12     12   3645 use Errno qw{ ENOENT EISDIR EINVAL EPERM EACCES EAGAIN ECANCELED EFBIG };
  12         2290  
  12         1136  
9 12     12   72 use Fcntl qw{ SEEK_CUR SEEK_SET SEEK_END };
  12         22  
  12         623  
10 12     12   10196 use Furl;
  12         761702  
  12         456  
11 12     12   25255 use IO::Socket::SSL;
  12         1450286  
  12         123  
12 12     12   13656 use Net::DNS::Lite;
  12         156493  
  12         49396  
13              
14             our $VERSION = 0.6;
15             our @EXPORT_OK = qw{ contents metadata putfile movefile copyfile createfolder deletefile };
16              
17             my $hosts = {
18             content => 'api-content.dropbox.com',
19             api => 'api.dropbox.com',
20             };
21              
22             my $version = 1;
23              
24             my $header1 = join ', ',
25             'OAuth oauth_version="1.0"',
26             'oauth_signature_method="PLAINTEXT"',
27             'oauth_consumer_key="%s"',
28             'oauth_token="%s"',
29             'oauth_signature="%s&%s"';
30              
31             my $header2 = 'Bearer %s';
32              
33             sub new {
34 13     13 1 1326 my $self = Symbol::gensym;
35 13         334 tie *$self, __PACKAGE__, my $this = { @_[1 .. @_ - 1] };
36              
37 12         38 *$self = $this;
38              
39 12         38 return $self;
40             } # new
41              
42             sub TIEHANDLE {
43 13   33 13   95 my $self = bless $_[1], ref $_[0] || $_[0];
44              
45 13   100     189 $self->{'chunk'} //= 4 << 20;
46 13   100     104 $self->{'root'} //= 'sandbox';
47              
48 13 100       126 die 'Unexpected root value'
49             unless $self->{'root'} =~ m{^(?:drop|sand)box$};
50              
51 12   100     179 $self->{'furl'} = Furl->new(
52             timeout => 10,
53             inet_aton => \&Net::DNS::Lite::inet_aton,
54             ssl_opts => {
55             SSL_verify_mode => SSL_VERIFY_PEER(),
56             },
57              
58 12         69 %{ $self->{'furlopts'} //= {} },
59             );
60              
61 12         841 $self->{'closed'} = 1;
62 12         38 $self->{'length'} = 0;
63 12         32 $self->{'position'} = 0;
64 12         43 $self->{'mode'} = '';
65 12         32 $self->{'buffer'} = '';
66              
67 12         221 return $self;
68             } # TIEHANDLE
69              
70             sub READ {
71 1     1   607 my ($self, undef, $length, $offset) = @_;
72              
73 1         3 undef $!;
74              
75 1 50       14 die 'Read is not supported on this handle'
76             if $self->{'mode'} ne '<';
77              
78 0 0 0     0 substr($_[1] //= '', $offset // 0) = '', return 0
      0        
79             if $self->EOF();
80              
81 0         0 my $furl = $self->{'furl'};
82              
83 0         0 my $url = 'https://';
84 0         0 $url .= join '/', $hosts->{'content'}, $version;
85 0         0 $url .= join '/', '/files', $self->{'root'}, $self->{'path'};
86              
87 0         0 my $response = $furl->get($url, [
88             Range => sprintf('bytes=%i-%i', $self->{'position'}, $self->{'position'} + ($length || 1)),
89              
90 0   0     0 @{ &__headers__ },
91             ]);
92              
93 0         0 given ($response->code()) {
94 0         0 when (206) { 1 }
  0         0  
95              
96 0         0 when ([401, 403]) {
97 0         0 $! = EACCES;
98 0         0 return 0;
99             }
100              
101 0         0 when (500) {
102 0 0       0 continue unless $response->content() =~ m{\A(?:Cannot|Failed)};
103              
104 0         0 $! = ECANCELED;
105 0         0 return 0;
106             }
107              
108 0         0 when (503) {
109 0         0 $self->{'meta'} = { retry => $response->header('Retry-After') };
110 0         0 $! = EAGAIN;
111 0         0 return 0;
112             }
113              
114 0         0 default {
115 0         0 die join ' ', $_, $response->decoded_content();
116             }
117             }
118              
119 0         0 my $meta = $response->header('X-Dropbox-Metadata');
120 0         0 my $bytes = $response->header('Content-Length');
121              
122 0 0       0 $self->{'position'} += $bytes > $length? $length : $bytes;
123              
124 0   0     0 substr($_[1] //= '', $offset // 0) = substr $response->content(), 0, $length;
      0        
125              
126 0         0 return $bytes;
127             } # READ
128              
129             sub READLINE {
130 1     1   527 my ($self) = @_;
131 1         2 my $length;
132              
133 1         3 undef $!;
134              
135 1 50       14 die 'Readline is not supported on this handle'
136             if $self->{'mode'} ne '<';
137              
138 0 0       0 if ($self->EOF()) {
139 0 0       0 return if wantarray;
140              
141             # Special case: slurp mode + scalar context + empty file
142             # return '' for first call and undef for subsequent
143 0 0 0     0 return ''
144             unless $self->{'eof'} or defined $/;
145              
146 0         0 $self->{'eof'} = 1;
147 0         0 return undef;
148             }
149              
150             {
151 0         0 $length = length $self->{'buffer'};
  0         0  
152              
153 0 0 0     0 if (not wantarray and $length and defined $/) {
      0        
154 0         0 my $position = index $self->{'buffer'}, $/;
155              
156 0 0       0 if (~$position) {
157 0         0 $self->{'position'} += ($position += length $/);
158 0         0 return substr $self->{'buffer'}, 0, $position, '';
159             }
160             }
161              
162 0         0 local $self->{'position'} = $self->{'position'} + $length;
163              
164 0         0 my $bytes = $self->READ($self->{'buffer'}, $self->{'chunk'}, $length);
165              
166 0 0       0 return if $!;
167 0 0 0     0 redo if not $length or $bytes;
168             }
169              
170 0         0 $length = length $self->{'buffer'};
171              
172 0 0       0 if ($length) {
173             # Multiline
174 0 0 0     0 if (wantarray and defined $/) {
175 0         0 $self->{'position'} += $length;
176              
177 0         0 my ($position, $length) = (0, length $/);
178 0         0 my @lines;
179              
180 0         0 foreach ($self->{'buffer'}) {
181 0         0 while (~(my $offset = index $_, $/, $position)) {
182 0         0 $offset += $length;
183 0         0 push @lines, substr $_, $position, $offset - $position;
184 0         0 $position = $offset;
185             }
186              
187 0 0       0 push @lines, substr $_, $position
188             if $position < length;
189              
190 0         0 $_ = '';
191             }
192              
193 0         0 return @lines;
194             }
195              
196             # Slurp or last chunk
197 0         0 $self->{'position'} += $length;
198 0         0 return substr $self->{'buffer'}, 0, $length, '';
199             }
200              
201 0         0 return undef;
202             } # READLINE
203              
204             sub SEEK {
205 1     1   619 my ($self, $position, $whence) = @_;
206              
207 1         3 undef $!;
208              
209 1 50       14 die 'Seek is not supported on this handle'
210             if $self->{'mode'} ne '<';
211              
212 0         0 $self->{'buffer'} = '';
213              
214 0         0 delete $self->{'eof'};
215              
216 0         0 given ($whence) {
217 0         0 when (SEEK_SET) {
218 0         0 $self->{'position'} = $position
219             }
220              
221 0         0 when (SEEK_CUR) {
222 0         0 $self->{'position'} += $position
223             }
224              
225 0         0 when (SEEK_END) {
226 0         0 $self->{'position'} = $self->{'length'} + $position
227             }
228              
229 0         0 default {
230 0         0 $! = EINVAL;
231 0         0 return 0;
232             }
233             }
234              
235 0 0       0 $self->{'position'} = 0
236             if $self->{'position'} < 0;
237              
238 0         0 return 1;
239             } # SEEK
240              
241             sub TELL {
242 1     1   5569 my ($self) = @_;
243              
244 1 50       18 die 'Tell is not supported on this handle'
245             if $self->{'mode'} ne '<';
246              
247 0         0 return $self->{'position'};
248             } # TELL
249              
250             sub WRITE {
251 1     1   619 my ($self, $buffer, $length, $offset) = @_;
252              
253 1         3 undef $!;
254              
255 1 50       14 die 'Write is not supported on this handle'
256             if $self->{'mode'} ne '>';
257              
258 0 0 0     0 die 'Append-only writes supported'
259             if $offset and $offset != $self->{'offset'} + $self->{'length'};
260              
261 0   0     0 $self->{'offset'} //= $offset;
262 0         0 $self->{'buffer'} .= $buffer;
263 0         0 $self->{'length'} += $length;
264              
265 0   0     0 $self->__flush__() or return 0
266             while $self->{'length'} >= $self->{'chunk'};
267              
268 0         0 return 1;
269             } # WRITE
270              
271             sub CLOSE {
272 0     0   0 my ($self) = @_;
273              
274 0         0 undef $!;
275              
276 0 0       0 return 1
277             if $self->{'closed'};
278              
279 0         0 my $mode = $self->{'mode'};
280              
281 0 0       0 if ($mode eq '>') {
282 0 0 0     0 if ($self->{'length'} or not $self->{'upload_id'}) {
283 0         0 do {
284 0 0 0     0 @{ $self }{qw{ closed mode }} = (1, '') and return 0
  0         0  
285             unless $self->__flush__();
286             } while length $self->{'buffer'};
287             }
288             }
289              
290 0         0 $self->{'closed'} = 1;
291 0         0 $self->{'mode'} = '';
292              
293 0 0       0 return $self->__flush__()
294             if $mode eq '>';
295              
296 0         0 return 1;
297             } # CLOSE
298              
299             sub OPEN {
300 0     0   0 my ($self, $mode, $file) = @_;
301              
302 0         0 undef $!;
303              
304 0 0       0 ($mode, $file) = $mode =~ m{^([<>]?)(.*)$}s
305             unless $file;
306              
307 0   0     0 given ($mode ||= '<') {
308 0         0 when (['>', '<']) { 1 }
  0         0  
309              
310 0         0 when ('r') {
311 0         0 $mode = '<';
312             }
313              
314 0         0 when (['a', 'w']) {
315 0         0 $mode = '>';
316             }
317              
318 0         0 default {
319 0         0 die 'Unsupported mode';
320             }
321             }
322              
323 0 0       0 $self->CLOSE()
324             unless $self->{'closed'};
325              
326 0         0 $self->{'length'} = 0;
327 0         0 $self->{'position'} = 0;
328 0         0 $self->{'buffer'} = '';
329              
330 0         0 delete $self->{'offset'};
331 0         0 delete $self->{'revision'};
332 0         0 delete $self->{'upload_id'};
333 0         0 delete $self->{'meta'};
334 0         0 delete $self->{'eof'};
335              
336 0 0       0 $self->{'path'} = $file
337             or die 'Path required';
338              
339 0 0 0     0 return 0
340             if $mode eq '<' and not $self->__meta__();
341              
342 0         0 $self->{'mode'} = $mode;
343 0         0 $self->{'closed'} = 0;
344              
345 0         0 return 1;
346             } # OPEN
347              
348             sub EOF {
349 1     1   528 my ($self) = @_;
350              
351 1 50       12 die 'Eof is not supported on this handle'
352             if $self->{'mode'} ne '<';
353              
354 0         0 return $self->{'position'} >= $self->{'length'};
355             } # EOF
356              
357 1     1   2314 sub BINMODE { 1 }
358              
359             sub __headers__ {
360             return [
361 0           'Authorization',
362             $_[0]->{'oauth2'}?
363             sprintf $header2, $_[0]->{'access_token'}:
364 0 0   0     sprintf $header1, @{ $_[0] }{qw{ app_key access_token app_secret access_secret }},
365             ];
366             }
367              
368             sub __flush__ {
369 0     0     my ($self) = @_;
370 0           my $furl = $self->{'furl'};
371 0           my $url;
372              
373 0           $url = 'https://';
374 0           $url .= join '/', $hosts->{'content'}, $version;
375              
376 0 0         $url .= join '/', '/commit_chunked_upload', $self->{'root'}, $self->{'path'}
377             if $self->{'closed'};
378              
379 0 0         $url .= '/chunked_upload'
380             unless $self->{'closed'};
381              
382 0           $url .= '?';
383              
384 0 0         $url .= join '=', 'upload_id', $self->{'upload_id'}
385             if $self->{'upload_id'};
386              
387 0 0         $url .= '&'
388             if $self->{'upload_id'};
389              
390 0 0 0       $url .= join '=', 'offset', $self->{'offset'} || 0
391             unless $self->{'closed'};
392              
393 0           my $response;
394              
395 0 0         unless ($self->{'closed'}) {
396 12     12   278 use bytes;
  12         27  
  12         128  
397              
398 0           my $buffer = substr $self->{'buffer'}, 0, $self->{'chunk'}, '';
399 0           my $length = length $buffer;
400              
401 0           $self->{'length'} -= $length;
402 0           $self->{'offset'} += $length;
403              
404 0           $response = $furl->put($url, &__headers__, $buffer);
405             } else {
406 0           $response = $furl->post($url, &__headers__);
407             }
408              
409 0           given ($response->code()) {
410 0           when (400) {
411 0           $! = EINVAL;
412 0           return 0;
413             }
414              
415 0           when ([401, 403]) {
416 0           $! = EACCES;
417 0           return 0;
418             }
419              
420 0           when (500) {
421 0 0         continue unless $response->content() =~ m{\A(?:Cannot|Failed)};
422              
423 0           $! = ECANCELED;
424 0           return 0;
425             }
426              
427 0           when (503) {
428 0           $self->{'meta'} = { retry => $response->header('Retry-After') };
429 0           $! = EAGAIN;
430 0           return 0;
431             }
432              
433 0           when (507) {
434 0           $! = EFBIG;
435 0           return 0;
436             }
437              
438 0           when (200) {
439 0 0         $self->{'meta'} = from_json($response->content())
440             if $self->{'closed'};
441             }
442              
443 0           default {
444 0           die join ' ', $_, $response->decoded_content();
445             }
446             }
447              
448 0 0         unless ($self->{'upload_id'}) {
449 0           $response = from_json($response->content());
450 0           $self->{'upload_id'} = $response->{'upload_id'};
451             }
452              
453 0           return 1;
454             } # __flush__
455              
456             sub __meta__ {
457 0     0     my ($self) = @_;
458 0           my ($url, $meta);
459              
460 0           my $furl = $self->{'furl'};
461              
462 0           $url = 'https://';
463 0           $url .= join '/', $hosts->{'api'}, $version;
464 0           $url .= join '/', '/metadata', $self->{'root'}, $self->{'path'};
465              
466 0 0         $url .= '?hash='. delete $self->{'hash'}
467             if $self->{'hash'};
468              
469 0           my $response = $furl->get($url, &__headers__);
470              
471 0           given ($response->code()) {
472 0           when ([401, 403]) {
473 0           $! = EACCES;
474 0           return 0;
475             }
476              
477 0           when (404) {
478 0           $! = ENOENT;
479 0           return 0;
480             }
481              
482 0           when (406) {
483 0           $! = EPERM;
484 0           return 0;
485             }
486              
487 0           when (500) {
488 0 0         continue unless $response->content() =~ m{\A(?:Cannot|Failed)};
489              
490 0           $! = ECANCELED;
491 0           return 0;
492             }
493              
494 0           when (503) {
495 0           $self->{'meta'} = { retry => $response->header('Retry-After') };
496 0           $! = EAGAIN;
497 0           return 0;
498             }
499              
500 0           when (200) {
501 0           $meta = $self->{'meta'} = from_json($response->content());
502              
503             # XXX: Dropbox returns metadata for recently deleted files
504 0 0         if ($meta->{'is_deleted'}) {
505 0           $! = ENOENT;
506 0           return 0;
507             }
508             }
509              
510 0           when (304) { 1 }
  0            
511              
512 0           default {
513 0           die join ' ', $_, $response->decoded_content();
514             }
515             }
516              
517 0 0         $! = EISDIR, return 0
518             if $meta->{'is_dir'};
519              
520 0           $self->{'revision'} = $meta->{'rev'};
521 0           $self->{'length'} = $meta->{'bytes'};
522              
523 0           return 1;
524             } # __meta__
525              
526             sub __fileops__ {
527 0     0     my ($type, $handle, $source, $target) = @_;
528              
529 0           my $self = *$handle{'HASH'};
530 0           my $furl = $self->{'furl'};
531 0           my ($url, @arguments);
532              
533 0           $url = 'https://';
534 0           $url .= join '/', $hosts->{'api'}, $version;
535 0           $url .= join '/', '/fileops', $type;
536              
537 0           given ($type) {
538 0           when (['move', 'copy']) {
539 0           @arguments = (
540             from_path => $source,
541             to_path => $target,
542             );
543             }
544              
545 0           default {
546 0           @arguments = (
547             path => $source,
548             );
549             }
550             }
551              
552 0           push @arguments, root => $self->{'root'};
553              
554 0           my $response = $furl->post($url, $self->__headers__(), \@arguments);
555              
556 0           given ($response->code()) {
557 0           when (200) {
558 0           $self->{'meta'} = from_json($response->content());
559             }
560              
561 0           when ([401, 403]) {
562 0           $! = EACCES;
563 0           return 0;
564             }
565              
566 0           when (404) {
567 0           $! = ENOENT;
568 0           return 0;
569             }
570              
571 0           when (406) {
572 0           $! = EPERM;
573 0           return 0;
574             }
575              
576 0           when (500) {
577 0 0         continue unless $response->content() =~ m{\A(?:Cannot|Failed)};
578              
579 0           $! = ECANCELED;
580 0           return 0;
581             }
582              
583 0           when (503) {
584 0           $self->{'meta'} = { retry => $response->header('Retry-After') };
585 0           $! = EAGAIN;
586 0           return 0;
587             }
588              
589 0           default {
590 0           die join ' ', $_, $response->decoded_content();
591             }
592             }
593              
594 0           return 1;
595             } # __fileops__
596              
597             sub contents ($;$$) {
598 0     0 1   my ($handle, $path, $hash) = @_;
599              
600 0 0         die 'GLOB reference expected'
601             unless ref $handle eq 'GLOB';
602              
603 0 0         *$handle->{'hash'} = $hash
604             if $hash;
605              
606 0 0 0       if (open $handle, '<', $path || '/' or $! != EISDIR) {
      0        
607 0           delete *$handle->{'meta'};
608 0           return;
609             }
610              
611 0           undef $!;
612 0           return @{ *$handle->{'meta'}{'contents'} };
  0            
613             } # contents
614              
615             sub putfile ($$$) {
616 0     0 1   my ($handle, $path, $data) = @_;
617              
618 0 0         die 'GLOB reference expected'
619             unless ref $handle eq 'GLOB';
620              
621 0 0         close $handle or return 0;
622              
623 0           my $self = *$handle{'HASH'};
624 0           my $furl = $self->{'furl'};
625 0           my ($url, $length);
626              
627 0           $url = 'https://';
628 0           $url .= join '/', $hosts->{'content'}, $version;
629 0           $url .= join '/', '/files_put', $self->{'root'}, $path;
630              
631             {
632 12     12   55814 use bytes;
  12         30  
  12         82  
  0            
633 0           $length = length $data;
634             }
635              
636 0           my $response = $furl->put($url, $self->__headers__(), $data);
637              
638 0           given ($response->code()) {
639 0           when (400) {
640 0           $! = EINVAL;
641 0           return 0;
642             }
643              
644 0           when ([401, 403]) {
645 0           $! = EACCES;
646 0           return 0;
647             }
648              
649 0           when (500) {
650 0 0         continue unless $response->content() =~ m{\A(?:Cannot|Failed)};
651              
652 0           $! = ECANCELED;
653 0           return 0;
654             }
655              
656 0           when (503) {
657 0           $self->{'meta'} = { retry => $response->header('Retry-After') };
658 0           $! = EAGAIN;
659 0           return 0;
660             };
661              
662 0           when (507) {
663 0           $! = EFBIG;
664 0           return 0;
665             }
666              
667 0           when (200) {
668 0           $self->{'path'} = $path;
669 0           $self->{'meta'} = from_json($response->content());
670             }
671              
672 0           default {
673 0           die join ' ', $_, $response->decoded_content();
674             }
675             }
676              
677 0           return 1;
678             } # putfile
679              
680 0     0 1   sub movefile ($$$) { __fileops__('move', @_) }
681 0     0 1   sub copyfile ($$$) { __fileops__('copy', @_) }
682 0     0 1   sub deletefile ($$) { __fileops__('delete', @_) }
683 0     0 1   sub createfolder ($$) { __fileops__('create_folder', @_) }
684              
685             sub metadata ($) {
686 0     0 1   my ($handle) = @_;
687              
688 0 0         die 'GLOB reference expected'
689             unless ref $handle eq 'GLOB';
690              
691 0           my $self = *$handle{'HASH'};
692              
693 0 0         die 'Meta is unavailable for incomplete upload'
694             if $self->{'mode'} eq '>';
695              
696 0           return $self->{'meta'};
697             } # metadata
698              
699             =head1 NAME
700              
701             File::Dropbox - Convenient and fast Dropbox API abstraction
702              
703             =head1 SYNOPSIS
704              
705             use File::Dropbox;
706             use Fcntl;
707              
708             # Application credentials
709             my %app = (
710             app_key => 'app key',
711             app_secret => 'app secret',
712             access_token => 'access token',
713             access_secret => 'access secret',
714             );
715              
716             my $dropbox = File::Dropbox->new(%app);
717              
718             # Open file for writing
719             open $dropbox, '>', 'example' or die $!;
720              
721             while (<>) {
722             # Upload data using 4MB chunks
723             print $dropbox $_;
724             }
725              
726             # Commit upload (optional, close will be called on reopen)
727             close $dropbox or die $!;
728              
729             # Open for reading
730             open $dropbox, '<', 'example' or die $!;
731              
732             # Download and print to STDOUT
733             # Buffered, default buffer size is 4MB
734             print while <$dropbox>;
735              
736             # Reset file position
737             seek $dropbox, 0, Fcntl::SEEK_SET;
738              
739             # Get first character (unbuffered)
740             say getc $dropbox;
741              
742             close $dropbox;
743              
744             =head1 DESCRIPTION
745              
746             C provides high-level Dropbox API abstraction based on L. Code required to get C and
747             C for signed OAuth 1.0 requests or C for OAuth 2.0 requests is not included in this module.
748             To get C and C you need to register your application with Dropbox.
749              
750             At this moment Dropbox API is not fully supported, C covers file read/write and directory listing methods. If you need full
751             API support take look at L. C main purpose is not 100% API coverage,
752             but simple and high-performance file operations.
753              
754             Due to API limitations and design you can not do read and write operations on one file at the same time. Therefore handle can be in read-only
755             or write-only state, depending on last call to L. Supported functions for read-only state are: L,
756             L, L, L, L, L,
757             L, L, L. For write-only state: L, L,
758             L, L, L, L.
759              
760             All API requests are done using L module. For more accurate timeouts L is used, as described in L. Furl settings
761             can be overriden using C.
762              
763             =head1 METHODS
764              
765             =head2 new
766              
767             my $dropbox = File::Dropbox->new(
768             access_secret => 'secret',
769             access_token => 'token',
770             app_secret => 'app secret',
771             app_key => 'app key',
772             chunk => 8 * 1024 * 1024,
773             root => 'dropbox',
774             furlopts => {
775             timeout => 20
776             }
777             );
778              
779             my $dropbox = File::Dropbox->new(
780             access_token => 'token',
781             oauth2 => 1
782             );
783              
784             Constructor, takes key-value pairs list
785              
786             =over
787              
788             =item access_secret
789              
790             OAuth 1.0 access secret
791              
792             =item access_token
793              
794             OAuth 1.0 access token or OAuth 2.0 access token
795              
796             =item app_secret
797              
798             OAuth 1.0 app secret
799              
800             =item app_key
801              
802             OAuth 1.0 app key
803              
804             =item oauth2
805              
806             OAuth 2.0 switch, defaults to false.
807              
808             =item chunk
809              
810             Upload chunk size in bytes. Also buffer size for C. Optional. Defaults to 4MB.
811              
812             =item root
813              
814             Access type, C for app-folder only access and C for full access.
815              
816             =item furlopts
817              
818             Parameter hash, passed to L constructor directly. Default options
819              
820             timeout => 10,
821             inet_aton => \&Net::DNS::Lite::inet_aton,
822             ssl_opts => {
823             SSL_verify_mode => SSL_VERIFY_PEER(),
824             }
825              
826             =back
827              
828             =head1 FUNCTIONS
829              
830             All functions are not exported by default but can be exported on demand.
831              
832             use File::Dropbox qw{ contents metadata putfile };
833              
834             First argument for all functions should be GLOB reference, returned by L.
835              
836             =head2 contents
837              
838             Arguments: $dropbox [, $path]
839              
840             Function returns list of hashrefs representing directory content. Hash fields described in L
841             docs|https://www.dropbox.com/developers/core/docs#metadata>. C<$path> defaults to C. If there is
842             unfinished chunked upload on handle, it will be commited.
843              
844             foreach my $file (contents($dropbox, '/data')) {
845             next if $file->{'is_dir'};
846             say $file->{'path'}, ' - ', $file->{'bytes'};
847             }
848              
849             =head2 metadata
850              
851             Arguments: $dropbox
852              
853             Function returns stored metadata for read-only handle, closed write handle or after
854             call to L or L.
855              
856             open $dropbox, '<', '/data/2013.dat' or die $!;
857              
858             my $meta = metadata($dropbox);
859              
860             if ($meta->{'bytes'} > 1024) {
861             # Do something
862             }
863              
864             =head2 putfile
865              
866             Arguments: $dropbox, $path, $data
867              
868             Function is useful for uploading small files (up to 150MB possible) in one request (at least
869             two API requests required for chunked upload, used in open-write-close sequence). If there is
870             unfinished chunked upload on handle, it will be commited.
871              
872             local $/;
873             open my $data, '<', '2012.dat' or die $!;
874              
875             putfile($dropbox, '/data/2012.dat', <$data>) or die $!;
876              
877             say 'Uploaded ', metadata($dropbox)->{'bytes'}, ' bytes';
878              
879             close $data;
880              
881             =head2 copyfile
882              
883             Arguments: $dropbox, $source, $target
884              
885             Function copies file or directory from one location to another. Metadata for copy
886             can be accessed using L function.
887              
888             copyfile($dropbox, '/data/2012.dat', '/data/2012.dat.bak') or die $!;
889              
890             say 'Created backup with revision ', metadata($dropbox)->{'revision'};
891              
892             =head2 movefile
893              
894             Arguments: $dropbox, $source, $target
895              
896             Function moves file or directory from one location to another. Metadata for moved file
897             can be accessed using L function.
898              
899             movefile($dropbox, '/data/2012.dat', '/data/2012.dat.bak') or die $!;
900              
901             say 'Created backup with size ', metadata($dropbox)->{'size'};
902              
903             =head2 deletefile
904              
905             Arguments: $dropbox, $path
906              
907             Function deletes file or folder at specified path. Metadata for deleted item
908             is accessible via L function.
909              
910             deletefile($dropbox, '/data/2012.dat.bak') or die $!;
911              
912             say 'Deleted backup with last modification ', metadata($dropbox)->{'modification'};
913              
914             =head2 createfolder
915              
916             Arguments: $dropbox, $path
917              
918             Function creates folder at specified path. Metadata for created folder
919             is accessible via L function.
920              
921             createfolder($dropbox, '/data/backups') or die $!;
922              
923             say 'Created folder at path ', metadata($dropbox)->{'path'};
924              
925             =head1 SEE ALSO
926              
927             L, L, L, L
928              
929             =head1 AUTHOR
930              
931             Alexander Nazarov
932              
933             =head1 COPYRIGHT AND LICENSE
934              
935             Copyright 2013, 2014 Alexander Nazarov
936              
937             This program is free software; you can redistribute it and/or modify it
938             under the same terms as Perl itself.
939              
940             =cut
941              
942             1;