#!/usr/bin/ruby curpath = __dir__ require 'rubygems' require 'optparse' require 'fileutils' require 'webrick' require 'typhoeus' require 'xml/smart' require 'zip' require 'pp' def wrap(s, width=78, indent=18) lines = [] line, s = s[0..indent-2], s[indent..-1] s.split(/\n/).each do |ss| ss.split(/[ \t]+/).each do |word| if line.size + word.size >= width lines << line line = (" " * (indent)) + word else line << " " << word end end lines << line if line line = (" " * (indent-1)) end return lines.join "\n" end ARGV.options { |opt| opt.summary_indent = ' ' * 2 opt.banner = "Usage:\n#{opt.summary_indent}#{File.basename($0)} [options] ui | cpui DIR | new DIR | archive DIR URL | abandon URL\n" opt.on("Options:") opt.on("--help", "-h", "This text") { puts opt; exit } opt.on("") opt.on(wrap("[archive DIR URL] save properties from all finished instances listed at URL into DIR. Examples:\ncpee archive ./archive http://localhost:9298/1/\ncpee archive ./archive http://localhost:9298/1-200\ncpee archive ./archive http://localhost:9298/*")) opt.on("") opt.on(wrap("[abandon URL] running processes are stopped; ready or stopped processes are abandoned. Examples:\ncpee abandon http://localhost:9298/1/\ncpee abandon http://localhost:9298/1-200\ncpee abandon http://localhost:9298/*")) opt.on("") opt.on(wrap("[start URL] stopped processes are started; all others are not touched. Examples:\ncpee start http://localhost:9298/1\ncpee start http://localhost:9298/1-200\ncpee start http://localhost:9298/*")) opt.on("") opt.on(wrap("[delete! URL] DANGER ZONE. Vanishes forever. Not in archive. Examples:\ncpee delete! http://localhost:9298/1/")) opt.on("") opt.on(wrap("[new DIR] scaffolds a sample execution engine. Everything except instances can be removed for default behaviour.")) opt.on("") opt.on(wrap("[inst DIR] scaffolds a sample instantiation service. Post a testset to a model to keep going in one operation.")) opt.on("") opt.on(wrap("[cpui DIR] scaffolds a sample html client. New versions might require manual merging if you changed something.")) opt.on("") opt.on(wrap("[ui] starts a simple static web server with the ui on http://localhost:8080. Use [cpui DIR] if you want stuff in apache or nginx.")) opt.parse! } if (ARGV.length == 0) || (ARGV.length == 1 && ARGV[0] != 'ui') || (ARGV.length == 2 && !(%w(abandon inst start delete! cpui new).include?(ARGV[0]))) || (ARGV.length == 3 && ARGV[0] != 'archive') || (ARGV.length > 3) puts ARGV.options exit end command = ARGV[0] p1 = ARGV[1] p2 = ARGV[2] cockpit = "#{curpath}/../cockpit/" def js_libs(cockpit) res = Typhoeus.get('http://cpee.org/js_libs/js_libs.zip') if res.success? File.write(File.join(cockpit,'js_libs.zip'),res.response_body) Zip::File.open(File.join(cockpit,'js_libs.zip')) do |zip_file| zip_file.each do |entry| case entry.ftype when :directory Dir.mkdir(File.join(cockpit,entry.name)) rescue nil when :file File.write(File.join(cockpit,entry.name),entry.get_input_stream.read) when :symlink FileUtils.ln_s(File.join('.',entry.get_input_stream.read),File.join(cockpit,entry.name), force: true) end end end true else puts 'Internet access required to download javascript libs from "http://cpee.org/js_libs/js_libs.zip".' false end end if command == 'ui' if js_libs(cockpit) s = WEBrick::HTTPServer.new(:Port => 8080, :DocumentRoot => "#{curpath}/../cockpit/") trap("INT"){ s.shutdown } s.start end elsif command == 'cpui' if js_libs(cockpit) if !File.exists?(p1) FileUtils.cp_r("#{curpath}/../cockpit/",p1) else puts "Directory already exists." end end elsif command == 'inst' if !File.exists?(p1) FileUtils.cp_r("#{curpath}/instantiation/",p1) else puts 'Directory already exists.' end elsif command == 'archive' p2 = File.join(p2,'*') if p2 =~ /([a-zA-Z]|\/)$/ base = File.dirname(p2) names = [] if File.basename(p2) =~ /(\d+)-(\d+)/ names = ($1.to_i..$2.to_i).to_a elsif File.basename(p2) == '*' res = Typhoeus.get(File.join(base)) if res.success? XML::Smart.string(res.response_body) do |doc| doc.find('//instance/@id').each do |ele| names << ele.value end end names.reverse! else exit end else names << File.basename(p2) end names.each do |name| res = Typhoeus.get(File.join(base,name.to_s,'/')) if res.success? if res.headers['Content-Type'] =~ /^(text|application)\/xml/ XML::Smart.string(res.response_body) do |doc| if doc.root.qname.to_s == "instances" doc.root.children.each do |i| if ["finished","abandoned"].include?(i.attributes['state']) prop = Typhoeus.get(File.join(base,name.to_s,i.attributes['id'],'properties','/')) if prop.success? File.write(File.join(p1,i.attributes['uuid'] + '.xml'),prop.response_body) if prop.headers['Content-Type'] =~ /^(text|application)\/xml/ Typhoeus.delete(File.join(base,name.to_s,i.attributes['id'],'/')) end end end elsif doc.root.qname.to_s == "info" prop = Typhoeus.get(File.join(base,name.to_s,'properties','/')) if prop.success? xprop = XML::Smart::string(prop.response_body) xprop.register_namespace 'p', 'http://riddl.org/ns/common-patterns/properties/1.0' if ["finished","abandoned"].include?(xprop.find("string(/p:properties/p:state)")) uuid = xprop.find("string(/p:properties/p:attributes/p:uuid)") id = name.to_s File.write(File.join(p1,uuid + '.xml'),prop.response_body) if prop.headers['Content-Type'] =~ /^(text|application)\/xml/ Typhoeus.delete(File.join(base,name.to_s,'/')) end end end end end end end elsif command == 'abandon' p1 = File.join(p1,'*') if p1 =~ /([a-zA-Z]|\/)$/ base = File.dirname(p1) names = [] if File.basename(p1) =~ /(\d+)-(\d+)/ names = ($1.to_i..$2.to_i).to_a elsif File.basename(p1) == '*' res = Typhoeus.get(File.join(base)) if res.success? XML::Smart.string(res.response_body) do |doc| doc.find('//instance/@id').each do |ele| names << ele.value end end names.reverse! else exit end else names << File.basename(p1) end names.each do |name| res = Typhoeus.get(File.join(base,name.to_s,'properties','values','state','/')) if res.success? case res.response_body when "ready", "stopped" then Typhoeus.put(File.join(base,name.to_s,'properties','values','state','/'), headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, body: "value=abandoned") end end end elsif command == 'start' p1 = File.join(p1,'*') if p1 =~ /([a-zA-Z]|\/)$/ base = File.dirname(p1) names = [] if File.basename(p1) =~ /(\d+)-(\d+)/ names = ($1.to_i..$2.to_i).to_a elsif File.basename(p1) == '*' res = Typhoeus.get(File.join(base)) if res.success? XML::Smart.string(res.response_body) do |doc| doc.find('//instance/@id').each do |ele| names << ele.value end end names.reverse! else exit end else names << File.basename(p1) end names.each do |name| res = Typhoeus.get(File.join(base,name.to_s,'properties','values','state','/')) if res.success? case res.response_body when "stopped" then keep_alive = Typhoeus.get(File.join(base,name.to_s,'properties','values','attributes','keep_alive','/')) if keep_alive.success? Typhoeus.put(File.join(base,name.to_s,'properties','values','state','/'), headers: {'Content-Type' => 'application/x-www-form-urlencoded'}, body: "value=running") end end end end elsif command == 'delete!' Typhoeus.delete(File.join(p1,'/')) elsif command == 'new' if !File.exists?(p1) FileUtils.cp_r("#{curpath}/server/",p1) FileUtils.mkdir("#{p1}/instances") rescue nil FileUtils.mkdir("#{p1}/resources") rescue nil FileUtils.mkdir("#{p1}/handlerwrappers") rescue nil else puts 'Directory already exists.' end end