feat: Add job cancellation support and update job status handling
This commit introduces a new `CANCELLED` status to the job management system, allowing users to cancel ongoing jobs. The frontend is updated to handle job cancellation requests, providing user feedback during the cancellation process. Additionally, the backend is enhanced to manage job statuses more effectively, ensuring that jobs can be marked as cancelled and that appropriate messages are displayed to users. This improvement enhances the overall user experience by providing better control over job processing.
This commit is contained in:
+64
-41
@@ -127,34 +127,44 @@ def process_faces_task(
|
||||
faces_detected: int,
|
||||
faces_stored: int,
|
||||
) -> None:
|
||||
"""Update job progress and check for cancellation."""
|
||||
if job:
|
||||
# Check if job was cancelled
|
||||
if job.meta and job.meta.get("cancelled", False):
|
||||
return # Don't update if cancelled
|
||||
|
||||
# Calculate progress: 10% for setup, 90% for processing
|
||||
if total == 0:
|
||||
# Setup phase
|
||||
progress = min(10, processed * 2) # 0-10% during setup
|
||||
else:
|
||||
# Processing phase
|
||||
progress = 10 + int((processed / total) * 90) if total > 0 else 10
|
||||
|
||||
job.meta = {
|
||||
"progress": progress,
|
||||
"message": f"Processing {current_file}... ({processed}/{total})" if total > 0 else current_file,
|
||||
"processed": processed,
|
||||
"total": total,
|
||||
"faces_detected": faces_detected,
|
||||
"faces_stored": faces_stored,
|
||||
}
|
||||
job.save_meta()
|
||||
|
||||
# Check for cancellation after updating
|
||||
if job.meta and job.meta.get("cancelled", False):
|
||||
print(f"[Task] Job {job.id} cancellation detected")
|
||||
raise KeyboardInterrupt("Job cancelled by user")
|
||||
"""Update job progress.
|
||||
|
||||
NOTE: This function does NOT check for cancellation to avoid interrupting
|
||||
photo processing. Cancellation is checked in process_unprocessed_photos
|
||||
BEFORE starting each photo and AFTER completing it, ensuring the current
|
||||
photo always finishes fully.
|
||||
"""
|
||||
try:
|
||||
if job:
|
||||
# Calculate progress: 10% for setup, 90% for processing
|
||||
if total == 0:
|
||||
# Setup phase
|
||||
progress = min(10, processed * 2) # 0-10% during setup
|
||||
else:
|
||||
# Processing phase
|
||||
progress = 10 + int((processed / total) * 90) if total > 0 else 10
|
||||
|
||||
# Avoid duplicating "Processing" if current_file already contains it
|
||||
if current_file.startswith("Processing "):
|
||||
message = f"{current_file} ({processed}/{total})" if total > 0 else current_file
|
||||
else:
|
||||
message = f"Processing {current_file}... ({processed}/{total})" if total > 0 else current_file
|
||||
|
||||
job.meta = {
|
||||
"progress": progress,
|
||||
"message": message,
|
||||
"processed": processed,
|
||||
"total": total,
|
||||
"faces_detected": faces_detected,
|
||||
"faces_stored": faces_stored,
|
||||
}
|
||||
job.save_meta()
|
||||
except Exception as e:
|
||||
# Log but don't fail the batch if progress update fails
|
||||
try:
|
||||
print(f"[Task] Warning: Failed to update progress: {e}")
|
||||
except (BrokenPipeError, OSError):
|
||||
pass
|
||||
|
||||
# Update progress - finding photos
|
||||
if job:
|
||||
@@ -169,15 +179,26 @@ def process_faces_task(
|
||||
job.save_meta()
|
||||
|
||||
# Process faces
|
||||
photos_processed, total_faces_detected, total_faces_stored = (
|
||||
process_unprocessed_photos(
|
||||
db,
|
||||
batch_size=batch_size,
|
||||
detector_backend=detector_backend,
|
||||
model_name=model_name,
|
||||
update_progress=update_progress,
|
||||
# Wrap in try-except to ensure we preserve progress even if process_unprocessed_photos fails
|
||||
try:
|
||||
photos_processed, total_faces_detected, total_faces_stored = (
|
||||
process_unprocessed_photos(
|
||||
db,
|
||||
batch_size=batch_size,
|
||||
detector_backend=detector_backend,
|
||||
model_name=model_name,
|
||||
update_progress=update_progress,
|
||||
)
|
||||
)
|
||||
)
|
||||
except Exception as e:
|
||||
# If process_unprocessed_photos fails, preserve any progress made
|
||||
# and re-raise so the outer handler can log it properly
|
||||
try:
|
||||
print(f"[Task] Error in process_unprocessed_photos: {e}")
|
||||
except (BrokenPipeError, OSError):
|
||||
pass
|
||||
# Re-raise to be handled by outer exception handler
|
||||
raise
|
||||
|
||||
# Final update
|
||||
result = {
|
||||
@@ -252,13 +273,15 @@ def process_faces_task(
|
||||
|
||||
if job:
|
||||
try:
|
||||
# Preserve progress information if available
|
||||
existing_meta = job.meta or {}
|
||||
job.meta = {
|
||||
"progress": 0,
|
||||
"progress": existing_meta.get("progress", 0),
|
||||
"message": error_msg,
|
||||
"processed": 0,
|
||||
"total": 0,
|
||||
"faces_detected": 0,
|
||||
"faces_stored": 0,
|
||||
"processed": existing_meta.get("processed", photos_processed),
|
||||
"total": existing_meta.get("total", 0),
|
||||
"faces_detected": existing_meta.get("faces_detected", total_faces_detected),
|
||||
"faces_stored": existing_meta.get("faces_stored", total_faces_stored),
|
||||
}
|
||||
job.save_meta()
|
||||
except Exception:
|
||||
|
||||
Reference in New Issue
Block a user