mirror of
https://github.com/maziggy/bambuddy.git
synced 2026-10-18 03:54:17 +02:00
Merge branch '0.1.8b' into feature/3d_plate_view_v2
This commit is contained in:
@@ -10,6 +10,7 @@
|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/maziggy/bambuddy/releases"><img src="https://img.shields.io/github/v/release/maziggy/bambuddy?style=flat-square&color=blue" alt="Release"></a>
|
||||
<img src="https://github.com/maziggy/bambuddy/actions/workflows/github-code-scanning/codeql/badge.svg">
|
||||
<img src="https://github.com/maziggy/bambuddy/actions/workflows/ci.yml/badge.svg?branch=main">
|
||||
<a href="https://github.com/maziggy/bambuddy/blob/main/LICENSE"><img src="https://img.shields.io/github/license/maziggy/bambuddy?style=flat-square" alt="License"></a>
|
||||
<a href="https://github.com/maziggy/bambuddy/stargazers"><img src="https://img.shields.io/github/stars/maziggy/bambuddy?style=flat-square" alt="Stars"></a>
|
||||
|
||||
@@ -5,7 +5,7 @@ import json
|
||||
import logging
|
||||
import xml.etree.ElementTree as ET
|
||||
import zipfile
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from pathlib import Path
|
||||
|
||||
from sqlalchemy import func, select
|
||||
@@ -76,6 +76,18 @@ class PrintScheduler:
|
||||
if item.scheduled_time and item.scheduled_time > datetime.utcnow():
|
||||
continue
|
||||
|
||||
# Safety: Skip stale items (older than 24 hours) to prevent phantom reprints
|
||||
# This protects against items that got stuck in "pending" status due to
|
||||
# crashes/restarts after the print already started
|
||||
stale_threshold = timedelta(hours=24)
|
||||
if item.created_at and datetime.utcnow() - item.created_at.replace(tzinfo=None) > stale_threshold:
|
||||
logger.warning(f"Queue item {item.id} is stale (created {item.created_at}), marking as expired")
|
||||
item.status = "expired"
|
||||
item.error_message = "Queue item expired - older than 24 hours"
|
||||
item.completed_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
continue
|
||||
|
||||
# Skip items that require manual start
|
||||
if item.manual_start:
|
||||
continue
|
||||
@@ -809,6 +821,28 @@ class PrintScheduler:
|
||||
logger.error(f"Queue item {item.id}: Archive {item.archive_id} not found")
|
||||
await self._power_off_if_needed(db, item)
|
||||
return
|
||||
|
||||
# Safety: Check if this archive was printed recently (within 4 hours)
|
||||
# This prevents phantom reprints if a queue item got stuck in "pending"
|
||||
# after its print already started due to a crash/restart
|
||||
if archive.status == "completed" and archive.completed_at:
|
||||
completed_at = (
|
||||
archive.completed_at.replace(tzinfo=None) if archive.completed_at.tzinfo else archive.completed_at
|
||||
)
|
||||
time_since_completed = datetime.utcnow() - completed_at
|
||||
if time_since_completed < timedelta(hours=4):
|
||||
logger.warning(
|
||||
f"Queue item {item.id}: Archive {item.archive_id} was already printed "
|
||||
f"{time_since_completed.total_seconds() / 3600:.1f} hours ago, skipping to prevent duplicate"
|
||||
)
|
||||
item.status = "skipped"
|
||||
item.error_message = (
|
||||
f"Archive was already printed {time_since_completed.total_seconds() / 3600:.1f} hours ago"
|
||||
)
|
||||
item.completed_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
return
|
||||
|
||||
file_path = settings.base_dir / archive.file_path
|
||||
filename = archive.filename
|
||||
|
||||
@@ -953,6 +987,17 @@ class PrintScheduler:
|
||||
except json.JSONDecodeError:
|
||||
logger.warning(f"Queue item {item.id}: Invalid AMS mapping JSON, ignoring")
|
||||
|
||||
# IMPORTANT: Set status to "printing" BEFORE sending the print command.
|
||||
# This prevents phantom reprints if the backend crashes/restarts after the
|
||||
# print command is sent but before the status update is committed.
|
||||
# If we crash after this commit but before start_print(), the item will be
|
||||
# in "printing" status without actually printing - but that's safer than
|
||||
# accidentally reprinting the same file hours later.
|
||||
item.status = "printing"
|
||||
item.started_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
logger.info(f"Queue item {item.id}: Status set to 'printing', sending print command...")
|
||||
|
||||
# Start the print with AMS mapping, plate_id and print options
|
||||
started = printer_manager.start_print(
|
||||
item.printer_id,
|
||||
@@ -968,10 +1013,7 @@ class PrintScheduler:
|
||||
)
|
||||
|
||||
if started:
|
||||
item.status = "printing"
|
||||
item.started_at = datetime.utcnow()
|
||||
await db.commit()
|
||||
logger.info(f"Queue item {item.id}: Print started - {filename}")
|
||||
logger.info(f"Queue item {item.id}: Print started successfully - {filename}")
|
||||
|
||||
# Get estimated time for notification
|
||||
estimated_time = None
|
||||
@@ -1003,6 +1045,7 @@ class PrintScheduler:
|
||||
except Exception:
|
||||
pass # Don't fail if MQTT fails
|
||||
else:
|
||||
# Print command failed - revert status
|
||||
item.status = "failed"
|
||||
item.error_message = "Failed to send print command to printer"
|
||||
item.completed_at = datetime.utcnow()
|
||||
|
||||
Reference in New Issue
Block a user