File Coverage

blib/lib/Yars/Client.pm
Criterion Covered Total %
statement 266 340 78.2
branch 78 148 52.7
condition 25 55 45.4
subroutine 38 44 86.3
pod 3 13 23.0
total 410 600 68.3


line stmt bran cond sub pod time code
1             package Yars::Client;
2              
3             # ABSTRACT: Yet Another RESTful-Archive Service Client
4             our $VERSION = '1.30'; # VERSION
5              
6 24     24   1324824 use strict;
  24         61  
  24         740  
7 24     24   140 use warnings;
  24         80  
  24         646  
8 24     24   425 use 5.010;
  24         87  
9 24     24   1015 use Clustericious::Client;
  24         11281387  
  24         166  
10 24     24   4904 use Clustericious::Client::Command;
  24         120730  
  24         932  
11 24     24   229 use Clustericious::Config;
  24         54  
  24         569  
12 24     24   138 use Mojo::Asset::File;
  24         49  
  24         456  
13 24     24   703 use Mojo::ByteStream 'b';
  24         61  
  24         1200  
14 24     24   157 use Mojo::URL;
  24         60  
  24         242  
15 24     24   637 use Mojo::Base '-base';
  24         53  
  24         93  
16 24     24   4616 use File::Basename;
  24         54  
  24         1552  
17 24     24   156 use File::Spec;
  24         61  
  24         677  
18 24     24   129 use Log::Log4perl qw(:easy);
  24         61  
  24         179  
19 24     24   20862 use Digest::file qw/digest_file_hex/;
  24         4264  
  24         1246  
20 24     24   173 use Data::Dumper;
  24         88  
  24         1358  
21 24     24   1102 use IO::Uncompress::Gunzip qw(gunzip $GunzipError);
  24         654747  
  24         3043  
22 24     24   626 use Number::Bytes::Human qw( format_bytes parse_bytes );
  24         10230  
  24         1394  
23 24     24   169 use File::Temp qw( tempdir );
  24         75  
  24         1243  
24 24     24   175 use File::Glob qw( bsd_glob );
  24         60  
  24         1349  
25 24     24   197 use YAML::XS;
  24         61  
  24         1274  
26 24     24   161 use Carp qw( carp );
  24         57  
  24         1162  
27 24     24   156 use JSON::MaybeXS qw( encode_json );
  24         53  
  24         100261  
