fix(dashcam): prevent site freeze during import/convert

- _convert_bg now runs in real threading.Thread (not gevent greenlet)
  so ffmpeg proc.wait() doesn't block gevent event loop
- _device_lock released before copy loop; only held for mount/unmount
  so /api/dash/files and other routes don't block during long copies
- Date now shows time (Aug 9, 10:30 PM) on mobile video rows
- Spacing added above converting spinner badge

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-10 01:23:53 -04:00
co-authored by Copilot
parent 77cd3fdadd
commit ba712bd6db
4 changed files with 21 additions and 17 deletions
+13 -11
View File
@@ -559,17 +559,19 @@ def import_dashcam(
"message": f"Failed to mount {device_path}.",
}
try:
summary = copy_files(source, destination_folder, relative_paths)
summary["status"] = (
"invalid_selection" if summary.pop("invalid_selection", False) else "ok"
)
if summary["status"] == "invalid_selection":
summary["message"] = summary["errors"][0]
summary["device"] = device_path
return summary
finally:
if mounted_here:
# Lock released — copy runs without blocking other requests
try:
summary = copy_files(source, destination_folder, relative_paths)
summary["status"] = (
"invalid_selection" if summary.pop("invalid_selection", False) else "ok"
)
if summary["status"] == "invalid_selection":
summary["message"] = summary["errors"][0]
summary["device"] = device_path
return summary
finally:
if mounted_here:
with _device_lock:
unmount_device(mount_point)
+5 -3
View File
@@ -2,6 +2,7 @@
import os
import subprocess
import time
import threading
import gevent
from gevent import sleep
@@ -35,7 +36,9 @@ def queue_convert(relative_path):
if relative_path in _converting:
return
_converting.add(relative_path)
gevent.spawn(_convert_bg, relative_path)
gevent.sleep(0) # yield before spawning thread
t = threading.Thread(target=_convert_bg, args=(relative_path,), daemon=True)
t.start()
def _convert_bg(relative_path):
@@ -59,8 +62,7 @@ def _convert_bg(relative_path):
tmp_path,
]
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
while proc.poll() is None:
sleep(1)
proc.wait() # real thread — blocking wait is fine
if proc.returncode == 0 and os.path.isfile(tmp_path) and os.path.getsize(tmp_path) > 0:
os.replace(tmp_path, mp4_path)
# Delete original now that MP4 is ready
+1 -1
View File
@@ -115,7 +115,7 @@
// Date shown only on mobile (desktop has its own column)
const mobileMeta = document.createElement("div");
mobileMeta.className = "dash-thumb-meta text-muted";
mobileMeta.textContent = new Date(file.modified_at * 1000).toLocaleDateString(undefined, { month: 'short', day: 'numeric' });
mobileMeta.textContent = new Date(file.modified_at * 1000).toLocaleString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' });
preview.appendChild(mobileMeta);
// Converting badge shown on mobile inside thumbnail cell
if (file.converting) {
+2 -2
View File
@@ -321,8 +321,8 @@ body {
.dash-thumb-name { display: block !important; font-size: .7rem; margin-top: .2rem; }
/* Date + converting badge under thumbnail (mobile only) */
.dash-thumb-meta { display: block; font-size: .65rem; line-height: 1.2; }
.dash-thumb-converting { display: inline-block; font-size: .65rem; padding: .1rem .3rem; }
.dash-thumb-meta { display: block; font-size: .65rem; line-height: 1.2; margin-top: .15rem; }
.dash-thumb-converting { display: inline-block; font-size: .65rem; padding: .1rem .3rem; margin-top: .25rem; }
/* Icon-only buttons */
.btn-icon-mobile .btn-label { display: none; }