arrange for Pars if possible even without Stemweb service
[scpubgit/stemmaweb.git] / lib / stemmaweb / Controller / Stemweb.pm
index c7f2072..75ae4d8 100644 (file)
@@ -2,10 +2,13 @@ package stemmaweb::Controller::Stemweb;
 use Moose;
 use namespace::autoclean;
 use Encode qw/ decode_utf8 /;
+use File::Which;
 use JSON;
+use List::Util qw/ max /;
 use LWP::UserAgent;
 use Safe::Isa;
 use Scalar::Util qw/ looks_like_number /;
+use Text::Tradition::StemmaUtil qw/ character_input phylip_pars /;
 use TryCatch;
 use URI;
 
@@ -16,6 +19,16 @@ has stemweb_url => (
        isa => 'Str',
        default => 'http://slinkola.users.cs.helsinki.fi',
        );
+       
+has pars_path => (
+       is => 'ro',
+       isa => 'Str',
+       );
+       
+has pars_pk => (
+       is => 'rw',
+       isa => 'Int',
+       );
 
 =head1 NAME
 
@@ -92,11 +105,28 @@ sub available :Local :Args(0) {
        my( $self, $c ) = @_;
        my $ua = LWP::UserAgent->new();
        my $resp = $ua->get( $self->stemweb_url . '/algorithms/available' );
+       my $parameters = [];
        if( $resp->is_success ) {
-               $c->stash->{'result'} = decode_json( $resp->content );
-       } else {
-               $c->stash->{'result'} = {};
+               $parameters = decode_json( $resp->content );
+       } # otherwise we have no available algorithms.
+       ## Temporary HACK: run Pars too
+       if( $self->_has_pars ) {
+               # Use the highest passed primary key + 1
+               my $parspk = max( map { $_->{pk} } 
+                       grep { $_->{model} eq 'algorithms.algorithm' } @$parameters ) + 1;
+               # Add Pars as an algorithm
+               $self->pars_pk( $parspk );
+               push( @$parameters, {
+                       pk => $parspk,
+                       model => 'algorithms.algorithm',
+                       fields => {
+                               args => [],
+                               name => 'Pars',
+                               desc => 'The program "pars", from the Phylip bio-statistical software package, produces a maximum-parsimony distance tree of the witnesses. More information on maximum parsimony can be found <a href="https://wiki.hiit.fi/display/stemmatology/Maximum+parsimony">here</a>. Please note that Phylip "pars" only supports a maximum of eight variants readings in any one variant location in the text. If your text displays more divergence than this at any point, please consider disregarding orthographic and spelling variation below, or use one of the other algorithms.'
+                       }
+               });
        }
+       $c->stash->{result} = $parameters;
        $c->forward('View::JSON');
 }
 
@@ -184,7 +214,7 @@ sub _process_stemweb_result {
                        }
                        $c->stash->{'result'} = { status => 'notfound' };
                }
