feat(dashcam): async import with polling status

- Import now returns immediately with {status: 'started'}
- Backend runs copy in a daemon thread, tracks job state in _import_job
- New GET /api/dash/import/status endpoint for polling
- UI polls every 3s, shows progress bar until done
- Page reload mid-import resumes polling automatically
- Prevents double-import with 'busy' guard

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 01:12:00 -04:00
co-authored by Copilot
parent 7d124b0fa0
commit 71f8ce2993
3 changed files with 115 additions and 63 deletions
+34
View File
@@ -37,6 +37,10 @@ _local_video_state_lock = threading.Lock()
_local_videos_in_use = {}
_local_videos_deleting = set()
# --- Async import job state ---
_import_lock = threading.Lock()
_import_job = None # dict: {status, found, copied, skipped, errors, message} or None
def begin_local_video_use(relative_path):
with _local_video_state_lock:
@@ -569,5 +573,35 @@ def import_dashcam(
unmount_device(mount_point)
def start_import_async(relative_paths=None):
"""Start a background import thread. Returns (True, None) if started,
or (False, message) if an import is already running."""
global _import_job
with _import_lock:
if _import_job and _import_job.get("status") == "running":
return False, "An import is already in progress."
_import_job = {
"status": "running",
"found": 0, "copied": 0, "skipped": 0,
"errors": [], "message": "",
}
def _run():
global _import_job
result = import_dashcam(relative_paths=relative_paths)
with _import_lock:
_import_job = result
t = threading.Thread(target=_run, daemon=True)
t.start()
return True, None
def get_import_status():
"""Return the current import job state dict, or None if no job has been run."""
with _import_lock:
return dict(_import_job) if _import_job else None
if __name__ == "__main__":
print(import_dashcam())