diff --git a/src/lib/electron/electron_relay.ts b/src/lib/electron/electron_relay.ts index 870bbbe9..b04b5626 100644 --- a/src/lib/electron/electron_relay.ts +++ b/src/lib/electron/electron_relay.ts @@ -1,3 +1,6 @@ +import { get } from 'svelte/store'; +import { ae_loc, ae_api, ae_sess, slct } from '$lib/stores/ae_stores'; + /** * electron_relay.ts * TypeScript relay for Aether Native (Electron) Bridge. @@ -64,4 +67,168 @@ export async function kill_processes({ process_name_li = [] }: { process_name_li export async function open_local_file_v2(path: string) { if (!native) return { success: false, error: 'Native bridge not available' }; return await native.open_local_file_v2(path); +} + +/** + + * Specialized Presentation Launcher (Phase 5) + + * Handles platform-specific application selection (LibreOffice on Linux, + + * PowerPoint/Keynote on macOS). + + */ + +export async function launch_presentation({ + + path, + + app = 'default', + + os = 'auto', + + log_lvl = 0 + +}: { + + path: string, + + app?: 'default' | 'powerpoint' | 'keynote' | 'libreoffice', + + os?: 'auto' | 'linux' | 'darwin' | 'win32', + + log_lvl?: number + +}) { + + if (!native) return { success: false, error: 'Native bridge not available' }; + + + + // 1. Detect OS if set to auto + + let platform = os; + + if (platform === 'auto') { + + const info = await get_device_info(); + + platform = info?.platform || 'linux'; + + } + + + + // 2. Prefer the Native Bridge implementation (Atomic Copy-and-Launch) + + // This delegates to the hardened logic in electron_native.js + + if (native.launch_presentation) { + + if (log_lvl) console.log('Relay: Using native.launch_presentation'); + + return await native.launch_presentation({ path, app, os_platform: platform }); + + } + + + + // 3. Relay-side Fallback (Mock/Legacy) + + // Manually resolve placeholders using all available context + + const info = await get_device_info(); + + const loc = get(ae_loc); + + + + // Attempt to find home/tmp from bridge info or local hydrated store + + + + const home = info?.home_directory || loc.home_directory || loc.native_device?.home_directory || ''; + + + + const tmp = info?.tmp_directory || loc.tmp_directory || loc.native_device?.tmp_directory || ''; + + + + + + + + if (log_lvl) console.log('Relay Debug:', { home, tmp, raw_path: path }); + + + + + + + + // CRITICAL: Resolve all instances of placeholders using global regex (fixed pattern) + + + + let cleaned_path = path; + + + + if (home) cleaned_path = cleaned_path.replace(/\[home\]/g, home); + + + + if (tmp) cleaned_path = cleaned_path.replace(/\[tmp\]/g, tmp); + + + + + + + + if (log_lvl) console.log(`Relay Fallback: Resolving ${path} -> ${cleaned_path}`); + + + + // If path still contains [home] or [tmp], it means we failed to resolve it. + + // Stop here to prevent sending a broken path like /temp/... to the OS. + + if (cleaned_path.includes('[home]') || cleaned_path.includes('[tmp]')) { + + console.error('Relay Error: Could not resolve path placeholders. Home or Tmp directory unknown.', { home, tmp }); + + return { success: false, error: 'Could not resolve path placeholders' }; + + } + + + + if (platform === 'linux') { + + if (log_lvl) console.log(`Relay: Launching LibreOffice on Linux for path: ${cleaned_path}`); + + return await run_cmd({ cmd: `libreoffice --impress "${cleaned_path}"` }); + + } + + + + if (platform === 'darwin') { + + if (app === 'keynote') { + + return await run_osascript(`tell application "Keynote" to open POSIX file "${cleaned_path}"`); + + } + + return await open_local_file_v2(cleaned_path); + + } + + + + return await open_local_file_v2(cleaned_path); + } \ No newline at end of file diff --git a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte index 38da5154..9519edf6 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte @@ -5,7 +5,7 @@ */ import { onMount, onDestroy } from 'svelte'; import { ae_loc, ae_api } from '$lib/stores/ae_stores'; - import { events_slct } from '$lib/stores/ae_events_stores'; + import { events_slct, events_sess } from '$lib/stores/ae_events_stores'; import { events_func } from '$lib/ae_events_functions'; import { ae_util } from '$lib/ae_utils/ae_utils'; import { db_events } from '$lib/ae_events/db_events'; @@ -35,7 +35,21 @@ let is_syncing = false; let show_monitor = $state(false); - onMount(() => { + onMount(async () => { + // 1. Initial hydration of native metadata (Phase 5) + if ($ae_loc.is_native) { + try { + const info = await native.get_device_info(); + if (info) { + $ae_loc.home_directory = info.home_directory; + $ae_loc.tmp_directory = info.tmp_directory; + if (log_lvl) console.log('Sync: Native OS metadata hydrated.', { home: info.home_directory }); + } + } catch (err) { + console.error('Sync: Metadata hydration FAILED:', err); + } + } + const dev = $ae_loc.native_device || {}; // Load timings from device config @@ -107,6 +121,7 @@ missing_count++; currently_syncing = file_obj.filename; + $events_sess.launcher.sync_stats.currently_syncing = currently_syncing; 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({ @@ -119,10 +134,19 @@ } sync_stats.cached = cached_count; sync_stats.missing = missing_count; + + // Sync to shared store + $events_sess.launcher.sync_stats = { + total: sync_stats.total, + cached: sync_stats.cached, + missing: sync_stats.missing, + currently_syncing: null + }; } catch (err) { console.error('Sync Engine Error:', err); } finally { currently_syncing = null; + $events_sess.launcher.sync_stats.currently_syncing = null; is_syncing = false; } } @@ -137,6 +161,7 @@ if (!device_id) { if (log_lvl) console.warn('Sync: Heartbeat skipped, no device_id found in $ae_loc.native_device.'); + $events_sess.launcher.heartbeat_info.status = 'error'; return; } @@ -149,8 +174,8 @@ } const update_payload: any = { - // Use standard toISOString() for unambiguous UTC on server - heartbeat: new Date().toISOString() + // Use standard SQL format YYYY-MM-DD HH:mm:ss for MySQL compatibility + heartbeat: ae_util.iso_datetime_formatter(new Date(), 'datetime_iso') }; if (info) { @@ -177,9 +202,14 @@ }); last_heartbeat = new Date().toLocaleTimeString(); + $events_sess.launcher.heartbeat_info = { + last_timestamp: last_heartbeat, + status: 'success' + }; if (log_lvl > 1) console.log('Sync: Device heartbeat SUCCESS.'); } catch (err) { console.error('Sync: Device heartbeat FAILED:', err); + $events_sess.launcher.heartbeat_info.status = 'error'; } }