use strict; # $Id: API.pm,v 1.7 2005/12/17 17:42:36 asc Exp $ # -*-perl-*- package Net::Flickr::API; $Net::Flickr::API::VERSION = '1.2'; =head1 NAME Net::Flickr::API - base API class for Net::Flickr::* libraries =head1 SYNOPSIS package Net::Flickr::RDF; use base qw (Net::Flickr::API); =head1 DESCRIPTION Base API class for Net::Flickr::* libraries =head1 OPTIONS Options are passed to Net::Flickr::Backup using a Config::Simple object or a valid Config::Simple config file. Options are grouped by "block". =head2 flick =over 4 =item * B String. I A valid Flickr API key. =item * B String. I A valid Flickr Auth API secret key. =item * B String. I A valid Flickr Auth API token. =back =cut use Config::Simple; use Flickr::API; use Readonly; use Log::Dispatch; use Log::Dispatch::Screen; Readonly::Scalar my $PAUSE_SECONDS_OK => 2; Readonly::Scalar my $PAUSE_SECONDS_UNAVAILABLE => 4; Readonly::Scalar my $PAUSE_MAXTRIES => 10; Readonly::Scalar my $PAUSE_ONSTATUS => 503; =head1 PACKAGE METHODS =cut =head2 __PACKAGE__->new($cfg) Where B<$cfg> is either a valid I object or the path to a file that can be parsed by I. Returns a I object. =cut sub new { my $pkg = shift; my $cfg = shift; my $self = {'__wait' => time() + $PAUSE_SECONDS_OK, '__paused' => 0}; bless $self,$pkg; if (! $self->init($cfg)) { unself $self; } return $self; } sub init { my $self = shift; my $cfg = shift; $self->{cfg} = (UNIVERSAL::isa($cfg,"Config::Simple")) ? $cfg : Config::Simple->new($cfg); # my $log_fmt = sub { my %args = @_; my $msg = $args{'message'}; chomp $msg; if ($args{'level'} eq "error") { my ($ln,$sub) = (caller(4))[2,3]; $sub =~ s/.*:://; return sprintf("[%s][%s, ln%d] %s\n", $args{'level'},$sub,$ln,$msg); } return sprintf("[%s] %s\n",$args{'level'},$msg); }; my $logger = Log::Dispatch->new(callbacks=>$log_fmt); my $error = Log::Dispatch::Screen->new(name => '__error', min_level => 'error', stderr => 1); $logger->add($error); $self->{'__logger'} = $logger; # $self->{api} = Flickr::API->new({key => $self->{cfg}->param("flickr.api_key"), secret => $self->{cfg}->param("flickr.api_secret")}); my $pkg = ref($self); my $version = undef; do { my $ref = join("::",$pkg,"VERSION"); no strict "refs"; $version = ${$ref}; }; my $agent_string = sprintf("%s/%s",$pkg,$version); $self->{api}->agent($agent_string); return 1; } =head1 OBJECT METHODS =cut =head2 $obj->api_call(\%args) Valid args are : =over 4 =item * B A string containing the name of the Flickr API method you are calling. =item * B A hash ref containing the key value pairs you are passing to I =back If the method encounters any errors calling the API, receives an API error or can not parse the response it will log an error event, via the B method, and return undef. Otherwise it will return a I object (if XML::LibXML is installed) or a I object. =cut sub api_call { my $self = shift; my $args = shift; # # check to see if we need to take # breather (are we pounding or are # we not?) while (time < $self->{'__wait'}) { my $debug_msg = sprintf("trying not to beat up the Flickr servers, pause for %.2f seconds\n", $PAUSE_SECONDS_OK); $self->log()->debug($debug_msg); sleep($PAUSE_SECONDS_OK); } # send request delete $args->{args}->{api_sig}; $args->{args}->{auth_token} = $self->{cfg}->param("flickr.auth_token"); my $req = Flickr::API::Request->new($args); $self->log()->debug("calling $args->{method}"); my $res = $self->{api}->execute_request($req); # check for 503 status if ($res->code() eq $PAUSE_ONSTATUS) { # you are in a dark and twisty corridor # where all the errors look the same - # just give up if we hit this ceiling $self->{'__paused'} ++; if ($self->{'__paused'} > $PAUSE_MAXTRIES) { my $errmsg = sprintf("service returned '%d' status %d times; exiting", $PAUSE_ONSTATUS,$PAUSE_MAXTRIES); $self->log()->error($errmsg); return undef; } my $retry_after = $res->header("Retry-After"); my $debug_msg = undef; if ($retry_after ) { $debug_msg = sprintf("service unavailable, requested to retry in %d seconds", $retry_after); } else { $retry_after = $PAUSE_SECONDS_UNAVAILABLE * $self->{'__paused'}; $debug_msg = sprintf("service unavailable, pause for %.2f seconds", $retry_after); } $self->log()->debug($debug_msg); sleep($retry_after); # try, try again return $self->_apicall($args); } $self->{'__wait'} = time + $PAUSE_SECONDS_OK; $self->{'__paused'} = 0; # my $xml = undef; eval "require XML::LibXML"; if ($@) { eval { eval "require XML::XPath"; $xml = XML::XPath->new(xml=>$res->content()); }; } else { eval { my $parser = XML::LibXML->new(); $xml = $parser->parse_string($res->content()); }; } # if (! $xml) { $self->log()->error("failed to parse API response, calling $args->{method} : $@"); $self->log()->error($res->content()); return undef; } # if ($xml->findvalue("/rsp/\@stat") eq "fail") { $self->log()->error(sprintf("[%s] %s (calling calling $args->{method})\n", $xml->findvalue("/rsp/err/\@code"), $xml->findvalue("/rsp/err/\@msg"))); return undef; } return ($@) ? undef : $xml; } =head2 $obj->log() Returns a I object. =cut sub log { my $self = shift; return $self->{'__logger'}; } =head1 VERSION 1.2 =head1 DATE $Date: 2005/12/17 17:42:36 $ =head1 AUTHOR Aaron Straup Cope Eascope@cpan.orgE =head1 SEE ALSO L L L L =head1 BUGS Please report all bugs via http://rt.cpan.org/ =head1 LICENSE Copyright (c) 2005 Aaron Straup Cope. All Rights Reserved. This is free software. You may redistribute it and/or modify it under the same terms as Perl itself. =cut return 1; __END__