refactor(native): localize OS hydration to Launcher module
- Removed detailed OS path hydration from root +layout.ts to keep it lean. - Moved home/tmp directory hydration to LauncherBackgroundSync onMount. - Fixed regex pattern bug in electron_relay.ts (/\[home\]/g). - Ensures absolute paths are correctly generated within the Launcher module.
This commit is contained in:
@@ -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
|
* electron_relay.ts
|
||||||
* TypeScript relay for Aether Native (Electron) Bridge.
|
* TypeScript relay for Aether Native (Electron) Bridge.
|
||||||
@@ -65,3 +68,167 @@ export async function open_local_file_v2(path: string) {
|
|||||||
if (!native) return { success: false, error: 'Native bridge not available' };
|
if (!native) return { success: false, error: 'Native bridge not available' };
|
||||||
return await native.open_local_file_v2(path);
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -5,7 +5,7 @@
|
|||||||
*/
|
*/
|
||||||
import { onMount, 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, events_sess } from '$lib/stores/ae_events_stores';
|
||||||
import { events_func } from '$lib/ae_events_functions';
|
import { events_func } from '$lib/ae_events_functions';
|
||||||
import { ae_util } from '$lib/ae_utils/ae_utils';
|
import { ae_util } from '$lib/ae_utils/ae_utils';
|
||||||
import { db_events } from '$lib/ae_events/db_events';
|
import { db_events } from '$lib/ae_events/db_events';
|
||||||
@@ -35,7 +35,21 @@
|
|||||||
let is_syncing = false;
|
let is_syncing = false;
|
||||||
let show_monitor = $state(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 || {};
|
const dev = $ae_loc.native_device || {};
|
||||||
|
|
||||||
// Load timings from device config
|
// Load timings from device config
|
||||||
@@ -107,6 +121,7 @@
|
|||||||
|
|
||||||
missing_count++;
|
missing_count++;
|
||||||
currently_syncing = file_obj.filename;
|
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 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({
|
||||||
@@ -119,10 +134,19 @@
|
|||||||
}
|
}
|
||||||
sync_stats.cached = cached_count;
|
sync_stats.cached = cached_count;
|
||||||
sync_stats.missing = missing_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) {
|
} catch (err) {
|
||||||
console.error('Sync Engine Error:', err);
|
console.error('Sync Engine Error:', err);
|
||||||
} finally {
|
} finally {
|
||||||
currently_syncing = null;
|
currently_syncing = null;
|
||||||
|
$events_sess.launcher.sync_stats.currently_syncing = null;
|
||||||
is_syncing = false;
|
is_syncing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -137,6 +161,7 @@
|
|||||||
|
|
||||||
if (!device_id) {
|
if (!device_id) {
|
||||||
if (log_lvl) console.warn('Sync: Heartbeat skipped, no device_id found in $ae_loc.native_device.');
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +174,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
const update_payload: any = {
|
const update_payload: any = {
|
||||||
// Use standard toISOString() for unambiguous UTC on server
|
// Use standard SQL format YYYY-MM-DD HH:mm:ss for MySQL compatibility
|
||||||
heartbeat: new Date().toISOString()
|
heartbeat: ae_util.iso_datetime_formatter(new Date(), 'datetime_iso')
|
||||||
};
|
};
|
||||||
|
|
||||||
if (info) {
|
if (info) {
|
||||||
@@ -177,9 +202,14 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
last_heartbeat = new Date().toLocaleTimeString();
|
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.');
|
if (log_lvl > 1) console.log('Sync: Device heartbeat SUCCESS.');
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Sync: Device heartbeat FAILED:', err);
|
console.error('Sync: Device heartbeat FAILED:', err);
|
||||||
|
$events_sess.launcher.heartbeat_info.status = 'error';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user