12 Commits

Author SHA1 Message Date
n07070
070f037449 Apply formating, light linting 2026-11-13 19:51:32 +01:00
n07070
adb5e9422d Add limits to API calls 2026-11-13 19:48:05 +01:00
n07070
d348acd88c Add Printers information via the API 2026-11-13 19:47:52 +01:00
n07070
e8568795b2 Configuration max text length via config file 2026-11-13 19:47:36 +01:00
n07070
086a7ae640 Make max text length configurable 2026-11-13 19:46:16 +01:00
n07070
836d6b0be9 Update configuration options in the web methods 2026-11-13 19:16:56 +01:00
n07070
8309399827 Update web interface for configuration options 2026-11-13 19:16:46 +01:00
n07070
b50b9abc22 Add more configuration options to the printers 2026-11-13 19:16:13 +01:00
n07070
d0f4ba8f01 Add more configuration and printing options 2026-11-13 19:15:59 +01:00
n07070
3fc41f806a Update configuration file to add printer specifics 2026-11-13 19:15:32 +01:00
n07070
9830917b1a Remove image after printing for brother-ql 2026-11-13 03:05:17 +01:00
n07070
ba7e854a6f Match case for printer type discovery 2026-11-13 03:01:21 +01:00
11 changed files with 318 additions and 135 deletions

View File

@@ -1,11 +1,28 @@
# Configuration file the LittlePrynter
[defaults]
signature = "Anonymous"
signature = ""
# Printer settings
[printer]
upload_folder = "src/static/uploads"
# How long do we wait before giving up on waiting for a printer connexion
waiting_time = 5
[printer.epson]
# Configurations for EPSON printers
profile = "TM-T20III"
label_type = 80 # 80 or 58 ( label width in mm )
align_text = "center"
text_max_length = 4096
signature_max_length = 256
[printer.brother]
# Configurations for Brother printers
label_type = "62" # See https://github.com/pklaus/brother_ql#usage for label types
text_max_length = 4096
signature_max_length = 256
# Raspberry Pi Configuration
[rpi]

View File

@@ -5,8 +5,25 @@ signature = "Anonymous"
# Printer settings
[printer]
vendor_id = "0x04b8"
device_id = "0x0e28"
# Where do we upload the pictures and such
upload_folder = "src/static/uploads"
# How long do we wait before giving up on waiting for a printer connexion
waiting_time = 5
[printer.epson]
# Configurations for EPSON printers
profile = "TM-T20III"
label_type = 80 # 80 or 58 ( label width in mm )
align_text = "center"
text_max_length = 4096
signature_max_length = 256
[printer.brother]
# Configurations for Brother printers
label_type = 62 # See https://github.com/pklaus/brother_ql#usage for label types
text_max_length = 4096
signature_max_length = 256
# Raspberry Pi Configuration
[rpi]

View File

