blob: 397d7c52955da5977d1c3b6ac3e0de1156372d93 [file] [log] [blame]
package Kalamar::Controller::Search2;
use Mojo::Base 'Mojolicious::Controller';
use Data::Dumper;
use Mojo::Collection 'c';
use Mojo::ByteStream 'b';
use POSIX 'ceil';
has no_cache => 0;
has items_per_page => 25;
# TODO:
# Support server timing API
# TODO:
# Add match_info template for HTML
# Query endpoint
sub query {
my $c = shift;
# Validate user input
my $v = $c->validation;
# In case the user is not known, it is assumed, the user is not logged in
my $user = $c->stash('user') // 'not_logged_in';
$v->optional('q', 'trim');
$v->optional('ql')->in(qw/poliqarp cosmas2 annis cql fcsql/);
$v->optional('collection', 'trim'); # Legacy
$v->optional('cq', 'trim');
# $v->optional('action'); # action 'inspect' is no longer valid
# $v->optional('snippet');
$v->optional('cutoff')->in(qw/true false/);
$v->optional('count')->num(1, undef);
$v->optional('p', 'trim')->num(1, undef); # Start page
$v->optional('o', 'trim')->num(1, undef); # Offset
$v->optional('context');
# Get query
my $query = $v->param('q');
# TODO:
# Check for validation errors!
# No query
unless ($query) {
return $c->render($c->loc('Template_intro', 'intro'));
};
my %query = ();
$query{q} = $v->param('q');
$query{ql} = $v->param('ql') // 'poliqarp';
$query{p} = $v->param('p') // 1; # Start page
$query{count} = $v->param('count') // $c->items_per_page;
$query{cq} = $v->param('cq') // $v->param('collection');
$query{cutoff} = $v->param('cutoff');
# Before: 'base/s:p'/'paragraph'
$query{context} = $v->param('context') // '40-t,40-t';
my $items_per_page = $c->items_per_page;
# Set count
if ($query{count} && $query{count} <= $c->items_per_page ) {
$items_per_page = delete $query{count};
};
$c->stash(items_per_page => $items_per_page);
# Set offset
# From Mojolicious::Plugin::Search::Index
$query{o} = $v->param('o') || ((($query{p} // 1) - 1) * ($items_per_page || 1));
# already set by stash - or use plugin param
# else {
# $items_per_page = $c->stash('search.count') // $plugin->items_per_page
# };
# Set start page based on param
#if ($query{p}) {
# $index->start_page(delete $param{start_page});
#}
## already set by stash
#elsif ($c->stash('search.start_page')) {
# $index->start_page($c->stash('search.start_page'));
#};
# Create remote request URL
my $url = Mojo::URL->new($c->korap->api);
$url->path('search');
# $url->query(%query);
$url->query(map { $_ => $query{$_}} sort keys %query);
# Check if total results is cached
my $total_results = -1;
unless ($c->no_cache) {
# Get total results value
$total_results = $c->chi->get('total-' . $user . '-' . $url->to_string);
# Set stash if cache exists
$c->stash(total_results => $total_results) if $total_results;
$c->app->log->debug('Get total result from cache');
# Set cutoff unless already set
$url->query({cutoff => 'true'});
};
# Choose the snippet based on the parameter
# TODO:
# scalar $v->param('snippet') ? 'snippet' : 'search2';
$c->stash(template => 'search2');
# Wait for rendering
$c->render_later;
# Fetch resource
$c->cached_koral_p('get', $url)->then(
# Process response
sub {
my $json = shift;
#######################
# Cache total results #
#######################
# The stash is set in case the total results value is from the cache,
# so in that case, it does not need to be cached again
my $total_results = $c->stash('total_results');
if (!$total_results) {
# There are results to remember
if ($json->{meta}->{totalResults} >= 0) {
# Remove cutoff requirement again
$url->query([cutoff => 'true']);
$total_results = $json->{meta}->{totalResults};
$c->stash(total_results => $total_results);
# Set cache
$c->chi->set(
'total-' . $user . '-' . $url->to_string => $total_results
);
};
}
else {
$c->stash(total_results => -1);
}
$c->stash(total_pages => 0);
# Set total pages
# From Mojolicious::Plugin::Search::Index
if ($total_results > 0) {
$c->stash(
total_pages => ceil($total_results / ($c->stash('items_per_page') || 1))
);
};
# Process match results
$c->_process_query_response($json);
# Render result
return $c->render(
q => $query,
ql => $query{ql},
start_page => $query{p},
);
}
# Deal with errors
)->catch(
sub {
# $c->_notify_on_errors(shift);
return $c->render(
results => c(),
q => $query,
ql => $query{ql},
start_page => 1
);
}
)
# Start IOLoop
->wait;
return 1;
};
# Match info endpoint
sub match_info {
my $c = shift;
# Validate user input
my $v = $c->validation;
$v->optional('foundry');
$v->optional('layer');
$v->optional('spans')->in(qw/true false/);
# Old API foundry/layer usage
my $foundry = '*';
my %query = (foundry => '*');
if ($v->param('foundry')) {
$query{foundry} = $v->param('foundry');
$query{layer} = $v->param('layer') if $v->param('layer');
$query{spans} = $v->param('spans') if $v->param('spans');
};
# Create new request API
my $url = Mojo::URL->new($c->korap->api);
# Use stash information to create url path
$url->path(
join('/', (
'corpus',
$c->stash('corpus_id'),
$c->stash('doc_id'),
$c->stash('text_id'),
$c->stash('match_id'),
'matchInfo'
))
);
# Set query parameters in order
$url->query(map { $_ => $query{$_}} sort keys %query);
$c->render_later;
$c->cached_koral_p('get', $url)->then(
sub {
my $json = shift;
# Process results
$json = _map_match($json);
$c->stash(results => $json);
return $c->render(
json => $c->notifications(json => $json),
status => 200
);
return $json;
}
)
# Deal with errors
->catch(
sub {
return $c->render(
json => $c->notifications('json')
)
}
)
# Start IOLoop
->wait;
return 1;
};
# Get information about
# This replaces the collections endpoint
sub corpus_info {
my $c = shift;
# Input validation
my $v = $c->validation;
$v->optional('cq');
my $url = Mojo::URL->new($c->korap->api);
# Use hash slice to create path
$url->path('statistics');
# Add query
$url->query(corpusQuery => $v->param('cq'));
# Set stash
$c->stash('search._resource_cache' => $url->to_string);
$c->app->log->debug("Statistics info: $url");
# Async
$c->render_later;
# Request koral, maybe cached
$c->cached_koral_p('get', $url)
# Process response
->then(
sub {
my $json = shift;
return $c->render(
json => $c->notifications(json => $json),
status => 200
);
}
)
# Deal with errors
->catch(
sub {
return $c->render(
json => $c->notifications('json')
)
}
)
# Start IOLoop
->wait;
return 1;
};
# Cleanup array of matches
sub _map_matches {
return c() unless $_[0];
c(map { _map_match($_) } @{ shift() });
};
# Cleanup single match
sub _map_match {
my $match = shift or return;
# Legacy match id
if ($match->{matchID}) {
$match->{matchID} =~ s/^match\-(?:[^!]+!|[^_]+_)[^\.]+?\.[^-]+?-// or
$match->{matchID} =~ s!^match\-(?:[^\/]+\/){2}[^-]+?-!!;
};
return unless $match->{textSigle};
# Set IDs based on the sigle
(
$match->{corpusID},
$match->{docID},
$match->{textID}
) = ($match->{textSigle} =~ /^([^_]+?)_+([^\.]+?)\.(.+?)$/);
return $match;
};
# Process response and set stash values
sub _process_query_response {
my ($self, $json) = @_;
# Process meta
my $meta = $json->{meta};
# TODO:
# Set benchmark in case of development mode only.
# Use server timing API
#
# Reformat benchmark counter
# my $benchmark = $meta->{benchmark};
# if ($benchmark && $benchmark =~ s/\s+(m)?s$//) {
# $benchmark = sprintf("%.2f", $benchmark) . ($1 ? $1 : '') . 's';
# };
#
# # Set benchmark
# $self->stash(benchmark => $benchmark);
# Set time exceeded
if ($meta->{timeExceeded} && $meta->{timeExceeded} eq Mojo::JSON::true) {
$self->stash(time_exceeded => 1);
};
# Set result values
$self->stash(items_per_page => $meta->{itemsPerPage});
## Bouncing query
## if ($json->{query}) {
## $index->query_jsonld($json->{query});
## };
## Legacy
## elsif ($json->{request}->{query}) {
## $index->query_jsonld($json->{request}->{query});
## };
if ($meta->{totalResults}) {
$self->stash(total_results => $meta->{totalResults});
};
# Bouncing collection query
if ($json->{corpus} || $json->{collection}) {
$self->stash(corpus_jsonld => ($json->{corpus} || $json->{collection}));
};
# Set results to stash
$self->stash(
results => _map_matches($json->{matches})
);
return;
};
1;
__END__