File Coverage

blib/lib/Catmandu/MediaHaven.pm
Criterion Covered Total %
statement 27 134 20.1
branch 0 42 0.0
condition 0 9 0.0
subroutine 9 17 52.9
pod 3 3 100.0
total 39 205 19.0


line stmt bran cond sub pod time code
1             package Catmandu::MediaHaven;
2              
3             =head1 NAME
4              
5             Catmandu::MediaHaven - Tools to communicate with the Zeticon MediaHaven server
6              
7             =head1 SYNOPSIS
8              
9             use Catmandu::MediaHaven;
10              
11             my $mh = Catmandu::MediaHaven->new(
12             url => '...' ,
13             username => '...' ,
14             password => '...');
15              
16             my $result = $mh->search('nature', start => 0 , num => 100);
17              
18             die "search failed" unless defined($result);
19              
20             for my $res (@{$result->mediaDataList}) {
21             my $id = $res->{externalId};
22             my $date = $res->{data};
23              
24             print "$id $date\n";
25             }
26              
27             my $record = $mh->record('q2136s817');
28             my $date = $record->{date};
29              
30             print "q2136s817 $date\n";
31              
32             $mh->export($id, sub {
33             my $data = shift;
34             print $data;
35             });
36              
37             =head1 DESCRIPTION
38              
39             The L<Catmandu::MediaHaven> module is a low end interface to the MediaHaven
40             REST api. See also: https://archief.viaa.be/mediahaven-rest-api
41              
42             =head1 METHODS
43              
44             =head2 new(url => ... , username => ... , password => ...)
45              
46             Create a new connection to the MediaHaven server.
47              
48             =cut
49 5     5   65976 use Moo;
  5         8387  
  5         30  
50 5     5   4475 use LWP::Simple;
  5         264375  
  5         47  
51 5     5   1909 use URI::Escape;
  5         55  
  5         303  
52 5     5   2030 use JSON;
  5         31410  
  5         38  
53 5     5   589 use LWP;
  5         45  
  5         121  
54 5     5   22 use Carp;
  5         19  
  5         248  
55 5     5   589 use Catmandu;
  5         289315  
  5         36  
56 5     5   2504 use Cache::LRU;
  5         2486  
  5         127  
57 5     5   1629 use REST::Client;
  5         8599  
  5         6028  
