File Coverage

blib/lib/Net/Google/Storage.pm
Criterion Covered Total %
statement 30 120 25.0
branch 0 34 0.0
condition 0 8 0.0
subroutine 10 19 52.6
pod 9 9 100.0
total 49 190 25.7


line stmt bran cond sub pod time code
1 1     1   13504 use strict;
  1         3  
  1         24  
2 1     1   5 use warnings;
  1         2  
  1         24  
3 1     1   431 use autodie;
  1         11796  
  1         4  
4             package Net::Google::Storage;
5             $Net::Google::Storage::VERSION = '0.2.0';
6             # ABSTRACT: Access the Google Storage JSON API (currently experimental).
7             # https://developers.google.com/storage/docs/json_api/
8              
9 1     1   7246 use Moose;
  1         387951  
  1         7  
10 1     1   7178 use LWP::UserAgent;
  1         31652  
  1         32  
11 1     1   491 use JSON;
  1         7216  
  1         6  
12 1     1   132 use HTTP::Status qw(:constants);
  1         3  
  1         325  
13              
14 1     1   389 use Net::Google::Storage::Bucket;
  1         4  
  1         44  
15 1     1   566 use Net::Google::Storage::Object;
  1         8  
  1         1975  
16              
17             with 'Net::Google::Storage::Agent';
18              
19              
20             has projectId => (
21             is => 'rw',
22             isa => 'Int',
23             );
24              
25             my $api_base = 'https://www.googleapis.com/storage/v1/b';
26             my $upload_api_base = 'https://www.googleapis.com/upload/storage/v1/b';
27              
28              
29             sub list_buckets
30             {
31 0     0 1   my $self = shift;
32            
33 0           my $projectId = $self->projectId;
34            
35 0           my $res = $self->_get("$api_base?project=$projectId");
36            
37 0 0         die 'Failed to list buckets' . $res->decoded_content unless $res->is_success;
38            
39 0           my $response = decode_json($res->decoded_content);
40            
41 0           my @buckets = map {Net::Google::Storage::Bucket->new($_)} @{$response->{items}};
  0            
  0            
42 0           return \@buckets;
43             }
44              
45              
46             sub get_bucket
47             {
48 0     0 1   my $self = shift;
49            
50 0           my $bucket_name = shift;
51            
52 0           my $res = $self->_get($self->_form_url("$api_base/%s", $bucket_name));
53 0 0         return undef if $res->code == HTTP_NOT_FOUND;
54 0 0         die "Failed to get bucket: $bucket_name" unless $res->is_success;
55            
56 0           my $response = decode_json($res->decoded_content);
57            
58 0           return Net::Google::Storage::Bucket->new($response);
59             }
60              
61              
62             sub insert_bucket
63             {
64 0     0 1   my $self = shift;
65            
66 0           my $bucket_args = shift;
67 0           my $res = $self->_json_post($api_base . '?project=' . $self->projectId, $bucket_args);
68 0 0         die "Failed to create bucket: $bucket_args->{id}" . $res->content unless $res->is_success;
69            
70 0           my $response = decode_json($res->decoded_content);
71            
72 0           return Net::Google::Storage::Bucket->new($response);
73             }
74              
75              
76             sub delete_bucket
77             {
78 0     0 1   my $self = shift;
79            
80 0           my $bucket_name = shift;
81            
82 0           my $res = $self->_delete($self->_form_url("$api_base/%s", $bucket_name));
83 0 0         die "Failed to delete bucket: $bucket_name" unless $res->is_success;
84            
85 0           return;
86             }
87              
88              
89             sub list_objects
90             {
91 0     0 1   my $self = shift;
92            
93 0           my $bucket = shift;
94            
95 0           my $res = $self->_get($self->_form_url("$api_base/%s/o", $bucket));
96            
97 0 0         die 'Failed to list objects' unless $res->is_success;
98            
99 0           my $response = decode_json($res->decoded_content);
100            
101 0           my @objects = map {Net::Google::Storage::Object->new($_)} @{$response->{items}};
  0            
  0            
102 0           return \@objects;
103             }
104              
105              
106             sub get_object
107             {
108 0     0 1   my $self = shift;
109            
110 0           my %args = @_;
111            
112 0           my $res = $self->_get($self->_form_url("$api_base/%s/o/%s?alt=json", $args{bucket}, $args{object}));
113 0 0         return undef if $res->code == HTTP_NOT_FOUND;
114 0 0         die "Failed to get object: $args{object} in bucket: $args{bucket}" unless $res->is_success;
115            
116 0           my $response = decode_json($res->decoded_content);
117            
118 0           return Net::Google::Storage::Object->new($response);
119             }
120              
121              
122             sub download_object
123             {
124 0     0 1   my $self = shift;
125            
126 0           my %args = @_;
127            
128 0           my $res = $self->_get($self->_form_url("$api_base/%s/o/%s?alt=media", $args{bucket}, $args{object}), ':content_file' => $args{filename});
129 0 0         return undef if $res->code == HTTP_NOT_FOUND;
130 0 0         die "Failed to get object: $args{object} in bucket: $args{bucket}" unless $res->is_success;
131             }
132              
133              
134             sub insert_object
135             {
136 0     0 1   my $self = shift;
137            
138 0           my %args = @_;
139            
140 0           my $url = $self->_form_url("$upload_api_base/%s/o?uploadType=resumable", $args{bucket});
141 0   0       my $filename = $args{filename} || die 'A filename is required';
142            
143 0 0         die "Unable to find $filename" unless -e $filename;
144 0           my $filesize = -s _;
145            
146 0           my $object_hash = $args{object};
147 0 0         unless($object_hash->{media}->{contentType})
148             {
149 0           require LWP::MediaTypes;
150 0           $object_hash->{media}->{contentType} = LWP::MediaTypes::guess_media_type($filename);
151             }
152            
153 0           my $content_type = $object_hash->{media}->{contentType};
154 0           my $res = $self->_json_post($url, 'X-Upload-Content-Type' => $content_type, 'X-Upload-Content-Length' => $filesize, $object_hash);
155 0           my $resumable_url = $res->header('Location');
156            
157 0           my %headers = (
158             'Content-Length' => $filesize,
159             'Content-Type' => $content_type,
160             );
161            
162 0           local $/;
163 0           open(my $fh, '<', $filename);
164 0           my $file_contents = <$fh>;
165            
166 0           $res = $self->_put($resumable_url, %headers, Content => $file_contents);
167            
168             #resuming code
169 0           my $retry_count = 0;
170 0           my $code = $res->code;
171 0   0       while($code >=500 && $code <600 && $retry_count++ < 8)
      0        
172             {
173 0           sleep 2**$retry_count;
174 0           $res = $self->_put($resumable_url, 'Content-Length' => 0, 'Content-Range' => "bytes */$filesize");
175 0 0         last if $res->is_success;
176 0 0         next unless $res->code == 308;
177            
178 0           my $range = $res->header('Range');
179 0 0         next unless $range;
180            
181 0 0         if($range =~ /bytes=0-(\d+)/)
182             {
183 0           my $offset = $1+1;
184 0           seek($fh, $offset, 0);
185 0           $file_contents = <$fh>;
186            
187 0           %headers = (
188             'Content-Length' => $filesize - $offset,
189             'Content-Range' => sprintf('bytes %d-%d/%d', $offset, $filesize-1, $filesize),
190             );
191 0           $res = $self->_put($resumable_url, %headers, Content => $file_contents);
192 0           $code = $res->code;
193             }
194             else
195             {
196 0           next;
197             }
198             }
199            
200 0           my $response = decode_json($res->decoded_content);
201            
202 0           return Net::Google::Storage::Object->new($response);
203             }
204              
205              
206             sub delete_object
207             {
208 0     0 1   my $self = shift;
209            
210 0           my %args = @_;
211            
212 0           my $res = $self->_delete($self->_form_url("$api_base/%s/o/%s", $args{bucket}, $args{object}));
213 0 0         die "Failed to delete object: $args{object} in bucket: $args{bucket}" unless $res->is_success;
214            
215 0           return;
216             }
217              
218 1     1   20 no Moose;
  1         5  
  1         12  
