diff --git a/printer/print_queue.py b/printer/print_queue.py new file mode 100644 index 0000000..be03119 --- /dev/null +++ b/printer/print_queue.py @@ -0,0 +1,36 @@ +import subprocess +import time +import logging + +class PrintQueue: + _queue = [] + + def add(self, file_name): + self._queue.append(file_name) + + def in_queue(self, file_name): + return file_name in self._queue + + def actual_queue_available(self): + proc = subprocess.Popen( + 'lpstat -o', + shell=True, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True, + ) + + printer_job_count = str(proc.stdout.read()).count("\n") + + return printer_job_count <= 2 + + def feed_into_printer(self): + while True: + time.sleep(1) + + if self._queue.__len__() == 0: + continue + + if self.actual_queue_available(): + logging.info("FED REQUEST INTO PRINTER") + self._queue.pop(0) diff --git a/printer/server.py b/printer/server.py index 1783831..a0ebfe1 100644 --- a/printer/server.py +++ b/printer/server.py @@ -7,6 +7,8 @@ import time import uuid import collector +import asyncio +from print_queue import PrintQueue from fastapi import FastAPI, File, Form, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware @@ -17,6 +19,8 @@ from metrics import MetricsHandler +print_queue = PrintQueue() +printer_lock = asyncio.Lock() metrics_handler = MetricsHandler.instance() app = FastAPI() @@ -35,7 +39,6 @@ logging.getLogger("uvicorn.access").setLevel(logging.WARNING) logging.getLogger("uvicorn.error").setLevel(logging.WARNING) - def get_args() -> argparse.Namespace: parser = argparse.ArgumentParser() parser.add_argument( @@ -113,6 +116,7 @@ def send_file_to_printer( PRINTER_NAME = os.environ.get("RIGHT_PRINTER_NAME") command = f"lp -n {num_copies} {maybe_page_range} -o sides={sides} -o media=na_letter_8.5x11in -d {PRINTER_NAME} {file_path}" metrics_handler.print_jobs_recieved.inc() + if args.development: logging.warning( f"server is in development mode, command would've been `{command}`" @@ -126,6 +130,7 @@ def send_file_to_printer( stderr=subprocess.PIPE, text=True, ) + print_job.wait() if print_job.returncode != 0: @@ -145,16 +150,14 @@ def send_file_to_printer( # with code 0 but the output could not be parsed for a job id. return '' - def maybe_delete_pdf(file_path): if args.dont_delete_pdfs: logging.info( - f"--dont-delete-pdfs is set, skipping deletion of file {file_path}" + f"--dont-delete-pdfs is set, skipping deletion of file {file_path}" ) return pathlib.Path(file_path).unlink() - @app.get("/healthcheck/printer") def api(): metrics_handler.last_health_check_request.set(int(time.time())) @@ -178,30 +181,42 @@ async def read_item( "sides": string value from user input on clark frontend; we insert this into the lp command, } """ - try: - base = pathlib.Path("/tmp") - file_id = str(uuid.uuid4()) - file_path = str(base / file_id) - with open(file_path, "wb") as f: - f.write(await file.read()) - print_id = send_file_to_printer( - str(file_path), - copies, - sides=sides, - ) + async with printer_lock: + if not args.development and not print_queue.actual_queue_available(): + print_queue.add(file.filename) + timeout = 0 + while print_queue.in_queue(file.filename): + timeout += 1 - maybe_delete_pdf(file_path) + if timeout > 300: + raise Exception("/print TIMED OUT AFTER 300 SECONDS WHILE WAITING IN THE PRINTER QUEUE") + + await asyncio.sleep(1) - if not args.development and print_id is None: - raise Exception("unable to extract print id from print request") - return {"print_id": print_id} - except Exception: - logging.exception("printing failed!") - return HTTPException( - status_code=500, - detail="printing failed, check logs", - ) + try: + base = pathlib.Path("/tmp") + file_id = str(uuid.uuid4()) + file_path = str(base / file_id) + with open(file_path, "wb") as f: + f.write(await file.read()) + print_id = send_file_to_printer( + str(file_path), + copies, + sides=sides, + ) + maybe_delete_pdf(file_path) + + if not args.development and print_id is None: + raise Exception("unable to extract print id from print request") + return {"print_id": print_id} + except Exception: + logging.exception("printing failed!") + return HTTPException( + status_code=500, + detail="printing failed, check logs", + ) + # we have a separate __name__ check here due to how FastAPI starts # a server. the file is first ran (where __name__ == "__main__") @@ -212,6 +227,13 @@ async def read_item( # the thread interacts with an instance different than the one the # server uses if __name__ == "server": + if not args.development: + queue_thread = threading.Thread( + target=print_queue.feed_into_printer, + daemon=True + ) + queue_thread.start() + if not args.development: # set the last time we opened an ssh tunnel to now because # when the script runs for the first time, we did so in what.sh