def cancel(operation_id):
    # Cancel an operation altogether (only available after pausing)
    operation_id = b64decode_id(operation_id)
    db = get_db()

    operation = db.execute("SELECT * FROM operations WHERE id = ?",
                           (operation_id, )).fetchone()

    if operation and operation["completion"] == "PAUSED":
        db.execute(
            """
            UPDATE operations
            SET completion = ?
            WHERE id = ?
            """,
            ("CANCELLED", operation_id),
        )
        db.commit()
        return {"operation_id": b64encode_id(operation_id), "success": True}
    elif not operation:
        return {
            "operation_id": b64encode_id(operation_id),
            "success": False,
            "message": "Invalid operation ID",
        }
    else:
        return {
            "operation_id": b64encode_id(operation_id),
            "success": False,
            "message": "Operation is not paused",
        }
def resume(operation_id):
    # Resume an operation
    operation_id = b64decode_id(operation_id)
    db = get_db()

    operation = db.execute("SELECT * FROM operations WHERE id = ?",
                           (operation_id, )).fetchone()

    if operation and operation["completion"] == "PAUSED":
        # Load the remaining workflow and the result (so far)
        with open(operation["workflow_store"]) as f:
            workflow = json.load(f)
        with open(operation["result_store"], "r") as f:
            result = json.load(f)
        # Initiate the remaining workflow and pass in the result
        # NOTE: The workflow itself is already tappable so pausing after
        # this point is also possible
        deserialize_chain(workflow).delay(result)

        db.execute(
            """
            UPDATE operations
            SET completion = ?
            WHERE id = ?
            """,
            ("IN PROGRESS", operation_id),
        )
        db.commit()
        return {"operation_id": b64encode_id(operation_id), "success": True}
    elif not operation:
        return {
            "operation_id": b64encode_id(operation_id),
            "success": False,
            "message": "Invalid operation ID",
        }
    else:
        return {
            "operation_id": b64encode_id(operation_id),
            "success": False,
            "message": "Operation is not paused",
        }
def pause(operation_id):
    # Request an operation to pause
    operation_id = b64decode_id(operation_id)
    db = get_db()

    operation = db.execute("SELECT * FROM operations WHERE id = ?",
                           (operation_id, )).fetchone()

    if operation and operation["completion"] == "IN PROGRESS":
        """
        Change operation status to "REQUESTING PAUSE" - next time
        the `app.tappable.pause_or_continue` task is called - it'll know
        it should pause
        """
        db.execute(
            """
            UPDATE operations
            SET completion = ?
            WHERE id = ?
            """,
            ("REQUESTING PAUSE", operation_id),
        )
        db.commit()
        return {"operation_id": b64encode_id(operation_id), "success": True}
    elif not operation:
        return {
            "operation_id": b64encode_id(operation_id),
            "success": False,
            "message": "Invalid operation ID",
        }
    else:
        return {
            "operation_id": b64encode_id(operation_id),
            "success": False,
            "message": "Operation is no longer in progress",
        }
def operation_info(operation_id):
    # Get information about an operation by id
    operation_id = b64decode_id(operation_id)
    db = get_db()

    operation = db.execute("SELECT * FROM operations WHERE id = ?",
                           (operation_id, )).fetchone()

    if operation["completion"] == "COMPLETED":
        # Load the result from json file if operation is complete
        with open(operation["result_store"], "r") as f:
            result = json.load(f)
    else:
        # Otherwise, result should just be an empty string
        result = ""
    return render_template(
        "operations/operation.html",
        operation_id=b64encode_id(operation_id),
        status=operation["completion"],
        result=result,
    )
def start():
    # Start a csv reading + parsing operation
    db = get_db()

    # Insert a record of the operation and grab its id
    operation_id: int = db.execute(
        "INSERT INTO operations (requester_id, completion) VALUES (?, ?)",
        (g.user["id"], "IN PROGRESS"),
    ).lastrowid
    """
    Brief description of the operation
    ----------------------------------
    `read_start` initiates reading from the csv file
    `read_next`, then continues reading from the same file and
    merges its results with the previous read
    `read_finish_continue`, determines whether or not the file has been
    read in full and continues reading accordingly

    Once the iterative reading is finished, `start_parsing` is called
    to start the parsing operation
    """
    csvpath = os.path.join(app.instance_path, "MOCK_DATA.csv")
    # Start the operation, using the usual tappable configuration
    tappable(
        # Chain of data reading + callback to data parsing
        read_start.s(csvpath)
        | read_next.s(csvpath)
        | read_finish_continue.s(start_parsing.s(operation_id), csvpath,
                                 operation_id),
        # Function to check whether or not operation should pause
        should_pause.s(operation_id),
        # Pause handler
        save_state.s(operation_id),
    ).delay()

    db.commit()
    return redirect(
        url_for("operation_info", operation_id=b64encode_id(operation_id)))