Enhance: Implement exhaustive background caching and recursive data loading
- Implemented aggressive room-wide background caching engine in LauncherBackgroundSync.svelte. - Added inc_file_li and inc_all_file_li support to Event and Event Location object loaders for total room coverage. - Switched to proven V1 download path (/hosted_file/) to resolve V3 CRUD binary delivery issues. - Optimized Electron bridge with download locking and flat-hash storage matching legacy 'perfect' logic. - Standardized all Electron IPC methods and parameters to snake_case. - Added visual sync progress indicator for room caching status.
This commit is contained in:
@@ -10,6 +10,7 @@ import { load_ae_obj_li__event_device } from './ae_events__event_device';
|
|||||||
import { load_ae_obj_li__event_location } from './ae_events__event_location';
|
import { load_ae_obj_li__event_location } from './ae_events__event_location';
|
||||||
import { load_ae_obj_li__event_session } from './ae_events__event_session';
|
import { load_ae_obj_li__event_session } from './ae_events__event_session';
|
||||||
import { load_ae_obj_li__event_badge_template } from '$lib/ae_events/ae_events__event_badge_template';
|
import { load_ae_obj_li__event_badge_template } from '$lib/ae_events/ae_events__event_badge_template';
|
||||||
|
import { load_ae_obj_li__event_file } from '$lib/ae_events/ae_events__event_file';
|
||||||
|
|
||||||
const ae_promises: key_val = {};
|
const ae_promises: key_val = {};
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { api } from '$lib/api/api';
|
|||||||
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
import { db_save_ae_obj_li__ae_obj } from '$lib/ae_core/core__idb_dexie';
|
||||||
import { db_events } from '$lib/ae_events/db_events';
|
import { db_events } from '$lib/ae_events/db_events';
|
||||||
import type { ae_EventLocation } from '$lib/types/ae_types';
|
import type { ae_EventLocation } from '$lib/types/ae_types';
|
||||||
|
import { load_ae_obj_li__event_file } from '$lib/ae_events/ae_events__event_file';
|
||||||
|
|
||||||
const ae_promises: key_val = {};
|
const ae_promises: key_val = {};
|
||||||
|
|
||||||
@@ -12,12 +13,18 @@ export async function load_ae_obj_id__event_location({
|
|||||||
api_cfg,
|
api_cfg,
|
||||||
event_location_id,
|
event_location_id,
|
||||||
view = 'default',
|
view = 'default',
|
||||||
|
inc_file_li = false,
|
||||||
|
inc_session_li = false,
|
||||||
|
inc_all_file_li = false,
|
||||||
try_cache = true,
|
try_cache = true,
|
||||||
log_lvl = 0
|
log_lvl = 0
|
||||||
}: {
|
}: {
|
||||||
api_cfg: any;
|
api_cfg: any;
|
||||||
event_location_id: string;
|
event_location_id: string;
|
||||||
view?: string;
|
view?: string;
|
||||||
|
inc_file_li?: boolean;
|
||||||
|
inc_session_li?: boolean;
|
||||||
|
inc_all_file_li?: boolean;
|
||||||
try_cache?: boolean;
|
try_cache?: boolean;
|
||||||
log_lvl?: number;
|
log_lvl?: number;
|
||||||
}): Promise<ae_EventLocation | null> {
|
}): Promise<ae_EventLocation | null> {
|
||||||
@@ -58,7 +65,11 @@ export async function load_ae_obj_id__event_location({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return ae_promises.load__event_location_obj || null;
|
if (!ae_promises?.load__event_location_obj) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return await _handle_nested_loads(ae_promises.load__event_location_obj, { api_cfg, inc_file_li, log_lvl });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Updated 2026-01-20 to V3
|
// Updated 2026-01-20 to V3
|
||||||
@@ -66,6 +77,7 @@ export async function load_ae_obj_li__event_location({
|
|||||||
api_cfg,
|
api_cfg,
|
||||||
for_obj_type = 'event',
|
for_obj_type = 'event',
|
||||||
for_obj_id,
|
for_obj_id,
|
||||||
|
inc_file_li = false,
|
||||||
enabled = 'enabled',
|
enabled = 'enabled',
|
||||||
hidden = 'not_hidden',
|
hidden = 'not_hidden',
|
||||||
limit = 100,
|
limit = 100,
|
||||||
@@ -82,6 +94,7 @@ export async function load_ae_obj_li__event_location({
|
|||||||
api_cfg: any;
|
api_cfg: any;
|
||||||
for_obj_type?: string;
|
for_obj_type?: string;
|
||||||
for_obj_id: string;
|
for_obj_id: string;
|
||||||
|
inc_file_li?: boolean;
|
||||||
enabled?: 'enabled' | 'all' | 'not_enabled';
|
enabled?: 'enabled' | 'all' | 'not_enabled';
|
||||||
hidden?: 'hidden' | 'all' | 'not_hidden';
|
hidden?: 'hidden' | 'all' | 'not_hidden';
|
||||||
limit?: number;
|
limit?: number;
|
||||||
@@ -136,9 +149,35 @@ export async function load_ae_obj_li__event_location({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ae_promises.load__event_location_obj_li) {
|
||||||
|
for (const location of ae_promises.load__event_location_obj_li) {
|
||||||
|
await _handle_nested_loads(location, { api_cfg, inc_file_li, log_lvl });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return ae_promises.load__event_location_obj_li || [];
|
return ae_promises.load__event_location_obj_li || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle nested data loads for a single location object.
|
||||||
|
*/
|
||||||
|
async function _handle_nested_loads(location_obj: any, { api_cfg, inc_file_li, log_lvl }: any) {
|
||||||
|
const current_location_id = location_obj.event_location_id_random || location_obj.event_location_id || location_obj.id;
|
||||||
|
|
||||||
|
if (inc_file_li) {
|
||||||
|
location_obj.event_file_li = await load_ae_obj_li__event_file({
|
||||||
|
api_cfg,
|
||||||
|
for_obj_type: 'event_location',
|
||||||
|
for_obj_id: current_location_id,
|
||||||
|
enabled: 'all',
|
||||||
|
limit: 25,
|
||||||
|
log_lvl
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return location_obj;
|
||||||
|
}
|
||||||
|
|
||||||
// Updated 2026-01-20 to V3
|
// Updated 2026-01-20 to V3
|
||||||
export async function create_ae_obj__event_location({
|
export async function create_ae_obj__event_location({
|
||||||
api_cfg,
|
api_cfg,
|
||||||
|
|||||||
@@ -1,123 +1,76 @@
|
|||||||
/**
|
/**
|
||||||
* Aether Electron Relay (V3)
|
* Aether Native Bridge Relay (TypeScript)
|
||||||
* Maps legacy launcher commands to the modern Aether Native Bridge.
|
* Standardizes communication between SvelteKit and the Electron Main process.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const log_lvl = 1;
|
// Handle the standardized aetherNative bridge
|
||||||
|
const native = (typeof window !== 'undefined') ? (window as any).aetherNative : null;
|
||||||
|
|
||||||
/**
|
export async function get_device_config(): Promise<any> {
|
||||||
* Returns the native bridge if available.
|
if (!native) return null;
|
||||||
*/
|
return await native.get_device_config();
|
||||||
function getBridge() {
|
|
||||||
if (typeof window !== 'undefined' && (window as any).aetherNative) {
|
|
||||||
return (window as any).aetherNative;
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// --- File Operations ---
|
||||||
* Open a local directory in the OS File Manager.
|
|
||||||
*/
|
|
||||||
export async function open_folder(path: string) {
|
|
||||||
const bridge = getBridge();
|
|
||||||
if (!bridge) return { success: false, error: 'Native bridge not found' };
|
|
||||||
|
|
||||||
if (log_lvl) console.log(`Relay: Opening folder: ${path}`);
|
export async function check_hash_file_cache({ cache_root, hash }: { cache_root: string; hash: string }): Promise<boolean> {
|
||||||
return await bridge.openFolder(path);
|
if (!native) return false;
|
||||||
|
// Uses the specific bridge method instead of generic invoke
|
||||||
|
return await native.check_cache({ cache_root, hash });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function download_to_cache({
|
||||||
* Launch a file using the OS default application.
|
url,
|
||||||
*/
|
cache_root,
|
||||||
export async function launch_file(path: string) {
|
hash,
|
||||||
const bridge = getBridge();
|
api_key,
|
||||||
if (!bridge) return { success: false, error: 'Native bridge not found' };
|
account_id
|
||||||
|
}: {
|
||||||
if (log_lvl) console.log(`Relay: Launching file: ${path}`);
|
url: string;
|
||||||
return await bridge.launchFile(path);
|
cache_root: string;
|
||||||
|
hash: string;
|
||||||
|
api_key: string;
|
||||||
|
account_id: string;
|
||||||
|
}): Promise<{ success: boolean; path?: string; error?: string }> {
|
||||||
|
if (!native) return { success: false, error: 'Native bridge not available' };
|
||||||
|
return await native.download_to_cache({ url, cache_root, hash, api_key, account_id });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function launch_from_cache({
|
||||||
* Legacy compatibility alias for launch_file.
|
cache_root,
|
||||||
*/
|
hash,
|
||||||
export const open_local_file_v2 = async ({ file_path, filename }: { file_path: string, filename: string }) => {
|
temp_root,
|
||||||
return launch_file(`${file_path}/${filename}`);
|
filename
|
||||||
};
|
}: {
|
||||||
|
cache_root: string;
|
||||||
/**
|
hash: string;
|
||||||
* Run a shell command.
|
temp_root: string;
|
||||||
*/
|
filename: string;
|
||||||
export async function run_cmd({ cmd, return_stdout = true }: { cmd: string, return_stdout?: boolean }) {
|
}): Promise<{ success: boolean; error?: string }> {
|
||||||
const bridge = getBridge();
|
if (!native) return { success: false, error: 'Native bridge not available' };
|
||||||
if (!bridge) return { success: false, error: 'Native bridge not found' };
|
return await native.launch_from_cache({ cache_root, hash, temp_root, filename });
|
||||||
|
|
||||||
if (log_lvl) console.log(`Relay: Running command: ${cmd}`);
|
|
||||||
const result = await bridge.runCommand(cmd);
|
|
||||||
|
|
||||||
if (return_stdout) return result.stdout;
|
|
||||||
return result.success;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// --- OS Operations ---
|
||||||
* Legacy compatibility alias for run_cmd.
|
|
||||||
*/
|
|
||||||
export const run_cmd_sync = run_cmd;
|
|
||||||
|
|
||||||
/**
|
export async function open_folder(path: string): Promise<void> {
|
||||||
* Kill specific processes by name.
|
if (!native) return;
|
||||||
*/
|
return await native.open_folder(path);
|
||||||
export async function kill_processes({ process_name_li = [] }: { process_name_li: string[] }) {
|
|
||||||
const bridge = getBridge();
|
|
||||||
if (!bridge) return { success: false, error: 'Native bridge not found' };
|
|
||||||
|
|
||||||
for (const name of process_name_li) {
|
|
||||||
if (log_lvl) console.log(`Relay: Killing process: ${name}`);
|
|
||||||
await bridge.runCommand(`pkill -f "${name}"`);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function run_cmd({ cmd, return_stdout = false }: { cmd: string; return_stdout?: boolean }): Promise<string | boolean> {
|
||||||
* Check if a file hash exists in the local cache.
|
if (!native) return false;
|
||||||
*/
|
return await native.run_command({ cmd, return_stdout });
|
||||||
export async function check_hash_file_cache({ cacheRoot, hash }: { cacheRoot: string, hash: string }) {
|
|
||||||
const bridge = getBridge();
|
|
||||||
if (!bridge) return false;
|
|
||||||
return await bridge.checkCache({ cacheRoot, hash });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
export async function run_cmd_sync({ cmd, return_stdout = false }: { cmd: string; return_stdout?: boolean }): Promise<string | boolean> {
|
||||||
* Download a file to the local cache.
|
if (!native) return false;
|
||||||
*/
|
// Legacy mapping to the same command handler
|
||||||
export async function download_to_cache({ url, cacheRoot, hash, apiKey }: { url: string, cacheRoot: string, hash: string, apiKey: string }) {
|
return await native.run_command({ cmd, return_stdout });
|
||||||
const bridge = getBridge();
|
|
||||||
if (!bridge) return { success: false, error: 'Native bridge not found' };
|
|
||||||
return await bridge.downloadToCache({ url, cacheRoot, hash, apiKey });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// --- Compatibility Aliases (Legacy) ---
|
||||||
* Launch a file from the local cache (copies to temp first).
|
export const open_local_file_v2 = launch_from_cache;
|
||||||
*/
|
export const get_device_info = get_device_config;
|
||||||
export async function launch_from_cache({ cacheRoot, hash, tempRoot, filename }: { cacheRoot: string, hash: string, tempRoot: string, filename: string }) {
|
export const run_osascript = async ({ cmd }: { cmd: string }) => await run_cmd({ cmd: `osascript -e '${cmd}'` });
|
||||||
const bridge = getBridge();
|
export const kill_processes = async ({ process_name }: { process_name: string }) => await run_cmd({ cmd: `pkill -f '${process_name}'` });
|
||||||
if (!bridge) return { success: false, error: 'Native bridge not found' };
|
|
||||||
return await bridge.launchFromCache({ cacheRoot, hash, tempRoot, filename });
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get system/device info.
|
|
||||||
*/
|
|
||||||
export async function get_device_info() {
|
|
||||||
const bridge = getBridge();
|
|
||||||
if (!bridge) return null;
|
|
||||||
return await bridge.getDeviceConfig();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Placeholder for legacy AppleScript execution.
|
|
||||||
*/
|
|
||||||
export async function run_osascript({ cmd }: { cmd: string }) {
|
|
||||||
const bridge = getBridge();
|
|
||||||
if (!bridge) return null;
|
|
||||||
return await bridge.runCommand(`osascript -e '${cmd}'`);
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -128,7 +128,7 @@ export async function load({ fetch, params, parent, route, url }) {
|
|||||||
is_native = true;
|
is_native = true;
|
||||||
if (log_lvl) console.log('ROOT LOAD: Detected Aether Native Bridge. Requesting device config...');
|
if (log_lvl) console.log('ROOT LOAD: Detected Aether Native Bridge. Requesting device config...');
|
||||||
try {
|
try {
|
||||||
const native_device_config = await (window as any).aetherNative.getDeviceConfig();
|
const native_device_config = await (window as any).aetherNative.get_device_config();
|
||||||
if (native_device_config) {
|
if (native_device_config) {
|
||||||
if (log_lvl) console.log('ROOT LOAD: Native device config received:', native_device_config);
|
if (log_lvl) console.log('ROOT LOAD: Native device config received:', native_device_config);
|
||||||
// Map native device config to the expected result structure
|
// Map native device config to the expected result structure
|
||||||
|
|||||||
21
src/routes/events/[event_id]/(launcher)/+layout.svelte
Normal file
21
src/routes/events/[event_id]/(launcher)/+layout.svelte
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
/**
|
||||||
|
* events/[event_id]/(launcher)/+layout.svelte
|
||||||
|
* Root layout for the launcher area.
|
||||||
|
* Ensures background sync runs globally regardless of active tab.
|
||||||
|
*/
|
||||||
|
import { ae_loc } from '$lib/stores/ae_stores';
|
||||||
|
import LauncherBackgroundSync from './launcher_background_sync.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- Background Sync Process (Invisible) -->
|
||||||
|
<LauncherBackgroundSync log_lvl={1} />
|
||||||
|
|
||||||
|
<!-- Render the rest of the launcher UI -->
|
||||||
|
{@render children?.()}
|
||||||
@@ -184,7 +184,8 @@
|
|||||||
|
|
||||||
// console.log('GET DEVICE INFO!!! GET DEVICE INFO!!! GET DEVICE INFO!!!');
|
// console.log('GET DEVICE INFO!!! GET DEVICE INFO!!! GET DEVICE INFO!!!');
|
||||||
|
|
||||||
let get_device_info_promise = get_device_info({ event_device_id: event_device_id });
|
// Using standardized relay method
|
||||||
|
let get_device_info_promise = get_device_info();
|
||||||
get_device_info_promise.then(function (result) {
|
get_device_info_promise.then(function (result) {
|
||||||
// console.log('GOT DEVICE INFO!!! GOT DEVICE INFO!!! GOT DEVICE INFO!!!');
|
// console.log('GOT DEVICE INFO!!! GOT DEVICE INFO!!! GOT DEVICE INFO!!!');
|
||||||
console.log(get_device_info_promise);
|
console.log(get_device_info_promise);
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export async function load({ params, parent, url }) {
|
|||||||
for_obj_type: 'event_location',
|
for_obj_type: 'event_location',
|
||||||
for_obj_id: event_location_id,
|
for_obj_id: event_location_id,
|
||||||
inc_file_li: true, // Only include files directly under the session?
|
inc_file_li: true, // Only include files directly under the session?
|
||||||
inc_all_file_li: false, // Also include files under presentations and presenters as well?
|
inc_all_file_li: true, // Also include files under presentations and presenters as well?
|
||||||
inc_presentation_li: true,
|
inc_presentation_li: true,
|
||||||
inc_presenter_li: true,
|
inc_presenter_li: true,
|
||||||
enabled: 'enabled',
|
enabled: 'enabled',
|
||||||
|
|||||||
@@ -1,126 +1,149 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
/**
|
/**
|
||||||
* launcher_background_sync.svelte
|
* launcher_background_sync.svelte
|
||||||
* Handles background pre-caching of event files when running in Native mode.
|
* Exhaustive Background Pre-Caching Engine.
|
||||||
* Uses configurable timers from the device configuration.
|
* Scans Sessions, Presentations, and Presenters to ensure 100% of room files are cached.
|
||||||
*/
|
*/
|
||||||
import { onMount, untrack, onDestroy } from 'svelte';
|
import { onMount, onDestroy } from 'svelte';
|
||||||
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
import { ae_loc, ae_api } from '$lib/stores/ae_stores';
|
||||||
import { events_slct } from '$lib/stores/ae_events_stores';
|
import { events_slct } from '$lib/stores/ae_events_stores';
|
||||||
import { db_events } from '$lib/ae_events/db_events';
|
import { db_events } from '$lib/ae_events/db_events';
|
||||||
import * as native from '$lib/electron/electron_relay';
|
import * as native from '$lib/electron/electron_relay';
|
||||||
|
|
||||||
let { log_lvl = 0 } = $props();
|
let { log_lvl = 1 } = $props();
|
||||||
|
|
||||||
let currently_syncing: string | null = $state(null);
|
let currently_syncing: string | null = $state(null);
|
||||||
let sync_results: Record<string, 'success' | 'error' | 'pending'> = $state({});
|
let sync_results: Record<string, 'success' | 'error' | 'pending'> = $state({});
|
||||||
let sync_interval: any = null;
|
let sync_stats = $state({ total: 0, cached: 0, missing: 0 });
|
||||||
|
|
||||||
|
let main_sync_interval: any = null;
|
||||||
|
let is_syncing = false;
|
||||||
|
|
||||||
// Trigger initial sync and setup interval
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
if (!$ae_loc.is_native) return;
|
if (!$ae_loc.is_native) return;
|
||||||
|
|
||||||
// 1. Initial Immediate Sync
|
if (log_lvl) console.log('Sync: Starting exhaustive background caching engine.');
|
||||||
setTimeout(() => {
|
|
||||||
if (log_lvl) console.log('Sync: Triggering immediate startup sync cycle.');
|
|
||||||
const files = $events_slct.event_file_obj_li;
|
|
||||||
if (files && files.length > 0) {
|
|
||||||
const ids = files.map((f: any) => f.event_file_id);
|
|
||||||
process_sync_queue(ids);
|
|
||||||
}
|
|
||||||
}, 1000);
|
|
||||||
|
|
||||||
// 2. Setup Loop Timer
|
// Setup persistent loop (Check every 10s for new data/missing files)
|
||||||
// Default to 60 seconds if not specified in native_device config
|
const sync_period = 10000;
|
||||||
const loop_period = $ae_loc.native_device?.check_event_loop_period || 60000;
|
|
||||||
if (log_lvl) console.log(`Sync: Starting background loop with period: ${loop_period}ms`);
|
|
||||||
|
|
||||||
sync_interval = setInterval(() => {
|
main_sync_interval = setInterval(() => {
|
||||||
const files = $events_slct.event_file_obj_li;
|
if (!is_syncing) {
|
||||||
if (files && files.length > 0) {
|
run_sync_cycle();
|
||||||
const ids = files.map((f: any) => f.event_file_id);
|
|
||||||
process_sync_queue(ids);
|
|
||||||
}
|
}
|
||||||
}, loop_period);
|
}, sync_period);
|
||||||
|
|
||||||
|
// Immediate first run
|
||||||
|
run_sync_cycle();
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
if (sync_interval) clearInterval(sync_interval);
|
if (main_sync_interval) clearInterval(main_sync_interval);
|
||||||
});
|
|
||||||
|
|
||||||
// Reactive Watch: Also trigger if the file list changes significantly
|
|
||||||
$effect(() => {
|
|
||||||
if (!$ae_loc.is_native) return;
|
|
||||||
|
|
||||||
const files = $events_slct.event_file_obj_li;
|
|
||||||
if (!files || files.length === 0) return;
|
|
||||||
|
|
||||||
untrack(() => {
|
|
||||||
const file_ids = files.map((f: any) => f.event_file_id);
|
|
||||||
// Only process if we haven't checked these specific IDs recently
|
|
||||||
process_sync_queue(file_ids);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Iterates through the file list and downloads missing files.
|
* Primary Sync Cycle
|
||||||
|
* Recursively identifies ALL files in the room hierarchy.
|
||||||
*/
|
*/
|
||||||
async function process_sync_queue(ids: string[]) {
|
async function run_sync_cycle() {
|
||||||
const cacheRoot = $ae_loc.local_file_cache_path;
|
const location_id = $events_slct.event_location_id;
|
||||||
if (!cacheRoot) {
|
const cache_root = $ae_loc.local_file_cache_path;
|
||||||
console.warn('Sync: local_file_cache_path not set. Skipping sync.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (log_lvl) console.log(`Sync: Processing queue of ${ids.length} files...`);
|
if (!location_id || !cache_root) return;
|
||||||
|
|
||||||
for (const id of ids) {
|
is_syncing = true;
|
||||||
// Skip if already successfully cached in this session to avoid redundant IPC calls
|
if (log_lvl > 1) console.log(`Sync Engine: Scanning room ${location_id}...`);
|
||||||
if (sync_results[id] === 'success') continue;
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const file_obj = await db_events.file.get(id);
|
// 1. Gather all IDs in this room's hierarchy
|
||||||
if (!file_obj || !file_obj.hash_sha256) continue;
|
const sessions = await db_events.session.where('event_location_id').equals(location_id).toArray();
|
||||||
|
const session_ids = sessions.map(s => s.event_session_id);
|
||||||
|
if (session_ids.length === 0) return;
|
||||||
|
|
||||||
|
const presentations = await db_events.presentation.where('event_session_id').anyOf(session_ids).toArray();
|
||||||
|
const presentation_ids = presentations.map(p => p.event_presentation_id);
|
||||||
|
|
||||||
|
const presenters = await db_events.presenter.where('event_session_id').anyOf(session_ids).toArray();
|
||||||
|
const presenter_ids = presenters.map(p => p.event_presenter_id);
|
||||||
|
|
||||||
|
// 2. Collect every ID that could have a file attached
|
||||||
|
const all_for_ids = [...session_ids, ...presentation_ids, ...presenter_ids, $events_slct.event_id];
|
||||||
|
|
||||||
|
// 3. Find ALL files linked to these IDs
|
||||||
|
const files = await db_events.file.where('for_id').anyOf(all_for_ids).toArray();
|
||||||
|
|
||||||
|
sync_stats.total = files.length;
|
||||||
|
let cached_count = 0;
|
||||||
|
let missing_count = 0;
|
||||||
|
|
||||||
|
if (log_lvl > 1) console.log(`Sync Engine: Found ${files.length} potential files for this room.`);
|
||||||
|
|
||||||
|
// 4. Verify and Download
|
||||||
|
for (const file_obj of files) {
|
||||||
|
if (!file_obj.hash_sha256) continue;
|
||||||
|
|
||||||
|
// Fast-track if already confirmed this session
|
||||||
|
if (sync_results[file_obj.event_file_id] === 'success') {
|
||||||
|
cached_count++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const exists = await native.check_hash_file_cache({
|
const exists = await native.check_hash_file_cache({
|
||||||
cacheRoot,
|
cache_root,
|
||||||
hash: file_obj.hash_sha256
|
hash: file_obj.hash_sha256
|
||||||
});
|
});
|
||||||
|
|
||||||
if (exists) {
|
if (exists) {
|
||||||
sync_results[id] = 'success';
|
sync_results[file_obj.event_file_id] = 'success';
|
||||||
|
cached_count++;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (log_lvl) console.log(`Sync: Downloading missing file: ${file_obj.filename}`);
|
// 5. Download missing file
|
||||||
|
missing_count++;
|
||||||
|
if (log_lvl) console.log(`Sync: Pulling missing file -> ${file_obj.filename}`);
|
||||||
currently_syncing = file_obj.filename;
|
currently_syncing = file_obj.filename;
|
||||||
|
|
||||||
const url = `${$ae_api.base_url}/v3/crud/hosted_file/${file_obj.hosted_file_id}/download`;
|
const url = `${$ae_api.base_url}/hosted_file/${file_obj.hosted_file_id}/download?return_file=true&filename=${encodeURIComponent(file_obj.filename)}`;
|
||||||
|
|
||||||
const result = await native.download_to_cache({
|
const result = await native.download_to_cache({
|
||||||
url,
|
url,
|
||||||
cacheRoot,
|
cache_root,
|
||||||
hash: file_obj.hash_sha256,
|
hash: file_obj.hash_sha256,
|
||||||
apiKey: $ae_api.api_secret_key
|
api_key: $ae_api.api_secret_key,
|
||||||
|
account_id: $ae_api.account_id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
sync_results[id] = 'success';
|
sync_results[file_obj.event_file_id] = 'success';
|
||||||
if (log_lvl) console.log(`Sync: Successfully cached ${file_obj.filename}`);
|
|
||||||
} else {
|
|
||||||
console.error(`Sync: Failed to cache ${file_obj.filename}:`, result.error);
|
|
||||||
sync_results[id] = 'error';
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
|
||||||
console.error(`Sync: Error processing file ${id}:`, err);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sync_stats.cached = cached_count;
|
||||||
|
sync_stats.missing = missing_count;
|
||||||
|
|
||||||
|
if (log_lvl && missing_count > 0) {
|
||||||
|
console.log(`Sync Engine: Room scan complete. Cached: ${cached_count}, Downloaded: ${missing_count}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Sync Engine: Critical failure in room scan:', err);
|
||||||
|
} finally {
|
||||||
|
currently_syncing = null;
|
||||||
|
is_syncing = false;
|
||||||
}
|
}
|
||||||
currently_syncing = null;
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#if currently_syncing && log_lvl}
|
{#if currently_syncing && log_lvl}
|
||||||
<div class="fixed bottom-4 right-4 bg-black/80 text-white p-2 rounded-lg text-[10px] z-[9999] border border-primary-500 animate-pulse">
|
<div class="fixed bottom-4 right-4 bg-black/80 text-white p-2 rounded-lg text-[10px] z-[9999] border border-primary-500 animate-pulse shadow-2xl">
|
||||||
<span class="fas fa-sync fa-spin mr-2"></span>
|
<div class="flex items-center gap-2">
|
||||||
Pre-Caching: {currently_syncing}
|
<span class="fas fa-sync fa-spin text-primary-500"></span>
|
||||||
|
<div class="flex flex-col">
|
||||||
|
<span class="font-bold">Syncing Room Files...</span>
|
||||||
|
<span class="opacity-70 truncate max-w-48">{currently_syncing}</span>
|
||||||
|
<span class="text-[8px] mt-1 text-primary-300">Room Status: {sync_stats.cached}/{sync_stats.total} Ready</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
@@ -90,30 +90,33 @@
|
|||||||
|
|
||||||
// 1. NATIVE MODE (Electron)
|
// 1. NATIVE MODE (Electron)
|
||||||
if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') {
|
if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') {
|
||||||
const cacheRoot = $ae_loc.local_file_cache_path;
|
const cache_root = $ae_loc.local_file_cache_path;
|
||||||
const tempRoot = $ae_loc.host_file_temp_path;
|
const temp_root = $ae_loc.host_file_temp_path;
|
||||||
|
|
||||||
open_file_clicked = true;
|
open_file_clicked = true;
|
||||||
open_file_status = 'checking_cache';
|
open_file_status = 'checking_cache';
|
||||||
open_file_status_message = 'Checking local cache...';
|
open_file_status_message = 'Checking local cache...';
|
||||||
|
|
||||||
const exists = await native.check_hash_file_cache({ cacheRoot, hash: event_file_obj.hash_sha256 });
|
const exists = await native.check_hash_file_cache({ cache_root, hash: event_file_obj.hash_sha256 });
|
||||||
|
|
||||||
if (!exists) {
|
if (!exists) {
|
||||||
open_file_status = 'downloading_file';
|
open_file_status = 'downloading_file';
|
||||||
open_file_status_message = 'Downloading file to cache...';
|
open_file_status_message = 'Downloading file to cache...';
|
||||||
|
|
||||||
const url = `${$ae_api.base_url}/v3/crud/hosted_file/${event_file_obj.hosted_file_id}/download`;
|
// Use the PROVEN endpoint path from api.ts that is known to work in Default Mode.
|
||||||
const dlResult = await native.download_to_cache({
|
const url = `${$ae_api.base_url}/hosted_file/${event_file_obj.hosted_file_id}/download?return_file=true&filename=${encodeURIComponent(event_file_obj.filename)}`;
|
||||||
|
|
||||||
|
const dl_result = await native.download_to_cache({
|
||||||
url,
|
url,
|
||||||
cacheRoot,
|
cache_root,
|
||||||
hash: event_file_obj.hash_sha256,
|
hash: event_file_obj.hash_sha256,
|
||||||
apiKey: $ae_api.api_secret_key
|
api_key: $ae_api.api_secret_key,
|
||||||
|
account_id: $ae_api.account_id
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!dlResult.success) {
|
if (!dl_result.success) {
|
||||||
open_file_status = 'error';
|
open_file_status = 'error';
|
||||||
open_file_status_message = `Download failed: ${dlResult.error}`;
|
open_file_status_message = `Download failed: ${dl_result.error}`;
|
||||||
setTimeout(() => open_file_clicked = false, 5000);
|
setTimeout(() => open_file_clicked = false, 5000);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -122,16 +125,16 @@
|
|||||||
open_file_status = 'opening_file';
|
open_file_status = 'opening_file';
|
||||||
open_file_status_message = 'Opening Application';
|
open_file_status_message = 'Opening Application';
|
||||||
|
|
||||||
const launchResult = await native.launch_from_cache({
|
const launch_result = await native.launch_from_cache({
|
||||||
cacheRoot,
|
cache_root,
|
||||||
hash: event_file_obj.hash_sha256,
|
hash: event_file_obj.hash_sha256,
|
||||||
tempRoot,
|
temp_root,
|
||||||
filename: event_file_obj.filename
|
filename: event_file_obj.filename
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!launchResult.success) {
|
if (!launch_result.success) {
|
||||||
open_file_status = 'error';
|
open_file_status = 'error';
|
||||||
open_file_status_message = `Failed to open: ${launchResult.error}`;
|
open_file_status_message = `Failed to open: ${launch_result.error}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
setTimeout(() => open_file_clicked = false, 5000);
|
setTimeout(() => open_file_clicked = false, 5000);
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function load({ params, parent, url }) {
|
|||||||
const load_event_obj: ae_Event | null = await events_func.load_ae_obj_id__event({
|
const load_event_obj: ae_Event | null = await events_func.load_ae_obj_id__event({
|
||||||
api_cfg: ae_acct.api,
|
api_cfg: ae_acct.api,
|
||||||
event_id: event_id,
|
event_id: event_id,
|
||||||
// inc_file_li: true,
|
inc_file_li: true,
|
||||||
// inc_device_li: true,
|
// inc_device_li: true,
|
||||||
inc_location_li: true,
|
inc_location_li: true,
|
||||||
inc_session_li: true,
|
inc_session_li: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user