Add support for running jobs with specified fillament
This commit is contained in:
parent
f995ccd113
commit
095ab9746f
14 changed files with 134 additions and 35 deletions
|
@ -21,15 +21,21 @@ from tentacles.db import Db
|
|||
from tentacles.globals import _ctx, Ctx, ctx
|
||||
from tentacles import workers
|
||||
from contextlib import closing
|
||||
from tentacles.blueprints.util import salt
|
||||
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def db_factory(app):
|
||||
store = Db(
|
||||
Path(
|
||||
p = Path(
|
||||
app.config["ROOT_FOLDER"],
|
||||
app.config.get("db", {}).get("uri"),
|
||||
)
|
||||
)
|
||||
|
||||
# log.info("Using db from %r", p)
|
||||
|
||||
store = Db(p)
|
||||
store.connect()
|
||||
return store
|
||||
|
||||
|
@ -117,6 +123,8 @@ def serve(hostname: str, port: int, config: Path, trace: bool):
|
|||
if trace:
|
||||
logging.getLogger("tentacles.db").setLevel(logging.TRACE)
|
||||
|
||||
logging.getLogger("aiosql.adapters.sqlite3").setLevel(logging.TRACE)
|
||||
|
||||
app = make_app()
|
||||
|
||||
if config:
|
||||
|
@ -124,8 +132,6 @@ def serve(hostname: str, port: int, config: Path, trace: bool):
|
|||
app.config.update(tomllib.load(fp))
|
||||
app.config["ROOT_FOLDER"] = str(Path(config).absolute().parent)
|
||||
|
||||
print(app.config)
|
||||
|
||||
# Run migrations once at startup rather than when connecting
|
||||
with closing(db_factory(app)) as db:
|
||||
db.migrate()
|
||||
|
|
|
@ -99,10 +99,14 @@ def create_file(location: Optional[str] = None):
|
|||
if os.path.exists(sanitized_path):
|
||||
return {"error": "file exists already"}, 409
|
||||
|
||||
# FIXME: Explicitly interpolating the path here kinda rots
|
||||
file.save(
|
||||
sanitized_path.replace("$ROOT_FOLDER", current_app.config["ROOT_FOLDER"])
|
||||
real_path = sanitized_path.replace(
|
||||
"$ROOT_FOLDER", current_app.config["ROOT_FOLDER"]
|
||||
)
|
||||
|
||||
print(file.filename, real_path)
|
||||
|
||||
# FIXME: Explicitly interpolating the path here kinda rots
|
||||
file.save(real_path)
|
||||
row = ctx.db.create_file(
|
||||
uid=ctx.uid,
|
||||
filename=file.filename,
|
||||
|
@ -110,7 +114,12 @@ def create_file(location: Optional[str] = None):
|
|||
)
|
||||
|
||||
if request.form.get("print", "").lower() == "true":
|
||||
ctx.db.create_job(uid=ctx.uid, fid=row.id)
|
||||
ctx.db.create_job(
|
||||
uid=ctx.uid,
|
||||
fid=row.id,
|
||||
cid=None,
|
||||
pid=None,
|
||||
)
|
||||
|
||||
return {"status": "ok"}, 202
|
||||
|
||||
|
|
|
@ -43,7 +43,10 @@ def manipulate_files():
|
|||
return render_template("files.html.j2"), code
|
||||
|
||||
case "download":
|
||||
file = ctx.db.fetch_file(uid=ctx.uid, fid=int(request.form.get("file_id")))
|
||||
file = ctx.db.fetch_file(
|
||||
uid=ctx.uid,
|
||||
fid=int(request.form.get("file_id")),
|
||||
)
|
||||
if file:
|
||||
return send_file(
|
||||
file.path,
|
||||
|
|
|
@ -18,6 +18,11 @@ log = logging.getLogger(__name__)
|
|||
BLUEPRINT = Blueprint("jobs", __name__)
|
||||
|
||||
|
||||
def maybe(f, x):
|
||||
if x is not None:
|
||||
return f(x)
|
||||
|
||||
|
||||
@BLUEPRINT.route("/jobs", methods=["GET"])
|
||||
@requires_auth
|
||||
def list_jobs():
|
||||
|
@ -29,7 +34,12 @@ def list_jobs():
|
|||
def manipulate_jobs():
|
||||
match request.form.get("action"):
|
||||
case "enqueue":
|
||||
ctx.db.create_job(uid=ctx.uid, fid=int(request.form.get("file_id")))
|
||||
job = ctx.db.create_job(
|
||||
uid=ctx.uid,
|
||||
fid=int(request.form.get("file_id")),
|
||||
cid=maybe(int, request.form.get("color_id")),
|
||||
pid=maybe(int, request.form.get("printer_id")),
|
||||
)
|
||||
flash("Job created!", category="info")
|
||||
|
||||
case "duplicate":
|
||||
|
|
|
@ -165,7 +165,13 @@ class Db(Queries):
|
|||
################################################################################
|
||||
# Wrappers for doing Python type mapping
|
||||
|
||||
def create_key(self, *, uid: int, name: str, ttl: Optional[timedelta]):
|
||||
def create_key(
|
||||
self,
|
||||
*,
|
||||
uid: int,
|
||||
name: str,
|
||||
ttl: Optional[timedelta],
|
||||
):
|
||||
return super().create_key(
|
||||
uid=uid,
|
||||
name=name,
|
||||
|
@ -173,7 +179,11 @@ class Db(Queries):
|
|||
)
|
||||
|
||||
def try_login(
|
||||
self, *, username: str, password: str, ttl: timedelta
|
||||
self,
|
||||
*,
|
||||
username: str,
|
||||
password: str,
|
||||
ttl: timedelta,
|
||||
) -> Optional[str]:
|
||||
"""Given a username and an (unsecured) password, attempt to authenticate the named user.
|
||||
|
||||
|
@ -190,7 +200,13 @@ class Db(Queries):
|
|||
return self.create_key(uid=res.id, name="web session", ttl=ttl)
|
||||
|
||||
def try_create_user(
|
||||
self, *, username: str, email: str, password: str, gid: int = 1, sid: int = -3
|
||||
self,
|
||||
*,
|
||||
username: str,
|
||||
email: str,
|
||||
password: str,
|
||||
gid: int = 1,
|
||||
sid: int = -3,
|
||||
):
|
||||
digest = sha3_256()
|
||||
digest.update(password.encode("utf-8"))
|
||||
|
@ -202,6 +218,25 @@ class Db(Queries):
|
|||
sid=sid,
|
||||
)
|
||||
|
||||
def force_create_user(
|
||||
self,
|
||||
*,
|
||||
username: str,
|
||||
email: str,
|
||||
password: str,
|
||||
gid: int = 1,
|
||||
sid: int = -3,
|
||||
):
|
||||
digest = sha3_256()
|
||||
digest.update(password.encode("utf-8"))
|
||||
return super().force_create_user(
|
||||
name=username,
|
||||
email=email,
|
||||
hash=digest.hexdigest(),
|
||||
gid=gid,
|
||||
sid=sid,
|
||||
)
|
||||
|
||||
def refresh_key(self, *, kid: str, ttl: timedelta):
|
||||
"""Automagically renew an API key which is still in use.
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ WHERE
|
|||
id = :cid
|
||||
;
|
||||
|
||||
-- name: list-color
|
||||
-- name: list-colors
|
||||
SELECT *
|
||||
FROM filament_color
|
||||
;
|
||||
|
|
|
@ -29,17 +29,27 @@ CREATE TABLE IF NOT EXISTS jobs (
|
|||
-- name: migration-0001-jobs-add-print-time#
|
||||
ALTER TABLE jobs ADD COLUMN time_left INTEGER DEFAULT (0);
|
||||
|
||||
-- name: migration-0002-jobs-add-print-color#
|
||||
ALTER TABLE jobs ADD COLUMN color_id INTEGER DEFAULT (0);
|
||||
|
||||
-- name: migration-0003-jobs-add-queued-at#
|
||||
ALTER TABLE jobs ADD COLUMN queued_at TEXT DEFAULT (datetime('now'));
|
||||
|
||||
-- name: create-job^
|
||||
INSERT INTO jobs (
|
||||
user_id
|
||||
, file_id
|
||||
, color_id
|
||||
, printer_id
|
||||
)
|
||||
VALUES (
|
||||
:uid
|
||||
, :fid
|
||||
, :cid
|
||||
, :pid
|
||||
)
|
||||
RETURNING
|
||||
id
|
||||
*
|
||||
;
|
||||
-- name: fetch-job^
|
||||
SELECT
|
||||
|
@ -73,6 +83,7 @@ WHERE
|
|||
SELECT
|
||||
j.id as id
|
||||
, j.file_id
|
||||
, coalesce(j.color_id, fa.color_id) as color_id
|
||||
, fa.id as analysis_id
|
||||
, fa.max_x
|
||||
, fa.max_y
|
||||
|
@ -81,7 +92,6 @@ SELECT
|
|||
, fa.max_end
|
||||
, fa.nozzle_diameter
|
||||
, fa.filament_id
|
||||
, fa.color_id
|
||||
, (SELECT name FROM filament WHERE id = fa.filament_id) AS filament_name
|
||||
, (SELECT name AS name FROM filament_color WHERE id = fa.color_id) AS color_name
|
||||
, (SELECT name FROM job_statuses WHERE id = j.status_id) AS status
|
||||
|
|
|
@ -71,7 +71,6 @@ INSERT OR IGNORE INTO filament (name) VALUES ('PETG');
|
|||
|
||||
ALTER TABLE printers ADD filament_id INTEGER REFERENCES filament(id) DEFAULT 1;
|
||||
|
||||
|
||||
-- name: migration-0004-create-printer-enabled#
|
||||
ALTER TABLE printers ADD enabled BOOLEAN DEFAULT TRUE;
|
||||
|
||||
|
@ -81,17 +80,24 @@ ALTER TABLE printers ADD nozzle_diameter FLOAT DEFAULT 0.4;
|
|||
-- name: migration-0006-create-printer-level-date#
|
||||
ALTER TABLE printers ADD last_level_date TEXT DEFAULT NULL;
|
||||
|
||||
-- name: migration-0006-create-printer-filament-color#
|
||||
-- name: migration-0007-create-printer-filament-color#
|
||||
ALTER TABLE printers ADD color_id INTEGER REFERENCES filament_color(id) DEFAULT 1;
|
||||
|
||||
-- name: try-create-printer^
|
||||
INSERT INTO printers (
|
||||
name
|
||||
, url
|
||||
, stream_url
|
||||
, api_key
|
||||
, status_id
|
||||
)
|
||||
VALUES (:name, :url, :api_key, :sid)
|
||||
VALUES (
|
||||
:name
|
||||
, :url
|
||||
, :stream_url
|
||||
, :api_key
|
||||
, :sid
|
||||
)
|
||||
RETURNING
|
||||
id
|
||||
;
|
||||
|
|
|
@ -81,7 +81,7 @@ WHERE
|
|||
OR u.group_id = 0) -- or the user is a root
|
||||
;
|
||||
|
||||
-- name: refresh-key
|
||||
-- name: refresh-key!
|
||||
UPDATE user_keys
|
||||
SET
|
||||
expiration = :expiration
|
||||
|
@ -89,7 +89,7 @@ WHERE
|
|||
id = :kid
|
||||
;
|
||||
|
||||
-- name: delete-key
|
||||
-- name: delete-key!
|
||||
DELETE FROM user_keys
|
||||
WHERE
|
||||
user_id = :uid
|
||||
|
|
|
@ -43,8 +43,20 @@ CREATE TABLE IF NOT EXISTS users (
|
|||
, UNIQUE(email)
|
||||
);
|
||||
|
||||
-- name: force-create-user^
|
||||
INSERT OR REPLACE INTO users (
|
||||
name
|
||||
, email
|
||||
, hash
|
||||
, group_id
|
||||
, status_id
|
||||
)
|
||||
VALUES (:name, :email, :hash, :gid, :sid)
|
||||
RETURNING *
|
||||
;
|
||||
|
||||
-- name: try-create-user^
|
||||
INSERT INTO users (
|
||||
INSERT OR IGNORE INTO users (
|
||||
name
|
||||
, email
|
||||
, hash
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
<div class="three columns">
|
||||
<label for="filament">Filament color</label>
|
||||
<select name="color_id">
|
||||
{% for c in ctx.db.list_color() %}
|
||||
{% for c in ctx.db.list_colors() %}
|
||||
<option value="{{ c.id }}" {% if printer.color_id == c.id %}selected{%endif%}>{{ c.name or c.code }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
|
|
@ -26,7 +26,7 @@
|
|||
</div>
|
||||
<div class="controls u-flex u-ml-auto u-mv-auto">
|
||||
{{ macros.download_file(file.id) }}
|
||||
{{ macros.start_job(file.id) }}
|
||||
{{ macros.start_job(file) }}
|
||||
{{ macros.delete_file(file.id) }}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
{# #################################################################################################### #}
|
||||
{# Job CRUD #}
|
||||
{% macro start_job(id) %}
|
||||
{% macro start_job(file) %}
|
||||
<form class="inline" method="post" action="/jobs">
|
||||
<input type="hidden" name="action" value="enqueue" />
|
||||
<input type="hidden" name="file_id" value="{{ id }}" />
|
||||
<input type="hidden" name="file_id" value="{{ file.id }}" />
|
||||
<select name="color_id">
|
||||
{% for c in ctx.db.list_colors() %}
|
||||
<option value="{{c.id}}" {% if file.color_id == c.id %}selected{%endif%}>{{c.name}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input id="submit" type="image" src="/static/print.svg" height="24" width="24" />
|
||||
</form>
|
||||
{% endmacro %}
|
||||
|
|
|
@ -186,14 +186,15 @@ def push_jobs(app: App, db: Db) -> None:
|
|||
continue
|
||||
|
||||
try:
|
||||
# FIXME: Add a helper for this replacement?
|
||||
p = file.path.replace("$ROOT_FOLDER", app.config["ROOT_FOLDER"])
|
||||
|
||||
# Nuke the file if it exists just in case
|
||||
if client.files_info("local", Path(file.path).name):
|
||||
client.delete(f"local/{Path(file.path).name}")
|
||||
if client.files_info("local", Path(p).name):
|
||||
client.delete(f"local/{Path(p).name}")
|
||||
|
||||
# FIXME: Explicitly interpolating the path here kinda rots
|
||||
client.upload(
|
||||
file.path.replace("$ROOT_FOLDER", app.config["ROOT_FOLDER"])
|
||||
)
|
||||
client.upload(p)
|
||||
|
||||
except HTTPError as e:
|
||||
if e.response.status_code == 409:
|
||||
|
@ -349,7 +350,9 @@ def send_emails(app, db: Db):
|
|||
|
||||
def analyze_files(app: App, db: Db):
|
||||
for file in db.list_unanalyzed_files():
|
||||
p = Path(file.path)
|
||||
# FIXME: Add a helper for this replacement?
|
||||
p = Path(file.path.replace("$ROOT_FOLDER", app.config["ROOT_FOLDER"]))
|
||||
|
||||
if not p.is_file():
|
||||
log.error(f"Deleting missing file {file.id}!")
|
||||
db.delete_file(uid=file.user_id, fid=file.id)
|
||||
|
|
Loading…
Reference in a new issue