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:
+13
-11
@@ -559,17 +559,19 @@ def import_dashcam(
|
|||||||
"message": f"Failed to mount {device_path}.",
|
"message": f"Failed to mount {device_path}.",
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
# Lock released — copy runs without blocking other requests
|
||||||
summary = copy_files(source, destination_folder, relative_paths)
|
try:
|
||||||
summary["status"] = (
|
summary = copy_files(source, destination_folder, relative_paths)
|
||||||
"invalid_selection" if summary.pop("invalid_selection", False) else "ok"
|
summary["status"] = (
|
||||||
)
|
"invalid_selection" if summary.pop("invalid_selection", False) else "ok"
|
||||||
if summary["status"] == "invalid_selection":
|
)
|
||||||
summary["message"] = summary["errors"][0]
|
if summary["status"] == "invalid_selection":
|
||||||
summary["device"] = device_path
|
summary["message"] = summary["errors"][0]
|
||||||
return summary
|
summary["device"] = device_path
|
||||||
finally:
|
return summary
|
||||||
if mounted_here:
|
finally:
|
||||||
|
if mounted_here:
|
||||||
|
with _device_lock:
|
||||||
unmount_device(mount_point)
|
unmount_device(mount_point)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
import threading
|
||||||
|
|
||||||
import gevent
|
import gevent
|
||||||
from gevent import sleep
|
from gevent import sleep
|
||||||
@@ -35,7 +36,9 @@ def queue_convert(relative_path):
|
|||||||
if relative_path in _converting:
|
if relative_path in _converting:
|
||||||
return
|
return
|
||||||
_converting.add(relative_path)
|
_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):
|
def _convert_bg(relative_path):
|
||||||
@@ -59,8 +62,7 @@ def _convert_bg(relative_path):
|
|||||||
tmp_path,
|
tmp_path,
|
||||||
]
|
]
|
||||||
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
proc = subprocess.Popen(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
while proc.poll() is None:
|
proc.wait() # real thread — blocking wait is fine
|
||||||
sleep(1)
|
|
||||||
if proc.returncode == 0 and os.path.isfile(tmp_path) and os.path.getsize(tmp_path) > 0:
|
if proc.returncode == 0 and os.path.isfile(tmp_path) and os.path.getsize(tmp_path) > 0:
|
||||||
os.replace(tmp_path, mp4_path)
|
os.replace(tmp_path, mp4_path)
|
||||||
# Delete original now that MP4 is ready
|
# Delete original now that MP4 is ready
|
||||||
|
|||||||
@@ -115,7 +115,7 @@
|
|||||||
// Date shown only on mobile (desktop has its own column)
|
// Date shown only on mobile (desktop has its own column)
|
||||||
const mobileMeta = document.createElement("div");
|
const mobileMeta = document.createElement("div");
|
||||||
mobileMeta.className = "dash-thumb-meta text-muted";
|
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);
|
preview.appendChild(mobileMeta);
|
||||||
// Converting badge shown on mobile inside thumbnail cell
|
// Converting badge shown on mobile inside thumbnail cell
|
||||||
if (file.converting) {
|
if (file.converting) {
|
||||||
|
|||||||
+2
-2
@@ -321,8 +321,8 @@ body {
|
|||||||
.dash-thumb-name { display: block !important; font-size: .7rem; margin-top: .2rem; }
|
.dash-thumb-name { display: block !important; font-size: .7rem; margin-top: .2rem; }
|
||||||
|
|
||||||
/* Date + converting badge under thumbnail (mobile only) */
|
/* Date + converting badge under thumbnail (mobile only) */
|
||||||
.dash-thumb-meta { display: block; font-size: .65rem; line-height: 1.2; }
|
.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; }
|
.dash-thumb-converting { display: inline-block; font-size: .65rem; padding: .1rem .3rem; margin-top: .25rem; }
|
||||||
|
|
||||||
/* Icon-only buttons */
|
/* Icon-only buttons */
|
||||||
.btn-icon-mobile .btn-label { display: none; }
|
.btn-icon-mobile .btn-label { display: none; }
|
||||||
|
|||||||
Reference in New Issue
Block a user