Skip to content

Instantly share code, notes, and snippets.

@reinouts
Created September 19, 2012 12:37
Show Gist options
  • Save reinouts/3749414 to your computer and use it in GitHub Desktop.
Save reinouts/3749414 to your computer and use it in GitHub Desktop.
t2web source
#!/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