File Coverage

blib/lib/Net/Amazon/S3/Client/Object.pm
Criterion Covered Total %
statement 167 201 83.0
branch 45 74 60.8
condition 7 31 22.5
subroutine 40 46 86.9
pod 19 23 82.6
total 278 375 74.1


line stmt bran cond sub pod time code
1             $Net::Amazon::S3::Client::Object::VERSION = '0.991';
2             use Moose 0.85;
3 99     99   721 use MooseX::StrictConstructor 0.16;
  99         1986  
  99         748  
4 99     99   560751 use DateTime::Format::HTTP;
  99         1659  
  99         685  
5 99     99   313565 use Digest::MD5 qw(md5 md5_hex);
  99         166583  
  99         3165  
6 99     99   647 use Digest::MD5::File qw(file_md5 file_md5_hex);
  99         208  
  99         6246  
7 99     99   38756 use File::stat;
  99         1858520  
  99         612  
8 99     99   18180 use MIME::Base64;
  99         228  
  99         1400  
9 99     99   41062 use Moose::Util::TypeConstraints;
  99         50043  
  99         4723  
10 99     99   725 use MooseX::Types::DateTime::MoreCoercions 0.07 qw( DateTime );
  99         198  
  99         1036  
11 99     99   187798 use IO::File 1.14;
  99         1963  
  99         778  
12 99     99   104335 use Ref::Util ();
  99         1771  
  99         12660  
13 99     99   661  
  99         198  
  99         2405  
14             # ABSTRACT: An easy-to-use Amazon S3 client object
15              
16             use Net::Amazon::S3::Constraint::ACL::Canned;
17 99     99   33671 use Net::Amazon::S3::Constraint::Etag;
  99         255  
  99         3552  
18 99     99   15026 use Net::Amazon::S3::Client::Object::Range;
  99         239  
  99         2293  
19 99     99   39034  
  99         271  
  99         244822  
