File Coverage

blib/lib/Net/Flickr/Simile/Exhibit.pm
Criterion Covered Total %
statement 6 6 100.0
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 8 8 100.0


line stmt bran cond sub pod time code
1             # $Id: Exhibit.pm,v 1.7 2007/09/03 05:30:37 asc Exp $
2              
3 1     1   2299 use strict;
  1         2  
  1         48  
4              
5             package Net::Flickr::Simile::Exhibit;
6 1     1   5 use base qw (Net::Flickr::Simile);
  1         2  
  1         151  
7              
8             $Net::Flickr::Simile::Exhibit::VERSION = '0.1';
9              
10             =head1 NAME
11              
12             Net::Flickr::Simile::Exhbit - OOP for generating Simile Exhibit files using the Flickr API
13              
14             =head1 SYNOPSIS
15              
16             use Getopt::Std;
17             use Config::Simple;
18             use Net::Flickr::Simile::Exhibit;
19              
20             my %opts = ();
21             getopts('c:j:h:t:', \%opts);
22              
23             my $cfg = Config::Simple->new($opts{'c'});
24              
25             my %args = ('exhibit_json' => $opts{'j'},
26             'exhibit_html' => $opts{'h'},
27             'tags' => $opts{'t'});
28              
29             my $fl = Net::Flickr::Simile::Exhibit->new($cfg);
30             $fl->search(\%args);
31            
32             # So then you might do :
33             # perl ./myscript -c /my/flickr.cfg -h ./mystuff.html -j ./mystuff.js -t kittens
34              
35             =head1 DESCRIPTION
36              
37             OOP for generating Simile Exhibit files using the Flickr API.
38              
39             =head1 OPTIONS
40              
41             Options are passed to Net::Flickr::Backup using a Config::Simple object or
42             a valid Config::Simple config file. Options are grouped by "block".
43              
44             =head2 flick
45              
46             =over 4
47              
48             =item * B
49              
50             String. I
51              
52             A valid Flickr API key.
53              
54             =item * B
55              
56             String. I
57              
58             A valid Flickr Auth API secret key.
59              
60             =item * B
61              
62             String. I
63              
64             A valid Flickr Auth API token.
65              
66             =item * B
67              
68             String. I
69              
70             The B defines which XML/XPath handler to use to process API responses.
71              
72             =over 4
73              
74             =item * B
75              
76             Use XML::LibXML.
77              
78             =item * B
79              
80             Use XML::XPath.
81              
82             =back
83              
84             =back
85              
86             =head2 reporting
87              
88             =over
89              
90             =item * B
91              
92             Boolean.
93              
94             Default is false.
95              
96             =item * B
97              
98             String.
99              
100             The default handler is B, as in C
101              
102             =item * B
103              
104             For example, the following :
105              
106             reporting_handler_args=name:foobar;min_level=info
107              
108             Would be converted as :
109              
110             (name => "foobar",
111             min_level => "info");
112              
113             The default B argument is "__report". The default B argument
114             is "info".
115              
116             =back
117              
118             =cut
119              
120             use JSON::Any;
121             use IO::AtomicFile;
122             use File::Basename;
123              
124             =head1 PACKAGE METHODS
125              
126             =cut
127              
128             =head2 __PACKAGE__->new($cfg)
129              
130             Where B<$cfg> is either a valid I object or the path
131             to a file that can be parsed by I.
132              
133             Returns a I object.
134              
135             =cut
136              
137             =head1 OBJECT METHODS YOU SHOULD CARE ABOUT
138              
139             Net::Flickr::Simile::Exhibit subclasses Net::Flickr::Simile and Net::Flickr::API so
140             all of those methods are available to your object. The following methods are also
141             defined.
142              
143             =cut
144              
145             =head2 $obj->getRecentPhotos(\%args)
146              
147             Valid arguments are anything you would (need to) pass to the I
148             API method and :
149              
150             =over 4
151              
152             =item * B
153              
154             String. I
155              
156             The path where Exhbit JSON data should be written to disk.
157              
158             =item * B
159              
160             String. I
161              
162             The path where Exhbit HTML data should be written to disk. It will contain
163             a relative pointer to I.
164              
165             =back
166              
167             The user_id bound to the Flickr Auth token defined in the object's config file
168             will automatically be added to the method arguments and used to scope the query.
169              
170             Returns true or false.
171              
172             =cut
173              
174             sub getRecentPhotos {
175             my $self = shift;
176             my $args = shift;
177             $args ||= {};
178              
179             my $nsid = $self->get_auth_nsid()
180             || return 0;
181              
182             $args->{'user_id'} = $nsid;
183              
184             use Data::Dumper;
185             print Dumper($args);
186              
187             return $self->search($args);
188             }
189              
190             =head2 $obj->search(\%args)
191              
192             Valid arguments are anything you would (need to) pass to the I
193             API method and :
194              
195             =over 4
196              
197             =item * B
198              
199             String. I
200              
201             The path where Exhbit JSON data should be written to disk.
202              
203             =item * B
204              
205             String. I
206              
207             The path where Exhbit HTML data should be written to disk. It will contain
208             a relative pointer to I.
209              
210             =back
211              
212             Returns true or false.
213              
214             =cut
215              
216             sub search {
217             my $self = shift;
218             my $args = shift;
219             $args ||= {};
220              
221             my $paths = $self->_output_paths($args)
222             || return 0;
223              
224             my $rsp = $self->api_call({'method' => 'flickr.photos.search',
225             'args' => $args});
226              
227             return $self->writeExhibitFiles($paths, $rsp);
228             }
229              
230             =head2 $obj->getContactsPhotos(\%args)
231              
232             Valid arguments are anything you would (need to) pass to the I
233             API method and :
234              
235             =over 4
236              
237             =item * B
238              
239             String. I
240              
241             The path where Exhbit JSON data should be written to disk.
242              
243             =item * B
244              
245             String. I
246              
247             The path where Exhbit HTML data should be written to disk. It will contain
248             a relative pointer to I.
249              
250             =back
251              
252             Returns true or false.
253              
254             =cut
255              
256             sub getContactsPhotos {
257             my $self = shift;
258             my $args = shift;
259             $args ||= {};
260              
261             my $paths = $self->_output_paths($args)
262             || return 0;
263              
264             my $rsp = $self->api_call({'method' => 'flickr.photos.getContactsPhotos',
265             'args' => $args});
266              
267             return $self->writeExhibitFiles($paths, $rsp);
268             }
269              
270             =head2 $obj->getContactsPublicPhotos(\%args)
271              
272             Valid arguments are anything you would (need to) pass to the I
273             API method and :
274              
275             =over 4
276              
277             =item * B
278              
279             String. I
280              
281             The path where Exhbit JSON data should be written to disk.
282              
283             =item * B
284              
285             String. I
286              
287             The path where Exhbit HTML data should be written to disk. It will contain
288             a relative pointer to I.
289              
290             =back
291              
292             Returns true or false.
293              
294             =cut
295              
296             sub getContactsPublicPhotos {
297             my $self = shift;
298             my $args = shift;
299             $args ||= {};
300              
301             my $paths = $self->_output_paths($args)
302             || return 0;
303              
304             my $rsp = $self->api_call({'method' => 'flickr.photos.getContactsPublicPhotos',
305             'args' => $args});
306              
307             return $self->writeExhibitFiles($paths, $rsp);
308             }
309              
310             =head1 OBJECT METHODS YOU MAY CARE ABOUT
311              
312             =cut
313              
314             =head2 $obj->rspToExhibitJson($rsp)
315              
316             Where I<$rsp> is the return value of a call to Iapi_call>.
317              
318             Returns a JSON string representing the data in I<$rsp> suitable for including with
319             a Simile Exhibit document.
320              
321             =cut
322              
323             sub rspToExhibitJson {
324             my $self = shift;
325             my $rsp = shift;
326              
327             my %data = (
328             'items' => [],
329             );
330              
331             foreach my $ph ($rsp->findnodes("/rsp/photos/photo")){
332              
333              
334             my $res = $self->api_call({'method' => 'flickr.photos.getInfo',
335             'args' => {'photo_id' => $ph->getAttribute('id')}});
336              
337             if (! $res) {
338             next;
339             }
340              
341             my $thumb = sprintf("http://farm%s.static.flickr.com/%s/%s_%s_t.jpg",
342             $ph->getAttribute("farm"), $ph->getAttribute("server"),
343             $ph->getAttribute("id"), $ph->getAttribute("secret"));
344              
345             my $taken = $res->findvalue("/rsp/photo/dates/\@taken");
346             $taken =~ s/\:\s{2}\:\d{2}//;
347              
348             my %info = (
349             'imageURL' => $thumb,
350             'label' => $res->findvalue("/rsp/photo/title"),
351             'description' => $res->findvalue("/rsp/photo/description"),
352             'date' => $taken,
353             'owner' => $res->findvalue("/rsp/photo/owner/\@username"),
354             'photoURL' => $res->findvalue("/rsp/photo/urls/url[\@type='photopage']"),
355             );
356              
357             foreach my $t ($res->findnodes("/rsp/photo/tags/tag")){
358             $info{'tags'} ||= [];
359              
360             my $raw = $t->getAttribute("raw");
361              
362             if ($t->getAttribute("machine_tag")){
363              
364             $info{'namespaces'} ||= [];
365             $info{'predicates'} ||= [];
366             $info{'values'} ||= [];
367              
368             if ($raw =~ /^([a-z](?:[a-z0-9_]+))\:([a-z](?:[a-z0-9_]+))=(.*)/){
369            
370             my $ns = $1;
371             my $pred = $2;
372             my $value = $3;
373              
374             push @{$info{'namespaces'}}, $ns;
375             push @{$info{'predicates'}}, $pred;
376             push @{$info{'values'}}, $value;
377             push @{$info{'tags'}}, $value;
378             }
379             }
380            
381             else {
382             push @{$info{'tags'}}, $raw;
383             }
384             }
385              
386             if (my $geo = ($res->findnodes("/rsp/photo/location"))[0]){
387              
388             foreach my $pl ("locality", "region", "country"){
389             $info{'places'} ||= [];
390              
391             if (my $label = $geo->findvalue($pl)){
392             push @{$info{'places'}}, $label;
393             }
394             }
395              
396             my %coords = ('id' => $info{'label'});
397             $coords{'addressLatLng'} = join(",", $geo->getAttribute("latitude"), $geo->getAttribute("longitude"));
398             push @{$data{'items'}}, \%coords;
399             }
400              
401             # use Data::Dumper;
402             # print STDERR Dumper(\%info);
403              
404             push @{$data{'items'}}, \%info;
405             }
406              
407             my $json = JSON::Any->new();
408             return $json->objToJson(\%data);
409             }
410              
411             =head2 $obj->writeExhbitFiles(\%paths, $rsp)
412              
413             Returns true or false.
414              
415             =cut
416              
417             sub writeExhibitFiles {
418             my $self = shift;
419             my $paths = shift;
420             my $rsp = shift;
421              
422             if (! $rsp){
423             $self->log()->error("Not a valid response; can not write Exhibit files");
424             return 0;
425             }
426              
427             my $src_json = basename($paths->{'exhibit_json'});
428              
429             if (! $self->writeExhibitJson($paths->{'exhibit_json'}, $rsp)){
430             return 0;
431             }
432              
433             if (! $self->writeExhibitHtml($paths->{'exhibit_html'}, $src_json)){
434             return 0;
435             }
436              
437             return 1;
438             }
439              
440             =head2 $obj->writeExhibitJson($path, $rsp)
441              
442             Returns true or false
443              
444             =cut
445              
446             sub writeExhibitJson {
447             my $self = shift;
448             my $path = shift;
449             my $rsp = shift;
450              
451             my $fh_json = IO::AtomicFile->open($path, "w");
452              
453             if (! $fh_json){
454             $self->log()->error("Failed to open '$path' for writing, $!");
455             return 0;
456             }
457              
458             $fh_json->print($self->rspToExhibitJson($rsp));
459             $fh_json->close();
460              
461             return 1;
462             }
463              
464             =head2 $obj->writeExhibitHtml($path, $rsp)
465              
466             Returns true or false
467              
468             =cut
469              
470             sub writeExhibitHtml {
471             my $self = shift;
472             my $path = shift;
473             my $src = shift;
474              
475             my $html = $self->readExhibitHtml();
476             $html =~ s/USER_JSON_DATA/$src/m;
477              
478             my $fh_html = IO::AtomicFile->open($path, "w");
479              
480             if (! $fh_html){
481             $self->log()->error("Failed to open '$path' for writing, $!");
482             return 0;
483             }
484              
485             $fh_html->print($html);
486             $fh_html->close();
487             return 1;
488             }
489              
490             sub readExhibitHtml {
491             my $self = shift;
492              
493             my $html = undef;
494              
495             {
496             local $/;
497             undef $/;
498             $html = ;
499             }
500              
501             return $html;
502             }
503              
504             sub _output_paths {
505             my $self = shift;
506             my $args = shift;
507              
508             my %paths = ();
509              
510             foreach my $which ("exhibit_json", "exhibit_html"){
511            
512             if (! exists($args->{$which})){
513             return undef;
514             }
515              
516             $paths{$which} = $args->{$which};
517             delete($args->{$which});
518             }
519              
520             return \%paths;
521             }
522              
523              
524             =head1 VERSION
525              
526             0.1
527              
528             =head1 AUTHOR
529              
530             Aaron Straup Cope <ascope@cpan.org>
531              
532             =head1 NOTES
533              
534             =over 4
535              
536             =item * B
537              
538             Basically anything that returns a "standard photo response" (or /rsp/photos/photo)
539             from the Flickr API can be used with this package to generate Exhibit data.
540              
541             As this time, however, only a handful of (API) methods have (Perl) helper methods.
542             There will be others.
543              
544             =item * B
545              
546             This package does not know how to account for pagination in the Flickr API.
547             That may change over time.
548              
549             =item * B
550              
551             This package contains a bare-bones template for generating an HTML file to
552             view your Exhibit data.
553              
554             You may need to tweak the output after the fact or you can subclass the
555             I method.
556              
557             =back
558              
559             =head1 EXAMPLES
560              
561             L
562              
563             L
564              
565             =head1 SEE ALSO
566              
567             L
568              
569             L
570              
571             =head1 BUGS
572              
573             Please report all bugs via http://rt.cpan.org/
574              
575             =head1 LICENSE
576              
577             Copyright (c) 2007 Aaron Straup Cope. All Rights Reserved.
578              
579             This is free software. You may redistribute it and/or
580             modify it under the same terms as Perl itself.
581              
582             =cut
583              
584             return 1;
585              
586             __DATA__