Просмотр исходного кода

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
maziggy 7 месяцев назад
Родитель
Сommit
2c761e7663
1 измененных файлов с 67 добавлено и 22 удалено
  1. 67 22
      backend/app/services/print_scheduler.py

+ 67 - 22
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."""
-        logger.info(f"Starting queue item {item.id}")
+        """Upload file and start print for a queue item.
 
-        # 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
+        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 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,