219             __PACKAGE__->meta->make_immutable;
220              
221             1;
222              
223             __END__
224              
225             =pod
226              
227             =encoding UTF-8
228              
229             =head1 NAME
230              
231             Net::Google::Storage - Access the Google Storage JSON API (currently experimental).
232              
233             =head1 VERSION
234              
235             version 0.2.0
236              
237             =head1 SYNOPSIS
238              
239             my $gs = Net::Google::Storage->new(
240             projectId => $projectId,
241             %agent_args
242             );
243              
244             See L<Net::Google::Storage::Agent> for a decription of C<%agent_args>.
245              
246             my $buckets = $gs->list_buckets();
247              
248             my $bucket = $gs->get_bucket($bucket_name);
249              
250             my $new_bucket = $gs->insert_bucket({id => $new_bucket_name});
251              
252             $gs->delete_bucket($bucket_name);
253              
254             my $objects = $gs->list_objects($bucket_name);
255              
256             my $object = $gs->get_object(bucket => $bucket_name, object => $object_name);
257              
258             $gs->download_object(bucket => $bucket_name, object => $object_name, filename => $filename);
259              
260             my $object = $gs->insert_object(bucket => $bucket_name, object => {name => $object_name}, filename => $filename);
261              
262             $gs->delete_object(bucket => $bucket_name, object => $object_name);
263              
264             =head1 DESCRIPTION
265              
266             Net::Google::Storage is a library for interacting with the JSON version of
267             the Google Storage API, which is currently (as at 2012-09-17) marked as
268             experimental.
269              
270             This module does not (yet) cover the entire surface of the API, but it is a
271             decent attempt at providing the most important functionality.
272              
273             See L<https://developers.google.com/storage/docs/json_api/> for documentation
274             of the API itself.
275              
276             =head1 ATTRIBUTES
277              
278             =head2 projectId
279              
280             Google's identifier of the project you are accessing. Available from the
281             L<API Console|https://code.google.com/apis/console/#:storage>.
282              
283             =head1 METHODS
284              
285             =head2 new
286              
287             Constructs a shiny new B<Net::Google::Storage> object. Arguments include
288             C<projectId> and the attributes of L<Net::Google::Storage::Agent>
289              
290             =head2 list_buckets
291              
292             Returns an arrayref of L<Net::Google::Storage::Bucket> objects for the
293             current projectId.
294              
295             =head2 get_bucket
296              
297             Takes a bucket name as the only argument, returns the matching
298             L<Net::Google::Storage::Bucket> object or undef if nothing matches.
299              
300             =head2 insert_bucket
301              
302             Takes some bucket metadata as the only argument (could be as simple as
303             C<< {id => $bucket_name} >>), creates a new bucket and returns the matching
304             L<Net::Google::Storage::Bucket> object.
305              
306             =head2 delete_bucket
307              
308             Takes a bucket name as the only argument, deletes the bucket.
309              
310             =head2 list_objects
311              
312             Takes a bucket name as the only argument, returns an arrayref of
313             L<Net::Google::Storage::Object> objects.
314              
315             =head2 get_object
316              
317             Takes a hash (not hashref) of arguments with keys: I<bucket> and I<object>
318             and returns the matching L<Net::Google::Storage::Object> object (or undef if
319             no match was found).
320              
321             =head2 download_object
322              
323             Takes a hash (not hashref) of arguments with keys: I<bucket>, I<object>, and
324             I<filename> and downloads the matching object as the desired filename.
325              
326             Returns undef if the object doesn't exist, true for success.
327              
328             =head2 insert_object
329              
330             Takes a hash of arguments with keys: I<bucket>, I<filename> and I<object>
331             where I<object> contains the necessary metadata to upload the file, which is,
332             at minimum, the I<name> field.
333              
334             =head2 delete_object
335              
336             Takes a hash of arguments with keys: I<bucket> and I<object> and deletes the
337             matching object.
338              
339             =head1 AUTHOR
340              
341             Glenn Fowler <cebjyre@cpan.org>
342              
343             =head1 COPYRIGHT AND LICENSE
344              
345             This software is copyright (c) 2012 by Glenn Fowler.
346              
347             This is free software; you can redistribute it and/or modify it under
348             the same terms as the Perl 5 programming language system itself.
349              
350             =cut