28              
29             route_doc upload => "<filename> [md5]";
30             route_doc download => "<filename> <md5> [dir]";
31             route_doc remove => "<filename> <md5>";
32              
33             route 'welcome' => "GET", '/';
34             route 'bucket_map' => "GET", '/bucket_map';
35             route 'disk_usage' => "GET", '/disk/usage';
36             route 'bucket_usage' => "GET", '/bucket/usage';
37             route 'servers_status' => "GET", '/servers/status';
38             route 'get' => "GET", '/file', \"<md5> <filename>";
39             route 'check' => "HEAD", '/file', \"<md5> <filename>";
40             route 'set_status' => "POST", '/disk/status';
41             route 'check_files' => "POST", '/check/manifest';
42              
43             route_meta 'welcome' => { auto_failover => 1, dont_read_files => 1 };
44             route_meta 'bucket_map' => { auto_failover => 1, dont_read_files => 1 };
45             route_meta 'disk_usage' => { auto_failover => 1, dont_read_files => 1 };
46             route_meta 'bucket_usage' => { auto_failover => 1, dont_read_files => 1 };
47             route_meta 'servers_status' => { auto_failover => 1, dont_read_files => 1 };
48             route_meta 'get' => { auto_failover => 1, dont_read_files => 1 };
49             route_meta 'check' => { auto_failover => 1, dont_read_files => 1 };
50             route_meta 'set_status' => { auto_failover => 1, dont_read_files => 1 };
51             route_meta 'check_files' => { auto_failover => 1, dont_read_files => 1 };
52              
53             route_meta 'upload' => { dont_read_files => 1 };
54             route_meta 'download' => { dont_read_files => 1 };
55             route_meta 'check_manifest' => { dont_read_files => 1 };
56             route_meta 'check' => { dont_read_files => 1 };
57              
58             route_args send => [
59             { name => 'content', type => '=s', required => 1 },
60             { name => 'name', type => '=s' },
61             ];
62              
63             route_args retrieve => [
64             { name => 'location', type => '=s' },
65             { name => 'name', type => '=s' },
66             { name => 'md5', type => '=s' },
67             ];
68              
69             sub new {
70 13     13 1 56642 my $self = shift->SUPER::new(@_);
71 13         7335 $self->ua->max_redirects(30);
72 13   100     167 $self->ua->connect_timeout($ENV{YARS_CONNECT_TIMEOUT} // 30);
73             $self->ua->on(start => sub {
74             # tx
75 98     98   446557 $_[1]->req->headers->header('X-Yars-Skip-Verify' => 'on');
76 98         4769 $_[1]->res->max_message_size(parse_bytes($self->config->max_message_size_client(default => 53687091200)));
77             $_[1]->on(finish => sub {
78 109         506186 my $tx = shift;
79 109 100       567 if(defined $tx->res->headers->header("X-Yars-Cache"))
80             {
81 3         80 $self->bucket_map_cached(0);
82             }
83 98         46591 });
84 13         238 });
85            
86 13         329 $self;
87             }
88              
89             sub client {
90 0     0 1 0 my($self, $new) = @_;
91              
92 0 0       0 $new ? do {
93 0         0 my $caller = caller;
94 0 0       0 carp "setting a new client is deprecated" if $caller ne 'Clustericious::Client';
95 0         0 $new->max_redirects(30);
96 0   0     0 $new->connect_timeout($ENV{YARS_CONNECT_TIMEOUT} // 30);
97 0         0 $self->SUPER::client($new);
98 0         0 $new;
99             } : $self->SUPER::client;
100             }
101              
102             sub _dist_data_dir
103             {
104 6     6   195 my $dir = bsd_glob('~/.yars');
105 6 50       290 mkdir $dir unless -d $dir;
106 6         125 $dir;
107             }
108              
109             sub bucket_map_cached {
110 72     72 0 25089 my($self, $new) = @_;
111              
112 72         189 state $fn = File::Spec->catfile(
113             _dist_data_dir,
114             'bucket_map_cache.yml',
115             );
116              
117 72 100       460 if(defined $new) {
    100          
118 19         93 $self->{bucket_map_cached} = $new;
119              
120 19 100       86 if(ref $new) {
121 16         132 YAML::XS::DumpFile($fn, $new);
122             } else {
123 3         308 unlink $fn;
124             }
125             }
126              
127             elsif(! defined $self->{bucket_map_cached})
128             {
129 12 100       198 if(-r $fn)
130             {
131 5         32 my $cache = YAML::XS::LoadFile($fn);
132              
133             # make sure the cache and config are in sync:
134             # ie, all of the urls in the config refer to
135             # hosts in the cache.
136             # NOTE: Considered doing this based on the
137             # md5 on the config, but:
138             # - doing it on the Yars.conf file requires
139             # changes to Clustericious::Config
140             # - doing it on the $self->config means that
141             # you can't randomly choose a different
142             # server in the mojo template of the config.
143             # (see https://github.com/plicease/Yars#randomizing-the-server-choices)
144 5         717 my %r = map { $_ => 1 } values %$cache;
  80         117  
145 5 100       34 if(grep { ! $r{$_} } ($self->config->url, $self->config->failover_urls( default => [] )))
  10         305  
146             {
147 1         4 $self->{bucket_map_cached} = 0;
148 1         76 unlink $fn;
149             }
150             else
151             {
152 4         16 $self->{bucket_map_cached} = $cache;
153             }
154             }
155             else
156             {
157 7         31 $self->{bucket_map_cached} = 0;
158             }
159             }
160              
161 72         4479 $self->{bucket_map_cached};
162             }
163              
164             sub _get_url {
165              
166             # Helper to create the Mojo URL objects
167 4     4   15 my ($self, $path) = @_;
168              
169 4         24 my $url = Mojo::URL->new( $self->server_url );
170 4 50       1417 $url->path($path) if $path;
171              
172 4         242 return $url;
173             }
174              
175             sub _hex2b64 {
176 25 50   25   6412 my $hex = shift or return;
177 25         394 my $b64 = b(pack 'H*', $hex)->b64_encode;
178 25         1402 local $/="\n";
179 25         142 chomp $b64;
180 25         663 return $b64;
181             }
182              
183             sub _b642hex {
184 0 0   0   0 my $b64 = shift or return;
185             # Mojo::Headers apparently become array refs sometimes
186 0 0       0 $b64 = $b64->[0] if ref($b64) eq 'ARRAY';
187 0         0 return unpack 'H*', b($b64)->b64_decode;
188             }
189              
190             sub location {
191 0     0 0 0 my ($self, $filename, $md5) = @_;
192              
193 0 0       0 ( $filename, $md5 ) = ( $md5, $filename ) if $filename =~ /^[0-9a-f]{32}$/i;
194 0 0       0 LOGDIE "Can't compute location without filename" unless defined($filename);
195 0 0       0 LOGDIE "Can't compute location without md5" unless $md5;
196 0         0 $self->server_url($self->_server_for($md5));
197 0         0 return $self->_get_url("/file/$md5/$filename")->to_abs->to_string;
198             }
199              
200             sub _sleep {
201             sleep @_
202 0     0   0 }
203              
204             sub download {
205             # Downloads a file and saves it to disk.
206 12     12 0 41149 my $self = shift;
207 12         56 my ( $filename, $md5, $dest ) = @_;
208 12         28 my $abs_url;
209 12 50       66 if (@_ == 1) {
210 0         0 $abs_url = shift;
211 0         0 ($filename) = $abs_url =~ m|/([^/]+)$|;
212             }
213 12 100       104 ( $filename, $md5 ) = ( $md5, $filename ) if $filename =~ /^[0-9a-f]{32}$/i;
214              
215 12 0 33     52 if (!$md5 && !$abs_url) {
216 0         0 LOGDIE "Need either an md5 or a url: download(url) or download(filename, md5, [dir] )";
217             }
218              
219 12         32 my @hosts;
220 12 50       90 @hosts = $self->_all_hosts($self->_server_for($md5)) unless $abs_url;
221 12         43 my $tries = 0;
222 12         27 my $success = 0;
223 12         28 my $host = 0;
224 12         52 while ($tries++ < 10) {
225              
226 21 100       108 if ($tries > @hosts + 1) {
227 8         72 TRACE "Attempt $tries";
228 8         12071 WARN "Waiting $tries seconds before retrying...";
229 8         12406 _sleep $tries;
230             }
231 21         12275 my $url;
232 21 50       82 if ($abs_url) {
233 0         0 $url = $abs_url;
234             } else {
235 21 100       104 $host = 0 if $host > $#hosts;
236 21         267 $url = Mojo::URL->new($hosts[$host++]);
237 21         7822 $url->path("/file/$filename/$md5");
238             }
239 21         733 TRACE "GET $url";
240 21         32954 my $tx = $self->ua->get($url => { "Connection" => "Close", "Accept-Encoding" => "gzip" });
241             # TODO: set timeout for mojo 4.0
242 21         14049 $self->res($tx->res);
243 21         392 $self->tx($tx);
244 21 100       1399 my $res = $tx->success or do {
245 11         425 my $error = $tx->error;
246 11         253 $tx->closed;
247 11 100       353 if ($error->{code}) {
248 1         16 ERROR "Yars download : $error->{code} $error->{message}";
249 1         1641 last;
250             }
251 10         282 WARN "Error (may retry) : " . encode_json($error);
252 10         18481 next;
253             };
254 10         311 DEBUG "Received asset with size ".$res->content->asset->size;
255 10         14472 TRACE "Received headers : ".$res->headers->to_string;
256              
257             # host == 0 means we tried the assigned host
258 10 50       8416 if($host == 0) {
259 0         0 my $prev = $res->previous;
260             # previous message in the chain was a 301
261             # Moved Permanently means our bucket map is
262             # wrong our out of date
263 0 0 0     0 if($prev && $prev->code == 301) {
264 0         0 $self->bucket_map_cached(0);
265             }
266             }
267              
268 10 100       141 my $out_file = $dest
    100          
269             ? File::Spec->catfile((ref $dest eq 'SCALAR' ? tempdir(CLEANUP => 1) : $dest), $filename)
270             : $filename;
271              
272 10         2494 DEBUG "Writing to $out_file";
273 10 50       11866 if (my $e = $res->headers->header("Content-Encoding")) {
274 0 0       0 LOGDIE "unsupported encoding" unless $e eq 'gzip';
275             # This violate the spec (MD5s depend on transfer-encoding
276             # not content-encoding, per
277             # http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
278             # but we must support it.
279 0         0 TRACE "unzipping $out_file";
280 0         0 my $asset = $res->content->asset;
281             gunzip($asset->is_file ? $asset->path : \( $asset->slurp )
282 0 0       0 => $out_file) or do {
    0          
283 0         0 unlink $out_file;
284 0         0 LOGDIE "Gunzip failed : $GunzipError";
285             };
286             } else {
287 10         251 $res->content->asset->move_to($out_file);
288             }
289 10         1748 my $verify = digest_file_hex($out_file,'MD5');
290 10   33     1022 $md5 ||= _b642hex($res->headers->header("Content-MD5"));
291              
292 10 50       48 unless ($md5) {
293 0         0 WARN "No md5 in response header";
294 0         0 next;
295             }
296 10 50       48 if ($verify eq $md5)
297             {
298 10 100       47 if(ref $dest eq 'SCALAR')
299             {
300 5         83 open my $fh, '<', $out_file;
301 5         17 binmode $fh;
302 5         13 $$dest = do { local $/; <$fh> };
  5         25  
  5         61  
303 5         32 close $fh;
304             }
305             }
306             else
307             {
308 0         0 WARN "Bad md5 for file (got $verify instead of $md5)";
309 0         0 WARN "Response headers : ".$res->headers->to_string;
310 0 0       0 unlink $out_file or WARN "couldn't remove $out_file : $!";
311 0         0 WARN "Removed $out_file. This is attempt $tries.";
312 0         0 next;
313             }
314              
315 10         29 $success = 1;
316 10         34 last;
317             }
318 12 100       63 ERROR "Download failed." unless $success;
319 12 100       2639 return '' unless $success;
320 10         136 return 'ok'; # return TRUE
321             }
322              
323             sub remove {
324             # Removes a file
325 4     4 0 14538 my ( $self, $filename, $md5 ) = @_;
326              
327 4 50 33     40 LOGDIE "file and md5 needed for remove"
328             unless $filename && $md5;
329              
330 4         34 my $url = $self->_get_url("/file/$md5/$filename");
331 4         33 TRACE("removing $filename $md5 from ", $url->to_string);
332              
333             # Delete the file
334 4         7023 $self->_doit(DELETE => $url);
335             }
336              
337             # Given an md5, determine the correct server
338             # using a cached list of bucket->server assignments.
339             sub _server_for {
340 36     36   111 my $self = shift;
341 36 50       161 my $md5 = shift or LOGDIE "Missing argument md5";
342 36         201 my $bucket_map = $self->bucket_map_cached;
343 36 50 66     452 unless ($bucket_map && ref($bucket_map) eq 'HASH' && keys %$bucket_map > 0) {
      66        
344 9 50       68 $bucket_map = $self->bucket_map or WARN $self->errorstring;
345 9 50 33     25231 $self->bucket_map_cached({ %$bucket_map }) if $bucket_map && ref $bucket_map && (keys %$bucket_map > 0);
      33        
346             }
347 36 50 33     428 unless ($bucket_map && ref $bucket_map && (keys %$bucket_map > 0)) {
      33        
348 0         0 WARN "Failed to retrieve bucket map, using ".$self->server_url;
349 0         0 return $self->server_url;
350             }
351 36         191 for (0..length($md5)) {
352 72         267 my $prefix = substr($md5,0,$_);
353 72 100       506 return $bucket_map->{ lc $prefix } if exists($bucket_map->{lc $prefix});
354 37 100       210 return $bucket_map->{ uc $prefix } if exists($bucket_map->{uc $prefix});
355             }
356 0         0 LOGDIE "Can't find url for $md5 in bucket map : ".Dumper($bucket_map);
357             }
358              
359             sub put {
360 2     2 0 7 my $self = shift;
361 2         6 my $remote_filename = shift;
362 2   33     12 my $content = shift || join '', <STDIN>;
363             # NB: slow for large content.
364 2         15 my $md5 = b($content)->md5_sum;
365 2         66 my $url = Mojo::URL->new($self->_server_for($md5));
366 2         580 $url->path("/file/$remote_filename");
367 2         53 TRACE "PUT $url";
368 2         3224 my $tx = $self->ua->put("$url" => { "Content-MD5" => _hex2b64($md5), "Connection" => "Close" } => $content);
369 2         1613 $self->res($tx->res);
370 2         28 $self->tx($tx);
371 2 50       136 return $tx->success ? 'ok' : '';
372             }
373              
374             sub _all_hosts {
375 32     32   149 my $self = shift;
376 32         94 my $assigned = shift;
377             # Return all the hosts, any parameter will be put first in
378             # the list.
379 32         109 my @servers = ($assigned);
380 32         219 push @servers, $self->server_url;
381 32         411 push @servers, $self->config->url;
382 32         1462 push @servers, @{ $self->config->failover_urls(default => []) };
  32         124  
383 32         1906 my %seen;
384 32         94 return grep { !$seen{$_}++ } @servers;
  116         527  
385             }
386              
387             sub upload {
388 20     20 0 157667 my $self = shift;
389 20 100       118 my $content = ref($_[-1]) eq 'SCALAR' ? pop : undef;
390              
391 20         55 my $nostash;
392             # OLD COMMENT (still valid):
393             # To avoid failover:
394             # yarsclient upload --nostash 1 foo
395             # Yars::Client->new->upload("--nostash" => 1, 'foo');
396             # This is undocumented since it is only intended to be
397             # used on a server when blanacing, not as a public interface.
398             # UPDATE (Graham Ollis 2015-11-10)
399             # This feature is only used by yars_fast_balance
400             # (see Yars::Command::yars_fast_balance for implementation).
401             # This used to match on /nostash$/ but the only place this
402             # is used it specifies it as a command line option like thing
403             # ("--nostash"), but that means that you can't upload files
404             # that end in *nostash. In truth the interface here should
405             # be a little better thought out and a little less batshitcrazy.
406 20 50 33     285 if(defined $_[0] && $_[0] eq '--nostash')
407             {
408 0         0 shift;
409 0         0 $nostash = shift;
410             }
411              
412 20         67 my $filename = shift;
413 20 100 66     149 my $md5 = defined $_[0] && $_[0] =~ /^[0-9a-f]+$/i ? lc shift : undef;
414              
415 20 50       85 if (@_) {
416 0         0 LOGDIE "unknown options to upload : @_";
417             }
418              
419 20 50       83 LOGDIE "file needed for upload" unless $filename;
420 20 100       71 if(defined $content) {
421             # intended mainly for testing only,
422             # may be ulsewise later
423 16         119 $filename = File::Spec->catfile( tempdir( CLEANUP => 1 ), $filename );
424 16         143527 open my $fh, '>', $filename;
425 16         91 binmode $fh;
426 16         290 print $fh $$content;
427 16         553 close $fh;
428             } else {
429 4         101 $filename = File::Spec->rel2abs($filename);
430             }
431 20 50       288 -r $filename or LOGDIE "Could not read " . $filename;
432              
433             # Don't read the file.
434 20         1118 my $basename = basename($filename);
435 20         332 my $asset = Mojo::Asset::File->new( path => $filename );
436 20   66     389 $md5 ||= digest_file_hex($filename, 'MD5');
437              
438 20         2174 my @servers = $self->_all_hosts( $self->_server_for($md5) );
439              
440 20         92 my $tx;
441             my $code;
442 20         0 my $host;
443              
444 20   66     158 while (!$code && ($host = shift @servers)) {
445 23         5502 my $url = Mojo::URL->new($host);
446 23         8220 $url->path("/file/$basename/$md5");
447 23         840 DEBUG "Sending $md5 to $url";
448              
449 23 50       42139 my @nostash = ($nostash ? ("X-Yars-NoStash" => 1) : ());
450 23         158 $tx = $self->ua->build_tx(
451             PUT => "$url" => {
452             @nostash,
453             "Content-MD5" => _hex2b64($md5),
454             "Connection" => "Close"
455             }
456             );
457 23         9241 $tx->req->content->asset($asset);
458             # TODO: set timeout for mojo 4.0
459 23         394 $tx = $self->ua->start($tx);
460 23         16030 $code = $tx->res->code;
461 23         243 $self->res($tx->res);
462 23         348 $self->tx($tx);
463              
464 23 100       1834 if (!$tx->success) {
465 4         707 my ($error) = $tx->error;
466 4 50       167 $error = encode_json $error if ref $error;
467 4         90 INFO "PUT to $host failed : $error";
468             }
469             }
470 20         2339 $self->res($tx->res);
471 20 100 66     302 return '' if !$code || !$tx->res->is_success;
472              
473 19         537 DEBUG "Response : ".$tx->res->code." ".$tx->res->message;
474 19         31061 return 'ok';
475             }
476              
477             sub _rand_filename {
478 1     1   2 my $a = '';
479 1         28 $a .= ('a'..'z','A'..'Z',0..9)[rand 62] for 1..43;
480 1         5 return $a;
481             }
482              
483             sub send {
484 2     2 0 7732 my $self = shift;
485 2         14 my $meta = $self->meta_for;
486 2         104 my %args = $meta->process_args(@_);
487 2         1239 my $content = $args{content};
488 2   66     16 my $filename = $args{name} || $self->_rand_filename;
489 2         13 my $status = $self->put($filename, $content);
490 2 50       70 return unless $status eq 'ok';
491 2         8 return $self->res->headers->location;
492             }
493              
494             sub retrieve {
495 2     2 0 1951 my $self = shift;
496 2         14 my %args = $self->meta_for->process_args(@_);
497 2 100       1611 if (my $location = $args{location}) {
498 1         7 my $tx = $self->ua->get($location);
499 1 50       479 my $res = $tx->success or do {
500 0         0 $self->tx($tx);
501 0         0 $self->res($tx->res);
502 0         0 return;
503             };
504 1         25 return $res->body;
505             }
506 1 50       7 my $md5 = $args{md5} or LOGDIE "need md5 or location to retrieve";
507 1 50       39 my $name = $args{name} or LOGDIE "need name or location to retrieve";
508 1         7 return $self->get($md5,$name);
509             }
510              
511             sub res_md5 {
512 1     1 0 29 my $self = shift;
513 1 50       5 my $res = $self->res or return;
514 1 50       9 if (my $b64 = $res->headers->header("Content-MD5")) {
515 0         0 return _b642hex($b64);
516             }
517 1 50       16 if (my $location = $res->headers->location) {
518 1         21 my ($md5) = $location =~ m[/file/([0-9a-f]{32})/];
519 1         4 return $md5;
520             }
521 0           return;
522             }
523              
524             sub check_manifest {
525 0     0 0   my $self = shift;
526 0           my @args = @_;
527 0           my $check = 0;
528 0           my $params = "";
529 0           my $manifest;
530 0           while ($_ = shift @_) {
531 0 0         /^-c$/ and do { $check = 1; next; };
  0            
  0            
532 0 0         /^--show_corrupt$/ and do { $params = "?show_corrupt=" . shift; next; };
  0            
  0            
533 0           $manifest = $_;
534             }
535 0 0         LOGDIE "Missing manifest" unless $manifest;
536 0 0         LOGDIE "Cannot open manifest $manifest" unless -e $manifest;
537 0           my $contents = Mojo::Asset::File->new(path => $manifest)->slurp;
538 0           my $got = $self->_doit(POST => "/check/manifest$params", { manifest => $contents });
539 0 0         return $got unless $self->tx->success;
540 0 0         $got->{$manifest} = (@{$got->{missing}}==0 ? 'ok' : 'not ok');
  0            
541 0 0         return { $manifest => $got->{$manifest} } if $check;
542 0           return $got;
543             }
544              
545             sub remote {
546 0     0 1   my $self = shift;
547 0           $self->bucket_map_cached(0);
548 0           $self->SUPER::remote(@_);
549             }
550              
551             1;
552              
553             __END__
554              
555             =pod
556              
557             =encoding UTF-8
558              
559             =head1 NAME
560              
561             Yars::Client - Yet Another RESTful-Archive Service Client
562              
563             =head1 VERSION
564              
565             version 1.30
566              
567             =head1 SYNOPSIS
568              
569             my $r = Yars::Client->new;
570              
571             # Send and retrieve content.
572             my $location = $y->send(content => 'hello, world') or die $y->errorstring;
573             say $y->retrieve(location => $location);
574              
575             # Alternatively, use names and md5s explicitly.
576             my $location = $y->send(content => 'hello there', name => "greeting");
577             my $md5 = $y->res_md5;
578             say $y->retrieve(filename => 'greeting', md5 => $md5);
579              
580             # Upload a file.
581             $r->upload($filename) or die $r->errorstring;
582             print $r->res->headers->location;
583              
584             # Download a file.
585             $r->download($filename, $md5) or die $r->errorstring;
586             $r->download($filename, $md5, '/tmp'); # download it to the /tmp directory
587             $r->download("http://yars/0123456890abc/filename.txt"); # Write filename.txt to current directory.
588              
589             # More concise version of retrieve.
590             my $content = $r->get($filename,$md5);
591              
592             # Delete a file.
593             $r->remove($filename, $md5) or die $r->errorstring;
594              
595             # Compute the URL of a file based on the md5 and the buckets.
596             print $r->location($filename, $md5);
597              
598             print "Server version is ".$r->status->{server_version};
599             my $usage = $r->disk_usage(); # Returns usage for a single server.
600             my $nother_usage = Yars::Client->new(url => "http://host1:9999")->disk_usage();
601             my $status = $r->servers_status(); # return a hash of servers, disks, and their statuses
602              
603             # Mark a disk down.
604             my $ok = $r->set_status({ root => "/acps/disk/one", state => "down" });
605             my $ok = $r->set_status({ root => "/acps/disk/one", state => "down", host => "http://host2" });
606              
607             # Mark a disk up.
608             my $ok = $r->set_status({ root => "/acps/disk/one", state => "up" });
609              
610             # Check a manifest file or list of files.
611             my $details = $r->check_manifest( $filename );
612             my $check = $r->check_manifest( "-c", $filename );
613             my $check = $r->check_manifest( "--show_corrupt" => 1, $filename );
614             my $ck = $r->check_files({ files => [
615             { filename => $f1, md5 => $m1 },
616             { filename => $f2, md5 => $m2 } ] });
617              
618             =head1 DESCRIPTION
619              
620             Client for L<Yars>.
621              
622             =head1 SEE ALSO
623              
624             L<yarsclient>, L<Clustericious::Client>
625              
626             =head1 AUTHOR
627              
628             Original author: Marty Brandon
629              
630             Current maintainer: Graham Ollis E<lt>plicease@cpan.orgE<gt>
631              
632             Contributors:
633              
634             Brian Duggan
635              
636             Curt Tilmes
637              
638             =head1 COPYRIGHT AND LICENSE
639              
640             This software is copyright (c) 2013 by NASA GSFC.
641              
642             This is free software; you can redistribute it and/or modify it under
643             the same terms as the Perl 5 programming language system itself.
644              
645             =cut