From 095ab9746f2405c78c65eb786dac4464f794ed20 Mon Sep 17 00:00:00 2001 From: Reid D McKenzie Date: Thu, 6 Feb 2025 02:15:45 -0700 Subject: [PATCH] Add support for running jobs with specified fillament --- projects/tentacles/src/tentacles/__main__.py | 20 +++++---- .../tentacles/src/tentacles/blueprints/api.py | 17 ++++++-- .../src/tentacles/blueprints/file_ui.py | 5 ++- .../src/tentacles/blueprints/job_ui.py | 12 +++++- projects/tentacles/src/tentacles/db.py | 41 +++++++++++++++++-- .../tentacles/src/tentacles/sql/color.sql | 2 +- projects/tentacles/src/tentacles/sql/jobs.sql | 14 ++++++- .../tentacles/src/tentacles/sql/printers.sql | 12 ++++-- .../tentacles/src/tentacles/sql/user_keys.sql | 4 +- .../tentacles/src/tentacles/sql/users.sql | 14 ++++++- .../tentacles/templates/edit_printer.html.j2 | 2 +- .../tentacles/templates/files_list.html.j2 | 2 +- .../src/tentacles/templates/macros.html.j2 | 9 +++- projects/tentacles/src/tentacles/workers.py | 15 ++++--- 14 files changed, 134 insertions(+), 35 deletions(-) diff --git a/projects/tentacles/src/tentacles/__main__.py b/projects/tentacles/src/tentacles/__main__.py index 2834899..53d1186 100644 --- a/projects/tentacles/src/tentacles/__main__.py +++ b/projects/tentacles/src/tentacles/__main__.py @@ -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( - app.config["ROOT_FOLDER"], - app.config.get("db", {}).get("uri"), - ) + 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() diff --git a/projects/tentacles/src/tentacles/blueprints/api.py b/projects/tentacles/src/tentacles/blueprints/api.py index b728b9b..1f9b9a5 100644 --- a/projects/tentacles/src/tentacles/blueprints/api.py +++ b/projects/tentacles/src/tentacles/blueprints/api.py @@ -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 diff --git a/projects/tentacles/src/tentacles/blueprints/file_ui.py b/projects/tentacles/src/tentacles/blueprints/file_ui.py index 377673b..b5f05da 100644 --- a/projects/tentacles/src/tentacles/blueprints/file_ui.py +++ b/projects/tentacles/src/tentacles/blueprints/file_ui.py @@ -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, diff --git a/projects/tentacles/src/tentacles/blueprints/job_ui.py b/projects/tentacles/src/tentacles/blueprints/job_ui.py index 7d3c5fc..05a5b07 100644 --- a/projects/tentacles/src/tentacles/blueprints/job_ui.py +++ b/projects/tentacles/src/tentacles/blueprints/job_ui.py @@ -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": diff --git a/projects/tentacles/src/tentacles/db.py b/projects/tentacles/src/tentacles/db.py index a374296..dafb25b 100644 --- a/projects/tentacles/src/tentacles/db.py +++ b/projects/tentacles/src/tentacles/db.py @@ -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. diff --git a/projects/tentacles/src/tentacles/sql/color.sql b/projects/tentacles/src/tentacles/sql/color.sql index bd0a403..9609cf2 100644 --- a/projects/tentacles/src/tentacles/sql/color.sql +++ b/projects/tentacles/src/tentacles/sql/color.sql @@ -54,7 +54,7 @@ WHERE id = :cid ; --- name: list-color +-- name: list-colors SELECT * FROM filament_color ; diff --git a/projects/tentacles/src/tentacles/sql/jobs.sql b/projects/tentacles/src/tentacles/sql/jobs.sql index 81bcfc5..ea737f1 100644 --- a/projects/tentacles/src/tentacles/sql/jobs.sql +++ b/projects/tentacles/src/tentacles/sql/jobs.sql @@ -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 diff --git a/projects/tentacles/src/tentacles/sql/printers.sql b/projects/tentacles/src/tentacles/sql/printers.sql index a7233d6..a2bed9b 100644 --- a/projects/tentacles/src/tentacles/sql/printers.sql +++ b/projects/tentacles/src/tentacles/sql/printers.sql @@ -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 ; diff --git a/projects/tentacles/src/tentacles/sql/user_keys.sql b/projects/tentacles/src/tentacles/sql/user_keys.sql index 9d841ef..d418b63 100644 --- a/projects/tentacles/src/tentacles/sql/user_keys.sql +++ b/projects/tentacles/src/tentacles/sql/user_keys.sql @@ -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 diff --git a/projects/tentacles/src/tentacles/sql/users.sql b/projects/tentacles/src/tentacles/sql/users.sql index dab1464..ff431c6 100644 --- a/projects/tentacles/src/tentacles/sql/users.sql +++ b/projects/tentacles/src/tentacles/sql/users.sql @@ -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 diff --git a/projects/tentacles/src/tentacles/templates/edit_printer.html.j2 b/projects/tentacles/src/tentacles/templates/edit_printer.html.j2 index cecad76..0f13eb6 100644 --- a/projects/tentacles/src/tentacles/templates/edit_printer.html.j2 +++ b/projects/tentacles/src/tentacles/templates/edit_printer.html.j2 @@ -47,7 +47,7 @@
diff --git a/projects/tentacles/src/tentacles/templates/files_list.html.j2 b/projects/tentacles/src/tentacles/templates/files_list.html.j2 index 283bce6..8872f02 100644 --- a/projects/tentacles/src/tentacles/templates/files_list.html.j2 +++ b/projects/tentacles/src/tentacles/templates/files_list.html.j2 @@ -26,7 +26,7 @@
{{ macros.download_file(file.id) }} - {{ macros.start_job(file.id) }} + {{ macros.start_job(file) }} {{ macros.delete_file(file.id) }}
diff --git a/projects/tentacles/src/tentacles/templates/macros.html.j2 b/projects/tentacles/src/tentacles/templates/macros.html.j2 index 62c3667..a003ba0 100644 --- a/projects/tentacles/src/tentacles/templates/macros.html.j2 +++ b/projects/tentacles/src/tentacles/templates/macros.html.j2 @@ -1,9 +1,14 @@ {# #################################################################################################### #} {# Job CRUD #} -{% macro start_job(id) %} +{% macro start_job(file) %}
- + +
{% endmacro %} diff --git a/projects/tentacles/src/tentacles/workers.py b/projects/tentacles/src/tentacles/workers.py index f47743b..f3659a7 100644 --- a/projects/tentacles/src/tentacles/workers.py +++ b/projects/tentacles/src/tentacles/workers.py @@ -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)