diff --git a/adc2019system.py b/adc2019system.py index 818b023eb65809693e5bcf3cce8fbcc945be387b..010353898189b830961bd2b2f9129cc6059aeddf 100644 --- a/adc2019system.py +++ b/adc2019system.py @@ -33,6 +33,7 @@ def set_role(role_name, config_data): def call_api(method, cmd, params): + print(f'I: API Received: {cmd}') if role is not None: return role.call_api(method, cmd, params) else: diff --git a/main.py b/main.py index 6074786a6a481c3838e8bf79685c11dce0bdd56c..fcefdf80097b5470420ef15d25407295bdec77aa 100644 --- a/main.py +++ b/main.py @@ -24,6 +24,14 @@ def webui_template_questions(): else: return abort(404) +@webui.route('/template/system-summary') +def webui_template_workers(): + if (adc2019system.role is not None) and (adc2019system.role.type == 'host'): + workers = adc2019system.role.get_workers() + return render_template('part_system_summary.html', workers=workers) + else: + return abort(404) + @webui.route('/template/question/') def webui_template_question_status(name=None): @@ -31,8 +39,11 @@ def webui_template_question_status(name=None): return abort(404) if (adc2019system.role is not None) and (adc2019system.role.type == 'host'): - questions = adc2019system.role.get_questions() - return render_template('part_question_status.html', questions=questions) + question = adc2019system.role.get_question(name) + if question is None: + return abort(404) + else: + return render_template('part_question_status.html', question=question) else: return abort(404) diff --git a/roles/host.py b/roles/host.py index cc211983a18844b1e49c6906084e04afa4152680..e8f8c03f0f037bef95dbff0f15f4bffae887f4ae 100644 --- a/roles/host.py +++ b/roles/host.py @@ -1,5 +1,8 @@ import glob import json +import sys +import threading +import time import requests from utils import Question @@ -10,11 +13,13 @@ class Host(object): self.type = 'host' self.config = config - self.questions = [] - self._load_questions(config['question_path']) + self.questions = dict() + self.worker_manager = None + self.request = dict() + self.processing_request = None - self.worker = dict() - self.setup_workers() + self._load_questions(config['question_path']) + self._setup_workers() def __repr__(self): return "Host" @@ -28,39 +33,199 @@ class Host(object): for v in questions_path: question = Question(v) + _key = question.name - self.questions.append(question) + self.questions[_key] = question - def setup_workers(self): + def _setup_workers(self): + self.worker_manager = WorkerManager(self.config['address']) for v in self.config['worker']: - worker_name = v['name'] - self.worker[worker_name] = Worker(v) + self.worker_manager.add_worker(v) + + def get_workers(self): + return self.worker_manager.get_workers() + + def distribute_question(self, question_key): + + question = self.get_question(question_key) + if question is None: + return {'status': 'key error'} + else: + request = Request(self.worker_manager, question.get_dict()) + request_id = request.get_id() + self.request[request_id] = request + + request.broadcast() + return { + 'status': 'processed', + 'request_id': request_id, + 'timeout': request.timeout + } def get_questions(self): return self.questions + def get_question(self, _key): + if _key in self.questions: + return self.questions[_key] + else: + return None + + def store_solution(self, solution): + # request_idをチェックする機能もつくておく + request_id = solution['request_id'] + if request_id in self.request: + self.request[request_id].store_response(solution) + else: + print(f'W: Unknown request_id: {request_id}') + + question_key = solution['question'] + + if question_key in self.questions: + self.questions[question_key].put_solution(solution) + return {'status': 'registered'} + else: + return {'status': 'error'} + + def get_request_status(self, request_id): + if request_id in self.request: + return self.request[request_id].get_status() + else: + return {'status': 'unknown request'} + def call_api(self, method, cmd, params): if cmd == 'role': + # サーバの役割確認 return {'role': self.type} + elif cmd == 'question/solve': + # params['question']に指定された問題をworkerに配信 + question_key = params['question'] + return self.distribute_question(question_key) + elif cmd == 'question/solution': + self.store_solution(params) + return {'status': 'received'} + elif cmd == 'request/status': + request_id = float(params['request_id']) + return self.get_request_status(request_id) else: return None +class WorkerManager(object): + + def __init__(self, host_address): + self.workers = dict() + self.host = host_address + + def add_worker(self, conf): + + worker_conf = dict() + worker_conf.update(conf) + worker_conf['host'] = self.host + + worker_address = worker_conf['address'] + self.workers[worker_address] = Worker(worker_conf) + + def get_workers(self): + return self.workers + + def broadcast(self, cmd, params): + + threads = [] + + def _sender(_worker, _cmd, _params): + _worker.post(_cmd, _params) + + for k, v in self.workers.items(): + _th = threading.Thread(name=v.address, target=_sender, args=(v, cmd, params), daemon=True) + _th.start() + threads.append(_th) + class Worker(object): def __init__(self, params): self.address = params['address'] self.name = params['name'] + self.host = params['host'] self.role = None + self.params = params + self.status = 'Setting up' self.set_role(params['role']) def post(self, path, data): - response = requests.post( - f'http://{self.address}/api/{path}', - json.dumps(data), - headers={'Content-Type': 'application/json'}) + try: + response = requests.post( + f'http://{self.address}/api/{path}', + json.dumps(data), + headers={'Content-Type': 'application/json'}, + timeout=2) + print(f"I: Post to {self.address}, API Cmd: {path}") + except Exception as e: + # sys.stderr.write(str(e) + "\n") + print(f"W: Failed to connect {self.address}") + self.status = 'Not connected' + response = None return response def set_role(self, role): - r = self.post('role', {'role': role}) + r = self.post('role', self.params) + +class Request(object): + + def __init__(self, worker_manager, data, timeout=10): + self.worker_manager = worker_manager + + self.data = data + self.timeout = timeout + + self.request_id = time.time() + + self.broadcast_time = None + + self.response = dict() + + @property + def request_data(self): + data = self.data + data['request_id'] = self.request_id + data['timeout'] = self.timeout + return data + + def get_id(self): + return self.request_id + + def get_dict(self): + return self.request_data + + def store_response(self, data): + # TODO: 1つのrequest_idに対し同一のworkerから2つ以上答えが返ってきた場合の例外処理 + worker = data['worker'] + self.response[worker] = data + + def get_status(self): + all_workers = self.worker_manager.get_workers().keys() + worker_count = 0 + response_count = 0 + for v in all_workers: + worker_count += 1 + if v in self.response: + response_count += 1 + + status = '' + if worker_count == response_count: + status = 'done' + elif time.time() - self.broadcast_time > self.timeout: + status = 'timeout' + else: + status = 'processing' + + return { + 'status': status, + 'workers': worker_count, + 'solutions': response_count + } + + def broadcast(self): + self.worker_manager.broadcast('solve', self.request_data) + self.broadcast_time = time.time() diff --git a/roles/solver.py b/roles/solver.py index 0b0a1856e4fa8d0ec66f8d9069f7e8d4f6f40a0a..a0088e1aa07ac68ab7a8b58f092a0ac0835f30af 100644 --- a/roles/solver.py +++ b/roles/solver.py @@ -1,13 +1,97 @@ +import importlib +import json +import os +import requests +import sys +import time +import threading + +class StoppableThread(threading.Thread): + + def __init__(self, target, args=()): + super(StoppableThread, self).__init__(target=target, args=args) + self._status = 'running' + def stop(self): + if self._status == 'running': + self._status = 'stopping' + def stopped(self): + self._status = 'stopped' + def is_running(self): + return (self._status == 'running') + def is_stopping(self): + return (self._status == 'stopping') + def is_stopped(self): + return (self._status == 'stopped') + class Solver(object): def __init__(self, config): self.type = 'solver' + self.host = config['host'] + self.address = config['address'] + self.name = config['name'] + self.solver = importlib.import_module(f"solvers.{config['solver']}") + self.thread = None + def __repr__(self): return "Solver" + + def solve(self, params): + + start_time = time.time() + solution = self.solver.solve(params) + end_time = time.time() + self.thread.stopped() + + elapsed_time = end_time - start_time + + if not 'elapsed_time' in solution: + solution['elapsed_time'] = elapsed_time + + self.submit_solution(params, solution) + self.thread = None + return True + + def post(self, path, data): + response = requests.post( + f'http://{self.host}/api/{path}', + json.dumps(data), + headers={'Content-Type': 'application/json'}) + print(f"I: Post to {self.host}, API Cmd: {path}") + return response + + def submit_solution(self, params, solution): + data = { + 'request_id': params['request_id'], + 'question': params['name'], + 'worker': self.address + } + data.update(solution) + self.post('question/solution', data) + + def start_solver(self, params): + + if self.thread is None: + print("I: Solver started") + self.thread = StoppableThread(target=self.solve, args=(params, )) + self.thread.start() + return {'status': 'started'} + else: + return {'status': 'busy'} + + def stop_solver(): + + if self.thread is not None: + if self.thread.is_running(): + self.thread.stop() + self.thread.join() + self.thread = None def call_api(self, method, cmd, params): if cmd == 'role': return {'role': self.type} + elif cmd == 'solve': + return self.start_solver(params) else: return None diff --git a/solvers/SampleSolver/main.py b/solvers/SampleSolver/main.py new file mode 100644 index 0000000000000000000000000000000000000000..053285369f656f37300cdf6c13820aea0228d438 --- /dev/null +++ b/solvers/SampleSolver/main.py @@ -0,0 +1,6 @@ +def solve(params): + print("This is a sample solver.") + return {'solution': 'aaa!'} + +def main(params): + solve(a) diff --git a/static/css/adc2019.css b/static/css/adc2019.css index d4e326cc6b9b50057c44888b7d02978af3e84dbb..1fd5b4f507741ba545e5d44edd4a25cc8ba2b161 100644 --- a/static/css/adc2019.css +++ b/static/css/adc2019.css @@ -15,30 +15,30 @@ body{ background-color: rgba(0, 0, 50, .5); } -#question-table-wrapper{ +#question-list-container{ height: calc(100vh - 50px); overflow-y: scroll; overflow-x: hidden; } -#question-table-wrapper th.large-cell, -#question-table-wrapper td.large-cell{ +#question-list-container th.large-cell, +#question-list-container td.large-cell{ width: 24%; } -#question-table-wrapper th.small-cell, -#question-table-wrapper td.small-cell{ +#question-list-container th.small-cell, +#question-list-container td.small-cell{ width: 14%; } -#question-table-wrapper tr.question-row, -#question-table-wrapper tr.question-row td{ +#question-list-container tr.question-row, +#question-list-container tr.question-row td{ cursor: pointer; } -#question-table-wrapper tr.question-row.q-selected{ +#question-list-container tr.question-row.q-selected{ background-color: rgba(200, 100, 100, .3); } -#question-table-wrapper tr.question-row:hover{ +#question-list-container tr.question-row:hover{ background-color: rgba(200, 100, 100, .15); } @@ -60,16 +60,23 @@ body{ display: inline-block; } -#client-control-pane tr.answer-detail-row, -#client-control-pane tr.answer-detail-row td{ +#client-control-pane tr.solution-detail-row, +#client-control-pane tr.solution-detail-row td{ cursor: pointer; } -#client-control-pane tr.answer-detail-row:hover{ +#client-control-pane tr.solution-detail-row:hover{ background-color: rgba(200, 100, 100, .15); } -#client-control-pane tr.answer-detail-row.submit-answer{ +#client-control-pane tr.solution-detail-row.submit-answer{ background-color: rgba(100, 200, 100, .3); } +#content-left h3{ + display: inline-block; +} + +#content-right h4.inline-heading{ + display: inline-block; +} diff --git a/static/js/adc2019.js b/static/js/adc2019.js index 01bfe65c3a102733fb69be694a89c56c286763db..e313a917234f7321996fbcf5a484ee27c512f5e0 100644 --- a/static/js/adc2019.js +++ b/static/js/adc2019.js @@ -1,286 +1,104 @@ -class ADC2019Board { +// 詳細表示画面 +class StatusView { constructor(selector) { this.container = $(selector); - this.width = this.container.width(); - this.height = $(window).height(); - - this.svg = d3.select(selector).append('svg'); - this.view = this.svg.append('g').attr('class', 'view'); - - this.currentTransform = null; - - this.cubeResolution = 50; - - this._init(); - this._draw_slider(); - + this.question_key = null; } - get_board_list(selector){ - - var _self = this; - - var container = $(selector); - - container.html(""); + // 問題詳細画面を表示 + show_question(question_key = null){ + var _this = this; + if(question_key != null){ + this.question_key = question_key; + } $.ajax({ - url: '/api/boards', type: 'GET', - }).done((data) => { - var $board_list = container.append("
"); - for(var i=0; i" + data[i] + ""); - } - if(data.indexOf(_self._get_hash()) >= 0){ - _self.draw(_self._get_hash()); - } - }).fail((data) => { - console.log("Error"); - }); - } - - draw(name){ - this._draw_init(); - this._get_board(name); - } - - _get_hash(){ - return location.hash.replace("#", ""); - } - - _init(){ - var _self = this; - - if (this.currentTransform){ - this.view.attr('transform', this.currentTransform); - } + dataType: 'html', + url: '/template/question/' + _this.question_key + }).done((d) => { + _this.container.empty(); + _this.container.html(d); - $(window).on('hashchange', () => { - _self.draw(_self._get_hash()); + _this.container.find('.start-button').click(()=>{ + _this.start_solver(); + }); }); } - _get_board(name){ - + // 問題を解くのをスタート + start_solver(){ + var _this = this; + $.ajax({ - url: '/api/board/' + name, - type: 'GET', - }).done((data) => { - this._draw_grid(data.size[0], data.size[1]); - this._draw_blocks(data.blocks); - }).fail((data) => { - console.log("Error"); + type: "POST", + dataType: "json", + url: "/api/question/solve", + data: JSON.stringify({ + "question": _this.question_key + }), + contentType: 'application/json' + }).done((d) => { + // TODO: タイムアウトになる時間まで,モーダル表示させる + console.log(d); + _this.container.find('#solver-processing-modal').modal('show'); }); - - } - - _draw_init(){ - this.view.selectAll("g").remove(); } - - _draw_grid(x_cnt, y_cnt){ - let _width = x_cnt * this.cubeResolution; - let _height = y_cnt * this.cubeResolution; - this.view.append("g") - .attr("class", "x axis") - .selectAll("line") - .data(d3.range(0, _width+1, this.cubeResolution)) - .enter().append("line") - .attr("x1", function (d) { return d; }) - .attr("y1", 0) - .attr("x2", function (d) { return d; }) - .attr("y2", _height) - .attr("stroke", "#888"); - - this.view.append("g") - .attr("class", "y axis") - .selectAll("line") - .data(d3.range(0, _height+1, this.cubeResolution)) - .enter().append("line") - .attr("x1", 0) - .attr("y1", function (d) { return d; }) - .attr("x2", _width) - .attr("y2", function (d) { return d; }) - .attr("stroke", "#888"); - } - - _draw_slider(){ - - let _self = this; - - var zoom = d3.zoom() - .scaleExtent([0.5, 5]) - .translateExtent([ - [-this.width * 2, -this.height * 2], - [this.width * 2, this.height * 2] - ]) - .on("zoom", zoomed); - - function zoomed() { - this.currentTransform = d3.event.transform; - _self.view.attr("transform", this.currentTransform); - // this.slider.property("value", d3.event.scale); - } - - function slided(d) { - zoom.scaleTo(this.svg, d3.select(this).property("value")); - } - - // var slider = d3.select("body").append("input") - // .datum({}) - // .attr("type", "range") - // .attr("value", 1) - // .attr("min", zoom.scaleExtent()[0]) - // .attr("max", zoom.scaleExtent()[1]) - // .attr("step", (zoom.scaleExtent()[1] - zoom.scaleExtent()[0]) / 100) - // .on("input", slided); - - this.svg.call(zoom).on('dblclick.zoom', null); - - } - - _draw_blocks(blocks){ - - let cubeResolution = this.cubeResolution; - - function snapToGrid(p, r) { - return Math.round(p / r) * r; - } - - function dragged(d) { - var el = d3.select(this); - var _x = parseInt(el.attr('data-x'), 10) + d3.event.x; - var _y = parseInt(el.attr('data-y'), 10) + d3.event.y; - el.attr("transform", (d) => { - return 'translate(' + snapToGrid(_x, cubeResolution) + ',' + snapToGrid(_y, cubeResolution) + ')' - }) - } - function dragended(d) { - var el = d3.select(this).classed("dragging", false); - var _x = parseInt(el.attr('data-x'), 10) + d3.event.x; - var _y = parseInt(el.attr('data-y'), 10) + d3.event.y; - d3.select(this) - .attr('data-x', snapToGrid(_x, cubeResolution)) - .attr('data-y', snapToGrid(_y, cubeResolution)); - } - function dragstarted(d) { - var el = d3.select(this); - el.raise().classed("dragging", true); - } - - let colors = d3.schemeCategory10 - - // Reference - // Grid: https://bl.ocks.org/ngminhtrung/7c5721a1504f3e29a36da9ddd9e5039b - // Snap: https://bl.ocks.org/evanjmg/ea3e59e67b4256c8831d3fc80f71294b - // Nested data structure: https://codeday.me/jp/qa/20190428/720184.html - - var itemContainer = this.view.selectAll("g.itemContainer") - .data(blocks) - .enter() - .append('g') - .attr('class', 'itemContainer') - .attr("transform", (d) => 'translate(' + d.x + ',' + d.y + ')') - .attr('data-x', (d) => d.x) - .attr('data-y', (d) => d.y) - .call(d3.drag() - .on("start", dragstarted) - .on("drag", dragged) - .on("end", dragended)); - - var cellContainer = itemContainer.append('g') - .attr('class', 'cellContainer') - .attr('data-color', (d, i) => colors[i]) - .attr('x', 0) - .attr('y', 0) - - cellContainer.selectAll('g') - .data((d) => d.cells) - .enter() - .append('rect') - .attr('x', (d) => d[0] * this.cubeResolution) - .attr('y', (d) => d[1] * this.cubeResolution) - .attr('width', this.cubeResolution) - .attr('height', this.cubeResolution) - .attr('cursor', 'move') - .attr('fill', (d, i, nodes) => { - return d3.select(nodes[i].parentNode).attr('data-color'); - }); - - cellContainer.selectAll('g') - .data((d) => d.cells) - .enter() - .append('text') - .attr('x', (d) => d[0] * this.cubeResolution + 0.5 * this.cubeResolution) - .attr('y', (d) => d[1] * this.cubeResolution + 0.5 * this.cubeResolution) - .attr('width', this.cubeResolution) - .attr('height', this.cubeResolution) - .attr('text-anchor', 'middle') - .attr('fill', 'white') - .attr('font-size', '20px') - .attr('cursor', 'move') - .text((d) => d[2]); - - } -} - -$(function(){ - - // const board = new ADC2019Board("#board-container"); - - // board.get_board_list("#board-list-container"); - // // board.draw("Q001_10X10_b8_n11.txt"); - var show_question_status = function(qname){ + // システム詳細画面を表示 + show_system(){ + var _this = this; + $.ajax({ type: "GET", dataType: "html", - url: "/template/question/" + qname, + url: "/template/system-summary" }).done((d) => { - $("#status-container").empty(); - $("#status-container").html(d); + _this.container.empty(); + _this.container.html(d); + + var button_action_with_ajax = function($obj, url){ + $obj.prop("disabled", "disabled"); + $.ajax({ + type: "GET", + url: url, + dataType: "json", + }).done((data)=>{ + $obj.prop("disabled", false); + alert(data['message']); + }); + }; + + $("#adccli-login-button").click(function(){ + button_action_with_ajax($(this), "/adccli-login"); + }); + $("#adccli-logout-button").click(function(){ + button_action_with_ajax($(this), "/adccli-logout"); + }); + $("#adccli-get-all-q").click(function(){ + button_action_with_ajax($(this), "/adccli-get-all-q"); + }); - // $("#status-container").find(".start-button").eq(0).click(function(){ - // var qname = $(this).data("qname"); - // pm.sendQuestion(qname); - // }); - // $("#status-container").find(".stop-button").eq(0).click(function(){ - // pm.sendStop(); - // }); - // $("#status-container").find(".save-button").eq(0).click(function(){ - // var qname = $(this).data("qname"); - // $.ajax({ - // type: "POST", - // dataType: "json", - // url: "/save", - // data: {qname: qname} - // }).done((data) => { - // alert(data['status']); - // }); - // }); - // $("#status-container").find(".submit-button").eq(0).click(function(){ - // var qname = $(this).data("qname"); - // $.ajax({ - // type: "POST", - // dataType: "json", - // url: "/adccli-put-a", - // data: {qname: qname} - // }).done((data) => { - // alert(data['message']); - // }); - // }); + $.ajax({ + type: "GET", + url: "/adccli-whoami", + dataType: "json", + }).done((data)=>{ + if(data['status']) + $("#adccli-status").text("ログイン中"); + else + $("#adccli-status").text("ログアウト"); + }); - // $(".answer-detail-row td").click(function(){ - // var json_name = $(this).parent("tr").data("json"); - // var qname = $(this).parent("tr").data("qname"); - // var viewer_url = "/board-viewer#" + qname + "," + json_name - // window.open(viewer_url, "_blank"); - // }) }); } +} + +$(function(){ + + const status_view = new StatusView('#status-container'); var refresh_questions = function(){ $.ajax({ @@ -308,13 +126,12 @@ $(function(){ }); } - $(window).on('hashchange', function(){ var hash = location.hash.replace("#", ""); if(hash == ""){ - // show_client_table(); + status_view.show_system(); }else{ - show_question_status(hash); + status_view.show_question(hash); } }).trigger('hashchange'); diff --git a/templates/index.html b/templates/index.html index 9321050c087ff5f6c2c66d089b55eff6611cbeca..6f28c74060ba607cf22330a1212c69f00f9f33f4 100644 --- a/templates/index.html +++ b/templates/index.html @@ -12,30 +12,6 @@ - - @@ -44,7 +20,7 @@ svg,
-
+

問題一覧

  システム状況
@@ -52,7 +28,7 @@ svg,
-
+
diff --git a/templates/part_question_status.html b/templates/part_question_status.html index 92f4c6fa5a9bc87ee6d2fd66518c5ee6c51ba6be..655e43c97a4be10aaff92c16b98f46349ef851f3 100644 --- a/templates/part_question_status.html +++ b/templates/part_question_status.html @@ -1,27 +1,24 @@ -
-
-

【{{qname}}】

- {#% # if localmode %#} -

- - -

-

- - -

- {#% else %#} - - {#% endif %#} -
-
+
+ +

【{{question.name}}】

+ {#% # if localmode %#} +

+ + + + +

+ {#% else %#} + + {#% endif %#} + + {% endfor %} -
+
-->
@@ -47,23 +44,42 @@ Client Score - {#% else %#} - + {#% endif %#} - {#{v.timestamp}#} - {#{v.solver}#} + {{v.timestamp_str}} + {{v.worker}} {#% if v.nlcheck == -1 %#} - Not solved + {#% else %#} {#{v.nlcheck}#} {#% endif %#} - {#% endfor %#} --> + {% endfor %}
+ diff --git a/templates/part_questions.html b/templates/part_questions.html index 46b4fd7d871be9758fec9c6667b4605804d4bd1d..d736d9087504f5502a3b445204a93e98791067a4 100644 --- a/templates/part_questions.html +++ b/templates/part_questions.html @@ -2,16 +2,16 @@ File Name - Size + Size #Blocks Status - {% for v in questions %} + {% for k, v in questions.items() %} {{v.name}} - {{v.size_str}} + {{v.size_str}} {{v.block_num}} {{v.status}} diff --git a/templates/part_system_summary.html b/templates/part_system_summary.html new file mode 100644 index 0000000000000000000000000000000000000000..275c74f873db214877fd54d03312f03f78810a20 --- /dev/null +++ b/templates/part_system_summary.html @@ -0,0 +1,43 @@ +

システム状況

+ +
+

動作モード

+ + {#% if local_mode %#} + Normal Mode + {#% else %#} + + {#% endif %#} + +
+ +
+ {#% if local_mode %#} +

自動運営システム

+ + + + + {#% endif %#} +
+ +
+

Worker

+ + + + + + + {% for k, w in workers.items() %} + + + + + + {% endfor %} +
WorkerRoleStatus
+ {{w.name}} ({{w.address}}) + {{w.role}}{{w.status}}
+
+ diff --git a/utils/data.py b/utils/data.py index d84027f9d5bfa5a9a3f9c85ac9a8a1458afea3da..930235402270490666efa40e1ae58d55378d0f5d 100644 --- a/utils/data.py +++ b/utils/data.py @@ -1,4 +1,7 @@ +import datetime import os +import time +import uuid class Question(object): @@ -9,6 +12,7 @@ class Question(object): self.size = (0, 0) self.block_num = 0 self.status = 'Ready' + self.solutions = dict() self._load_question(path) @@ -37,3 +41,44 @@ class Question(object): self.block_num = block_num self.name = name self.status = 'Ready' + + def get_dict(self): + return { + 'name': self.name, + 'size': self.size, + 'size_str': self.size_str, + 'block_num': self.block_num, + 'status': self.status + } + + def put_solution(self, data): + solution = Solution(data) + solution_id = solution.get_id() + print(f'I: Put a solution: {solution_id}') + self.solutions[solution_id] = solution + + def get_solutions(self): + return self.solutions + +class Solution(object): + + def __init__(self, data): + + self.question = data['question'] + self.request_id = data['request_id'] + self.worker = data['worker'] + self.elapsed_time = data['elapsed_time'] + self.solution = data['solution'] + + self.timestamp = time.time() + self._id = str(uuid.uuid4()) + + def get_id(self): + return self._id + + @property + def timestamp_str(self): + dt = datetime.datetime.fromtimestamp( + self.timestamp, + datetime.timezone(datetime.timedelta(hours=9))) + return dt.strftime('%H:%M:%S.%f')