Skip to content

Commit 92ff8bc

Browse files
committed
committed first version
1 parent bccc0f9 commit 92ff8bc

13 files changed

Lines changed: 1260 additions & 0 deletions

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,5 @@ target/
6060

6161
#Ipython Notebook
6262
.ipynb_checkpoints
63+
64+
./configs

configs_model.py

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import json
2+
import os
3+
4+
5+
class Config(object):
6+
config_path = None
7+
script_path = None
8+
name = None
9+
description = None
10+
parameters = None
11+
12+
def __init__(self):
13+
self.parameters = []
14+
15+
def get_config_path(self):
16+
return self.config_path
17+
18+
def get_script_path(self):
19+
return self.script_path
20+
21+
def get_name(self):
22+
return self.name
23+
24+
def get_description(self):
25+
return self.description
26+
27+
def add_parameter(self, parameter):
28+
self.parameters.append(parameter)
29+
30+
def get_parameters(self):
31+
return self.parameters
32+
33+
34+
class Parameter(object):
35+
name = None
36+
param = None
37+
no_value = None
38+
description = None
39+
40+
def get_name(self):
41+
return self.name
42+
43+
def get_param(self):
44+
return self.param
45+
46+
def is_no_value(self):
47+
return self.no_value
48+
49+
def get_description(self):
50+
return self.description
51+
52+
def set_name(self, value):
53+
self.name = value
54+
55+
def set_param(self, value):
56+
self.param = value
57+
58+
def set_no_value(self, value):
59+
self.no_value = value
60+
61+
def set_description(self, value):
62+
self.description = value
63+
64+
65+
def from_json(file_path, json_string):
66+
json_object = json.loads(json_string)
67+
config = Config()
68+
69+
config.file_path = file_path
70+
config.name = json_object.get("name")
71+
if not (config.name):
72+
filename = os.path.basename(file_path)
73+
config.name = os.path.splitext(filename)[0]
74+
75+
config.script_path = json_object.get("script_path")
76+
config.description = json_object.get("description")
77+
78+
parameters_json = json_object.get("parameters")
79+
80+
if parameters_json is not None:
81+
for parameter_json in parameters_json:
82+
parameter = Parameter()
83+
parameter.set_name(parameter_json.get("name"))
84+
parameter.set_param(parameter_json.get("param"))
85+
parameter.set_no_value(parameter_json.get("no_value"))
86+
parameter.set_description(parameter_json.get("description"))
87+
88+
config.add_parameter(parameter)
89+
90+
return config

external_model.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import json
2+
3+
4+
class ExecutionInfo(object):
5+
script = None
6+
param_values = {}
7+
8+
def set_script(self, value):
9+
self.script = value
10+
11+
def get_script(self):
12+
return self.script
13+
14+
def set_param_values(self, value):
15+
self.param_values = value
16+
17+
def get_param_values(self):
18+
return self.param_values
19+
20+
21+
def config_to_json(config):
22+
parameters = []
23+
for parameter in config.get_parameters():
24+
parameters.append({
25+
"name": parameter.get_name(),
26+
"description": parameter.get_description(),
27+
"withoutValue": parameter.is_no_value(),
28+
})
29+
30+
return json.dumps({
31+
"name": config.get_name(),
32+
"description": config.get_description(),
33+
"parameters": parameters
34+
})
35+
36+
37+
def to_execution_info(request_data):
38+
json_object = json.loads(request_data)
39+
40+
script = json_object.get("script")
41+
42+
info = ExecutionInfo()
43+
info.set_script(script)
44+
45+
param_values = {}
46+
parameters = json_object.get("parameters")
47+
if parameters:
48+
for parameter in parameters:
49+
name = parameter.get("name")
50+
value = parameter.get("value")
51+
52+
param_values[name] = value
53+
54+
info.set_param_values(param_values)
55+
56+
return info