20             with 'Net::Amazon::S3::Role::ACL';
21              
22             enum 'StorageClass' =>
23             # Current list at https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html#AmazonS3-PutObject-request-header-StorageClass
24             [ qw(standard reduced_redundancy standard_ia onezone_ia intelligent_tiering glacier deep_archive) ];
25              
26             has 'client' =>
27             ( is => 'ro', isa => 'Net::Amazon::S3::Client', required => 1 );
28             has 'bucket' =>
29             ( is => 'ro', isa => 'Net::Amazon::S3::Client::Bucket', required => 1 );
30             has 'key' => ( is => 'ro', isa => 'Str', required => 1 );
31             has 'etag' => ( is => 'ro', isa => 'Net::Amazon::S3::Constraint::Etag', required => 0 );
32             has 'size' => ( is => 'ro', isa => 'Int', required => 0 );
33             has 'last_modified' =>
34             ( is => 'ro', isa => DateTime, coerce => 1, required => 0, default => sub { shift->last_modified_raw }, lazy => 1 );
35             has 'last_modified_raw' =>
36             ( is => 'ro', isa => 'Str', required => 0 );
37             has 'expires' => ( is => 'rw', isa => DateTime, coerce => 1, required => 0 );
38             has 'content_type' => (
39             is => 'ro',
40             isa => 'Str',
41             required => 0,
42             default => 'binary/octet-stream'
43             );
44             has 'content_disposition' => (
45             is => 'ro',
46             isa => 'Str',
47             required => 0,
48             );
49             has 'content_encoding' => (
50             is => 'ro',
51             isa => 'Str',
52             required => 0,
53             );
54             has 'cache_control' => (
55             is => 'ro',
56             isa => 'Str',
57             required => 0,
58             );
59             has 'storage_class' => (
60             is => 'ro',
61             isa => 'StorageClass',
62             required => 0,
63             default => 'standard',
64             );
65             has 'user_metadata' => (
66             is => 'ro',
67             isa => 'HashRef',
68             required => 0,
69             default => sub { {} },
70             );
71             has 'website_redirect_location' => (
72             is => 'ro',
73             isa => 'Str',
74             required => 0,
75             );
76             has 'encryption' => (
77             is => 'ro',
78             isa => 'Maybe[Str]',
79             required => 0,
80             );
81              
82             __PACKAGE__->meta->make_immutable;
83              
84             my ($self, $range) = @_;
85              
86 1     1 1 4 return Net::Amazon::S3::Client::Object::Range->new (
87             object => $self,
88 1         12 range => $range,
89             );
90             }
91              
92             my $self = shift;
93              
94             my $response = $self->_perform_operation (
95 6     6 1 13 'Net::Amazon::S3::Operation::Object::Head',
96             );
97 6         20  
98             return $response->is_success;
99             }
100              
101 3         29 my $self = shift;
102              
103             my $response = $self->_perform_operation (
104             'Net::Amazon::S3::Operation::Object::Fetch',
105 6     6   13  
106             method => 'GET',
107 6         20 );
108              
109             $self->_load_user_metadata ($response->http_response);
110              
111             return $response;
112             }
113 1         26  
114             my $self = shift;
115 1         9 return $self->_get->content;
116             }
117              
118             my $self = shift;
119 5     5 1 5 return $self->_get->decoded_content(@_);
120 5         15 }
121              
122             my ( $self, $callback ) = @_;
123              
124 1     1 1 2 my $response = $self->_perform_operation (
125 1         6 'Net::Amazon::S3::Operation::Object::Fetch',
126             filename => $callback,
127             method => 'GET',
128             );
129 1     1 0 4  
130             return $response->http_response;
131 1         5 }
132              
133             my ( $self, $filename ) = @_;
134              
135             my $response = $self->_perform_operation (
136             'Net::Amazon::S3::Operation::Object::Fetch',
137 0         0 filename => $filename,
138             method => 'GET',
139             );
140              
141 1     1 1 5 $self->_load_user_metadata($response->http_response);
142             }
143 1         5  
144             my ( $self, $http_response ) = @_;
145              
146             my %user_metadata;
147             for my $header_name ($http_response->header_field_names) {
148             my ($metadata_name) = lc($header_name) =~ /\A x-amz-meta- (.*) \z/xms
149 0         0 or next;
150             $user_metadata{$metadata_name} = $http_response->header($header_name);
151             }
152              
153 1     1   3 %{ $self->user_metadata } = %user_metadata;
154             }
155 1         2  
156 1         8 my ( $self, $value ) = @_;
157 6 50       87 $self->_put( $value, length $value, md5_hex($value) );
158             }
159 0         0  
160             my ( $self, $value, $size, $md5_hex ) = @_;
161              
162 1         3 my $md5_base64 = encode_base64( pack( 'H*', $md5_hex ) );
  1         42  
163             chomp $md5_base64;
164              
165             my $conf = {
166 11     11 1 30 'Content-MD5' => $md5_base64,
167 11         89 'Content-Length' => $size,
168             'Content-Type' => $self->content_type,
169             };
170              
171 12     12   37 if ( $self->expires ) {
172             $conf->{Expires}
173 12         122 = DateTime::Format::HTTP->format_datetime( $self->expires );
174 12         29 }
175             if ( $self->content_encoding ) {
176 12         362 $conf->{'Content-Encoding'} = $self->content_encoding;
177             }
178             if ( $self->content_disposition ) {
179             $conf->{'Content-Disposition'} = $self->content_disposition;
180             }
181             if ( $self->cache_control ) {
182 12 100       345 $conf->{'Cache-Control'} = $self->cache_control;
183             }
184 3         87 if ( $self->storage_class && $self->storage_class ne 'standard' ) {
185             $conf->{'x-amz-storage-class'} = uc $self->storage_class;
186 12 100       1642 }
187 3         75 if ( $self->website_redirect_location ) {
188             $conf->{'x-amz-website-redirect-location'} = $self->website_redirect_location;
189 12 100       361 }
190 1         25 $conf->{"x-amz-meta-\L$_"} = $self->user_metadata->{$_}
191             for keys %{ $self->user_metadata };
192 12 100       308  
193 1         23 my $response = $self->_perform_operation (
194             'Net::Amazon::S3::Operation::Object::Add',
195 12 100 66     323  
196 1         25 value => $value,
197             headers => $conf,
198 12 100       336 acl => $self->acl,
199 2         64 encryption => $self->encryption,
200             );
201              
202 12         23 my $http_response = $response->http_response;
  12         306  
