File Coverage

blib/lib/WebService/IMDB.pm
Criterion Covered Total %
statement 114 121 94.2
branch 20 34 58.8
condition 10 18 55.5
subroutine 21 21 100.0
pod 2 3 66.6
total 167 197 84.7


line stmt bran cond sub pod time code
1             # $Id: IMDB.pm 7370 2012-04-09 01:17:33Z chris $
2              
3             =head1 NAME
4              
5             WebService::IMDB - OO Perl interface to the Internet Movie Database imdb.com
6              
7              
8             =head1 SYNOPSIS
9              
10              
11             use WebService::IMDB;
12              
13             my $ws = WebService::IMDB->new(cache => 1, cache_exp => "12h");
14              
15             my $movie = $ws->search(type => "Title", tconst => "tt0114814");
16              
17             print $movie->title(), ": \n\n";
18             print $movie->synopsis(), "\n\n";
19              
20             foreach ( @{$movie->cast_summary()} ) {
21             print $_->name()->name(), " : ", $_->char(), "\n";
22             }
23              
24              
25             =head1 LEGAL
26              
27             The data accessed via this API is provided by IMDB, and is currently supplied
28             with the following copyright notice.
29              
30             =over 4
31              
32             For use only by clients authorized in writing by IMDb. Authors and users of unauthorized clients accept full legal exposure/liability for their actions.
33              
34             =back
35              
36             Anyone using WebService::IMDB must abide by the above requirements.
37              
38             =cut
39              
40             package WebService::IMDB;
41              
42 2     2   116498 use strict;
  2         4  
  2         71  
43 2     2   8 use warnings;
  2         3  
  2         83  
44              
45             our $VERSION = '0.05';
46              
47 2     2   11 use base qw(Class::Accessor);
  2         8  
  2         2423  
48              
49 2     2   7825 use Cache::FileCache;
  2         144840  
  2         111  
50              
51 2     2   18 use Carp;
  2         3  
  2         392  
52              
53 2     2   12 use File::Spec::Functions qw(tmpdir);
  2         3  
  2         124  
54              
55 2     2   2065 use HTTP::Request::Common;
  2         70184  
  2         347  
56              
57 2     2   4371 use JSON;
  2         65996  
  2         15  
58              
59 2     2   3322 use LWP::ConnCache;
  2         2488  
  2         63  
60 2     2   6482 use LWP::UserAgent;
  2         67529  
  2         76  
61              
62 2     2   1557 use WebService::IMDB::Title;
  2         12  
  2         14  
63 2     2   86 use WebService::IMDB::Name;
  2         6  
  2         16  
64              
65 2     2   56 use URI;
  2         4  
  2         2492  
