File Coverage

blib/lib/Flickr/Upload.pm
Criterion Covered Total %
statement 83 114 72.8
branch 28 70 40.0
condition 4 41 9.7
subroutine 11 12 91.6
pod 5 6 83.3
total 131 243 53.9


line stmt bran cond sub pod time code
1             package Flickr::Upload;
2              
3 6     6   281391 use strict;
  6         13  
  6         241  
4 6     6   30 use warnings;
  6         11  
  6         170  
5              
6 6     6   10884 use LWP::UserAgent;
  6         565133  
  6         229  
7 6     6   31430 use HTTP::Request::Common;
  6         27220  
  6         678  
8 6     6   10679 use Flickr::API;
  6         230221  
  6         227  
9 6     6   63 use XML::Parser::Lite::Tree;
  6         15  
  6         10630  
10              
11             our $VERSION = '1.4';
12              
13             our @ISA = qw(Flickr::API);
14              
15             sub response_tag {
16 2     2 0 4 my $t = shift;
17 2         3 my $node = shift;
18 2         3 my $tag = shift;
19              
20 2 50 33     11 return undef unless defined $t and exists $t->{'children'};
21              
22 0         0 for my $n ( @{$t->{'children'}} ) {
  0         0  
23 0 0 0     0 next unless defined $n and exists $n->{'name'} and exists $n->{'children'};
      0        
24 0 0       0 next unless $n->{'name'} eq $node;
25              
26 0         0 for my $m (@{$n->{'children'}} ) {
  0         0  
27 0 0 0     0 next unless exists $m->{'name'}
      0        
28             and $m->{'name'} eq $tag
29             and exists $m->{'children'};
30              
31 0         0 return $m->{'children'}->[0]->{'content'};
32             }
33             }
34 0         0 return undef;
35             }
36              
37             =head1 NAME
38              
39             Flickr::Upload - Upload images to C
40              
41             =head1 SYNOPSIS
42              
43             use Flickr::Upload;
44              
45             my $ua = Flickr::Upload->new(
46             {
47             'key' => '90909354',
48             'secret' => '37465825'
49             });
50             $ua->upload(
51             'photo' => '/tmp/image.jpg',
52             'auth_token' => $auth_token,
53             'tags' => 'me myself eye',
54             'is_public' => 1,
55             'is_friend' => 1,
56             'is_family' => 1
57             ) or die "Failed to upload /tmp/image.jpg";
58              
59             =head1 DESCRIPTION
60              
61             Upload an image to L.
62              
63             =head1 METHODS
64              
65             =head2 new
66              
67             my $ua = Flickr::Upload->new(
68             {
69             'key' => '90909354',
70             'secret' => '37465825'
71             });
72              
73             Instantiates a L instance. The C argument is your
74             API key and the C is the API secret associated with it. To get an
75             API key and secret, go to L.
76              
77             The resulting L instance is a subclass of L
78             and can be used for any other Flickr API calls. As such,
79             L is also a subclass of L.
80              
81             =head2 upload
82              
83             my $photoid = $ua->upload(
84             'photo' => '/tmp/image.jpg',
85             'auth_token' => $auth_token,
86             'tags' => 'me myself eye',
87             'is_public' => 1,
88             'is_friend' => 1,
89             'is_family' => 1
90             'async' => 0,
91             );
92              
93             Taking a L instance C<$ua> as an argument, this is
94             basically a direct interface to the Flickr Photo Upload API. Required
95             parameters are C and C. Note that the C
96             must have been issued against the API key and secret used to instantiate
97             the uploader.
98              
99             Returns the resulting identifier of the uploaded photo on success,
100             C on failure. According to the API documentation, after an upload the
101             user should be directed to the page
102             L.
103              
104             If the C option is non-zero, the photo will be uploaded
105             asynchronously and a successful upload returns a ticket identifier. See
106             L. The caller can then
107             periodically poll for a photo id using the C method. Note
108             that photo and ticket identifiers aren't necessarily numeric.
109              
110             =cut
111              
112             sub upload {
113 1     1 1 93554 my $self = shift;
114 1 50       15 die '$self is not a Flickr::Upload' unless $self->isa('Flickr::Upload');
115 1         10 my %args = @_;
116              
117             # these are the only things _required_ by the uploader.
118 1 50 33     50 die "Can't read photo '$args{'photo'}'" unless $args{'photo'} and -f $args{'photo'};
119 1 50       4 die "Missing 'auth_token'" unless defined $args{'auth_token'};
120              
121             # create a request object and execute it
122 1         8 my $req = $self->make_upload_request( %args );
123 1 50       9 return undef unless defined $req;
124              
125 1         6 return $self->upload_request( $req );
126             }
127              
128             =head2 check_upload
129              
130             my %status2txt = (0 => 'not complete', 1 => 'completed', 2 => 'failed');
131             my @rc = $ua->check_upload( @ticketids );
132             for( @rc ) {
133             print "Ticket $_->{id} has $status2txt{$_->{complete}}\n";
134             print "\tPhoto id is $_->{photoid}\n" if exists $_->{photoid};
135             }
136              
137             This function will check the status of one or more asynchronous uploads. A
138             list of ticket identifiers are provided (C<@ticketids>) and each is
139             checked. This is basically just a wrapper around the Flickr API
140             C method.
141              
142             On success, a list of hash references is returned. Each
143             hash contains a C (the ticket id), C and, if
144             completed, C members. C may also be returned.
145             Status codes (for C) are as documented at
146             L and, actually, the
147             returned fields are identical to those listed in the C tag of the
148             response. The returned list isn't guaranteed to be in any particular order.
149              
150             This function polls a web server, so avoid calling it too frequently.
151              
152             =cut
153              
154             sub check_upload {
155 0     0 1 0 my $self = shift;
156 0 0       0 die '$self is not a Flickr::API' unless $self->isa('Flickr::API');
157              
158 0 0       0 return () unless @_; # no tickets
159              
160 0 0       0 my $res = $self->execute_method( 'flickr.photos.upload.checkTickets',
161             { 'tickets' => ((@_ == 1) ? $_[0] : join(',', @_)) } );
162 0 0 0     0 return () unless defined $res and $res->{success};
163              
164             # FIXME: better error feedback
165              
166 0         0 my @rc;
167 0 0 0     0 return undef unless defined $res->{tree} and exists $res->{tree}->{'children'};
168 0         0 for my $n ( @{$res->{tree}->{'children'}} ) {
  0         0  
169 0 0 0     0 next unless defined $n and exists $n->{'name'} and $n->{'children'};
      0        
170 0 0       0 next unless $n->{'name'} eq "uploader";
171              
172 0         0 for my $m (@{$n->{'children'}} ) {
  0         0  
173 0 0 0     0 next unless exists $m->{'name'}
      0        
174             and $m->{'name'} eq 'ticket'
175             and exists $m->{'attributes'};
176              
177             # okay, this is maybe a little lazy...
178 0         0 push @rc, $m->{'attributes'};
179             }
180             }
181              
182 0         0 return @rc;
183             }
184              
185             =head2 make_upload_request
186              
187             my $req = $uploader->make_upload_request(
188             'auth_token' => '82374523',
189             'tags' => 'me myself eye',
190             'is_public' => 1,
191             'is_friend' => 1,
192             'is_family' => 1
193             );
194             $req->header( 'X-Greetz' => 'hi cal' );
195             my $resp = $ua->request( $req );
196              
197             Creates an L object loaded with all the flick upload
198             parameters. This will also sign the request, which means you won't be able to
199             mess any further with the upload request parameters.
200              
201             Takes all the same parameters as L, except that the photo argument
202             isn't required. This in intended so that the caller can include it by
203             messing directly with the HTTP content (via C<$DYNAMIC_FILE_UPLOAD> or
204             the L class, among other things). See C directory from
205             the source distribution for examples.
206              
207             Returns a standard L POST object. The caller can manually
208             do the upload or just call the L function.
209              
210             =cut
211              
212             sub make_upload_request {
213 1     1 1 2 my $self = shift;
214 1 50       6 die '$self is not a Flickr::Upload' unless $self->isa('Flickr::Upload');
215 1         5 my %args = @_;
216              
217             # _required_ by the uploader.
218 1 50       4 die "Missing 'auth_token' argument" unless $args{'auth_token'};
219              
220 1   50     8 my $uri = $args{'uri'} || 'https://api.flickr.com/services/upload/';
221              
222             # passed in separately, so remove from the hash
223 1         3 delete $args{uri};
224              
225             # Flickr::API includes this with normal requests, but we're building a custom
226             # message.
227 1         3 $args{'api_key'} = $self->{'api_key'};
228              
229             # photo is _not_ included in the sig
230 1         2 my $photo = $args{photo};
231 1         3 delete $args{photo};
232              
233             # HACK: sign_args() is an internal Flickr::API method
234 1         10 $args{'api_sig'} = $self->sign_args(\%args);
235              
236             # unlikely that the caller would set up the photo as an array,
237             # but...
238 1 50       54 if( defined $photo ) {
239 1 50       5 $photo = [ $photo ] if ref $photo ne "ARRAY";
240 1         3 $args{photo} = $photo;
241             }
242              
243 1         7 my $req = POST $uri, 'Content_Type' => 'form-data', 'Content' => \%args;
244              
245 1         34309 return $req;
246             }
247              
248             =head2 upload_request
249              
250             my $photoid = upload_request( $ua, $request );
251              
252             Taking (at least) L and L objects as
253             arguments, this executes the request and processes the result as a
254             flickr upload. It's assumed that the request looks a lot like something
255             created with L. Note that the request must be signed
256             according to the Flickr API authentication rules.
257              
258             Returns the resulting identifier of the uploaded photo (or ticket for
259             asynchronous uploads) on success, C on failure. According to the
260             API documentation, after an upload the user should be directed to the
261             page L.
262              
263             =cut
264              
265             sub upload_request {
266 1     1 1 3 my $self = shift;
267 1 50       8 die "$self is not a LWP::UserAgent" unless $self->isa('LWP::UserAgent');
268 1         3 my $req = shift;
269 1 50       11 die "expecting a HTTP::Request" unless $req->isa('HTTP::Request');
270              
271             # Try 3 times to upload data. Without this flickr_upload is bound
272             # to die on large uploads due to some miscellaneous network
273             # issues. Timeouts on flickr or something else.
274 1         2 my ($res, $tree);
275 1         2 my $tries = 3;
276 1         4 for my $try (1 .. $tries) {
277             # Try to upload
278 3         24 $res = $self->request( $req );
279 3 50       4767 return () unless defined $res;
280              
281 3 50       14 if ($res->is_success) {
282 0         0 $tree = XML::Parser::Lite::Tree::instance()->parse($res->decoded_content());
283 0 0       0 return () unless defined $tree;
284 0         0 last;
285             } else {
286 3 100       31 my $what_next = ($try == $tries ? "giving up" : "trying again");
287 3         12 my $status = $res->status_line;
288              
289 3         143 print STDERR "Failed uploading attempt attempt $try/$tries, $what_next. Message from server was: '$status'\n";
290 3         12 next;
291             }
292             }
293              
294 1         30 my $photoid = response_tag($tree, 'rsp', 'photoid');
295 1         6 my $ticketid = response_tag($tree, 'rsp', 'ticketid');
296 1 50 33     12 unless( defined $photoid or defined $ticketid ) {
297 1         13 print STDERR "upload failed:\n", $res->decoded_content(), "\n";
298 1         642 return undef;
299             }
300              
301 0 0       0 return (defined $photoid) ? $photoid : $ticketid;
302             }
303              
304             =head2 file_length_in_encoded_chunk
305              
306             $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD = 1;
307             my $photo = 'image.jpeg';
308             my $photo_size = (stat($photo))[7];
309             my $req = $ua->make_upload_request( ... );
310             my $gen = $req->content();
311             die unless ref($gen) eq "CODE";
312              
313             my $state;
314             my $size;
315              
316             $req->content(
317             sub {
318             my $chunk = &$gen();
319              
320             $size += Flickr::Upload::file_length_in_encoded_chunk(\$chunk, \$state, $photo_size);
321              
322             warn "$size bytes have now been uploaded";
323              
324             return $chunk;
325             }
326             );
327              
328             $rc = $ua->upload_request( $req );
329              
330             This subroutine is tells you how much of a chunk in a series of
331             variable size multipart HTTP chunks contains a single file being
332             uploaded given a reference to the current chunk, a reference to a
333             state variable that lives between calls, and the size of the file
334             being uploaded.
335              
336             It can be used used along with L's
337             $HTTP::Request::Common::DYNAMIC_FILE_UPLOAD facility to implement
338             upload progress bars or other upload monitors, see L
339             for a practical example and F for tests.
340              
341             =cut
342              
343             sub file_length_in_encoded_chunk
344             {
345 78438     78438 1 10572015 my ($chunk, $s, $img_size) = @_;
346              
347 78438 100       334250 $$s = {} unless ref $$s eq 'HASH';
348              
349             # If we've run past the end of the image there's nothing to do but
350             # report no image content in this sector.
351 78438 100       320310 return 0 if $$s->{done};
352              
353 63209 100       223195 unless ($$s->{in}) {
354             # Since we haven't found the image yet append this chunk to
355             # our internal data store, we do this because we have to do a
356             # regex match on m[Content-Type...] which might be split
357             # across multiple chunks
358 23471 50       130340 $$s->{data} .= defined $$chunk ? $$chunk : '';
359              
360 23471 100       210187 if ($$s->{data} =~ m[Content-Type: .*?\r\n\r\n]g) {
361             # We've found the image inside the stream, record this,
362             # delete ->{data} since we don't need it, and see how much
363             # of the image this particular chunk gives us.
364 9001         26663 $$s->{in} = 1;
365 9001         30563 my $size = length substr($$s->{data}, pos($$s->{data}), -1);
366 9001         40275 delete $$s->{data};
367              
368 9001         36866 $$s->{size} = $size;
369              
370 9001 50       32338 if ($$s->{size} >= $img_size) {
371             # The image could be so small that we've already run
372             # through it in chunk it starts in, mark as done and
373             # return the total image size
374              
375 0         0 $$s->{done} = 1;
376 0         0 return $img_size;
377             } else {
378 9001         51984 return $$s->{size};
379             }
380             } else {
381             # Are we inside the image yet? No!
382 14470         79515 return 0;
383             }
384             } else {
385 39738         65321 my $size = length $$chunk;
386              
387 39738 100       134641 if (($$s->{size} + $size) >= $img_size) {
388             # This chunk finishes the image
389              
390 9001         20511 $$s->{done} = 1;
391              
392             # Return what we had left
393 9001         46381 return $img_size - $$s->{size};
394             } else {
395             # This chunk isn't the last one
396              
397 30737         76784 $$s->{size} += $size;
398              
399 30737         161548 return $size;
400             }
401             }
402             }
403              
404             1;
405             __END__