203              
204 12         311 confess 'Error uploading ' . $http_response->as_string
205             unless $http_response->is_success;
206              
207             return '';
208             }
209              
210             my ( $self, $filename ) = @_;
211              
212             my $md5_hex = $self->etag || file_md5_hex($filename);
213 7         177 my $size = $self->size;
214             unless ($size) {
215 7 50       28 my $stat = stat($filename) || confess("No $filename: $!");
216             $size = $stat->size;
217             }
218 7         76  
219             $self->_put( $self->_content_sub($filename), $size, $md5_hex );
220             }
221              
222 1     1 1 4 my $self = shift;
223              
224 1   33     33 my $response = $self->_perform_operation (
225 1         1002 'Net::Amazon::S3::Operation::Object::Delete',
226 1 50       5 );
227 1   33     11  
228 1         257 return $response->is_success;
229             }
230              
231 1         14 my ($self, %params) = @_;
232              
233             my $response = $self->_perform_operation (
234             'Net::Amazon::S3::Operation::Object::Acl::Set',
235 6     6 1 12 %params,
236             );
237 6         21  
238             return $response->is_success;
239             }
240              
241 1         11 my ($self, %params) = @_;
242              
243             my $response = $self->_perform_operation (
244             'Net::Amazon::S3::Operation::Object::Tags::Add',
245 10     10 0 30 %params,
246             );
247 10         37  
248             return $response->is_success;
249             }
250              
251             my ($self, %params) = @_;
252 5         272  
253             my $response = $self->_perform_operation (
254             'Net::Amazon::S3::Operation::Object::Tags::Delete',
255              
256 6     6 1 16 (version_id => $params{version_id}) x!! defined $params{version_id},
257             );
258 6         22  
259             return $response->is_success;
260             }
261              
262             my $self = shift;
263 1         9 my %args = ref($_[0]) ? %{$_[0]} : @_;
264              
265             $args{acl} = $args{acl_short} if exists $args{acl_short};
266             delete $args{acl_short};
267 7     7 1 19 $args{acl} = $self->acl unless $args{acl};
268              
269             my $response = $self->_perform_operation (
270             'Net::Amazon::S3::Operation::Object::Upload::Create',
271              
272             encryption => $self->encryption,
273 7         33 ($args{acl} ? (acl => $args{acl}) : ()),
274             ($args{headers} ? (headers => $args{headers}) : ()),
275 2         15 );
276              
277             return unless $response->is_success;
278              
279 10     10 1 15 confess "Couldn't get upload id from initiate_multipart_upload response XML"
280 10 100       25 unless $response->upload_id;
  5         13  
281              
282 10 100       23 return $response->upload_id;
283 10         13 }
284 10 100       155  
285             my $self = shift;
286              
287             my %args = ref($_[0]) ? %{$_[0]} : @_;
288              
289             my $response = $self->_perform_operation (
290             'Net::Amazon::S3::Operation::Object::Upload::Complete',
291 10 100       243  
    100          
292             upload_id => $args{upload_id},
293             etags => $args{etags},
294 0 0       0 part_numbers => $args{part_numbers},
295             );
296 0 0       0  
297             return $response->http_response;
298             }
299 0         0  
300             my $self = shift;
301              
302             my %args = ref($_[0]) ? %{$_[0]} : @_;
303 2     2 1 4  
304             my $response = $self->_perform_operation (
305 2 100       6 'Net::Amazon::S3::Operation::Object::Upload::Abort',
  1         4  
306              
307             upload_id => $args{upload_id},
308             );
309              
310             return $response->http_response;
311             }
312              
313 2         16  
314             my $self = shift;
315 0         0  
316             my %args = ref($_[0]) ? %{$_[0]} : @_;
317              
318             #work out content length header
319 2     2 0 4 $args{headers}->{'Content-Length'} = length $args{value}
320             if(defined $args{value});
321 2 100       7  
  1         12  
322             my $response = $self->_perform_operation (
323             'Net::Amazon::S3::Operation::Object::Upload::Part',
324              
325             upload_id => $args{upload_id},
326             part_number => $args{part_number},
327 2         10 acl_short => $args{acl_short},
328             copy_source => $args{copy_source},
329 0         0 headers => $args{headers},
330             value => $args{value},
331             );
332              
333             return $response->http_response;
334 2     2 1 6 }
335              
336 2 100       6 confess "Not implemented";
  1         7  
