Phase 12: remove phone-relay dashcam upload feature

Removes the phone-relay feature entirely per the Phase 11 finding
that it hit a fundamental phone-OS limitation (a browser page can't
route WiFi and cellular traffic simultaneously, no code fix possible)
- see PLAN.md for the full test writeup.

- app.py: dropped /api/dash/pending, /api/dash/file/<path>,
  /api/dash/relay/config, /api/dash/relay/heartbeat,
  /api/dash/relay/stop, /api/dash/relay/mark_uploaded routes and
  their now-unused imports (os, send_file, abort, safe_join).
- modules/app_upload.py: dropped list_pending_dashcam_files(),
  relay_heartbeat()/relay_stop()/is_relay_active(),
  RELAY_LEASE_SECONDS/_relay_lease_until, and the relay-lease check
  inside _upload_dashcam_files()/get_status(). Kept the chunked
  upload protocol, dashcam_hash_cache/_hash_dashcam_file() (still
  used by the Pi's own background uploader), and DASHCAM_CHUNK_SIZE -
  none of that was relay-specific.
- templates/dash.html: removed the 'Relay via phone' card.
- static/js/dash.js: removed all relay JS (relayOneFile,
  fetchChunkWithRetry, uploadChunkWithRetry, heartbeat timers,
  pending-file list rendering).
- server/app.py: removed the CORS after_request hook, added solely
  for the phone's cross-origin browser calls (the Pi's own Python
  requests-based client was never subject to CORS).
- readme.md: dropped the phone-relay feature bullet, updated the
  project-status blurb to explain the removal + point at the planned
  native Android app as the real fix for that use case.
- PLAN.md: marked Phase 12 done with a verification writeup.

Verified: py_compile on all touched Python files, node --check on
dash.js, grepped all touched files for any leftover relay/RELAY
reference (none), and smoke-tested by importing app.py + hitting it
with Flask's test client - app starts cleanly, every relay route is
gone from url_map, and both / and /dash still render HTTP 200 with
zero 'relay' occurrences in /dash's HTML.

Co-authored-by: Copilot <[email protected]>
This commit is contained in:
Mathieu Moran
2026-08-09 17:12:30 -04:00
co-authored by Copilot
parent 0b729adb28
commit f17c5c8681
7 changed files with 46 additions and 430 deletions
+8 -55
View File
@@ -67,17 +67,10 @@ DASHCAM_CHUNK_SIZE = 40 * 1024 * 1024
DASHCAM_UPLOAD_SETTING_KEY = "dashcam_upload_enabled"
# How long a phone-relay "lease" stays valid without a fresh heartbeat before
# the Pi's own background uploader assumes the phone gave up/closed its tab
# and resumes uploading dashcam files itself. The phone page is expected to
# heartbeat well under this (every ~15-20s) - see /api/dash/relay/heartbeat.
RELAY_LEASE_SECONDS = 45
_upload_thread = None
_last_error = None
_last_success_at = None
_last_network_path = None # "wifi" or "cellular" - last path a batch actually went out on
_relay_lease_until = None # monotonic time.time() the phone-relay lease expires, or None
def _headers():
@@ -107,30 +100,9 @@ def get_status():
"last_success_at": _last_success_at,
"last_network_path": _last_network_path,
"cellular_fallback_configured": app_cellular.is_configured(),
"relay_active": is_relay_active(),
}
# --- Phone-relay lease (pauses the Pi's own background dashcam uploader
# while a phone is manually relaying files, so they don't fight over the
# Pi's single, often WiFi-congested, radio) -------------------------------
def relay_heartbeat():
"""Called by the phone-relay page every ~15-20s while active."""
global _relay_lease_until
_relay_lease_until = time.time() + RELAY_LEASE_SECONDS
def relay_stop():
"""Called when the phone-relay page is done/closed cleanly."""
global _relay_lease_until
_relay_lease_until = None
def is_relay_active():
return _relay_lease_until is not None and time.time() < _relay_lease_until
def _sha256_file(path):
hasher = hashlib.sha256()
with open(path, "rb") as f:
@@ -145,9 +117,8 @@ def _sha256_file(path):
def _hash_dashcam_file(abs_path, rel_path):
"""sha256 of a dashcam file, cached by (path, size, mtime) so unchanged
files already copied from the dashcam never get rehashed - real backlogs
here run 20GB+, and both the background loop and the phone-relay pending
list would otherwise rehash everything, including already-uploaded files,
on every single call."""
here run 20GB+, and the background upload loop would otherwise rehash
everything, including already-uploaded files, on every single call."""
stat = os.stat(abs_path)
cached = app_sql.get_cached_dashcam_hash(rel_path, stat.st_size, stat.st_mtime)
if cached is not None:
@@ -157,22 +128,6 @@ def _hash_dashcam_file(abs_path, rel_path):
return sha256
def list_pending_dashcam_files():
"""Return [{relative_path, sha256, size}, ...] for dashcam files not yet
confirmed uploaded - used by the phone-relay page to pick what to fetch."""
pending = []
for abs_path, rel_path in app_dash.find_video_files(app_dash.DEFAULT_DESTINATION):
sha256 = _hash_dashcam_file(abs_path, rel_path)
if app_sql.dashcam_file_marked_uploaded(rel_path, sha256):
continue
pending.append({
"relative_path": rel_path,
"sha256": sha256,
"size": os.path.getsize(abs_path),
})
return pending
def _upload_tracking_batch():
"""Send up to TRACKING_BATCH_SIZE not-yet-uploaded rows. Returns count sent.
@@ -290,11 +245,10 @@ def _upload_video_chunked(abs_path, rel_path, sha256):
def _upload_dashcam_files():
"""Upload any not-yet-uploaded dashcam files, one at a time. Returns count sent.
Skipped entirely (returns 0) while the dashcam-upload toggle is off, or
while a phone-relay session is actively leased - tracking-log uploads are
unaffected by either of these.
Skipped entirely (returns 0) while the dashcam-upload toggle is off -
tracking-log uploads are unaffected.
"""
if not is_dashcam_upload_enabled() or is_relay_active():
if not is_dashcam_upload_enabled():
return 0
uploaded_count = 0
@@ -307,10 +261,9 @@ def _upload_dashcam_files():
app_sql.mark_dashcam_file_uploaded(rel_path, sha256)
uploaded_count += 1
# Re-check between files so flipping the toggle off, or a phone
# relay starting, mid-run stops further uploads promptly without
# waiting for the next cycle.
if not is_dashcam_upload_enabled() or is_relay_active():
# Re-check between files so flipping the toggle off mid-run stops
# further uploads promptly without waiting for the next cycle.
if not is_dashcam_upload_enabled():
break
return uploaded_count