-       } elsif( $answer->{status} < 1 ) {
+       } elsif( $answer->{status} == -1 ) {
                $c->stash->{'result'} = { 'status' => 'running' };
        } else {
                return _json_error( $c, 500,
@@ -217,43 +247,72 @@ sub request :Local :Args(0) {
                        'You do not have permission to update stemmata for this tradition' ) )
                unless $ok eq 'full';
        
-       # Form the request for Stemweb.
        my $algorithm = delete $reqparams->{algorithm};
-       my $return_uri = URI->new( $c->uri_for( '/stemweb/result' ) );
-       my $stemweb_request = {
-               return_path => $return_uri->path,
-               return_host => $return_uri->host_port,
-               data => $t->collation->as_tsv({noac => 1}),
-               userid => $c->user->get_object->email,
-               textid => $tid,
-               parameters => _cast_nonstrings( $reqparams ) };
-               
-       # Call to the appropriate URL with the request parameters.
-       my $ua = LWP::UserAgent->new();
-       $c->log->debug( 'Sending request to Stemweb: ' . to_json( $stemweb_request ) ); 
-       my $resp = $ua->post( $self->stemweb_url . "/algorithms/process/$algorithm/",
-               'Content-Type' => 'application/json; charset=utf-8', 
-               'Content' => encode_json( $stemweb_request ) ); 
-       if( $resp->is_success ) {
-               # Process it
-               $c->log->debug( 'Got a response from the server: '
-                       . decode_utf8( $resp->content ) );
-               my $stemweb_response = decode_json( $resp->content );
+       my $mergetypes = delete $reqparams->{merge_reltypes};
+       if( $self->_has_pars && $algorithm == $self->pars_pk ) {
+               my $start_time = scalar( gmtime( time() ) );
+               $t->set_stemweb_jobid( 'local' );
+               my $cdata = character_input( $t, { collapse => $mergetypes } );
+               my $newick;
                try {
-                       $t->set_stemweb_jobid( $stemweb_response->{jobid} );
-               } catch( Text::Tradition::Error $e ) {
-                       return _json_error( $c, 429, $e->message );
+                       $newick = phylip_pars( $cdata, { parspath => $self->_has_pars } );
+               } catch ( Text::Tradition::Error $e ) {
+                       return _json_error( $c, 503, "Parsimony tree generation failed: "
+                               . $e->message );
                }
-               $c->model('Directory')->save( $t );
-               $c->stash->{'result'} = $stemweb_response;
-               $c->forward('View::JSON');
-       } elsif( $resp->code == 500 && $resp->header('Client-Warning')
-               && $resp->header('Client-Warning') eq 'Internal response' ) {
-               # The server was unavailable.
-               return _json_error( $c, 503, "The Stemweb server is currently unreachable." );
+               # We have a result, so form an answer to process.
+               my $answer = {
+                       status => 0,
+                       algorithm => 'pars',
+                       'format' => 'newick',
+                       textid => $tid,
+                       jobid => 'local',
+                       result => $newick,
+                       start_time => $start_time
+               };
+               return _process_stemweb_result( $c, $answer );
        } else {
-               return _json_error( $c, 500, "Stemweb error: " . $resp->code . " / "
-                       . $resp->content );
+               # Form the request for Stemweb.
+               my $return_uri = URI->new( $c->uri_for( '/stemweb/result' ) );
+               my $tsv_options = { noac => 1 };
+               if( $mergetypes && @$mergetypes ) {
+                       $tsv_options->{mergetypes} = $mergetypes;
+               }
+               my $stemweb_request = {
+                       return_path => $return_uri->path,
+                       return_host => $return_uri->host_port,
+                       data => $t->collation->as_tsv( $tsv_options ),
+                       userid => $c->user->get_object->email,
+                       textid => $tid,
+                       parameters => _cast_nonstrings( $reqparams ) };
+               
+               # Call to the appropriate URL with the request parameters.
+               my $ua = LWP::UserAgent->new();
+               $c->log->debug( 'Sending request to Stemweb: ' . to_json( $stemweb_request ) ); 
+               my $resp = $ua->post( $self->stemweb_url . "/algorithms/process/$algorithm/",
+                       'Content-Type' => 'application/json; charset=utf-8', 
+                       'Content' => encode_json( $stemweb_request ) ); 
+               if( $resp->is_success ) {
+                       # Process it
+                       $c->log->debug( 'Got a response from the server: '
+                               . decode_utf8( $resp->content ) );
+                       my $stemweb_response = decode_json( $resp->content );
+                       try {
+                               $t->set_stemweb_jobid( $stemweb_response->{jobid} );
+                       } catch( Text::Tradition::Error $e ) {
+                               return _json_error( $c, 429, $e->message );
+                       }
+                       $c->model('Directory')->save( $t );
+                       $c->stash->{'result'} = $stemweb_response;
+                       $c->forward('View::JSON');
+               } elsif( $resp->code == 500 && $resp->header('Client-Warning')
+                       && $resp->header('Client-Warning') eq 'Internal response' ) {
+                       # The server was unavailable.
+                       return _json_error( $c, 503, "The Stemweb server is currently unreachable." );
+               } else {
+                       return _json_error( $c, 500, "Stemweb error: " . $resp->code . " / "
+                               . $resp->content );
+               }
        }
 }
 
@@ -300,5 +359,10 @@ sub _json_bool {
        return $_[0] ? JSON::true : JSON::false;
 }
 
+sub _has_pars {
+       my $self = shift;
+       return $self->pars_path || which('pars');
+}
+
 
 1;