337             # TODO - Net::Amazon::S3::Request:ListParts is implemented, but need to
338             # define better interface at this level. Currently returns raw XML.
339             }
340 2 50       9  
341             my $self = shift;
342             return Net::Amazon::S3::Operation::Object::Fetch::Request->new (
343             s3 => $self->client->s3,
344             bucket => $self->bucket->name,
345             key => $self->key,
346             method => 'GET',
347             )->http_request->uri;
348             }
349              
350             my ($self, $query_form) = @_;
351 2         11 return $self->query_string_authentication_uri_for_method (GET => $query_form);
352             }
353 0         0  
354             my ($self, $method, $query_form) = @_;
355             return Net::Amazon::S3::Operation::Object::Fetch::Request->new (
356             s3 => $self->client->s3,
357 0     0 0 0 bucket => $self->bucket->name,
358             key => $self->key,
359             method => $method,
360             )->query_string_authentication_uri ($self->expires->epoch, $query_form);
361             }
362              
363 0     0 1 0 my $self = shift;
364 0         0  
365             my $response = $self->_perform_operation (
366             'Net::Amazon::S3::Operation::Object::Fetch',
367              
368             key => $self->key,
369             method => 'HEAD',
370             );
371              
372             return unless $
373 0     0 1 0 response->is_success;
374 0         0 my $http_response = $response->http_response;
375              
376             my %metadata;
377             for my $header_name ($http_response->header_field_names) {
378 4     4 1 11 if ($self->_is_metadata_header ($header_name)) {
379 4         108 my $metadata_name = $self->_format_metadata_name ($header_name);
380             $metadata{$metadata_name} = $http_response->header ($header_name);
381             }
382             }
383              
384             return \%metadata;
385             }
386              
387             my (undef, $header) = @_;
388 5     5 1 11 $header = lc($header);
389              
390 5         132 my %valid_metadata_headers = map +($_ => 1), (
391             'accept-ranges',
392             'cache-control',
393             'etag',
394             'expires',
395             'last-modified',
396             );
397 1 50       9  
398             return 1 if exists $valid_metadata_headers{$header};
399 1         42 return 1 if $header =~ m/^x-amz-(?!id-2$)/;
400             return 1 if $header =~ m/^content-/;
401 1         3 return 0;
402 1         9 }
403 6 100       188  
404 4         8 my (undef, $header) = @_;
405 4         13 $header = lc($header);
406             $header =~ s/^x-amz-//;
407              
408             my $metadata_name = join('', map (ucfirst, split(/-/, $header)));
409 1         44 $metadata_name = 'ETag' if ($metadata_name eq 'Etag');
410              
411             return $metadata_name;
412             }
413 6     6   10  
414 6         10 my $self = shift;
415              
416 6         21 my %metadata = %{$self->head};
417              
418             # An object is available if:
419             # - the storage class isn't GLACIER;
420             # - the storage class is GLACIER and the object was fully restored (Restore: ongoing-request="false");
421             my $glacier = (exists($metadata{StorageClass}) and $metadata{StorageClass} eq 'GLACIER') ? 1 : 0;
422             my $restored = (exists($metadata{Restore}) and $metadata{Restore} =~ m/ongoing-request="false"/) ? 1 : 0;
423             return (!$glacier or $restored) ? 1 :0;
424 6 100       15 }
425 5 100       15  
426 4 100       11 my $self = shift;
427 2         6 my (%conf) = @_;
428              
429             my $request = $self->_perform_operation (
430             'Net::Amazon::S3::Operation::Object::Restore',
431 4     4   8  
432 4         17 key => $self->key,
433 4         9 days => $conf{days},
434             tier => $conf{tier},
435 4         17 );
436 4 100       10  
437             return $request->http_response;
438 4         7 }
439              
440             my $self = shift;
441             my $filename = shift;
442 0     0 1 0 my $stat = stat($filename);
443             my $remaining = $stat->size;
444 0         0 my $blksize = $stat->blksize || 4096;
  0         0  