launcher.py

Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
1+
import json
2+
import os
3+
import queue
4+
import subprocess
5+
import threading
6+
import time
7+
import traceback
8+
9+
import six
10+
from flask import Flask, request, send_from_directory, abort, Response
11+
12+
import configs_model
13+
import external_model
14+
import utils.file_utils as file_utils
15+
16+
app = Flask(__name__)
17+
script_inputs = {}
18+
19+
20+
def read_configs():
21+
configs_dir = "configs"
22+
files = os.listdir(configs_dir)
23+
24+
configs = [file for file in files if file.lower().endswith(".json")]
25+
26+
result = []
27+
28+
for config_path in configs:
29+
path = os.path.join(configs_dir, config_path)
30+
content = file_utils.read_file(path)
31+
result.append(configs_model.from_json(path, content))
32+
33+
return result
34+
35+
36+
@app.route("/scripts/list", methods=["GET"])
37+
def get_scripts():
38+
configs = read_configs()
39+
40+
return json.dumps([config.get_name() for config in configs])
41+
42+
43+
@app.route("/scripts/info", methods=["GET"])
44+
def get_script_info():
45+
params = request.values
46+
47+
if not ("name" in params):
48+
return abort("Name is not specified")
49+
50+
name = params["name"]
51+
52+
config = find_config_by_name(name)
53+
54+
if not config:
55+
return "Couldn't find a script by name"
56+
57+
return external_model.config_to_json(config)
58+
59+
60+
def find_config_by_name(name):
61+
configs = read_configs()
62+
config_by_name = None
63+
for config in configs:
64+
if config.get_name() == name:
65+
config_by_name = config
66+
break
67+
return config_by_name
68+
69+
70+
@app.route("/<path:filename>")
71+
def web_resources(filename):
72+
if not filename:
73+
filename = "index.html"
74+
75+
return send_from_directory("web", filename)
76+
77+
78+
def build_parameter_string(param_values, config):
79+
result = []
80+
81+
for parameter in config.get_parameters():
82+
name = parameter.get_name()
83+
84+
if name in param_values:
85+
value = param_values[name]
86+
87+
if parameter.is_no_value():
88+
# do not replace == True, since REST service can start accepting boolean as string
89+
if (value == True) or (value == "true"):
90+
result.append(parameter.get_param())
91+
else:
92+
if value:
93+
result.append(parameter.get_param())
94+
95+
# value_string = '"' + value.replace("\"", "\\\"") + '"'
96+
# result.append(value_string)
97+
result.append(value)
98+
99+
return result
100+
101+
102+
@app.route("/scripts/execute/input", methods=["POST"])
103+
def post_user_input():
104+
request_data = request.data.decode("UTF-8")
105+
id = json.loads(request_data).get("id")
106+
value = json.loads(request_data).get("value")
107+
108+
script_inputs[id].put(value)
109+
110+
return ""
111+
112+
113+
@app.route("/scripts/execute", methods=["POST"])
114+
def execute_script():
115+
request_data = request.data.decode("UTF-8")
116+
117+
execution_info = external_model.to_execution_info(request_data)
118+
119+
script_name = execution_info.get_script()
120+
121+
config = find_config_by_name(script_name)
122+
123+
if not config:
124+
return abort("Script with name '" + str(script_name) + "' not found")
125+
126+
try:
127+
script_path = file_utils.normalize_path(config.get_script_path())
128+
script_args = build_parameter_string(execution_info.get_param_values(), config)
129+
130+
command = []
131+
command.append(script_path)
132+
command.extend(script_args)
133+
134+
six.print_("Calling script: " + " ".join(command))
135+
136+
process = subprocess.Popen(command,
137+
stdin=subprocess.PIPE,
138+
stdout=subprocess.PIPE,
139+
stderr=subprocess.STDOUT)
140+
141+
input_id = int(round(time.time() * 1000))
142+
input_queue = queue.Queue()
143+
script_inputs[input_id] = input_queue
144+
145+
output = queue.Queue()
146+
147+
to_process = threading.Thread(target=pipe_http_to_process, args=(input_id, process, output))
148+
to_process.start()
149+
150+
from_process = threading.Thread(target=pipe_process_to_http, args=(process, output))
151+
from_process.start()
152+
153+
output.put(to_script_output(" --- OUTPUT --- \n"))
154+
155+
def response_stream():
156+
while True:
157+
try:
158+
output_object = output.get(timeout=1)
159+
yield output_object
160+
except queue.Empty:
161+
if not (process.poll() is None):
162+
break
163+
164+
return Response(response_stream(), mimetype='text/html')
165+
166+
except Exception as e:
167+
traceback.print_exc()
168+
if (hasattr(e, "strerror") and e.strerror):
169+
error_output = e.strerror
170+
else:
171+
error_output = "Unknown error occurred, contact the administrator"
172+
173+
result = " --- ERRORS --- \n"
174+
result += error_output
175+
return to_script_output(result)
176+
177+
178+
def pipe_http_to_process(input_id, process, output):
179+
output.put(json.dumps({
180+
"input": "your input >>",
181+
"id": input_id
182+
}))
183+
184+
input_queue = script_inputs.get(input_id)
185+
186+
while True:
187+
try:
188+
value = input_queue.get(timeout=1)
189+
input_value = value + "\n"
190+
191+
output.put(to_script_output(input_value))
192+
193+
process.stdin.write(input_value.encode())
194+
process.stdin.flush()
195+
except queue.Empty:
196+
if process.poll() is not None:
197+
break
198+
199+
200+
def pipe_process_to_http(process, output):
201+
empty_count = 0
202+
203+
while True:
204+
line_bytes = process.stdout.readline()
205+
206+
if not line_bytes:
207+
empty_count += 1
208+
209+
if process.poll() is None:
210+
break
211+
212+
time.sleep(min(1, 0.1 * empty_count))
213+
214+
else:
215+
line = line_bytes.decode("UTF-8")
216+
output.put(to_script_output(line))
217+
218+
219+
def to_script_output(text):
220+
return json.dumps({
221+
"output": text
222+
})
223+
224+
225+
def main():
226+
app.debug = True
227+
app.run(threaded=True)
228+
229+
230+
if __name__ == "__main__":
231+
main()

testing/configs/destroy_world.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"script_path": "./testing/scripts/destroy_world.py",
3+
"description": "This is a very dangerous script, please be careful when running. Don't forget your protective helmet."
4+
}

0 commit comments

Comments
 (0)