Created
September 19, 2012 12:37
-
-
Save reinouts/3749414 to your computer and use it in GitHub Desktop.
t2web source
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env ruby | |
require 'rubygems' if RUBY_VERSION < '1.9' | |
require 'sinatra/base' | |
require 'haml' | |
require 'open-uri' | |
require 'rest-client' | |
require 't2-server' | |
require 'myexperiment-rest' | |
require 'cgi' | |
require 'stringio' | |
#require "t2-web" | |
# TODO: move whole of T2WebApp in lib | |
# TODO: add constants.rb file in lib for all constants (e.g. myExp user URL, | |
# TMP_UPLOAD_PATH, other URLs -- consider adding those in rack config.ru | |
class T2WebApp < Sinatra::Base | |
WEB_APP_NAME = "t2web" | |
set :views, File.dirname(__FILE__) + '/../views' | |
#set :public_folder, '/var/lib/gems/1.9.1/gems/t2-web-0.1.9' | |
set :public_folder, File.dirname(__FILE__) + '/../public' | |
# puts settings.public_folder | |
ENV.each { |e| puts "#{e[0]} => #{e[1]}" } | |
# Can be used from routes and views (haml) -- not public methods | |
helpers do | |
# Used before we sent the workflow inputs to the taverna server. Just converts | |
# double quotations to single ones. That is because the t2-server library | |
# is confused with double quotes and apparently it is not easy to fix. | |
def t2_library_sanitize(string) | |
ex = string.gsub('"', "'") | |
end | |
# Deletes last new line of file if it exists! It is needed for t2 workflows that | |
# do not sanitize properly, i.e. via a user-provided beanshell script | |
def chomp_last_newline(file) | |
if File.file?(file) and File.size(file) > 1 | |
f = open(file, "rb+") | |
f.seek(-1, File::SEEK_END) | |
f.truncate(File.size(file) - 1) if f.read(1) == "\n" | |
f.close | |
end | |
end | |
# Construct the complete or partial URL as MyExperimentRest lib expects it | |
# TODO: Maybe update myExperimentREST lib to accept only wid and wkf_version | |
def get_url_from_wid(wid, wkf_version) | |
if wkf_version == "default" | |
"/workflows/#{wid}" | |
else | |
"/workflows/#{wid}?version=#{wkf_version}" | |
end | |
end | |
# Generates the contents of the header frame | |
def generate_header_frame(my_exp_wkf, my_exp_usr) | |
<<END | |
<html> | |
<head><link href='/t2web/css/form.css' rel='stylesheet'/></head> | |
<body> | |
#{generate_header_table(my_exp_wkf, my_exp_usr)} | |
</body> | |
</html> | |
END | |
end | |
# Generates the contents of the header frame table | |
def generate_header_table(my_exp_wkf, my_exp_usr) | |
<<END | |
<div id='header'> | |
<table class='header'> | |
<tr> | |
<td class='left'> | |
<img alt='NBIC logo' src='/t2web/images/nbic_logo.gif' /> | |
</td> | |
<td> | |
<table class='header-title'> | |
<tr> | |
<td class='header-title'>#{@my_exp_wkf.title}</td> | |
</tr> | |
<tr> | |
<td class='right'>workflow by <a href='http://www.myexperiment.org/users/#{@my_exp_usr.id}.html' target='_blank'>#{@my_exp_usr.name}</a></td> | |
</tr> | |
</table> | |
</td> | |
<td> | |
<img alt='LUMC logo' class='right' src='/t2web/images/lumc_logo2.png' /> | |
</td> | |
</tr> | |
</table> | |
</div> | |
END | |
end | |
# Generates tooltip html text with type prefix | |
def generate_tooltip(type, input) | |
if type =~ /description/i | |
type << CGI::unescapeHTML(input.descriptions[0]) | |
else | |
type << CGI::unescapeHTML(input.examples[0]) | |
end | |
end | |
# Generates the contents of the data-navigation frame | |
def generate_data_navigation_frame(my_exp_wkf, uuid, wid, wkf_version, t2_server, finished_execution) | |
data_navigation_frame = "<html><head><link href='/t2web/css/results_navigation.css' rel='stylesheet'/></head><body><table>" | |
if my_exp_wkf.outputs.size >=1 | |
my_exp_wkf.outputs.each do |output| | |
data_navigation_frame << "<tr>" | |
if finished_execution | |
# TODO: currently t2_server, wid and wkf_version are not used | |
data_navigation_frame << "<td><a href='javascript:void(0);' onclick='parent.readTmpOutput(\\\"#{t2_server}\\\", \\\"#{uuid}\\\", " << | |
"\\\"#{output.name}\\\", \\\"#{wid}\\\", \\\"#{wkf_version}\\\");'>#{output.name}</a></td><td></td>" | |
else | |
data_navigation_frame << "<td>#{output.name}</td><td><img src='/t2web/images/snake_transparent.gif' alt='Loading Content...'></td>" | |
end | |
data_navigation_frame << "</tr>" | |
end | |
end | |
data_navigation_frame << "</table></body></html>" | |
end | |
# Takes string list as input and prepares for HTML | |
def generate_html_result(data) | |
data.gsub(/[\n]/, '<br>') | |
end | |
# Takes image url and prepares for HTML | |
def generate_image_result(img) | |
"<img src=\"#{img}\" />" | |
end | |
# Takes list as input (or single element), returns as multi-line string | |
# TODO: when 'typing' issues are resolved think of using system_flatten_list_results method | |
# TODO: <b></b> is added to single images -- remember to clean after 'typing' work | |
def flatten_list_results(data_lists) | |
data = "" | |
data_lists.each do |list| | |
if list.instance_of? Array | |
tmp_data = list.flatten.compact.map {|e| e.chomp} | |
tmp_data[0] = "<b>" + tmp_data[0] + "</b>" | |
data += tmp_data.join("\n") + "\n" | |
else | |
data += list.chomp + "\n" | |
end | |
end | |
data | |
end | |
# Takes list as input (or single element), returns as file stream | |
# TODO: for images it works when not a list! | |
#def system_flatten_list_results(list) | |
# data = "" | |
# if list.instance_of? Array | |
# # flatten (sub-)list and remove nils and if present, newlines | |
# # then join list as a multi-line string | |
# data = list.flatten.compact.map {|e| e.chomp}.join("\n") | |
# else | |
# data = list.chomp | |
# end | |
# StringIO.new data | |
#end | |
# Copies output stream to a tmp file in the specified subdir | |
def copy_to_tmp(subdir, filename, out) | |
# used to copy them at tmp | |
#FileUtils.mkdir_p("/tmp/#{WEB_APP_NAME}/#{subdir}") unless File.exist?("/tmp/#{WEB_APP_NAME}/#{subdir}") | |
#tmp_file = File.open("/tmp/#{WEB_APP_NAME}/#{subdir}/#{filename}", "w") | |
# copy at our tmp dir below public folder | |
FileUtils.mkdir_p("#{settings.public_folder}/#{WEB_APP_NAME}/tmp/#{subdir}") unless File.exist?("#{settings.public_folder}/#{WEB_APP_NAME}/tmp/#{subdir}") | |
tmp_file = File.open("#{settings.public_folder}/#{WEB_APP_NAME}/tmp/#{subdir}/#{filename}", "w") | |
FileUtils.copy_stream(out, tmp_file) | |
tmp_file.close | |
"Successfully copied #{filename}!!" | |
end | |
end | |
# Create the Web UI for that workflow | |
get "/#{WEB_APP_NAME}/workflow/:wid" do | |
# Get http request's parameters | |
# TODO: consider adding the following to a session | |
@wid = params[:wid] | |
@wkf_version = params[:wkf_version] || nil | |
@t2_server = params[:server] || "http://test.mybiobank.org/taverna-server" | |
# Get myExperiment workflow object | |
# TODO: catch exception | |
@my_exp_wkf = MyExperimentREST::Workflow.from_id_and_version(@wid, @wkf_version) | |
# Get myExperiment user object | |
# TODO: catch exception | |
@my_exp_usr = MyExperimentREST::User.from_uri(@my_exp_wkf.uploader_uri) | |
haml :form | |
end | |
# Enact the workflow with the submitted parameters and returns | |
# result display (involves checking run's status and getting results) | |
post "/#{WEB_APP_NAME}/enact" do | |
#p params | |
# Get http POST request's parameters | |
# TODO: consider adding the following to a session | |
@wid = params[:wid] | |
@wkf_version = params[:wkf_version] | |
@t2_server = params[:server] | |
@my_exp_wkf = MyExperimentREST::Workflow.from_id_and_version(@wid, @wkf_version) | |
# Get myExperiment user object (TODO: session!, exceptions) | |
@my_exp_usr = MyExperimentREST::User.from_uri(@my_exp_wkf.uploader_uri) | |
# use the uri reference to download the workflow locally | |
#wkf_file = URI.parse(@my_exp_wkf.content_uri) | |
wkf = open(@my_exp_wkf.content_uri).read | |
# create run | |
begin | |
run = T2Server::Run.create(@t2_server, wkf) | |
rescue T2Server::T2ServerError => e | |
return "404 Not Found: run could not be instantiated!" | |
end | |
# Get the run as instance member to make visible in views | |
@run_uuid = run.uuid | |
# Set workflow inputs | |
@my_exp_wkf.inputs.each do |input| | |
if params[:"upload-checkbox-#{input.name}"] == "yes" | |
filename = params[:"#{input.name}-file"] | |
tmp_filename = "#{settings.public_folder}/#{WEB_APP_NAME}/tmp/#{filename}" | |
# make sure that the file is uploaded completely, i.e. all it's | |
# connections are closed | |
#while `lsof -c cp | grep /tmp/#{WEB_APP_NAME}/#{filename}` != "" | |
# sleep 1 | |
#end | |
chomp_last_newline(tmp_filename) | |
run.upload_input_file(input.name, tmp_filename) | |
else | |
run.set_input(input.name, t2_library_sanitize(params["#{input.name}-input".to_sym]) ) | |
end | |
end | |
# start run and wait until it is finished | |
run.start | |
haml :results | |
end | |
# Proxy operation (to bypass cross-domain AJAX) to get T2 run's status | |
get "/#{WEB_APP_NAME}/runs/:uuid/status" do | |
uuid = params[:uuid] | |
t2_server = params[:server] | |
response = RestClient.get t2_server + "/rest/runs/" + uuid + "/status", :content_type => "text/plain" | |
response.to_str | |
end | |
# Get results from all outputs and store in tmp folder | |
# Cross-domain AJAX to get result from T2 | |
get "/#{WEB_APP_NAME}/run/:uuid/outputs" do | |
@run_uuid = params[:uuid] | |
@t2_server = params[:server] | |
# Get t2 server and then the run using uuid | |
server = T2Server::Server.connect(@t2_server) | |
run = server.run(@run_uuid) | |
# Get output ports from t2 server and store in tmp folder | |
# TODO: TMP folder constant from config! | |
# TODO: that gets the whole output.. request client lib to support partial | |
# download if result too large! | |
run.get_output_ports.each do |t2_output| | |
begin | |
data_lists = run.get_output(t2_output, false) | |
rescue Exception => e | |
data_lists = run.get_output("#{t2_output}.error", false) | |
end | |
# flatten the results | |
data_stream = flatten_list_results(data_lists) | |
# copy results to our app's tmp | |
copy_to_tmp("outputs/#{@run_uuid}", t2_output, (StringIO.new data_stream)) | |
end | |
# return success! | |
"Successful download!" | |
end | |
# Get results for specified run and output (stored locally from tmp) | |
# and display it in data-display div | |
# Cross-domain AJAX to get result from T2 | |
get "/#{WEB_APP_NAME}/run/:uuid/output/:out" do | |
# change public only for this route so that /tmp/t2web can be accessed | |
# unfortunately contrary to an article the following does not work | |
#set :public_folder, "./tmp/#{WEB_APP_NAME}" | |
run_uuid = params[:uuid] | |
t2_output = params[:out] | |
display_file = "#{settings.public_folder}/#{WEB_APP_NAME}/tmp/outputs/#{run_uuid}/#{t2_output}" | |
data = File.open(display_file, "r").read | |
# Add HTML tags to display properly | |
# Check what kind of file it is. for now use system 'file' -- note that it will only | |
# work with one image since flatten_list_results only deals with multi-line strings | |
# and single strings or single images (indirectly!) | |
mime_type = %x( file --mime-type #{display_file} ) | |
# TODO: image types can go to array and regex auto-generated | |
# TODO: application/octet-stream is a temporary hack until we deal with 'typing' properly | |
if mime_type =~ /image\/png|image\/jpg|image\/gif|image\/bmp|application\/octet-stream/ | |
# clean image from <b></b> --- will not be needed after we deal with 'typing' properly t2-server-0.9.x | |
if mime_type =~ /octet-stream/ | |
unless File.file?("#{settings.public_folder}/#{WEB_APP_NAME}/tmp/outputs/#{run_uuid}/clean_#{t2_output}") | |
new_length = File.size?(display_file) - 3 - 5 # for <b> and </b>\n respectively | |
%x( dd if="#{display_file}" of="#{settings.public_folder}/#{WEB_APP_NAME}/tmp/outputs/#{run_uuid}/clean_#{t2_output}" bs=1 skip=3 count="#{new_length}" ) | |
end | |
generate_image_result("/#{WEB_APP_NAME}/tmp/outputs/#{run_uuid}/clean_#{t2_output}") | |
else | |
generate_image_result("/#{WEB_APP_NAME}/tmp/outputs/#{run_uuid}/#{t2_output}") | |
end | |
else | |
generate_html_result(data) | |
end | |
end | |
# Used from input's upload-form to upload files without refresh (actually | |
# a hidden iframe is refreshed). | |
# TODO: use copy_to_tmp method!! Uploads still go to system's tmp and not | |
# to our webapp's public tmp | |
post "/#{WEB_APP_NAME}/upload" do | |
filename = params[:file][:filename] | |
tempfile = params[:file][:tempfile] | |
FileUtils.mkdir("/tmp/#{WEB_APP_NAME}") unless File.exist?("/tmp/#{WEB_APP_NAME}") | |
FileUtils.copy(tempfile.path, "/tmp/#{WEB_APP_NAME}/#{filename}") | |
"Successfully copied #{filename}!!" | |
end | |
# start the server if ruby file executed directly | |
run! if app_file == $0 | |
end | |
# Start Web App from the ./bin directory in deamon mode at port 9494 | |
# $ thin -d -p 9494 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment