notice and save changes to graph name / stemma identifier (tla/stemmaweb#28)
[scpubgit/stemmatology.git] / analysis / idp_server / graphcalc.cgi
CommitLineData
0f093b35 1#!/usr/bin/perl -T
2
3use strict;
4use warnings;
0f093b35 5use CGI;
6use Encode qw/ decode /;
7use Gearman::Client;
8use JSON;
9use Text::Tradition::Directory;
10use Text::Tradition::Analysis::Result;
11use TryCatch;
12
58ad1c17 13### Configurable variables
d1dfc40d 14my %VARS = (
15 DBTYPE => 'mysql',
16 DBHOST => '127.0.0.1',
17 DBPORT => '3006',
18 DBNAME => 'idpresult',
19 DSN => undef,
20 DBUSER => undef,
21 DBPASS => undef,
22 GEARMAN_SERVER => '127.0.0.1:4730',
23);
24
25if( -f "/etc/graphcalc.conf" ) {
26 # Read the variables in from here.
27 open( GCCONF, "/etc/graphcalc.conf" )
28 or die "Could not open configuration file /etc/graphcalc.conf";
29 while(<GCCONF>) {
30 chomp;
31 s/^\s+//;
32 my( $name, $val ) = split( /\s*\=\s*/, $_ );
33 if( exists $VARS{$name} ) {
34 $VARS{$name} = $val;
35 }
36 }
37 close GCCONF;
38}
39unless( $VARS{DSN} ) {
40 $VARS{DSN} = sprintf( "dbi:%s:dbname=%s;host=%s;port=%s",
41 $VARS{DBTYPE}, $VARS{DBNAME}, $VARS{DBHOST}, $VARS{DBPORT} );
42}
58ad1c17 43
44### Main program
45
0f093b35 46my %status = (
47 '400' => '400 Bad Request',
48 '500' => '500 Internal Server Error',
49);
50
51my $q = CGI->new(\*STDIN);
52# check that Content-Type is application/json
53my $ctype = $q->content_type;
54my $encoding = 'UTF-8'; # default
55if( $ctype =~ m!^(\w+/[\w+]+);\s*charset=(.*)$! ) {
56 ( $ctype, $encoding ) = ( $1, $2 );
57}
58error( 400, 'Content type must be application/json' )
59 unless $ctype eq 'application/json';
60
61# Get the post data, and decode it according to the given character set
62my $jsonstr = decode( $encoding, $q->param('POSTDATA') );
63$jsonstr =~ s/\&/\n/g;
0f839794 64# Validate the JSON
0f093b35 65my $request;
66try {
67 $request = from_json( $jsonstr );
68} catch( $err ) {
69 error( 400, "JSON parsing error: $err" );
70}
71# Request should be a hash that can be used to instantiate an Analysis::Result,
72# or else an array of such hashes.
73my @problems;
74my $first = ref( $request ) eq 'ARRAY' ? shift @$request : $request;
75try {
76 my $result = Text::Tradition::Analysis::Result->new( $first );
77 push( @problems, $result );
78} catch( $err ) {
79 error( 400, "Argument $first is neither a Result serialization nor an array: $err" );
80}
81# Now parse the rest of the result objects
82unless( $first eq $request ) {
83 foreach my $rj ( @$request ) {
84 try {
85 my $result = Text::Tradition::Analysis::Result->new( $rj );
86 push( @problems, $result );
87 } catch( $err ) {
88 error( 400, "Argument $rj is not a Result serialization: $err" );
89 }
90 }
91}
92
93# For each of the result objects, see if its key exists in the DB. Kick off the
94# calculation of any that need to be calculated, but don't wait more than two
95# seconds for a result. Return the DB version of each of the objects.
58ad1c17 96my $dbargs = {};
d1dfc40d 97$dbargs->{user} = $VARS{DBUSER} if $VARS{DBUSER};
98$dbargs->{password} = $VARS{DBPASS} if $VARS{DBPASS};
99my $dir = Text::Tradition::Directory->new(
100 'dsn' => $VARS{DSN}, 'extra_args' => $dbargs );
0f093b35 101my $scope = $dir->new_scope;
102my %results;
103my @resultorder; # Keep track of the order in which we should return the results
104my @needcalc;
105foreach my $p ( @problems ) {
106 my $key = $p->object_key;
107 push( @resultorder, $key );
108 my $result = $dir->lookup( $key );
109 if( $result ) {
110 $results{$key} = $result;
111 } else {
112 push( @needcalc, $p );
113 $dir->store( $key => $p );
114 }
115}
116
117# Now if any of the results need calculation, dispatch them for the purpose.
118if( @needcalc ) {
119 my $arg = join( ',', map { $_->object_key } @needcalc );
120 my $client = Gearman::Client->new;
d1dfc40d 121 $client->job_servers( $VARS{GEARMAN_SERVER} );
0f093b35 122 my $task = $client->dispatch_background( run_idp => $arg );
123 # See if it finishes quickly
124 my $wait = 3;
125 sleep( $wait );
126 # Now replace the problems in the results hash with the DB results,
127 # whether finished or still calculating.
128 foreach my $p ( @needcalc ) {
129 # this should NOT fail as we stored it above
130 $results{$p->object_key} = $dir->lookup( $p->object_key );
131 }
132}
133
134# Finally, assemble our answer.
135my $answer;
136if( $first eq $request ) {
137 $answer = $results{$resultorder[0]};
138} else {
139 foreach my $key ( @resultorder ) {
140 push( @$answer, $results{$key} );
141 }
142}
143
144
145# Now return the response as UTF-8 encoded JSON.
146print $q->header(-type => 'application/json', -charset => 'UTF-8' );
147print JSON->new->allow_blessed->convert_blessed->utf8->encode( $answer );
148exit 0;
149
150sub error {
151 my( $code, $msg ) = @_;
152 print $q->header( -type => 'text/plain', -charset => 'UTF-8', -status => $status{$code} );
153 print "$msg\n";
154 exit 0;
155}