File Coverage

blib/lib/WebService/Flixster.pm
Criterion Covered Total %
statement 28 30 93.3
branch n/a
condition n/a
subroutine 10 10 100.0
pod n/a
total 38 40 95.0


line stmt bran cond sub pod time code
1             # $Id: Flixster.pm 7373 2012-04-09 18:00:33Z chris $
2              
3             =head1 NAME
4              
5             WebService::Flixster - OO Perl interface to flixster.com
6              
7              
8             =head1 SYNOPSIS
9              
10              
11             use WebService::Flixster;
12              
13             my $ws = WebService::Flixster->new(cache => 1, cache_exp => "12h");
14              
15             my $movie = $ws->search(type => "Movie", tconst => "tt0033467");
16              
17             print $movie->title(), ": \n\n";
18             print $movie->synopsis(), "\n\n";
19              
20             =cut
21              
22             package WebService::Flixster;
23              
24 2     2   99919 use strict;
  2         5  
  2         77  
25 2     2   17 use warnings;
  2         3  
  2         176  
26              
27             our $VERSION = '0.02';
28              
29 2     2   10 use base qw(Class::Accessor);
  2         8  
  2         2628  
30              
31 2     2   7938 use Cache::FileCache;
  2         145748  
  2         93  
32              
33 2     2   1343 use Carp;
  2         5  
  2         152  
34              
35 2     2   10 use File::Spec::Functions qw(tmpdir);
  2         6  
  2         100  
36              
37 2     2   2457 use JSON;
  2         43255  
  2         11  
38              
39 2     2   2372 use LWP::ConnCache;
  2         2804  
  2         66  
40 2     2   2317 use LWP::UserAgent;
  2         110850  
  2         73  
41              
42 2     2   1409 use WebService::Flixster::Actor;
  0            
  0            
43             use WebService::Flixster::Movie;
44              
45             use URI;
46              
47             __PACKAGE__->mk_accessors(qw(
48             _cache
49             _cache_exp
50             _cache_root
51             _cache_obj
52             _domain
53             _useragent
54             ));
55              
56              
57             =head1 METHODS
58              
59             =head2 new(%opts)
60              
61             Constructor.
62              
63             %opts can contain:
64              
65             =over 4
66              
67             =item cache - Whether to cache responses. Defaults to true
68              
69             =item cache_root - The root dir for the cache. Defaults to tmpdir();
70              
71             =item cache_exp - How long to cache responses for. Defaults to "1h"
72              
73             =item domain - Domain from which to request data. Defaults to "api.flixster.com"
74              
75             =back
76              
77             =cut
78              
79             sub new {
80             my $class = shift;
81             my %args = @_;
82             my $self = {};
83              
84             bless $self, $class;
85              
86             $self->_cache_root($args{'cache_root'} || tmpdir());
87             $self->_cache_exp($args{'cache_exp'} || "1h");
88             $self->_cache(defined $args{'cache'} ? $args{'cache'} : 1);
89              
90             $self->_domain($args{'domain'} || "api.flixster.com");
91              
92             if ($self->_cache()) {
93             $self->_cache_obj( Cache::FileCache->new( {'cache_root' => $self->_cache_root(), 'namespace' => "WebService-Flixster", 'default_expires_in' => $self->_cache_exp()} ) );
94             }
95              
96             $self->_useragent(LWP::UserAgent->new());
97             $self->_useragent()->env_proxy();
98             $self->_useragent()->agent("WebService::Flixster/$VERSION");
99             $self->_useragent()->conn_cache(LWP::ConnCache->new());
100             $self->_useragent()->conn_cache()->total_capacity(3);
101              
102             return $self;
103             }
104              
105              
106             =head2 search(%args)
107              
108             %args can contain:
109              
110             =over 4
111              
112             =item type - Resource type: "Movie", "Actor"
113              
114             =item id - Flixster id e.g. "10074" (Movie, Actor)
115              
116             =item imdbid - IMDB tconst/nconst e.g. "tt0000001", "nm0000002" (Movie, Actor)
117              
118             =back
119              
120             =cut
121              
122             sub search {
123             my $self = shift;
124             my $q = { @_ };
125              
126             if (!exists $q->{'type'}) {
127             croak "TODO: Return generic resultset";
128             } elsif ($q->{'type'} eq "Actor") {
129             delete $q->{'type'};
130             return WebService::Flixster::Actor->_new($self, $q);
131             } elsif ($q->{'type'} eq "Movie") {
132             delete $q->{'type'};
133             return WebService::Flixster::Movie->_new($self, $q);
134             } else {
135             croak "Unknown resource type '" . $q->{'type'} . "'";
136             }
137              
138             }
139              
140              
141             sub _request_cache_key {
142             my $request = shift;
143             my $type = shift;
144              
145             my $version = "0"; # Use a version number as the first part of the key to avoid collisions should we change the structure of the key later.
146              
147             # Using | as field separator as this shouldn't ever appear in a URL (or any of the other fields).
148             my $cache_key = $version . "|" . $type . "|" . $request->method() . "|" . $request->uri();
149             if ($request->method() eq "POST") {
150             $cache_key .= "|" . $request->content();
151             }
152              
153             return $cache_key;
154             }
155              
156             sub _response {
157             my $self = shift;
158             my $request = shift;
159             my $cacheCodes = shift || {'404' => 1}; # Only cache 404 responses by default
160              
161             my $cache_key = _request_cache_key($request, "RESPONSE");
162              
163             my $response;
164              
165             if ($self->_cache()) {
166             $response = $self->_cache_obj()->get($cache_key);
167             }
168              
169             if (!defined $response) {
170             $response = $self->_useragent()->request($request);
171              
172             if ($self->_cache() && exists $cacheCodes->{$response->code()}) {
173             $self->_cache_obj()->set($cache_key, $response);
174             }
175              
176             }
177              
178             return $response;
179              
180             }
181              
182             sub _response_decoded_content {
183             my $self = shift;
184             my $request = shift;
185              
186             my $saveToCache = shift;
187             if (!defined $saveToCache) { $saveToCache = 1; }
188              
189             my $cache_key = _request_cache_key($request, "DECODED_CONTENT");
190              
191             my $content;
192              
193             if ($self->_cache()) {
194             $content = $self->_cache_obj()->get($cache_key);
195             }
196              
197             if (!defined $content) {
198              
199             my $response = $self->_response($request);
200              
201             if($response->code() ne "200") {
202             croak "URL (", $request->uri(), ") Request Failed - Code: ", $response->code(), " Error: ", $response->message(), "\n";
203             }
204              
205             $content = $response->decoded_content();
206              
207             if ($self->_cache() && $saveToCache) {
208             $self->_cache_obj()->set($cache_key, $content);
209             }
210             }
211              
212             return $content;
213              
214             }
215              
216             sub _response_decoded_json {
217             my $self = shift;
218             my $request = shift;
219              
220             my $content = $self->_response_decoded_content($request);
221              
222             my $json = JSON->new();
223             $json->utf8(0);
224              
225             my $resp = $json->decode($content);
226              
227             if (ref $resp eq "HASH" && exists $resp->{'error'}) { # Some pages (e.g. photos) return an array as the top level object.
228             croak $resp->{'error'}->{'status'} . " " . $resp->{'error'}->{'code'} . ": " . $resp->{'error'}->{'message'};
229             } else {
230             return $resp;
231             }
232              
233             }
234              
235             1;