File Coverage

blib/lib/AWS/S3/File.pm
Criterion Covered Total %
statement 53 56 94.6
branch 10 14 71.4
condition 1 2 50.0
subroutine 10 10 100.0
pod 3 4 75.0
total 77 86 89.5


line stmt bran cond sub pod time code
1              
2             package AWS::S3::File;
3              
4 6     6   42 use Moose;
  6         13  
  6         49  
5 6     6   53816 use Carp 'confess';
  6         14  
  6         575  
6              
7 6     6   4693 use MooseX::Types -declare => [qw/fileContents/];
  6         220175  
  6         93  
8 6     6   42464 use MooseX::Types::Moose qw/Str ScalarRef CodeRef/;
  6         38773  
  6         95  
9              
10             subtype fileContents, as ScalarRef;
11             coerce fileContents,
12             from CodeRef,
13             via {
14             my $ref = $_[0];
15             my $v = $ref->();
16             ref $v ? $v : \$v
17             }
18             ;
19              
20             has 'key' => (
21             is => 'ro',
22             isa => 'Str',
23             required => 1,
24             );
25              
26             has 'bucket' => (
27             is => 'ro',
28             isa => 'AWS::S3::Bucket',
29             required => 1,
30             weak_ref => 0,
31             );
32              
33             has 'size' => (
34             is => 'ro',
35             isa => 'Int',
36             required => 0,
37             default => sub {
38             my $self = shift;
39             return length ${$self->contents};
40             }
41             );
42              
43             has 'etag' => (
44             is => 'ro',
45             isa => 'Str',
46             required => 0,
47             );
48              
49             has 'owner' => (
50             is => 'ro',
51             isa => 'AWS::S3::Owner',
52             required => 0,
53             weak_ref => 1,
54             );
55              
56             has 'storage_class' => (
57             is => 'ro',
58             isa => 'Str',
59             default => 'STANDARD',
60             required => 1,
61             );
62              
63             has 'lastmodified' => (
64             is => 'ro',
65             isa => 'Str',
66             required => 0,
67             );
68              
69             has 'contenttype' => (
70             is => 'rw',
71             isa => 'Str',
72             required => 0,
73             default => 'binary/octet-stream'
74             );
75              
76             has 'is_encrypted' => (
77             is => 'rw',
78             isa => 'Bool',
79             required => 1,
80             lazy => 1,
81             default => sub {
82             my $s = shift;
83              
84             my $type = 'GetFileInfo';
85             my $req = $s->bucket->s3->request(
86             $type,
87             bucket => $s->bucket->name,
88             key => $s->key,
89             );
90              
91             return $req->request->response->header( 'x-amz-server-side-encryption' ) ? 1 : 0;
92             },
93             );
94              
95             has 'contents' => (
96             is => 'rw',
97             isa => fileContents,
98             required => 0,
99             lazy => 1,
100             coerce => 1,
101             default => \&_get_contents,
102             trigger => \&_set_contents
103             );
104              
105             sub BUILD {
106 14     14 0 26 my $s = shift;
107              
108 14 50       504 return unless $s->etag;
109 14         436 ( my $etag = $s->etag ) =~ s{^"}{};
110 14         69 $etag =~ s{"$}{};
111 14         333 $s->{etag} = $etag;
112             } # end BUILD()
113              
114             sub update {
115 4     4 1 24 my $s = shift;
116 4         12 my %args = @_;
117 4         12 my @args_ok = grep { /^content(?:s|type)$/ } keys %args;
  2         19  
118 4 100       16 if ( @args_ok ) {
119 2         11 $s->{$_} = $args{$_} for @args_ok;
120 2         13 $s->_set_contents();
121 2         25 return 1;
122             }
123 2         12 return;
124             } # end update()
125              
126             sub _get_contents {
127 2     2   6 my $s = shift;
128              
129 2         5 my $type = 'GetFileContents';
130 2         82 my $req = $s->bucket->s3->request(
131             $type,
132             bucket => $s->bucket->name,
133             key => $s->key,
134             );
135              
136 2         14 return \$req->request->response->decoded_content;
137             } # end contents()
138              
139             sub _set_contents {
140 6     6   20 my ( $s, $ref ) = @_;
141              
142 6         12 my $type = 'SetFileContents';
143 6         16 my %args = ();
144 6 100       266 my $response = $s->bucket->s3->request(
145             $type,
146             bucket => $s->bucket->name,
147             file => $s,
148             contents => $ref,
149             content_type => $s->contenttype,
150             server_side_encryption => $s->is_encrypted ? 'AES256' : undef,
151             )->request();
152              
153 6         833 ( my $etag = $response->response->header( 'etag' ) ) =~ s{^"}{};
154 6         71 $etag =~ s{"$}{};
155 6         76 $s->{etag} = $etag;
156              
157 6 50       237 if ( my $msg = $response->friendly_error() ) {
158 0         0 die $msg;
159             } # end if()
160             } # end _set_contents()
161              
162             sub signed_url {
163 2     2 1 6 my $s = shift;
164 2   50     9 my $expires = shift || 3600;
165              
166             # expiry for v4 signature is in seconds, not epoch time
167 2 50       10 if ( $expires > time ) {
168 0         0 $expires -= time;
169             }
170              
171 2         80 my $key = $s->key;
172              
173 2 100       75 if ( ! $s->bucket->s3->honor_leading_slashes ) {
174 1         6 $key =~ s!^/!!;
175             }
176              
177 2         6 my $type = "GetPreSignedUrl";
178 2         47 my $uri = $s->bucket->s3->request(
179             $type,
180             bucket => $s->bucket->name,
181             key => $key,
182             expires => $expires,
183             )->request;
184              
185 2         73 return $uri;
186             }
187              
188             sub delete {
189 2     2 1 173 my $s = shift;
190              
191 2         7 my $type = 'DeleteFile';
192 2         78 my $req = $s->bucket->s3->request(
193             $type,
194             bucket => $s->bucket->name,
195             key => $s->key,
196             );
197 2         16 my $response = $req->request();
198              
199 2 50       411 if ( my $msg = $response->friendly_error() ) {
200 0         0 die $msg;
201             } # end if()
202              
203 2         65 return 1;
204             } # end delete()
205              
206             __PACKAGE__->meta->make_immutable;
207              
208             __END__
209              
210             =pod
211              
212             =head1 NAME
213              
214             AWS::S3::File - A single file in Amazon S3
215              
216             =head1 SYNOPSIS
217              
218             my $file = $bucket->file('foo/bar.txt');
219            
220             # contents is a scalarref:
221             print @{ $file->contents };
222             print $file->size;
223             print $file->key;
224             print $file->etag;
225             print $file->lastmodified;
226            
227             print $file->owner->display_name;
228            
229             print $file->bucket->name;
230            
231             # Set the contents with a scalarref:
232             my $new_contents = "This is the new contents of the file.";
233             $file->contents( \$new_contents );
234            
235             # Set the contents with a coderef:
236             $file->contents( sub {
237             return \$new_contents;
238             });
239            
240             # Alternative update
241             $file->update(
242             contents => \'New contents', # optional
243             contenttype => 'text/plain' # optional
244             );
245              
246             # Get signed URL for the file for public access
247             print $file->signed_url( $expiry_time );
248            
249             # Delete the file:
250             $file->delete();
251              
252             =head1 DESCRIPTION
253              
254             AWS::S3::File provides a convenience wrapper for dealing with files stored in S3.
255              
256             =head1 PUBLIC PROPERTIES
257              
258             =head2 bucket
259              
260             L<AWS::S3::Bucket> - read-only.
261              
262             The L<AWS::S3::Bucket> that contains the file.
263              
264             =head2 key
265              
266             String - read-only.
267              
268             The 'filename' (for all intents and purposes) of the file.
269              
270             =head2 size
271              
272             Integer - read-only.
273              
274             The size in bytes of the file.
275              
276             =head2 etag
277              
278             String - read-only.
279              
280             The Amazon S3 'ETag' header for the file.
281              
282             =head2 owner
283              
284             L<ASW::S3::Owner> - read-only.
285              
286             The L<ASW::S3::Owner> that the file belongs to.
287              
288             =head2 storage_class
289              
290             String - read-only.
291              
292             The type of storage used by the file.
293              
294             =head2 lastmodified
295              
296             String - read-only.
297              
298             A date in this format:
299              
300             2009-10-28T22:32:00
301              
302             =head2 contents
303              
304             ScalarRef|CodeRef - read-write.
305              
306             Returns a scalar-reference of the file's contents.
307              
308             Accepts either a scalar-ref or a code-ref (which would return a scalar-ref).
309              
310             Once given a new value, the file is instantly updated on Amazon S3.
311              
312             # GOOD: (uses scalarrefs)
313             my $value = "A string";
314             $file->contents( \$value );
315             $file->contents( sub { return \$value } );
316            
317             # BAD: (not scalarrefs)
318             $file->contents( $value );
319             $file->contents( sub { return $value } );
320              
321             =head1 PUBLIC METHODS
322              
323             =head2 delete()
324              
325             Deletes the file from Amazon S3.
326              
327             =head2 update()
328              
329             Update contents and/or contenttype of the file.
330              
331             =head2 signed_url( $expiry_time )
332              
333             Will return a signed URL for public access to the file. $expiry_time should be a
334             Unix seconds since epoch, and will default to now + 1 hour is not passed.
335              
336             Note that the Signature parameter value will be URI encoded to prevent reserved
337             characters (+, =, etc) causing a bad request.
338              
339             =head1 SEE ALSO
340              
341             L<The Amazon S3 API Documentation|http://docs.amazonwebservices.com/AmazonS3/latest/API/>
342              
343             L<AWS::S3>
344              
345             L<AWS::S3::Bucket>
346              
347             L<AWS::S3::FileIterator>
348              
349             L<AWS::S3::Owner>
350              
351             =cut
352