2 Commits

Author SHA1 Message Date
Scott Idem
e4e2174c97 fix(upload): replace db_events.file.clear() with targeted per-object reload
db_events.file.clear() after every upload was nuking the entire Dexie file
table. Every liveQuery watching any file list (Launcher, other locations,
sessions, presenters) would immediately show 0 results. Only the uploaded-to
object's files were reloaded; all others remained empty until their own
background syncs fired — intermittent disappearance that depended on timing.

Fix: targeted load_ae_obj_li__event_file for only the current link_to_id,
which uses the SWR pattern (returns cache + background refresh that includes
the newly created file). Other objects' file caches are untouched.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:33:12 -04:00
Scott Idem
d3bf314c62 fix(launcher): refresh file lists periodically to prune deleted/hidden files
The Launcher's background sync never called load_ae_obj_li__event_file for
presenter/session files. That function contains stale-record pruning that
removes deleted or hidden files from Dexie; without it, the Launcher's IDB
retained stale file records indefinitely until manually cleared.

Changes:
- refresh_presenter_data: add inc_file_li=true so presenter files are pruned
  every 120s via the existing presenter loop
- refresh_current_session_files(): new function that fetches/prunes session-
  level file lists for the selected session
- timer__file_list: 60s interval for refresh_current_session_files
- $effect on event_session_id: fires refresh_current_session_files immediately
  on session switch (no wait for next timer tick)

Propagation time: deleted/hidden files visible on remote Launchers within
~60s (session files) or ~120s (presenter files) automatically.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 20:12:10 -04:00
2 changed files with 52 additions and 4 deletions

View File

@@ -124,6 +124,7 @@ let timer__session: any = $state(null);
let timer__presentation: any = $state(null);
let timer__presenter: any = $state(null);
let timer__file_sync: any = $state(null);
let timer__file_list: any = $state(null);
let is_syncing = false;
let show_monitor = $state(false);
@@ -196,6 +197,15 @@ onMount(async () => {
// 3. Native File Sync Loop (Dexie -> Disk)
timer__file_sync = setInterval(() => run_sync_cycle(), loop_info.file_sync);
// 4. File List Refresh Loop (API -> Dexie, prunes deleted/hidden session files)
// WHY: The presenter/session refresh loops don't fetch file lists, so deleted or
// hidden files can stay in Dexie on the Launcher machine indefinitely (until IDB is
// manually cleared). This loop calls load_ae_obj_li__event_file for the selected
// session, which triggers the stale-record pruning in _refresh_file_li_background.
// 60s matches the session/location loops; presenter files are pruned via inc_file_li
// in refresh_presenter_data which runs every 120s.
timer__file_list = setInterval(() => refresh_current_session_files(), 60000);
// Immediate first run for metadata
refresh_event_data();
run_device_heartbeat();
@@ -211,6 +221,7 @@ onMount(async () => {
setTimeout(() => refresh_session_data(), 1000);
setTimeout(() => refresh_presentation_data(), 3000);
setTimeout(() => refresh_presenter_data(), 5000);
setTimeout(() => refresh_current_session_files(), 7000);
// Clean up stale .tmp files (interrupted downloads) on startup.
// Age threshold is user-configurable (cfg → General → Cache Maintenance), default 24h.
@@ -235,6 +246,7 @@ onDestroy(() => {
if (timer__presentation) clearInterval(timer__presentation);
if (timer__presenter) clearInterval(timer__presenter);
if (timer__file_sync) clearInterval(timer__file_sync);
if (timer__file_list) clearInterval(timer__file_list);
});
// WHY: refresh_location_config() runs on a 60s timer. Without this effect,
@@ -248,6 +260,15 @@ $effect(() => {
}
});
// WHY: fires refresh_current_session_files() immediately when the operator switches
// sessions so deleted/hidden files are pruned without waiting for the 60s timer.
$effect(() => {
const session_id = $events_slct.event_session_id;
if (session_id) {
refresh_current_session_files();
}
});
// Force Sync Trigger: fetches ALL child metadata for ALL sessions in the room.
// WHY: by default, the launcher only warms presentation/presenter caches for the
// *selected* session to save bandwidth. Onsite operators use this trigger to
@@ -341,6 +362,7 @@ async function refresh_presenter_data() {
api_cfg: $ae_api,
for_obj_type: 'event_session',
for_obj_id: session_id,
inc_file_li: true, // WHY: triggers pruning of deleted/hidden presenter files in Dexie
try_cache: true,
log_lvl: 0
});
@@ -600,6 +622,29 @@ async function refresh_location_config() {
}
}
/**
* Session File List Refresh
* Fetches the file list for the SELECTED session and prunes stale/deleted/hidden records.
* WHY: The regular session/presenter refresh loops don't fetch file lists, so deleted or
* hidden files persist in the Launcher's Dexie indefinitely. This runs on a 60s timer
* and also fires immediately when the selected session changes. Presenter-level files are
* handled separately via inc_file_li=true in refresh_presenter_data (120s timer).
*/
async function refresh_current_session_files() {
if ($events_loc.launcher.sync_paused) return;
const session_id = $events_slct.event_session_id;
if (!session_id) return;
try {
await events_func.load_ae_obj_li__event_file({
api_cfg: $ae_api,
for_obj_type: 'event_session',
for_obj_id: session_id,
try_cache: true,
log_lvl: 0
});
} catch (err) {}
}
/**
* Force Location Sync
* Fetches ALL sessions and their children (presentations, presenters, files)

View File

@@ -24,7 +24,6 @@ import {
events_trig
} from '$lib/stores/ae_events_stores';
import { events_func } from '$lib/ae_events/ae_events_functions';
import { db_events } from '$lib/ae_events/db_events';
interface Props {
log_lvl?: number;
@@ -110,9 +109,13 @@ async function handle_submit_form_files(event: SubmitEvent) {
$events_sess.files.processed_file_list = [];
target.reset();
// Refresh Local Cache (Optimistic Revalidation)
await db_events.file.clear();
events_func.load_ae_obj_li__event_file({
// Refresh the file list for this object only.
// WHY: Previously used db_events.file.clear() here, which nuked the ENTIRE
// file table in Dexie. Every liveQuery watching any file list would immediately
// show 0 results, and only this object's files were reloaded. Files for all
// other objects (locations, sessions, presenters) remained missing until their
// own background syncs fired. Targeted reload avoids that side effect.
await events_func.load_ae_obj_li__event_file({
api_cfg: $ae_api,
for_obj_type: link_to_type,
for_obj_id: link_to_id,