from flask import Flask, Response, request, redirect
import subprocess
import tempfile
import json
import yaml
import signal
import threading
import time
import copy

app = Flask(__name__)

jobs_lock = threading.Lock()
jobs = []


class Job(threading.Thread):
    def __init__(self, jobid, path, inputobj):
        super(Job, self).__init__()
        self.jobid = jobid
        self.path = path
        self.inputobj = inputobj
        self.updatelock = threading.Lock()
        self.begin()

    def begin(self):
        loghandle, self.logname = tempfile.mkstemp()
        with self.updatelock:
            self.outdir = tempfile.mkdtemp()
            self.proc = subprocess.Popen(["cwl-runner", self.path, "-"],
                                         stdin=subprocess.PIPE,
                                         stdout=subprocess.PIPE,
                                         stderr=loghandle,
                                         close_fds=True,
                                         cwd=self.outdir)
            self.status = {
                "id": "%sjobs/%i" % (request.url_root, self.jobid),
                "log": "%sjobs/%i/log" % (request.url_root, self.jobid),
                "run": self.path,
                "state": "Running",
                "input": json.loads(self.inputobj),
                "output": None}

    def run(self):
        self.stdoutdata, self.stderrdata = self.proc.communicate(self.inputobj)
        if self.proc.returncode == 0:
            outobj = yaml.load(self.stdoutdata, Loader=yaml.FullLoader)
            with self.updatelock:
                self.status["state"] = "Success"
                self.status["output"] = outobj
        else:
            with self.updatelock:
                self.status["state"] = "Failed"

    def getstatus(self):
        with self.updatelock:
            return self.status.copy()

    def cancel(self):
        if self.status["state"] == "Running":
            self.proc.send_signal(signal.SIGQUIT)
            with self.updatelock:
                self.status["state"] = "Canceled"

    def pause(self):
        if self.status["state"] == "Running":
            self.proc.send_signal(signal.SIGTSTP)
            with self.updatelock:
                self.status["state"] = "Paused"

    def resume(self):
        if self.status["state"] == "Paused":
            self.proc.send_signal(signal.SIGCONT)
            with self.updatelock:
                self.status["state"] = "Running"


@app.route("/run", methods=['POST'])
def runworkflow():
    path = request.args["wf"]
    with jobs_lock:
        jobid = len(jobs)
        job = Job(jobid, path, request.stream.read())
        job.start()
        jobs.append(job)
    return redirect("/jobs/%i" % jobid, code=303)


@app.route("/jobs/<int:jobid>", methods=['GET', 'POST'])
def jobcontrol(jobid):
    with jobs_lock:
        job = jobs[jobid]
    if request.method == 'POST':
        action = request.args.get("action")
        if action:
            if action == "cancel":
                job.cancel()
            elif action == "pause":
                job.pause()
            elif action == "resume":
                job.resume()

    status = job.getstatus()
    return json.dumps(status, indent=4), 200, ""


def logspooler(job):
    with open(job.logname, "r") as f:
        while True:
            r = f.read(4096)
            if r:
                yield r
            else:
                with job.updatelock:
                    if job.status["state"] != "Running":
                        break
                time.sleep(1)


@app.route("/jobs/<int:jobid>/log", methods=['GET'])
def getlog(jobid):
    with jobs_lock:
        job = jobs[jobid]
    return Response(logspooler(job))


@app.route("/jobs", methods=['GET'])
def getjobs():
    with jobs_lock:
        jobscopy = copy.copy(jobs)

    def spool(jc):
        yield "["
        first = True
        for j in jc:
            if first:
                yield json.dumps(j.getstatus(), indent=4)
                first = False
            else:
                yield ", " + json.dumps(j.getstatus(), indent=4)
        yield "]"
    return Response(spool(jobscopy))


if __name__ == "__main__":
    # app.debug = True
    app.run()

[((11, 6, 11, 21), 'flask.Flask', 'Flask', ({(11, 12, 11, 20): '__name__'}, {}), '(__name__)', False, 'from flask import Flask, Response, request, redirect\n'), ((13, 12, 13, 28), 'threading.Lock', 'threading.Lock', ({}, {}), '()', False, 'import threading\n'), ((86, 11, 86, 49), 'flask.redirect', 'redirect', (), '', False, 'from flask import Flask, Response, request, redirect\n'), ((23, 26, 23, 42), 'threading.Lock', 'threading.Lock', ({}, {}), '()', False, 'import threading\n'), ((27, 34, 27, 52), 'tempfile.mkstemp', 'tempfile.mkstemp', ({}, {}), '()', False, 'import tempfile\n'), ((94, 17, 94, 43), 'flask.request.args.get', 'request.args.get', ({(94, 34, 94, 42): '"""action"""'}, {}), "('action')", False, 'from flask import Flask, Response, request, redirect\n'), ((104, 11, 104, 39), 'json.dumps', 'json.dumps', (), '', False, 'import json\n'), ((130, 19, 130, 34), 'copy.copy', 'copy.copy', ({(130, 29, 130, 33): 'jobs'}, {}), '(jobs)', False, 'import copy\n'), ((29, 26, 29, 44), 'tempfile.mkdtemp', 'tempfile.mkdtemp', ({}, {}), '()', False, 'import tempfile\n'), ((30, 24, 35, 57), 'subprocess.Popen', 'subprocess.Popen', (), '', False, 'import subprocess\n'), ((47, 21, 47, 71), 'yaml.load', 'yaml.load', (), '', False, 'import yaml\n'), ((83, 31, 83, 52), 'flask.request.stream.read', 'request.stream.read', ({}, {}), '()', False, 'from flask import Flask, Response, request, redirect\n'), ((41, 25, 41, 50), 'json.loads', 'json.loads', ({(41, 36, 41, 49): 'self.inputobj'}, {}), '(self.inputobj)', False, 'import json\n'), ((117, 16, 117, 29), 'time.sleep', 'time.sleep', ({(117, 27, 117, 28): '(1)'}, {}), '(1)', False, 'import time\n')]