Add library file support to print queue scheduler

The print queue now supports two sources:
- archive_id: Print from existing archive (existing behavior)
- library_file_id: Print from file manager library files (NEW)

Previously, queue items from the file manager failed with "Archive not
found" because the scheduler only handled archive-based prints.

Changes:
- Add LibraryFile model import
- Update _start_print() to handle both archive and library file sources
- Handle absolute vs relative file paths correctly
- Update logging and MQTT notifications to use correct filename

Fixes GitHub issue #163
This commit is contained in:
maziggy
2026-01-27 15:40:27 +01:00
parent 14e8e512f2
commit 2c761e7663
+67 -22
View File
@@ -10,6 +10,7 @@ from sqlalchemy.ext.asyncio import AsyncSession
from backend.app.core.config import settings
from backend.app.core.database import async_session
from backend.app.models.archive import PrintArchive
from backend.app.models.library import LibraryFile
from backend.app.models.print_queue import PrintQueueItem
from backend.app.models.printer import Printer
from backend.app.models.smart_plug import SmartPlug
@@ -220,22 +221,15 @@ class PrintScheduler:
await tasmota_service.turn_off(plug)
async def _start_print(self, db: AsyncSession, item: PrintQueueItem):
"""Upload file and start print for a queue item."""
"""Upload file and start print for a queue item.
Supports two sources:
- archive_id: Print from an existing archive
- library_file_id: Print from a library file (file manager)
"""
logger.info(f"Starting queue item {item.id}")
# Get archive
result = await db.execute(select(PrintArchive).where(PrintArchive.id == item.archive_id))
archive = result.scalar_one_or_none()
if not archive:
item.status = "failed"
item.error_message = "Archive not found"
item.completed_at = datetime.utcnow()
await db.commit()
logger.error(f"Queue item {item.id}: Archive {item.archive_id} not found")
await self._power_off_if_needed(db, item)
return
# Get printer
# Get printer first (needed for both paths)
result = await db.execute(select(Printer).where(Printer.id == item.printer_id))
printer = result.scalar_one_or_none()
if not printer:
@@ -257,11 +251,60 @@ class PrintScheduler:
await self._power_off_if_needed(db, item)
return
# Get file path
file_path = settings.base_dir / archive.file_path
# Determine source: archive or library file
archive = None
library_file = None
file_path = None
filename = None
if item.archive_id:
# Print from archive
result = await db.execute(select(PrintArchive).where(PrintArchive.id == item.archive_id))
archive = result.scalar_one_or_none()
if not archive:
item.status = "failed"
item.error_message = "Archive not found"
item.completed_at = datetime.utcnow()
await db.commit()
logger.error(f"Queue item {item.id}: Archive {item.archive_id} not found")
await self._power_off_if_needed(db, item)
return
file_path = settings.base_dir / archive.file_path
filename = archive.filename
elif item.library_file_id:
# Print from library file (file manager)
result = await db.execute(select(LibraryFile).where(LibraryFile.id == item.library_file_id))
library_file = result.scalar_one_or_none()
if not library_file:
item.status = "failed"
item.error_message = "Library file not found"
item.completed_at = datetime.utcnow()
await db.commit()
logger.error(f"Queue item {item.id}: Library file {item.library_file_id} not found")
await self._power_off_if_needed(db, item)
return
# Library files store absolute paths
from pathlib import Path
lib_path = Path(library_file.file_path)
file_path = lib_path if lib_path.is_absolute() else settings.base_dir / library_file.file_path
filename = library_file.filename
else:
# Neither archive nor library file specified
item.status = "failed"
item.error_message = "No source file specified"
item.completed_at = datetime.utcnow()
await db.commit()
logger.error(f"Queue item {item.id}: No archive_id or library_file_id specified")
await self._power_off_if_needed(db, item)
return
# Check file exists on disk
if not file_path.exists():
item.status = "failed"
item.error_message = "Archive file not found on disk"
item.error_message = "Source file not found on disk"
item.completed_at = datetime.utcnow()
await db.commit()
logger.error(f"Queue item {item.id}: File not found: {file_path}")
@@ -270,7 +313,7 @@ class PrintScheduler:
# Upload file to printer via FTP
# Use a clean filename to avoid issues with double extensions like .gcode.3mf
base_name = archive.filename
base_name = filename
if base_name.endswith(".gcode.3mf"):
base_name = base_name[:-10] # Remove .gcode.3mf
elif base_name.endswith(".3mf"):
@@ -332,9 +375,11 @@ class PrintScheduler:
return
# Register as expected print so we don't create a duplicate archive
from backend.app.main import register_expected_print
# Only applicable for archive-based prints
if archive:
from backend.app.main import register_expected_print
register_expected_print(item.printer_id, remote_filename, archive.id)
register_expected_print(item.printer_id, remote_filename, archive.id)
# Parse AMS mapping if stored
ams_mapping = None
@@ -364,7 +409,7 @@ class PrintScheduler:
item.status = "printing"
item.started_at = datetime.utcnow()
await db.commit()
logger.info(f"Queue item {item.id}: Print started - {archive.filename}")
logger.info(f"Queue item {item.id}: Print started - {filename}")
# MQTT relay - publish queue job started
try:
@@ -372,7 +417,7 @@ class PrintScheduler:
await mqtt_relay.on_queue_job_started(
job_id=item.id,
filename=archive.filename,
filename=filename,
printer_id=printer.id,
printer_name=printer.name,
printer_serial=printer.serial_number,