445              
446             confess "$filename not a readable file with fixed size"
447             unless -r $filename and ( -f _ || $remaining );
448             my $fh = IO::File->new( $filename, 'r' )
449 0 0 0     0 or confess "Could not open $filename: $!";
450 0 0 0     0 $fh->binmode;
451 0 0 0     0  
452             return sub {
453             my $buffer;
454              
455 1     1 1 3 # upon retries the file is closed and we must reopen it
456 1         5 unless ( $fh->opened ) {
457             $fh = IO::File->new( $filename, 'r' )
458             or confess "Could not open $filename: $!";
459             $fh->binmode;
460             $remaining = $stat->size;
461             }
462              
463             # warn "read remaining $remaining";
464 1         30 unless ( my $read = $fh->read( $buffer, $blksize ) ) {
465              
466 0         0 # warn "read $read buffer $buffer remaining $remaining";
467             confess
468             "Error while reading upload content $filename ($remaining remaining) $!"
469             if $! and $remaining;
470 1     1   3  
471 1         3 # otherwise, we found EOF
472 1         5 $fh->close
473 1         133 or confess "close of upload content $filename failed: $!";
474 1   50     20 $buffer ||= ''
475             ; # LWP expects an emptry string on finish, read returns 0
476 1 50 33     32 }
      33        
477             $remaining -= length($buffer);
478 1 50       11 return $buffer;
479             };
480 1         151 }
481              
482             my ( $self, $etag ) = @_;
483 0     0   0 return 1 if($etag =~ /\-\d+$/);
484             }
485              
486 0 0       0 my ($self, $operation, %params) = @_;
487 0 0       0  
488             $self->bucket->_perform_operation ($operation => (
489 0         0 key => $self->key,
490 0         0 %params,
491             ));
492             }
493              
494 0 0       0 1;
495              
496              
497 0 0 0     0 =pod
498              
499             =encoding UTF-8
500              
501             =head1 NAME
502 0 0       0  
503             Net::Amazon::S3::Client::Object - An easy-to-use Amazon S3 client object
504 0   0     0  
505             =head1 VERSION
506              
507 0         0 version 0.991
508 0         0  
509 1         23 =head1 SYNOPSIS
510              
511             # show the key
512             print $object->key . "\n";
513 0     0   0  
514 0 0       0 # show the etag of an existing object (if fetched by listing
515             # a bucket)
516             print $object->etag . "\n";
517              
518 78     78   277 # show the size of an existing object (if fetched by listing
519             # a bucket)
520 78         2064 print $object->size . "\n";
521              
522             # to create a new object
523             my $object = $bucket->object( key => 'this is the key' );
524             $object->put('this is the value');
525              
526             # to get the value of an object
527             my $value = $object->get;
528              
529             # to get the metadata of an object
530             my %metadata = %{$object->head};
531              
532             # to see if an object exists
533             if ($object->exists) { ... }
534              
535             # to delete an object
536             $object->delete;
537              
538             # to create a new object which is publically-accessible with a
539             # content-type of text/plain which expires on 2010-01-02
540             my $object = $bucket->object(
541             key => 'this is the public key',
542             acl => Net::Amazon::S3::ACL::CANNED->PUBLIC_READ,
543             content_type => 'text/plain',
544             expires => '2010-01-02',
545             );
546             $object->put('this is the public value');
547              
548             # return the URI of a publically-accessible object
549             my $uri = $object->uri;
550              
551             # to view if an object is available for downloading
552             # Basically, the storage class isn't GLACIER or the object was
553             # fully restored
554             $object->available;
555              
556             # to restore an object on a GLACIER storage class
557             $object->restore(
558             days => 1,
559             tier => 'Standard',
560             );
561              
562             # to store a new object with server-side encryption enabled
563             my $object = $bucket->object(
564             key => 'my secret',
565             encryption => 'AES256',
566             );
567             $object->put('this data will be stored using encryption.');
568              
569             # upload a file
570             my $object = $bucket->object(
571             key => 'images/my_hat.jpg',
572             content_type => 'image/jpeg',
573             );
574             $object->put_filename('hat.jpg');
575              
576             # upload a file if you already know its md5_hex and size
577             my $object = $bucket->object(
578             key => 'images/my_hat.jpg',
579             content_type => 'image/jpeg',
580             etag => $md5_hex,
581             size => $size,
582             );
583             $object->put_filename('hat.jpg');
584              
585             # download the value of the object into a file
586             my $object = $bucket->object( key => 'images/my_hat.jpg' );
587             $object->get_filename('hat_backup.jpg');
588              
589             # use query string authentication for object fetch
590             my $object = $bucket->object(
591             key => 'images/my_hat.jpg',
592             expires => '2009-03-01',
593             );
594             my $uri = $object->query_string_authentication_uri();
595              
596             # use query string authentication for object upload
597             my $object = $bucket->object(
598             key => 'images/my_hat.jpg',
599             expires => '2009-03-01',
600             );
601             my $uri = $object->query_string_authentication_uri_for_method('PUT');
602              
603             =head1 DESCRIPTION
604              
605             This module represents objects in buckets.
606              
607             =for test_synopsis no strict 'vars'
608              
609             =head1 METHODS
610              
611             =head2 range
612              
613             my $value = $object->range ('bytes=1024-10240')->get;
614              
615             Provides simple interface to ranged download. See also L<Net::Amazon::S3::Client::Object::Range>.
616              
617             =head2 etag
618              
619             # show the etag of an existing object (if fetched by listing
620             # a bucket)
621             print $object->etag . "\n";
622              
623             =head2 delete
624              
625             # to delete an object
626             $object->delete;
627              
628             =head2 exists
629              
630             # to see if an object exists
631             if ($object->exists) { ... }
632              
633             Method doesn't report error when bucket or key doesn't exist.
634              
635             =head2 get
636              
637             # to get the vaue of an object
638             my $value = $object->get;
639              
640             =head2 head
641              
642             # to get the metadata of an object
643             my %metadata = %{$object->head};
644              
645             Unlike C<exists> this method does report error.
646              
647             =head2 get_decoded
648              
649             # get the value of an object, and decode any Content-Encoding and/or
650             # charset; see decoded_content in HTTP::Response
651             my $value = $object->get_decoded;
652              
653             =head2 get_filename
654              
655             # download the value of the object into a file
656             my $object = $bucket->object( key => 'images/my_hat.jpg' );
657             $object->get_filename('hat_backup.jpg');
658              
659             =head2 last_modified, last_modified_raw
660              
661             # get the last_modified data as DateTime (slow)
662             my $dt = $obj->last_modified;
663             # or raw string in form '2015-05-15T10:12:40.000Z' (fast)
664             # use this form if you are working with thousands of objects and
665             # do not actually need an expensive DateTime for each of them
666             my $raw = $obj->last_modified_raw;
667              
668             =head2 key
669              
670             # show the key
671             print $object->key . "\n";
672              
673             =head2 available
674              
675             # to view if an object is available for downloading
676             # Basically, the storage class isn't GLACIER or the object was
677             # fully restored
678             $object->available;
679              
680             =head2 restore
681              
682             # to restore an object on a GLACIER storage class
683             $object->restore(
684             days => 1,
685             tier => 'Standard',
686             );
687              
688             =head2 put
689              
690             # to create a new object
691             my $object = $bucket->object( key => 'this is the key' );
692             $object->put('this is the value');
693              
694             # to create a new object which is publically-accessible with a
695             # content-type of text/plain
696             my $object = $bucket->object(
697             key => 'this is the public key',
698             acl => 'public-read',
699             content_type => 'text/plain',
700             );
701             $object->put('this is the public value');
702              
703             For C<acl> refer L<Net::Amazon::S3::ACL>.
704              
705             You may also set Content-Encoding using C<content_encoding>, and
706             Content-Disposition using C<content_disposition>.
707              
708             You may specify the S3 storage class by setting C<storage_class> to either
709             C<standard>, C<reduced_redundancy>, C<standard_ia>, C<onezone_ia>,
710             C<intelligent_tiering>, C<glacier>, or C<deep_archive>; the default
711             is C<standard>.
712              
713             You may set website-redirect-location object metadata by setting
714             C<website_redirect_location> to either another object name in the same
715             bucket, or to an external URL.
716              
717             =head2 put_filename
718              
719             # upload a file
720             my $object = $bucket->object(
721             key => 'images/my_hat.jpg',
722             content_type => 'image/jpeg',
723             );
724             $object->put_filename('hat.jpg');
725              
726             # upload a file if you already know its md5_hex and size
727             my $object = $bucket->object(
728             key => 'images/my_hat.jpg',
729             content_type => 'image/jpeg',
730             etag => $md5_hex,
731             size => $size,
732             );
733             $object->put_filename('hat.jpg');
734              
735             You may also set Content-Encoding using C<content_encoding>, and
736             Content-Disposition using C<content_disposition>.
737              
738             You may specify the S3 storage class by setting C<storage_class> to either
739             C<standard>, C<reduced_redundancy>, C<standard_ia>, C<onezone_ia>,
740             C<intelligent_tiering>, C<glacier>, or C<deep_archive>; the default
741             is C<standard>.
742              
743             You may set website-redirect-location object metadata by setting
744             C<website_redirect_location> to either another object name in the same
745             bucket, or to an external URL.
746              
747             User metadata may be set by providing a non-empty hashref as
748             C<user_metadata>.
749              
750             =head2 query_string_authentication_uri
751              
752             # use query string authentication, forcing download with custom filename
753             my $object = $bucket->object(
754             key => 'images/my_hat.jpg',
755             expires => '2009-03-01',
756             );
757             my $uri = $object->query_string_authentication_uri({
758             'response-content-disposition' => 'attachment; filename=abc.doc',
759             });
760              
761             =head2 query_string_authentication_uri_for_method
762              
763             my $uri = $object->query_string_authentication_uri_for_method ('PUT');
764              
765             Similar to L</query_string_authentication_uri> but creates presigned uri
766             for specified HTTP method (Signature V4 uses also HTTP method).
767              
768             Methods providee authenticated uri only for direct object operations.
769              
770             See L<https://docs.aws.amazon.com/AmazonS3/latest/dev/PresignedUrlUploadObject.html>
771              
772             =head2 size
773              
774             # show the size of an existing object (if fetched by listing
775             # a bucket)
776             print $object->size . "\n";
777              
778             =head2 uri
779              
780             # return the URI of a publically-accessible object
781             my $uri = $object->uri;
782              
783             =head2 initiate_multipart_upload
784              
785             #initiate a new multipart upload for this object
786             my $object = $bucket->object(
787             key => 'massive_video.avi',
788             acl => ...,
789             );
790             my $upload_id = $object->initiate_multipart_upload;
791              
792             For description of C<acl> refer C<Net::Amazon::S3::ACL>.
793              
794             =head2 put_part
795              
796             #add a part to a multipart upload
797             my $put_part_response = $object->put_part(
798             upload_id => $upload_id,
799             part_number => 1,
800             value => $chunk_content,
801             );
802             my $part_etag = $put_part_response->header('ETag')
803              
804             Returns an L<HTTP::Response> object. It is necessary to keep the ETags for
805             each part, as these are required to complete the upload.
806              
807             =head2 complete_multipart_upload
808              
809             #complete a multipart upload
810             $object->complete_multipart_upload(
811             upload_id => $upload_id,
812             etags => [$etag_1, $etag_2],
813             part_numbers => [$part_number_1, $part_number2],
814             );
815              
816             The etag and part_numbers parameters are ordered lists specifying the part
817             numbers and ETags for each individual part of the multipart upload.
818              
819             =head2 user_metadata
820              
821             my $object = $bucket->object(key => $key);
822             my $content = $object->get; # or use $object->get_filename($filename)
823              
824             # return the user metadata downloaded, as a hashref
825             my $user_metadata = $object->user_metadata;
826              
827             To upload an object with user metadata, set C<user_metadata> at construction
828             time to a hashref, with no C<x-amz-meta-> prefixes on the key names. When
829             downloading an object, the C<get>, C<get_decoded> and C<get_filename>
830             ethods set the contents of C<user_metadata> to the same format.
831              
832             =head2 add_tags
833              
834             $object->add_tags (
835             tags => { tag1 => 'val1', tag2 => 'val2' },
836             );
837              
838             $object->add_tags (
839             tags => { tag1 => 'val1', tag2 => 'val2' },
840             version_id => $version_id,
841             );
842              
843             =head2 delete_tags
844              
845             $object->delete_tags;
846              
847             $object->delete_tags (
848             version_id => $version_id,
849             );
850              
851             =head1 AUTHOR
852              
853             Branislav Zahradník <barney@cpan.org>
854              
855             =head1 COPYRIGHT AND LICENSE
856              
857             This software is copyright (c) 2022 by Amazon Digital Services, Leon Brocard, Brad Fitzpatrick, Pedro Figueiredo, Rusty Conover, Branislav Zahradník.
858              
859             This is free software; you can redistribute it and/or modify it under
860             the same terms as the Perl 5 programming language system itself.
861              
862             =cut