58              
59             our $VERSION = '0.04';
60              
61             with 'Catmandu::Logger';
62              
63             has 'url' => (is => 'ro' , required => 1);
64             has 'username' => (is => 'ro' , required => 1);
65             has 'password' => (is => 'ro' , required => 1);
66             has 'record_query' => (is => 'ro' , default => sub { "q=%%2B(MediaObjectExternalId:%s)"; });
67             has 'sleep' => (is => 'ro' , default => sub { 1 });
68              
69             has 'cache' => (is => 'lazy');
70             has 'cache_size' => (is => 'ro' , default => '1000');
71              
72             sub _build_cache {
73 0     0     my $self = shift;
74              
75 0           return Cache::LRU->new(size => $self->cache_size);
76             }
77              
78             =head2 search($query, start => ... , num => ...)
79              
80             Execute a search query against the MediaHaven server and return the result_list
81             as a HASH
82              
83             =cut
84             sub search {
85 0     0 1   my ($self,$query,%opts) = @_;
86              
87 0           my @param = ();
88              
89 0 0 0       if (defined($query) && length($query)) {
90 0           push @param , sprintf("q=%s",uri_escape($query));
91             }
92              
93 0 0         if ($opts{start}) {
94 0           push @param , sprintf("startIndex=%d",$opts{start});
95             }
96              
97 0 0         if ($opts{num}) {
98 0           push @param , sprintf("nrOfResults=%d",$opts{num});
99             }
100              
101 0 0         if (my $sort = $opts{sort}) {
102 0           my $direction;
103              
104 0 0         if ($sort =~ /^[+]/) {
    0          
105 0           $direction = 'up';
106 0           $sort = substr($sort,1);
107             }
108             elsif ($sort =~ /^[-]/) {
109 0           $direction = 'down';
110 0           $sort = substr($sort,1);
111             }
112             else {
113 0           $direction = 'up';
114             }
115 0           push @param , sprintf("sort=%s",uri_escape($sort));
116 0           push @param , sprintf("direction=%s",uri_escape($direction));
117             }
118              
119 0           $self->log->info("searching with params: " . join("&",@param));
120              
121 0           my $res = $self->_rest_get(@param);
122              
123 0 0         if (! defined $res) {
    0          
124 0           $self->log->error("got a null response");
125 0           return undef;
126             }
127             elsif ($res->{code}) {
128 0           $self->log->error("got an error response: " . $res->{message});
129 0           return undef;
130             }
131              
132 0           $self->log->info("found: " . $res->{totalNrOfResults} . " hits");
133              
134 0           for my $hit (@{$res->{mediaDataList}}) {
  0            
135 0           my $id;
136              
137 0           INNER: for my $prop (@{ $hit->{mdProperties} }) {
  0            
138 0 0         if ($prop->{attribute} eq 'dc_identifier_localid') {
139 0           $id = $prop->{value};
140 0           $id =~ s{^\S+:}{};
141 0           last INNER;
142             }
143             }
144              
145 0 0         $self->cache->set($id => $hit) if defined($id);
146             }
147              
148 0           $res;
149             }
150              
151             =head2 record($id)
152              
153             Retrieve one record from the MediaHaven server based on an identifier. Returns
154             a HASH of results.
155              
156             =cut
157             sub record {
158 0     0 1   my ($self,$id) = @_;
159              
160 0 0         croak "need an id" unless defined($id);
161              
162 0 0         if (my $hit = $self->cache->get($id)) {
163 0           return $hit;
164             }
165              
166 0           my $query = sprintf $self->record_query , $id;
167              
168 0           $self->log->info("retrieve query: $query");
169              
170 0           my $res = $self->_rest_get($query);
171              
172 0 0         if (exists $res->{code}) {
173 0           $self->log->error("retrieve query '$query' failed: " . $res->{message});
174              
175 0           return undef;
176             }
177              
178 0 0         if ($res->{mediaDataList}) {
179 0           return $res->{mediaDataList}->[0];
180             }
181             else {
182 0           return undef;
183             }
184             }
185              
186             =head2 export($id, $callback)
187              
188             Export the binary content of a record from the MediaHaven server. The callback
189             will retrieve a stream of data when the download is available,
190              
191             =cut
192             sub export {
193 0     0 1   my ($self,$id,$callback) = @_;
194              
195 0 0 0       croak "need an id and callback" unless defined($id) && defined($callback);
196              
197 0           $self->log->info("export record $id");
198              
199 0           my $record = $self->record($id);
200              
201 0 0         return undef unless $record;
202              
203 0           my $mediaObjectId = $record->{mediaObjectId};
204              
205 0 0         return undef unless $mediaObjectId;
206              
207 0           my $media_url = sprintf "%s/%s/export" , $self->_rest_base , $mediaObjectId;
208              
209 0           $self->log->info("posting $media_url");
210              
211 0           my ($export_job,$next) = $self->_post_json($media_url);
212              
213 0 0         return undef unless $export_job;
214              
215 0           my $downloadUrl;
216              
217 0           while (1) {
218 0           my $exportId = $export_job->[0]->{exportId};
219 0           my $status = $export_job->[0]->{status};
220              
221 0           $self->log->debug("exportId = $exportId ; status = $status");
222              
223 0 0         last if $status =~ /^(failed|cancelled)$/;
224              
225 0           $downloadUrl = $export_job->[0]->{downloadUrl};
226              
227 0 0         if ($downloadUrl =~ /^htt/) {
228 0           last;
229             }
230              
231 0           $self->log->debug("sleep " . $self->sleep);
232 0           sleep $self->sleep;
233              
234 0           $export_job = $self->_get_json($next);
235             }
236              
237 0           my $rest_url = $self->_rest_base($downloadUrl);
238              
239 0           $self->log->debug("download: $rest_url");
240              
241 0           my $browser = LWP::UserAgent->new();
242              
243 0           my $response = $browser->get($rest_url, ':content_cb' => $callback);
244              
245 0 0         if ($response->is_success) {
246 0           return 1;
247             }
248             else {
249 0           $self->log->error("failed to contact the download url $rest_url");
250 0           return undef;
251             }
252             }
253              
254             sub _get_json {
255 0     0     my ($self,$url) = @_;
256              
257 0           $self->log->debug($url);
258              
259 0           my $client = REST::Client->new();
260 0           $client->GET($url);
261 0           my $json = $client->responseContent();
262              
263 0           decode_json $json;
264             }
265              
266             sub _post_json {
267 0     0     my ($self,$url) = @_;
268              
269 0           $self->log->debug($url);
270              
271 0           my $client = REST::Client->new();
272 0           $client->POST($url);
273 0           my $json = $client->responseContent();
274              
275 0           my $location = $self->_rest_base( $client->responseHeader('Location') );
276              
277 0           my $perl = decode_json $json;
278              
279 0           ($perl,$location);
280             }
281              
282             sub _rest_base {
283 0     0     my ($self,$url) = @_;
284              
285 0           my $authen = sprintf "%s:%s" , uri_escape($self->username) , uri_escape($self->password);
286 0   0       my $media_url = $url // $self->url;
287              
288 0           $media_url =~ s{https://}{};
289 0           $media_url = 'https://' . $authen . '@' . $media_url;
290              
291 0           $media_url;
292             }
293              
294             sub _rest_get {
295 0     0     my ($self,@param) = @_;
296              
297 0           my $media_url = $self->_rest_base . '?';
298              
299 0           $media_url .= join("&",@param);
300              
301 0           $self->_get_json($media_url);
302             }
303              
304              
305             =head1 MODULES
306              
307             L<Catmandu::Importer::MediaHaven>
308              
309             L<Catmandu::Store::File::MediaHaven>
310              
311             L<Catmandu::Store::File::MediaHaven::Bag>
312              
313             L<Catmandu::Store::File::MediaHaven::Index>
314              
315             =head1 AUTHOR
316              
317             =over
318              
319             =item * Patrick Hochstenbach, C<< <patrick.hochstenbach at ugent.be> >>
320              
321             =back
322              
323             =head1 LICENSE AND COPYRIGHT
324              
325             This program is free software; you can redistribute it and/or modify it under the terms
326             of either: the GNU General Public License as published by the Free Software Foundation;
327             or the Artistic License.
328              
329             See L<http://dev.perl.org/licenses/> for more information.
330              
331             =cut
332              
333             1;