66              
67             __PACKAGE__->mk_accessors(qw(
68             _cache
69             _cache_exp
70             _cache_root
71             _cache_obj
72             _domain
73             _useragent
74             ));
75              
76              
77             =head1 METHODS
78              
79             =head2 new(%opts)
80              
81             Constructor.
82              
83             %opts can contain:
84              
85             =over 4
86              
87             =item cache - Whether to cache responses. Defaults to true
88              
89             =item cache_root - The root dir for the cache. Defaults to tmpdir();
90              
91             =item cache_exp - How long to cache responses for. Defaults to "1h"
92              
93             =item domain - Domain from which to request data. Defaults to "app.imdb.com"
94              
95             =back
96              
97             =cut
98              
99             sub new {
100 2     2 1 174 my $class = shift;
101 2         9 my %args = @_;
102 2         8 my $self = {};
103              
104 2         7 bless $self, $class;
105              
106 2   66     21 $self->_cache_root($args{'cache_root'} || tmpdir());
107 2   100     174 $self->_cache_exp($args{'cache_exp'} || "1h");
108 2 50       34 $self->_cache(defined $args{'cache'} ? $args{'cache'} : 1);
109              
110 2   50     39 $self->_domain($args{'domain'} || "app.imdb.com");
111              
112 2 50       24 if ($self->_cache()) {
113 2         41 $self->_cache_obj( Cache::FileCache->new( {'cache_root' => $self->_cache_root(), 'namespace' => "WebService-IMDB", 'default_expires_in' => $self->_cache_exp()} ) );
114             }
115              
116 2         714 $self->_useragent(LWP::UserAgent->new());
117 2         7539 $self->_useragent()->env_proxy();
118 2   66     1104840 $self->_useragent()->agent($args{'agent'} || "WebService::IMDB/$VERSION");
119 2         218 $self->_useragent()->conn_cache(LWP::ConnCache->new());
120 2         157 $self->_useragent()->conn_cache()->total_capacity(3);
121              
122 2         66 return $self;
123             }
124              
125              
126             =head2 search(%args)
127              
128             %args can contain:
129              
130             =over 4
131              
132             =item type - Resource type: "Title", "Name
133              
134             =item tconst - IMDB tconst e.g. "tt0000001" (Title)
135              
136             =item nconst - IMDB nconst e.g. "nm0000002" (Name)
137              
138             =item imdbid - More tolerant version of tconst, nconst e.g. "123", "0000456", "tt0000001", "nm0000002" (Title, Name)
139              
140             =back
141              
142             =cut
143              
144             sub search {
145 11     11 1 16354 my $self = shift;
146 11         60 my $q = { @_ };
147              
148 11 50       96 if (!exists $q->{'type'}) {
    100          
    50          
149 0         0 croak "TODO: Return generic resultset";
150             } elsif ($q->{'type'} eq "Title") {
151 6         18 delete $q->{'type'};
152 6         75 return WebService::IMDB::Title->_new($self, $q);
153             } elsif ($q->{'type'} eq "Name") {
154 5         18 delete $q->{'type'};
155 5         64 return WebService::IMDB::Name->_new($self, $q);
156             } else {
157 0         0 croak "Unknown resource type '" . $q->{'type'} . "'";
158             }
159              
160             }
161              
162             sub copyright {
163 1     1 0 412 my $self = shift;
164              
165 1         17 my $request = GET sprintf("http://app.imdb.com/title/tt0033467/maindetails?ts=%d", time()); # Crude, use timestamp in query string to bypass our own caching
166              
167 1         8318 return $self->_response_copyright($request);
168              
169             }
170              
171             sub _request_cache_key {
172 46     46   82 my $request = shift;
173 46         95 my $type = shift;
174              
175 46         91 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.
176              
177             # Using | as field separator as this shouldn't ever appear in a URL (or any of the other fields).
178 46         195 my $cache_key = $version . "|" . $type . "|" . $request->method() . "|" . $request->uri();
179 46 50       1147 if ($request->method() eq "POST") {
180 0         0 $cache_key .= "|" . $request->content();
181             }
182              
183 46         481 return $cache_key;
184             }
185              
186             sub _response {
187 1     1   2 my $self = shift;
188 1         2 my $request = shift;
189 1   50     8 my $cacheCodes = shift || {'404' => 1}; # Only cache 404 responses by default
190              
191 1         3 my $cache_key = _request_cache_key($request, "RESPONSE");
192              
193 1         3 my $response;
194              
195 1 50       4 if ($self->_cache()) {
196 1         11 $response = $self->_cache_obj()->get($cache_key);
197             }
198              
199 1 50       136 if (!defined $response) {
200 1         7 $response = $self->_useragent()->request($request);
201              
202 1 50 33     609386 if ($self->_cache() && exists $cacheCodes->{$response->code()}) {
203 0         0 $self->_cache_obj()->set($cache_key, $response);
204             }
205              
206             }
207              
208 1         39 return $response;
209              
210             }
211              
212             sub _response_decoded_content {
213 45     45   93 my $self = shift;
214 45         75 my $request = shift;
215              
216 45         81 my $saveToCache = shift;
217 45 50       126 if (!defined $saveToCache) { $saveToCache = 1; }
  45         77  
218              
219 45         168 my $cache_key = _request_cache_key($request, "DECODED_CONTENT");
220              
221 45         76 my $content;
222              
223 45 50       183 if ($self->_cache()) {
224 45         574 $content = $self->_cache_obj()->get($cache_key);
225             }
226              
227 45 100       119823 if (!defined $content) {
228              
229 1         5 my $response = $self->_response($request);
230              
231 1 50       5 if($response->code() ne "200") {
232 0         0 croak "URL (", $request->uri(), ") Request Failed - Code: ", $response->code(), " Error: ", $response->message(), "\n";
233             }
234              
235 1         20 $content = $response->decoded_content();
236              
237 1 50 33     190 if ($self->_cache() && $saveToCache) {
238 1         25 $self->_cache_obj()->set($cache_key, $content);
239             }
240             }
241              
242 45         2963 return $content;
243              
244             }
245              
246             sub _response_decoded_json {
247 44     44   6502 my $self = shift;
248 44         78 my $request = shift;
249              
250 44         162 my $content = $self->_response_decoded_content($request);
251              
252 44         502 my $json = JSON->new();
253 44         220 $json->utf8(0);
254              
255 44         13149 my $resp = $json->decode($content);
256             # TODO: Honour $resp->{'exp'}, and check $resp->{'copyright'}
257              
258 44 50       259 if (exists $resp->{'error'}) {
    100          
    50          
259 0         0 croak $resp->{'error'}->{'status'} . " " . $resp->{'error'}->{'code'} . ": " . $resp->{'error'}->{'message'};
260             } elsif (exists $resp->{'data'}) {
261 42         432 return $resp->{'data'};
262             } elsif (exists $resp->{'news'}) {
263 2         32 return $resp->{'news'};
264             } else {
265 0         0 croak "Failed to parse response";
266             }
267              
268             }
269              
270             sub _response_copyright {
271 1     1   2 my $self = shift;
272 1         2 my $request = shift;
273              
274 1         6 my $content = $self->_response_decoded_content($request);
275              
276 1         20 my $json = JSON->new();
277 1         11 $json->utf8(0);
278              
279 1         212 my $resp = $json->decode($content);
280              
281 1         80 return $resp->{'copyright'};
282              
283             }
284              
285             1;