File Coverage

blib/lib/Catmandu/MediaHaven.pm
Criterion Covered Total %
statement 30 161 18.6
branch 0 52 0.0
condition 0 12 0.0
subroutine 10 20 50.0
pod 4 4 100.0
total 44 249 17.6


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->{fragmentId};
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 6     6   48420 use Moo;
  6         7733  
  6         28  
50 6     6   4298 use HTTP::Request::Common;
  6         105764  
  6         413  
51 6     6   1537 use LWP::Simple;
  6         190127  
  6         53  
52 6     6   2100 use URI::Escape;
  6         14  
  6         299  
53 6     6   2241 use JSON;
  6         38839  
  6         34  
54 6     6   680 use LWP;
  6         13  
  6         134  
55 6     6   30 use Carp;
  6         11  
  6         312  
56 6     6   931 use Catmandu;
  6         392950  
  6         49  
57 6     6   3081 use Cache::LRU;
  6         3273  
  6         171  
58 6     6   1553 use REST::Client;
  6         10577  
  6         9338  
59              
60             our $VERSION = '0.05';
61              
62             with 'Catmandu::Logger';
63              
64             has 'url' => (is => 'ro' , required => 1);
65             has 'username' => (is => 'ro' , required => 1);
66             has 'password' => (is => 'ro' , required => 1);
67             has 'record_query' => (is => 'ro' , default => sub { "q=%%2B(MediaObjectFragmentId:%s)"; });
68             has 'sleep' => (is => 'ro' , default => sub { 1 });
69              
70             has 'cache' => (is => 'lazy');
71             has 'cache_size' => (is => 'ro' , default => '1000');
72              
73             sub _build_cache {
74 0     0     my $self = shift;
75              
76 0           return Cache::LRU->new(size => $self->cache_size);
77             }
78              
79             =head2 search($query, start => ... , num => ...)
80              
81             Execute a search query against the MediaHaven server and return the result_list
82             as a HASH
83              
84             =cut
85             sub search {
86 0     0 1   my ($self,$query,%opts) = @_;
87              
88 0           my @param = ();
89              
90 0 0 0       if (defined($query) && length($query)) {
91 0           push @param , sprintf("q=%s",uri_escape($query));
92             }
93              
94 0 0         if ($opts{start}) {
95 0           push @param , sprintf("startIndex=%d",$opts{start});
96             }
97              
98 0 0         if ($opts{num}) {
99 0           push @param , sprintf("nrOfResults=%d",$opts{num});
100             }
101              
102 0 0         if (my $sort = $opts{sort}) {
103 0           my $direction;
104              
105 0 0         if ($sort =~ /^[+]/) {
    0          
106 0           $direction = 'up';
107 0           $sort = substr($sort,1);
108             }
109             elsif ($sort =~ /^[-]/) {
110 0           $direction = 'down';
111 0           $sort = substr($sort,1);
112             }
113             else {
114 0           $direction = 'up';
115             }
116 0           push @param , sprintf("sort=%s",uri_escape($sort));
117 0           push @param , sprintf("direction=%s",uri_escape($direction));
118             }
119              
120 0           $self->log->info("searching with params: " . join("&",@param));
121              
122 0           my $res = $self->_rest_get(@param);
123              
124 0 0         if (! defined $res) {
    0          
125 0           $self->log->error("got a null response");
126 0           return undef;
127             }
128             elsif ($res->{code}) {
129 0           $self->log->error("got an error response: " . $res->{message});
130 0           return undef;
131             }
132              
133 0           $self->log->info("found: " . $res->{totalNrOfResults} . " hits");
134              
135 0           for my $hit (@{$res->{mediaDataList}}) {
  0            
136 0           my $id;
137              
138 0           INNER: for my $prop (@{ $hit->{mdProperties} }) {
  0            
139 0 0         if ($prop->{attribute} eq 'dc_identifier_localid') {
140 0           $id = $prop->{value};
141 0           $id =~ s{^\S+:}{};
142 0           last INNER;
143             }
144             }
145              
146 0 0         $self->cache->set($id => $hit) if defined($id);
147             }
148              
149 0           $res;
150             }
151              
152             =head2 record($id)
153              
154             Retrieve one record from the MediaHaven server based on an identifier. Returns
155             a HASH of results.
156              
157             =cut
158             sub record {
159 0     0 1   my ($self,$id) = @_;
160              
161 0 0         croak "need an id" unless defined($id);
162              
163 0 0         if (my $hit = $self->cache->get($id)) {
164 0           return $hit;
165             }
166              
167 0           my $query = sprintf $self->record_query , $id;
168              
169 0           $self->log->info("retrieve query: $query");
170              
171 0           my $res = $self->_rest_get($query);
172              
173 0 0         if (exists $res->{code}) {
174 0           $self->log->error("retrieve query '$query' failed: " . $res->{message});
175              
176 0           return undef;
177             }
178              
179 0 0         if ($res->{mediaDataList}) {
180 0           return $res->{mediaDataList}->[0];
181             }
182             else {
183 0           return undef;
184             }
185             }
186              
187             =head2 edit($id,$field,@values)
188              
189             Edit the metadata of a record
190              
191             =cut
192             sub edit {
193 0     0 1   my ($self,$id,$field,@values) = @_;
194              
195 0 0 0       croak "need an id and $field" unless defined($id) && defined($field);
196              
197 0           $self->log->info("edit record $id");
198              
199 0           my $record = $self->record($id);
200              
201 0 0         unless ($record) {
202 0           $self->log->error("no such record $id");
203 0           return undef;
204             }
205              
206 0           my $fragmentId = $record->{fragmentId};
207              
208 0           my @param;
209              
210 0           for (@values) {
211 0           push @param , 'value' , $_;
212             }
213              
214 0           my $res = $self->_rest_post("$fragmentId/$field", @param);
215            
216 0           return $res;
217             }
218              
219             =head2 export($id, $callback)
220              
221             Export the binary content of a record from the MediaHaven server. The callback
222             will retrieve a stream of data when the download is available,
223              
224             =cut
225             sub export {
226 0     0 1   my ($self,$id,$callback) = @_;
227              
228 0 0 0       croak "need an id and callback" unless defined($id) && defined($callback);
229              
230 0           $self->log->info("export record $id");
231              
232 0           my $record = $self->record($id);
233              
234 0 0         unless ($record) {
235 0           $self->log->error("no such record $id");
236 0           return undef;
237             }
238              
239 0           my $mediaObjectId = $record->{mediaObjectId};
240              
241 0 0         return undef unless $mediaObjectId;
242              
243 0           my $media_url = sprintf "%s/%s/export" , $self->_rest_base , $mediaObjectId;
244              
245 0           $self->log->info("posting $media_url");
246              
247 0           my ($export_job,$next) = $self->_post_json($media_url);
248              
249 0 0         return undef unless $export_job;
250              
251 0           my $downloadUrl;
252              
253 0           while (1) {
254 0           my $exportId = $export_job->[0]->{exportId};
255 0           my $status = $export_job->[0]->{status};
256              
257 0           $self->log->debug("exportId = $exportId ; status = $status");
258              
259 0 0         last if $status =~ /^(failed|cancelled)$/;
260              
261 0           $downloadUrl = $export_job->[0]->{downloadUrl};
262              
263 0 0         if ($downloadUrl =~ /^htt/) {
264 0           last;
265             }
266              
267 0           $self->log->debug("sleep " . $self->sleep);
268 0           sleep $self->sleep;
269              
270 0           $export_job = $self->_get_json($next);
271             }
272              
273 0           my $rest_url = $self->_rest_base($downloadUrl);
274              
275 0           $self->log->debug("download: $rest_url");
276              
277 0           my $browser = LWP::UserAgent->new();
278              
279 0           my $response = $browser->get($rest_url, ':content_cb' => $callback);
280              
281 0 0         if ($response->is_success) {
282 0           return 1;
283             }
284             else {
285 0           $self->log->error("failed to contact the download url $rest_url");
286 0           return undef;
287             }
288             }
289              
290             sub _get_json {
291 0     0     my ($self,$url) = @_;
292              
293 0           $self->log->debug($url);
294              
295 0           my $client = REST::Client->new();
296 0           $client->GET($url);
297 0           my $json = $client->responseContent();
298              
299 0           decode_json $json;
300             }
301              
302             sub _post_json {
303 0     0     my ($self,$url,$body) = @_;
304              
305 0           $self->log->debug($url);
306              
307 0           my $client = REST::Client->new();
308              
309 0 0         if ($body) {
310 0           my $response = $client->getUseragent->request(POST $url , $body , Content_Type => 'form-data');
311              
312 0 0         if ($response->is_success) {
313 0           return { ok => 1};
314             }
315             else {
316 0           my $json = $response->decoded_content;
317 0           return decode_json $json;
318             }
319             }
320             else {
321 0           $client->POST($url);
322 0           my $json = $client->responseContent();
323              
324 0           my $location = $self->_rest_base( $client->responseHeader('Location') );
325              
326 0           my $perl = decode_json $json;
327              
328 0 0         wantarray ? ($perl,$location) : $perl;
329             }
330             }
331              
332             sub _rest_base {
333 0     0     my ($self,$url) = @_;
334              
335 0           my $authen = sprintf "%s:%s" , uri_escape($self->username) , uri_escape($self->password);
336 0   0       my $media_url = $url // $self->url;
337              
338 0           $media_url =~ s{https://}{};
339 0           $media_url = 'https://' . $authen . '@' . $media_url;
340              
341 0           $media_url;
342             }
343              
344             sub _rest_get {
345 0     0     my ($self,@param) = @_;
346              
347 0           my $media_url = $self->_rest_base . '?';
348              
349 0           $media_url .= join("&",@param);
350              
351 0           $self->_get_json($media_url);
352             }
353              
354             sub _rest_post {
355 0     0     my ($self,$fragment,@param) = @_;
356              
357 0           my $media_url = $self->_rest_base .
358             '/' .
359             $fragment;
360              
361 0           $self->_post_json($media_url,\@param);
362             }
363              
364             =head1 MODULES
365              
366             L<Catmandu::Importer::MediaHaven>
367              
368             L<Catmandu::Store::File::MediaHaven>
369              
370             L<Catmandu::Store::File::MediaHaven::Bag>
371              
372             L<Catmandu::Store::File::MediaHaven::Index>
373              
374             =head1 AUTHOR
375              
376             =over
377              
378             =item * Patrick Hochstenbach, C<< <patrick.hochstenbach at ugent.be> >>
379              
380             =back
381              
382             =head1 LICENSE AND COPYRIGHT
383              
384             This program is free software; you can redistribute it and/or modify it under the terms
385             of either: the GNU General Public License as published by the Free Software Foundation;
386             or the Artistic License.
387              
388             See L<http://dev.perl.org/licenses/> for more information.
389              
390             =cut
391              
392             1;