@@ -111,7 +111,7 @@ web = Web(app, print_queue)
# Create a pool of printers
try:
# comment: Find all printers connected to the system
printers_pool = Printers(app)
printers_pool = Printers(app, configuration_file)
except Exception as e:
app.logger.error("Could not create a pool of printers because %s", str(e))
sys.exit(-1)
@@ -179,7 +179,13 @@ def web_print_sms():
do_cut = False
try:
web.print_sms(txt, sign, do_cut)
# Clamp value between 1 and
many = max(1, min(int(request.form["many"]), 20))
except werkzeug.exceptions.BadRequestKeyError:
many = 1
try:
web.print_sms(txt, sign, do_cut, many)
except RuntimeError as e:
app.logger.error("Whoops, we could not print an SMS because : %s ", str(e))
flash("Whoops, we could not print an SMS because :" + str(e), "error")
@@ -233,9 +239,15 @@ def web_print_img():
except werkzeug.exceptions.BadRequestKeyError:
process_image = False
try:
# Clamp value between 1 and
many = max(1, min(int(request.form["many"]), 20))
except werkzeug.exceptions.BadRequestKeyError:
many = 1
try:
app.logger.debug("Sending the image to the printer.")
web.print_image(file, sign, do_cut, process_image)
web.print_image(file, sign, do_cut, process_image, many)
except RuntimeError as e:
app.logger.error("The image could not be printed because : %s ", str(e))
flash("The image could not be printed because : " + str(e), "error")
@@ -286,9 +298,15 @@ def api_print_sms():
except werkzeug.exceptions.BadRequestKeyError:
do_cut = False
try:
# Clamp value between 1 and
many = max(1, min(int(request.form["many"]), 20))
except werkzeug.exceptions.BadRequestKeyError:
many = 1
try:
# comment: We try to print the SMS
web.print_sms(txt, sign, do_cut)
web.print_sms(txt, sign, do_cut, many)
except RuntimeError as e:
return str(e), 500
# end try
@@ -331,9 +349,15 @@ def api_print_image():
except werkzeug.exceptions.BadRequestKeyError:
process_image = False
try:
# Clamp value between 1 and
many = max(1, min(int(request.form["many"]), 20))
except werkzeug.exceptions.BadRequestKeyError:
many = 1
try:
app.logger.debug("Sending the image to the printer.")
web.print_image(file, sign, do_cut, process_image)
web.print_image(file, sign, do_cut, process_image, many)
except RuntimeError as e:
return str(e), 500
@@ -342,6 +366,7 @@ def api_print_image():
# TODO: This might not depend on the Raspberry Pi
@app.route("/api/camera/picture", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def camera_picture():
"""Returns a picture taken by the camera on a raspberry pi"""
if RASPBERRY_PI_CONNECTED:
@@ -354,24 +379,28 @@ def camera_picture():
@app.route("/api/queue", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_queue_status():
"""API endpoint for entire queue"""
return jsonify(web.get_queue_state())
@app.route("/api/queue/completed", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_queue_completed():
"""API endpoint that returns the finished tasks"""
return jsonify(web.get_queue_completed())
@app.route("/api/worker", methods=["GET"])
@limiter.limit("6/minute", override_defaults=False)
def api_worker_state():
"""API endpoint to get the worker state"""
return jsonify(worker.current_state())
@app.route("/api/worker/start")
@limiter.limit("6/minute", override_defaults=False)
def api_worker_start():
"""
Enable to worker. This starts to process the print queue.
@@ -381,6 +410,7 @@ def api_worker_start():
@app.route("/api/worker/stop")
@limiter.limit("6/minute", override_defaults=False)
def api_worker_stop():
"""
Stops the print queue. This stops the processing of the print queue.
@@ -390,6 +420,7 @@ def api_worker_stop():
@app.route("/api/printers")
@limiter.limit("6/minute", override_defaults=False)
def api_printers_info():
"""Returns the current state of every printers"""
return jsonify(printers_pool.printers_info())

View File

@@ -10,9 +10,9 @@ import time
from enum import Enum
import uuid
import threading
import usb.core
from pathlib import Path
from uuid import uuid4
import usb.core
from PIL import Image, ImageEnhance, ImageDraw, ImageFont
import numpy as np
@@ -107,7 +107,7 @@ class EscPosPrinter(Printer):
Create a new ESC/POS based printer.
"""
def __init__(self, app, vendor_id, device_id):
def __init__(self, app, vendor_id, device_id, configuration_file):
"""
Create a connexion to a ESC/POS Printer via USB,
Making sure the printer is alive,
@@ -119,11 +119,14 @@ class EscPosPrinter(Printer):
self.usb_args = {}
self.usb_args["idVendor"] = self.vendor_id
self.usb_args["idProduct"] = self.device_id
self.configuration_file = configuration_file
try:
# This also calls open(), which we need to close()
# or else the device will appear as busy.
p = escpos.printer.Usb(self.vendor_id, self.device_id, 0, profile="TM-P80")
p = escpos.printer.Usb(
self.vendor_id, self.device_id, 0, profile=configuration_file["profile"]
)
except escpos.exceptions.DeviceNotFoundError as e:
self.app.logger.error(
"The USB device is not plugged in : %s",
@@ -140,9 +143,21 @@ class EscPosPrinter(Printer):
except Exception as e:
raise e
self.printer_info = PrinterInfo(
identifier=configuration_file["profile"],
backend="pyusb",
model=configuration_file["profile"],
protocol="ESC/POS",
vendor_id=self.vendor_id,
product_id=self.device_id,
serial_number="",
label_type=self.configuration_file["label_type"],
)
# Setting up the printing options.
# TODO : Expose this to the web interface
p.set(
align="center",
align=self.configuration_file["align_text"],
font="a",
bold=False,
underline=0,
@@ -201,25 +216,25 @@ class EscPosPrinter(Printer):
clean_signature = str(signature)
# Make checks on the size of the message being printed
# TODO: make the max length configurable
if len(clean_msg) > 4096:
if len(clean_msg) > int(self.configuration_file["text_max_length"]):
self.app.logger.warning(
"Could not print message of this length: " + str(len(clean_msg))
)
raise RuntimeError(
"Could not print message of this length :"
+ str(len(clean_msg))
+ ", needs to be below 4096 caracters long."
+ ", needs to be below %s caracters long.",
str(self.configuration_file["text_max_length"]),
)
# TODO: make the max length configurable
if len(signature) > 256:
if len(signature) > int(self.configuration_file["signature_max_length"]):
self.app.logger.warning(
"Could not print signature of this length: " + str(len(clean_signature))
)
raise RuntimeError(
"Could not print signature of this length :"
+ str(len(clean_signature))
+ ", needs to be below 256 caracters long."
+ ", needs to be below %s caracters long.",
str(self.configuration_file["signature_max_length"]),
)
# Do the actual printing
@@ -244,15 +259,15 @@ class EscPosPrinter(Printer):
def _print_img(self, path, signature="", center=True, process=False):
clean_signature = str(signature)
# TODO: make the max length configurable
if len(signature) > 256:
if len(signature) > int(self.configuration_file["signature_max_length"]):
self.app.logger.warning(
"Could not print signature of this length: " + str(len(clean_signature))
)
raise ValueError(
raise RuntimeError(
"Could not print signature of this length :"
+ str(len(clean_signature))
+ ", needs to be below 256 caracters long."
+ ", needs to be below %s caracters long.",
str(self.configuration_file["signature_max_length"]),
)
if not os.path.isfile(str(path)):
@@ -287,22 +302,19 @@ class EscPosPrinter(Printer):
except Exception as e:
self.app.logger.error(str(e))
raise RuntimeError("Could not print the picture") from e
finally:
try:
os.remove(path)
os.remove(processed_path)
except OSError as e:
raise e
self.app.logger.debug("Removed image : " + str(processed_path))
self.app.logger.debug("Removed image : " + str(path))
# finally:
# try:
# os.remove(path)
# os.remove(processed_path)
# self.app.logger.debug("Removed image : " + str(processed_path))
# self.app.logger.debug("Removed image : " + str(path))
# except OSError as e:
# raise e
try:
self.printer.close()
except Exception as e:
self.app.logger.error(
"Could not close the printer connexion %s", str(e)
)
self.app.logger.error("Could not close the printer connexion %s", str(e))
raise RuntimeError("Could not close the printer connexion. ") from e
self.app.logger.info("Printed a picture")
@@ -392,7 +404,7 @@ class BrotherPrinter(Printer):
Manages connexion and capabilities of a BrotherQL Printer
"""
def __init__(self, app, vendor_id, device_id):
def __init__(self, app, vendor_id, device_id, configuration_file):
super().__init__(app, vendor_id, device_id, printer_type=PrinterType.BROTHER)
self.printer = None
@@ -446,7 +458,9 @@ class BrotherPrinter(Printer):
vendor_id=vendor_id,
product_id=self.device_id,
serial_number=serial_number,
label_type="62",
label_type=configuration_file[
"label_type"
], # TODO: Implement automatic label type detection : https://framagit.org/stickoeur/diagnostickoeur/-/blob/no-masters/printit.py?ref_type=heads#L239
)
self.app.logger.debug("Created a Brother printer !")
self.ready = True
@@ -462,10 +476,16 @@ class BrotherPrinter(Printer):
self._print_img(text_to_image(msg))
self._print_img(text_to_image(signature))
def _print_img(self, data, signature="", process=False):
def _print_img(
self,
data,
signature="",
process=False,
):
"""
Print a raster image via a Brother QL printer
"""
self.app.logger.debug("Printing an image %s via Brother printer", data)
if process:
try:
self.app.logger.debug("Proccessing the image")
@@ -478,6 +498,7 @@ class BrotherPrinter(Printer):
else:
processed_path = data
self.app.logger.warning("Not proccessing the image")
try:
selected_backend = guess_backend(self.printer_info.identifier)
BACKEND_CLASS = backend_factory(selected_backend)["backend_class"]
@@ -491,33 +512,93 @@ class BrotherPrinter(Printer):
try:
im = Image.open(processed_path)
qlr = BrotherQLRaster(self.printer_info.model)
create_label(qlr, im, self.printer_info.label_type, cut=False, dither=True)
# create_label(qlr, im, self.printer_info.label_type, cut=False, dither=True)
# noinspection PyCallingNonCallable
be = BACKEND_CLASS(BACKEND_STRING_DESCR)
be.write(qlr.data)
be.dispose()
del be
except Exception as e:
raise e
# be = BACKEND_CLASS(BACKEND_STRING_DESCR)
# be.write(qlr.data)
# be.dispose()
# del be
if signature != "":
try:
im = Image.open(text_to_image(signature))
qlr = BrotherQLRaster(self.printer_info.model)
create_label(
qlr, im, self.printer_info.label_type, cut=False, dither=True
instructions = convert(
qlr=qlr,
images=[im],
label=self.printer_info.label_type,
dither=True,
compress=False,
dpi_600=False,
cut=False,
)
# noinspection PyCallingNonCallable
be = BACKEND_CLASS(BACKEND_STRING_DESCR)
be.write(qlr.data)
be.dispose()
del be
success = send(
instructions=instructions,
printer_identifier=BACKEND_STRING_DESCR,
backend_identifier="pyusb",
blocking=False,
)
self.app.logger.debug(str(success))
self.app.logger.debug("Printed image")
time.sleep(2)
except usb.core.USBError as e:
if "timeout error" in str(e):
self.app.logger.error("USB Timeout Error %s", str(e))
except Exception as e:
raise e
# finally:
# try:
# os.remove(processed_path)
# self.app.logger.debug("Removed image : " + str(processed_path))
# except OSError as e:
# raise e
if signature != "":
self.app.logger.debug("Printing signature for the image %s", data)
try:
signature_file = text_to_image(signature)
im = Image.open(signature_file)
qlr = BrotherQLRaster(self.printer_info.model)
# create_label(
# qlr, im, self.printer_info.label_type, cut=False, dither=True
# )
# noinspection PyCallingNonCallable
# be = BACKEND_CLASS(BACKEND_STRING_DESCR)
# be.write(qlr.data)
# be.dispose()
# del be
instructions = convert(
qlr=qlr,
images=[signature_file],
label=self.printer_info.label_type,
dither=True,
compress=False,
dpi_600=False,
cut=False,
)
success = send(
instructions=instructions,
printer_identifier=BACKEND_STRING_DESCR,
backend_identifier="pyusb",
blocking=False,
)
self.app.logger.debug(str(success))
self.app.logger.debug("Printed signature")
time.sleep(2)
except Exception as e:
raise e
finally:
try:
# os.remove(signature_file)
self.app.logger.debug("Removed image : " + str(signature_file))
except OSError as e:
raise e
def _cut(self):
self.app.logger.debug("Cutting via Brother Printer")
try:
selected_backend = guess_backend(self.printer_info.identifier)
BACKEND_CLASS = backend_factory(selected_backend)["backend_class"]
@@ -541,12 +622,13 @@ class BrotherPrinter(Printer):
be.write(qlr.data)
be.dispose()
del be
self.app.logger.debug("Finished cutting via Brother Printer")
except Exception as e:
raise e
def _qr(self):
# This should not be reached by a print on a BrotherQL
raise NotImplementedError
raise NotImplementedError()
def print_task(self, task_type, data):
"""Execute actual print based on task type"""

View File

@@ -18,11 +18,12 @@ class Printers:
Finds and creates a set of Printer that can be used by the Workers to print.
"""
def __init__(self, app):
def __init__(self, app, configuration_file):
"""
Discovers printers connected to the computer
"""
self.app = app
self.configuration_file = configuration_file
self.printers = list(self._discover_printers())
self._lock = threading.Lock()
@@ -42,8 +43,7 @@ class Printers:
printers = set()
# TODO : this could go in the config ?
waiting_time_for_printer = 5
waiting_time_for_printer = self.configuration_file["printer"]["waiting_time"]
while True:
# Find all connected USB devices
@@ -90,14 +90,18 @@ class Printers:
hex(dev.idProduct),
)
if manufacturer == "EPSON":
match (manufacturer):
case "EPSON":
try:
# We create a new EscPosPrinter()
self.app.logger.debug("Trying to create a new EPSON printer")
prid = dev.idProduct
vendir = dev.idVendor
escpos_printer = EscPosPrinter(
self.app, vendor_id=vendir, device_id=prid
self.app,
vendir,
prid,
self.configuration_file["printer"]["epson"],
)
except Exception as e:
raise e
@@ -105,19 +109,16 @@ class Printers:
# If the object creation is successfull, we add it to the list of Printers
printers.add(escpos_printer)
self.app.logger.debug("Found a %s printer", manufacturer)
# We already found the type of printer,
# we don't need an extra comparaison.
continue
# or a Brother Printer
if manufacturer == "Brother":
case "Brother":
try:
# We create a new BrotherPrinter()
self.app.logger.debug("Trying to create a new BROTHER printer")
brother_printer = BrotherPrinter(
self.app, vendor_id=dev.idVendor, device_id=dev.idProduct
self.app,
dev.idVendor,
dev.idProduct,
self.configuration_file["printer"]["brother"],
)
except Exception as e:
self.app.logger.error(
@@ -131,6 +132,11 @@ class Printers:
# If the object creation is successfull, we add it to the list of Printers
printers.add(brother_printer)
self.app.logger.debug("Found a %s printer", manufacturer)
case _:
self.app.logger.debug(
"Found a printer of unknown manufacturer : %s", manufacturer
)
# end match
self.app.logger.debug("Found %s printers", len(printers))
@@ -150,6 +156,8 @@ class Printers:
return None
with self._lock:
# So don't always look at the first printer available if need be
# this allows for a round-robin lookup
for offset in range(len(printers)):
index = (start_index + offset) % len(printers)
printer = printers[index]
@@ -162,7 +170,18 @@ class Printers:
def printers_info(self):
"""Return the current list of printers"""
return self.printers
printers_info_list = []
for i in self.printers:
printers_info_list.append(
[
i.printer_type.name,
"Used: " + str(i.used),
"Ready: " + str(i.ready),
i.printer_info,
]
)
return printers_info_list
class _FindClass:

View File

@@ -5,6 +5,6 @@
<title>LittlePrynter API</title>
</head>
<body>
"Welcome to the printing software's API. Please see the index page to get started.
Welcome to the printing software's API. Please see the index page to get started.
</body>
</html>

View File

@@ -53,7 +53,7 @@
<hr>
<footer class="row">
<p class=" text-center">Little Prynter is built by <a href="https://n07070.xyz/about-me/">n07070</a> because it's fun :) | <a href="https://git.n07070.xyz/n07070/littleprynter">Source code - AGPLv3</a></p>
<p class=" text-center">Little Prynter is built by <a href="https://n07070.xyz/about-me/">n07070</a> because it's fun :) | <a href="/api">API interface</a> | <a href="https://git.n07070.xyz/n07070/littleprynter">Source code - AGPLv3</a></p>
</footer>
</body>
<script src="https://cdn.socket.io/4.0.0/socket.io.min.js"></script>

View File

@@ -1,5 +0,0 @@
<hr>
<footer>Little Prynter is built by <a href="https://n07070.xyz/about-me/">n07070</a> because it's fun :) | <a href="https://git.n07070.xyz/n07070/littleprynter/src/branch/epson-tm-t20iii">Source code - AGPLv3</a></footer>
</div>
</body>
</html>

View File

@@ -9,14 +9,20 @@
<textarea class="form-control" type="text" name="txt" placeholder="4096 caractères maximum " maxlength="4096"></textarea>
<br>
<input class="form-control" type="text" name="signature" placeholder="Signature ou pseudo" maxlength="200"><br>
<div class="input-group">
<span class="input-group-text">Combien ?</span>
<input type="number" aria-label="how-many" class="form-control" name="many" max="20" min="1" value="1">
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</div>
<br>
<div class="form-check">
<input class="form-check-input" type="checkbox" value="True" id="flexCheckDefault" name="cut" checked>
<label class="form-check-label" for="flexCheckDefault">
Couper après impression ?
</label>
</div>
<br>
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</form>
</div>
</div>
@@ -29,6 +35,12 @@
<form enctype="multipart/form-data" class="form-group" action="/web/print/img" method="post">
<input class="form-control" type="file" name="img" required><br>
<input class="form-control" type="text" name="signature" placeholder="Signature ou pseudo" maxlength="200"><br>
<div class="input-group">
<span class="input-group-text">Combien ?</span>
<input type="number" aria-label="how-many" class="form-control" name="many" max="20" min="1" value="1">
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</div>
<br>
<div class="form-check .form-check-inline">
<input class="form-check-input" type="checkbox" value="True" id="flexCheckDefault" name="cut" checked>
<label class="form-check-label" for="flexCheckDefault">
@@ -41,8 +53,7 @@
Traiter l'image ?
</label>
</div>
<br>
<input class="btn btn-primary float-right" type="submit" value="Imprimer" name="imprimer">
</form>
</div>
</div>

View File

@@ -16,7 +16,7 @@ class Web:
self.print_queue = print_queue
self.app = app
def print_sms(self, texte, sign: str, do_cut: bool) -> bool:
def print_sms(self, texte, sign: str, do_cut: bool, many: int = 1) -> bool:
"""
Get text and a signature, prints the text and cuts after that.
"""
@@ -28,19 +28,22 @@ class Web:
# !! there is no garantee !! that both the SMS task and the Cut task
# are added back to back, another task could be
# inserted between the two.
for i in range(many):
sms = self.print_queue.enqueue(TextTask(content=texte, signature=sign))
self.app.logger.info("Added a SMS task at position %s", sms)
if do_cut:
cut = self.print_queue.enqueue(CutTask())
self.app.logger.info("Added a CUT task at position %s", cut)
except Exception as e:
self.app.logger.error(e)
raise RuntimeError("Could not add SMS to queue, " + str(e)) from e
return True
def print_image(self, image, sign: str, do_cut: bool, process_image: bool) -> bool:
def print_image(
self, image, sign: str, do_cut: bool, process_image: bool, many: int = 1
) -> bool:
"""
Get an image and a signature, prints the image and cuts after that.
"""
@@ -53,6 +56,7 @@ class Web:
if file_uploaded:
self.app.logger.debug("File has been uploaded, printing...")
try:
for i in range(many):
img = self.print_queue.enqueue(
ImageTask(
os.path.join(

View File

@@ -6,7 +6,6 @@ This is the main printing thread. A worker thread consums Tasks from
import threading
import time
import traceback
from printers import Printers
class PrintWorker(threading.Thread):
@@ -21,7 +20,7 @@ class PrintWorker(threading.Thread):
tasks. TODO : Change that
"""
def __init__(self, app, print_queue, printers_pool):
def __init__(self, app, print_queue, printers_pool, printer_index=0):
super().__init__(daemon=True)
self.app = app
self.print_queue = print_queue
@@ -33,7 +32,9 @@ class PrintWorker(threading.Thread):
# All workers share the same pool of printers.
self.printers = printers_pool
self.printers_obj = self.printers.printers
self._printer_index = 0
# If we create several workers, we can assign an index
# so that we lookup the nth printer for the nth worker
self._printer_index = printer_index
self.app.logger.debug("Worker initialized.")
self.app.logger.debug("Printers connected:")
@@ -44,11 +45,15 @@ class PrintWorker(threading.Thread):
self.app.logger.debug("Ho great, I'm alive... I'm ready to work another day...")
def _acquire_available_printer(self):
result = self.printers.acquire_available_printer(self._printer_index)
result = self.printers.acquire_available_printer(
start_index=self._printer_index
)
if result is None:
return None
index, printer = result
# In case we need to lookup another printer, we don't
# look at the same printer in the printer list
self._printer_index = (index + 1) % len(self.printers_obj)
return printer
@@ -128,7 +133,6 @@ class PrintWorker(threading.Thread):
self.app.logger.error("Could not print : %s", str(e))
self.app.logger.error(traceback.format_exc())
self.printer.free_printer()
raise e
task.status = "completed"
self.print_queue.mark_completed(task.task_id, "completed")
@@ -156,7 +160,10 @@ class PrintWorker(threading.Thread):
"""
self.app.logger.debug("Giving the worker a break")
self.state = "drinking-a-beer"
# Free the printer during the worker break
# so it can be used by someone else
self.printer.free_printer()
self.printer = None
self.running = False
def start_worker(self):