diff --git a/backend/app/services/print_scheduler.py b/backend/app/services/print_scheduler.py index af5440f06..603499d79 100644 --- a/backend/app/services/print_scheduler.py +++ b/backend/app/services/print_scheduler.py @@ -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,