From 683ea0394d20ebd6b4b70676b60c53fc18016130 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Fri, 23 Jan 2026 15:17:50 -0500 Subject: [PATCH] Fix: Restore Native caching business logic and implement 3-mode launcher - Implemented Default, Onsite, and Native launcher modes in launcher_file_cont.svelte. - Restored background pre-caching logic with configurable timers in new LauncherBackgroundSync component. - Fixed standard browser download regression for regular website mode. - Modernized electron_relay to TypeScript and standardized bridge detection in layout. - Detailed startup and background sync technical flow in documentation. --- documentation/NATIVE_APP_V3_REWRITE_PLAN.md | 81 ++ src/lib/electron/electron_relay.js | 333 -------- src/lib/electron/electron_relay.ts | 123 +++ src/routes/+layout.svelte | 7 +- src/routes/+layout.ts | 6 + .../launcher/[event_location_id]/+page.svelte | 4 +- .../launcher_background_sync.svelte | 126 +++ .../[event_id]/(launcher)/launcher_cfg.svelte | 74 ++ .../(launcher)/launcher_file_cont.svelte | 757 ++++-------------- .../(launcher)/launcher_session_view.svelte | 4 +- .../ae_comp__modal_journal_export.svelte | 4 +- 11 files changed, 558 insertions(+), 961 deletions(-) delete mode 100644 src/lib/electron/electron_relay.js create mode 100644 src/lib/electron/electron_relay.ts create mode 100644 src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte diff --git a/documentation/NATIVE_APP_V3_REWRITE_PLAN.md b/documentation/NATIVE_APP_V3_REWRITE_PLAN.md index de7fdd6a..a6f836ba 100644 --- a/documentation/NATIVE_APP_V3_REWRITE_PLAN.md +++ b/documentation/NATIVE_APP_V3_REWRITE_PLAN.md @@ -11,6 +11,17 @@ The sole purpose of this Native App is to serve as the **AE Events Launcher**. I - Execute OS-level shell commands and scripts (e.g., launching presentation software). - Provide a "Zero-Config" experience for onsite event laptops. +### 0.1 Application Modes +The system must support three distinct operational modes: + +| Mode | Refresh Logic | File Handling | +| --- | --- | --- | +| **Default** | Slower auto-refresh timers. | Standard browser downloads. No local caching. | +| **Onsite** | Faster auto-refresh timers. | Downloads files with modified extensions (e.g., .pptxwin). | +| **Native** | Fastest auto-refresh timers. | Full background pre-caching. Launching from local temp directory with original names. | + +--- + ## 1. Minimalist Configuration Strategy To simplify laptop deployment, we will move away from large local JSON files. @@ -36,6 +47,76 @@ Each laptop will contain a `seed.json`. For development, this is located at `~/s `http://[app_base_url]/events/[event_id]/launcher/[event_location_id]` 5. **Inject:** Config and JWT are injected into the SvelteKit frontend via the Preload script. +### 1.3 Technical Flow: Startup & Background Caching +The system utilizes a multi-layered hydration and synchronization strategy to ensure files are available instantly. + +#### Step 1: Zero-Config Initialization (Main Process) +- **Seed Discovery:** Main process reads `seed.json`. +- **Identity Exchange:** Main process authenticates with the API using the `aether_api_key`. +- **Global Injections:** Once hydrated, the Main process provides the SvelteKit frontend with: + - `native_device`: Full record from `event_device` table (contains timers). + - `aether_api_key`: For authorized background downloads. + - `local_file_cache_path`: Root for the permanent hashed cache. + - `host_file_temp_path`: Root for the operational launch directory. + +#### Step 2: Session-Driven Caching (Renderer Process) +- **View Trigger:** When a user navigates to a session or location, the SvelteKit frontend populates the `event_file_obj_li` store. +- **Sync Cycle:** The `LauncherBackgroundSync` component detects the new file list and: + 1. Extracts all `event_file_id` values. + 2. Fetches full metadata (hashes) from the local Dexie IndexedDB. + 3. Asynchronously checks the Native Cache for each hash. +- **Background Download:** Missing files are downloaded directly to the hashed cache using the authorized Native API Key. +- **Timer Loop:** A background loop runs every `check_event_loop_period` (configurable via API) to ensure the cache stays in sync with server-side changes. + +#### Step 3: Hashed Cache Pattern (Filesystem) +To prevent filename collisions and handle versioning, the permanent cache follows the server's structure: +- **Root:** `[local_file_cache_path]` +- **Subdirectory:** First 2 characters of SHA256 (e.g., `ab/`) +- **Filename:** Full SHA256 with `.file` extension (e.g., `abc123...file`) + +#### Step 4: Safe Handover (Launch Sequence) +When a user clicks "Open", the system follows a non-destructive handover: +1. **Verify:** Confirm hash exists in `local_file_cache_path`. +2. **Prepare:** Copy the hashed file to `host_file_temp_path`. +3. **Restore:** Rename the copy back to its original filename (e.g., `Presentation.pptx`). +4. **Execute:** Trigger OS-level `shell.openPath()` on the temp file. +*This ensures the permanent cache remains clean while the third-party app (PowerPoint, etc.) can operate on a file with a familiar name.* + +### 1.3 Technical Flow: Startup & Background Caching +The system utilizes a multi-layered hydration and synchronization strategy to ensure files are available instantly. + +#### Step 1: Zero-Config Initialization (Main Process) +- **Seed Discovery:** Main process reads `seed.json`. +- **Identity Exchange:** Main process authenticates with the API using the `aether_api_key`. +- **Global Injections:** Once hydrated, the Main process provides the SvelteKit frontend with: + - `native_device`: Full record from `event_device` table (contains timers). + - `aether_api_key`: For authorized background downloads. + - `local_file_cache_path`: Root for the permanent hashed cache. + - `host_file_temp_path`: Root for the operational launch directory. + +#### Step 2: Session-Driven Caching (Renderer Process) +- **View Trigger:** When a user navigates to a session or location, the SvelteKit frontend populates the `event_file_obj_li` store. +- **Sync Cycle:** The `LauncherBackgroundSync` component detects the new file list and: + 1. Extracts all `event_file_id` values. + 2. Fetches full metadata (hashes) from the local Dexie IndexedDB. + 3. Asynchronously checks the Native Cache for each hash. +- **Background Download:** Missing files are downloaded directly to the hashed cache using the authorized Native API Key. +- **Timer Loop:** A background loop runs every `check_event_loop_period` (configurable via API) to ensure the cache stays in sync with server-side changes. + +#### Step 3: Hashed Cache Pattern (Filesystem) +To prevent filename collisions and handle versioning, the permanent cache follows the server's structure: +- **Root:** `[local_file_cache_path]` +- **Subdirectory:** First 2 characters of SHA256 (e.g., `ab/`) +- **Filename:** Full SHA256 with `.file` extension (e.g., `abc123...file`) + +#### Step 4: Safe Handover (Launch Sequence) +When a user clicks "Open", the system follows a non-destructive handover: +1. **Verify:** Confirm hash exists in `local_file_cache_path`. +2. **Prepare:** Copy the hashed file to `host_file_temp_path`. +3. **Restore:** Rename the copy back to its original filename (e.g., `Presentation.pptx`). +4. **Execute:** Trigger OS-level `shell.openPath()` on the temp file. +*This ensures the permanent cache remains clean while the third-party app (PowerPoint, etc.) can operate on a file with a familiar name.* + ## 2. macOS Hardening (Permissions) ... macOS requires explicit user consent for several features. The new app will handle these during the "Splash Screen" phase. diff --git a/src/lib/electron/electron_relay.js b/src/lib/electron/electron_relay.js deleted file mode 100644 index c3d72e3a..00000000 --- a/src/lib/electron/electron_relay.js +++ /dev/null @@ -1,333 +0,0 @@ -/* ### Electron Specific JavaScript ### */ -// import crypto from 'crypto'; -// import {fs} from 'fs'; -// import path from 'path'; -// const crypto = require('crypto'); -// const fs = require('fs'); -// const fs_promises = require('node:fs/promises'); -// const path = require('path'); -// const { ipcRenderer } = require('electron'); - -// function sleep(milliseconds) { -// const date = Date.now(); -// let currentDate = null; -// do { -// currentDate = Date.now(); -// } while (currentDate - date < milliseconds); -// } - -// // exports.check_hash_file_cache should no longer be needed with this. -// // Updated 2022-10-11 -// export let check_hash_file_cache_v2 = async function check_hash_file_cache_v2({local_file_cache_path, hash, check_hash=false}) { -// console.log('*** check_hash_file_cache_v2() ***'); -// console.log(`Host File Cache Path: ${local_file_cache_path}; Hash: ${hash}`); - -// let hash_filename = `${hash}.file`; - -// let subdirectory = hash_filename.substring(0,2); -// let subdirectory_path = path.join(local_file_cache_path, subdirectory); -// if (fs.existsSync(subdirectory_path)) { -// } else { -// console.log(`Hashed file subdirectory not found in cache: ${subdirectory_path}`); -// return null; -// } - -// let hash_file_cache_path = path.join(subdirectory_path, hash_filename); - -// if (fs.existsSync(hash_file_cache_path)) { -// console.log(`Hashed file exists in cache: ${hash_file_cache_path}`); - -// if (check_hash) { -// const file_buffer = fs.readFileSync(hash_file_cache_path); -// const file_hash_sha256 = crypto.createHash('sha256'); -// file_hash_sha256.update(file_buffer); - -// const file_hash_sha256_check = file_hash_sha256.digest('hex'); -// if (file_hash_sha256_check == hash) { -// console.log('File hash match', file_hash_sha256_check); -// } else { -// console.log('File hash does not match', file_hash_sha256_check); -// return false; -// } -// } - -// return true; -// } else { -// console.log(`Hashed file not found in cache: ${hash_file_cache_path}`); -// return null; -// } -// } - -// @ts-nocheck -// Updated 2022-05-07 -export let kill_processes = async function kill_processes({ process_name_li = [] }) { - console.log('*** kill_processes() ***'); - console.log(`Process Name List: ${process_name_li}`); - - let fail_flag = null; - if (process_name_li) { - for (let i = 0; i < process_name_li.length; i++) { - // separate the keys and the values - let process_name = process_name_li[i]; - let signal = null; - - if (process_name == 'osit_aperture_wrapper') { - signal = 'INT'; // INT (interrupt) correctly stops the wrapper - } - - let kill_processes_result = await native_app.kill_processes({ - process_name: process_name, - signal: signal - }); - console.log(kill_processes_result); - if (kill_processes_result) { - console.log('Killed process.'); - // return kill_processes_result; - } else { - console.log('Did not kill process. Something went wrong.'); - fail_flag = true; - // return false; - } - } - } - - return fail_flag; - - // let kill_processes_result = await native_app.kill_processes({process_name: process_name}); - // console.log(kill_processes_result); - // if (kill_processes_result) { - // console.log('Killed process.'); - // return kill_processes_result; - // } else { - // console.log('Did not kill process. Something went wrong.'); - // return false; - // } -}; - -// Updated 2022-05-06 -export let open_local_file = async function open_local_file({ file_path, filename }) { - console.log('*** open_local_file() ***'); - console.log(`File Path: ${file_path}; Filename: ${filename}`); - - console.log( - 'Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory' - ); - - // let check_local_file_result = await native_app.check_local_file({local_file_path: file_path, filename: filename}); - // console.log(check_local_file_result); - // if (check_local_file_result) { - // console.log('Local file found.'); - // } else { - // console.log('Local file not found. Will not attempt to open.'); - // return false; - // } - - // console.log('Local file file found and ready to be opened.'); - let open_local_file_result = await native_app.open_local_file({ - local_file_path: file_path, - filename: filename - }); - console.log(open_local_file_result); - if (open_local_file_result) { - console.log('Local file was opened.'); - return open_local_file_result; - } else { - console.log('Local file was not opened. Something went wrong.'); - return false; - } -}; - -// exports.open_local_file should no longer be needed with this. -// Updated 2022-10-11 -export let open_local_file_v2 = async function open_local_file_v2({ file_path, filename }) { - console.log('*** open_local_file_v2() ***'); - console.log(`Local File Path: ${file_path}; Filename: ${filename}`); - - console.log( - 'Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory' - ); - - let open_local_file_result = await ipcRenderer - .invoke('open_local_file', file_path, filename) - .then((result) => { - console.log('IPC open local file finished'); - if (result) { - console.log('Local file was opened.'); - return result; - } else { - console.log('Local file was not opened. Something went wrong.'); - console.log(result); - return false; - } - - console.log(result); - return true; - }); - - return open_local_file_result; -}; - -// // Updated 2022-05-06 -// export let open_hash_file_to_temp = async function open_hash_file_to_temp({local_file_cache_path, hash, host_file_temp_path, filename}) { -// console.log('*** open_hash_file_to_temp() ***'); -// console.log(`Host File Cache Path: ${local_file_cache_path}; Hash: ${hash}; Host File Temp Path: ${host_file_temp_path}; Filename: ${filename}`); - -// console.log('Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory'); - -// let open_hash_file_to_temp_result = await native_app.open_hash_file_to_temp({local_file_cache_path: local_file_cache_path, hash: hash, host_file_temp_path: host_file_temp_path, filename: filename}); -// console.log(open_hash_file_to_temp_result); -// if (open_hash_file_to_temp_result) { -// console.log('Local hash file was opened from temp directory.'); -// return open_hash_file_to_temp_result; -// } else { -// console.log('Local hash file was not opened from the temp directory. Something went wrong.'); -// return false; -// } -// } - -// // exports.open_hash_file_to_temp should no longer be needed with this. -// // Updated 2022-10-11 -// export let open_hash_file_to_temp_v2 = async function open_hash_file_to_temp_v2({local_file_cache_path, hash, host_file_temp_path, filename}) { -// console.log('*** open_hash_file_to_temp_v2() ***'); -// console.log(`Host File Cache Path: ${local_file_cache_path}; Hash: ${hash}; Host File Temp Path: ${host_file_temp_path}; Filename: ${filename}`); - -// console.log('Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory'); - -// let subdirectory = hash.substring(0,2); -// let subdirectory_path = path.join(local_file_cache_path, subdirectory); -// if (fs.existsSync(subdirectory_path)) { -// } else { -// console.log(`Hashed file subdirectory not found in cache: ${subdirectory_path}`); -// return null; -// } - -// let hash_filename = hash+'.file'; -// let full_cache_file_path = path.join(subdirectory_path, hash_filename); -// console.log(full_cache_file_path); - -// const file_buffer = fs.readFileSync(full_cache_file_path); -// const file_hash_sha256 = crypto.createHash('sha256'); -// file_hash_sha256.update(file_buffer); - -// const file_hash_sha256_check = file_hash_sha256.digest('hex'); -// if (file_hash_sha256_check == hash) { -// console.log('File hash match', file_hash_sha256_check); -// } else { -// console.log('File hash does not match', file_hash_sha256_check); -// // await setTimeout(async () => {console.log('Done waiting????'); open_file_clicked = false;}, 5000); -// // sleep(6000); -// // console.log('???????? WAITED X SECONDS ????????'); -// return false; -// } - -// let open_hash_file_to_temp_result = await ipcRenderer.invoke('open_hash_file_to_temp', subdirectory_path, hash, host_file_temp_path, filename).then((result) => { -// console.log('IPC open hash file to temp finished'); -// if (result) { -// console.log('Local hash file was opened from temp directory.'); -// return result; -// } else { -// console.log('Local hash file was not opened from the temp directory. Something went wrong.'); -// console.log(result); -// return false; -// } -// }) - -// return open_hash_file_to_temp_result; -// } - -// Updated 2022-05-07 -export let run_cmd = async function run_cmd({ cmd = null, return_stdout = null }) { - console.log('*** run_cmd() ***'); - - let run_cmd_result = await native_app - .run_cmd({ cmd: cmd, return_stdout: return_stdout }) - .then(function (result) { - if (result) { - console.log('Command ran'); - } else { - console.log('Command did not run. Something went wrong.'); - return false; - } - if (return_stdout) { - return result; - } else { - return true; - } - }); - - console.log('Run Command Result:', run_cmd_result); - - return run_cmd_result; -}; - -// Updated 2022-10-27 -export let run_cmd_sync = function run_cmd_sync({ cmd = null, return_stdout = null }) { - console.log('*** run_cmd_sync() ***'); - - let run_cmd_result = native_app.run_cmd_sync({ cmd: cmd, return_stdout: return_stdout }); - - // if (run_cmd_result) { - // console.log('Command ran'); - // } else { - // console.log('Command did not run. Something went wrong.'); - // // return false; - // } - - // if (return_stdout) { - // return run_cmd_result; - // } else { - // return true; - // } - - console.log('Run Command Result:', run_cmd_result); - - return run_cmd_result; -}; - -// Updated 2022-05-07 -export let run_osascript = async function run_osascript({ - cmd = null, - interactive = false, - language = null, - flags = 'h', - program_file = null -}) { - console.log('*** run_osascript() ***'); - - let run_osascript_result = await native_app.run_osascript({ - cmd: cmd, - interactive: interactive, - language: language, - flags: flags, - program_file: program_file - }); - - console.log(run_osascript_result); - - if (run_osascript_result) { - console.log('Apple Script ran'); - } else { - console.log('Apple Script did not run. Something went wrong.'); - } - - return run_osascript_result; -}; - -// Updated 2022-05-07 -export let get_device_info = async function get_device_info({ event_device_id }) { - console.log('*** get_device_info() ***'); - - console.log(event_device_id); - - let get_device_info_result = await native_app.get_device_info(); - - console.log(get_device_info_result); - - if (get_device_info_result) { - console.log('Success'); - } else { - console.log('Failed? Something went wrong.'); - } - - return get_device_info_result; -}; diff --git a/src/lib/electron/electron_relay.ts b/src/lib/electron/electron_relay.ts new file mode 100644 index 00000000..6da0bd12 --- /dev/null +++ b/src/lib/electron/electron_relay.ts @@ -0,0 +1,123 @@ +/** + * Aether Electron Relay (V3) + * Maps legacy launcher commands to the modern Aether Native Bridge. + */ + +const log_lvl = 1; + +/** + * Returns the native bridge if available. + */ +function getBridge() { + if (typeof window !== 'undefined' && (window as any).aetherNative) { + return (window as any).aetherNative; + } + return null; +} + +/** + * 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}`); + return await bridge.openFolder(path); +} + +/** + * Launch a file using the OS default application. + */ +export async function launch_file(path: string) { + const bridge = getBridge(); + if (!bridge) return { success: false, error: 'Native bridge not found' }; + + if (log_lvl) console.log(`Relay: Launching file: ${path}`); + return await bridge.launchFile(path); +} + +/** + * Legacy compatibility alias for launch_file. + */ +export const open_local_file_v2 = async ({ file_path, filename }: { file_path: string, filename: string }) => { + return launch_file(`${file_path}/${filename}`); +}; + +/** + * Run a shell command. + */ +export async function run_cmd({ cmd, return_stdout = true }: { cmd: string, return_stdout?: boolean }) { + const bridge = getBridge(); + if (!bridge) return { success: false, error: 'Native bridge not found' }; + + if (log_lvl) console.log(`Relay: Running command: ${cmd}`); + const result = await bridge.runCommand(cmd); + + if (return_stdout) return result.stdout; + return result.success; +} + +/** + * Legacy compatibility alias for run_cmd. + */ +export const run_cmd_sync = run_cmd; + +/** + * Kill specific processes by name. + */ +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; +} + +/** + * Check if a file hash exists in the local cache. + */ +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 }); +} + +/** + * Download a file to the local cache. + */ +export async function download_to_cache({ url, cacheRoot, hash, apiKey }: { url: string, cacheRoot: string, hash: string, apiKey: string }) { + const bridge = getBridge(); + if (!bridge) return { success: false, error: 'Native bridge not found' }; + return await bridge.downloadToCache({ url, cacheRoot, hash, apiKey }); +} + +/** + * Launch a file from the local cache (copies to temp first). + */ +export async function launch_from_cache({ cacheRoot, hash, tempRoot, filename }: { cacheRoot: string, hash: string, tempRoot: string, filename: string }) { + const bridge = getBridge(); + 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}'`); +} \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index af5dab4b..9a61d0e7 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -645,11 +645,12 @@ } // *** Electron Native Mode Detection *** - // If window.native_app exists, we are running inside the Electron bridge - // @ts-ignore - native_app is injected by the Electron preload script - if (window.native_app) { + // If window.native_app or window.aetherNative exists, we are running inside the Electron bridge + // @ts-ignore - native_app is injected by legacy, aetherNative by new V3 bridge + if (window.native_app || window.aetherNative) { console.log('ELECTRON: Native environment detected. Switching to native app_mode.'); $events_loc.launcher.app_mode = 'native'; + $ae_loc.is_native = true; } } diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 80cd02ba..f3c64a09 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -148,6 +148,12 @@ export async function load({ fetch, params, parent, route, url }) { ae_loc_init['host_file_temp_path'] = native_device_config.native_device.host_file_temp_path; ae_loc_init['recording_path'] = native_device_config.native_device.recording_path; } + + // IMPORTANT: Update API settings with the native-authorized key if present + if (native_device_config.aether_api_key) { + ae_api_init['api_secret_key'] = native_device_config.aether_api_key; + ae_api_headers['x-aether-api-key'] = native_device_config.aether_api_key; + } } } catch (err) { console.error('ROOT LOAD: Failed to fetch native device config.', err); diff --git a/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte b/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte index 45ec54ec..f2e12221 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher/[event_location_id]/+page.svelte @@ -45,7 +45,9 @@ run_cmd_sync, run_osascript, get_device_info - } from '$lib/electron/electron_relay.js'; + } from '$lib/electron/electron_relay'; + + import LauncherBackgroundSync from '../../launcher_background_sync.svelte'; // import Event_launcher_menu from '../../launcher_menu.svelte'; // import Event_launcher_session_view from '../../launcher_session_view.svelte'; diff --git a/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte new file mode 100644 index 00000000..64f90305 --- /dev/null +++ b/src/routes/events/[event_id]/(launcher)/launcher_background_sync.svelte @@ -0,0 +1,126 @@ + + +{#if currently_syncing && log_lvl} +
+ + Pre-Caching: {currently_syncing} +
+{/if} diff --git a/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte b/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte index c83ae9fb..ccb2fb09 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_cfg.svelte @@ -22,6 +22,10 @@ events_trigger, events_trig } from '$lib/stores/ae_events_stores'; + + import * as native from '$lib/electron/electron_relay'; + + let test_cmd_result = $state('');
+ + {#if $ae_loc.is_native} +
+

+ +

+ +
+
+ + + +
+ +
+ + {#if test_cmd_result} +
{test_cmd_result}
+ {/if} +
+
+
+ {/if} +
{ - console.log('** Component Mounted: ** Event Launcher File Container (Hash Open)'); - console.log(`Session Type: ${session_type}; Open Method: ${open_method}`); - if (screen_saver_exts.includes(event_file_obj.extension)) { - // $events_loc.launcher.screen_saver_img_kv[event_file_id] = event_file_obj; - - let temp_obj = Object.create(event_file_obj); - - $events_loc.launcher.screen_saver_img_kv[temp_obj.event_file_id] = - Object.create(temp_obj); - - // $ae_event_launcher = $ae_event_launcher; + if (!$events_loc.launcher.screen_saver_img_kv) $events_loc.launcher.screen_saver_img_kv = {}; + $events_loc.launcher.screen_saver_img_kv[event_file_id] = { ...event_file_obj }; } - - // window.addEventListener('message', function(event) { - // console.log('Message received in event file uploaded manage component:'); - // console.log(event); - - // if (event.data.type == 'api_download_blob') { - // console.log('Download blob (file) message received:'); - // console.log(event.data); - - // // Get the event_file_id from the event.data.endpoint value. - // // Example: /event/file/abc123/download - - // let endpoint = event.data.endpoint; - // let event_file_id = endpoint.split('/')[3]; - - // ae_downloads[event_file_id] = { - // 'size_total': event.data.size_total, - // 'size_loaded': event.data.size_loaded, - // 'percent_completed': event.data.percent_completed, - // }; - - // // let event_file_id = event.data.event_file_id; - // // let filename = event.data.filename; - // // let auto_download = event.data.auto_download; - - // // ae_promises[event_file_id] - // // ae_promises[event_file_id] = download_event_file({ 'event_file_id': event_file_id, 'return_file': true, filename: filename, auto_download: auto_download, log_lvl: 1 }); - // } - // }); }); async function handle_open_file() { - console.log('*** handle_open_file() ***'); - console.log( - `App Mode: ${$events_loc.launcher.app_mode}; Cache Path: ${$events_loc.launcher.local_file_cache_path}; Temp Path: ${$events_loc.launcher.host_file_temp_path}` - ); + if (log_lvl) console.log('*** handle_open_file() ***'); + + $events_slct.event_file_id = event_file_id; + $events_slct.event_file_obj = event_file_obj; - // null or "default" is for regular use in their preferred browser. - // "app" mode is for Electron and node.js. - // "onsite" mode is for Chrome or Firefox and downloading files with modified extensions and other slight changes. - - if ($events_loc.launcher.app_mode == 'native' && window.native_app) { - console.log('* ** *** **** BEGIN TESTING **** *** ** *'); - console.log( - 'Process: Check local hash file cache, Download hash file to cache, and Open cached hash file after copying to temp directory' - ); + // 1. NATIVE MODE (Electron) + if ($ae_loc.is_native && $events_loc.launcher.app_mode === 'native') { + const cacheRoot = $ae_loc.local_file_cache_path; + const tempRoot = $ae_loc.host_file_temp_path; open_file_clicked = true; - // await tick(); - // setTimeout(() => {console.log('Finished waiting to hide the message.'); open_file_clicked = false;}, 15000); - open_file_status = 'checking_cache'; open_file_status_message = 'Checking local cache...'; - let check_hash_file_cache_result = await window.native_app.check_hash_file_cache_v2({ - local_file_cache_path: $events_loc.launcher.local_file_cache_path, - hash: event_file_obj.hash_sha256, - check_hash: true - }); - if (check_hash_file_cache_result) { - console.log('Cached hash file found.'); - } else if (check_hash_file_cache_result == null) { - console.log( - `Cached hash file not found. Need to download from API server. Base URL ${$ae_api.base_url}` - ); + + const exists = await native.check_hash_file_cache({ cacheRoot, hash: event_file_obj.hash_sha256 }); + + if (!exists) { open_file_status = 'downloading_file'; - open_file_status_message = 'Downloading file...'; - let download_hash_file_to_cache_result = - await window.native_app.download_hash_file_to_cache_v2({ - api_base_url: $ae_api.base_url, - api_base_url_backup: $ae_api.base_url_bak, - local_file_cache_path: $events_loc.launcher.local_file_cache_path, - event_file_id: event_file_id, - hash: event_file_obj.hash_sha256, - verify_hash: true, - overwrite_existing: false - }); - // console.log(download_hash_file_to_cache_result); - if (download_hash_file_to_cache_result) { - console.log('Hash file downloaded to cache.'); - } else if (download_hash_file_to_cache_result == null) { - console.log('Hash file was not found in cache and or could not be downloaded.'); - open_file_status = 'file_not_found'; - open_file_status_message = - 'File was not found in cache and could not be downloaded.'; - setTimeout(() => { - console.log(`Hiding Open File Message: ${open_file_status_message}`); - open_file_clicked = false; - }, 10000); // Default 15 seconds (15000) - return null; - } else { - console.log( - 'Hash file may be in the process of being downloaded or something went wrong.' - ); - open_file_status = 'downloading_file_or_failed'; - open_file_status_message = - 'Hash file may be in the process of being downloaded or something went wrong.'; - setTimeout(() => { - console.log(`Hiding Open File Message: ${open_file_status_message}`); - open_file_clicked = false; - }, 10000); // Default 15 seconds (15000) - return false; - } - } else { - console.log( - `Cached hash file found, but hash did not match. May still be downloading from the API server. Base URL ${$events_loc.launcher.api.base_url}` - ); - open_file_status = 'try_again'; - open_file_status_message = 'Please try again...'; - return null; - } - - setTimeout(() => { - console.log('Finished waiting to hide the message.'); - open_file_clicked = false; - }, 15000); // Default 15 seconds (15000) - - console.log('Cached hash file found and ready to be opened from the temp directory.'); - open_file_status = 'opening_file'; - open_file_status_message = 'Opening file...'; - - let filename = ''; - if ( - (event_file_obj.extension == 'ppt' || event_file_obj.extension == 'pptx') && - event_file_obj.open_in_os == 'win' - ) { - if (event_file_obj.extension == 'ppt') { - filename = event_file_obj.filename.replace('.ppt', '.pptwin'); - } else if (event_file_obj.extension == 'pptx') { - filename = event_file_obj.filename.replace('.pptx', '.pptxwin'); - } - } else { - filename = event_file_obj.filename; - } - console.log(`Opening ${filename}`); - - let electron_open_hash_file_to_temp_result = await window.native_app.open_hash_file_to_temp_v2( - { - local_file_cache_path: $events_loc.launcher.local_file_cache_path, + 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`; + const dlResult = await native.download_to_cache({ + url, + cacheRoot, hash: event_file_obj.hash_sha256, - host_file_temp_path: $events_loc.launcher.host_file_temp_path, - filename: filename, - verify_hash: true + apiKey: $ae_api.api_secret_key + }); + + if (!dlResult.success) { + open_file_status = 'error'; + open_file_status_message = `Download failed: ${dlResult.error}`; + setTimeout(() => open_file_clicked = false, 5000); + return; } - ); - if (electron_open_hash_file_to_temp_result) { - console.log('Local hash file was opened to temp.'); - open_file_status = 'opening_file_success'; - open_file_status_message = 'Opening Application'; - } else if (electron_open_hash_file_to_temp_result == null) { - console.log('Local hash file was not found. It may still be downloading.'); - open_file_status = 'file_not_found'; - open_file_status_message = 'File not found!'; - } else { - console.log('Local hash file was not opened to temp. Something went wrong.'); - open_file_status = 'opening_file_failed'; - open_file_status_message = 'Failed to open the file!'; - return false; } - console.log( - 'Done with test of cache check, download file, and open cached file from temp directory.' - ); - } else if ($events_loc.launcher.app_mode == 'onsite') { - open_file_clicked = true; - await tick(); - setTimeout(() => { - console.log('Finished waiting to hide the message.'); - open_file_clicked = false; - }, 15000); - } else { - // NOTE: Add first test controller command here. - // $events_sess.launcher.controller_cmd = `ae:open:event_file_id=${$slct.event_file_id}`; - // $events_sess.launcher.controller_trigger_send = true; // Trigger the controller to send. - // $events_sess.launcher.controller_cmd = null; // Reset command to null + open_file_status = 'opening_file'; + open_file_status_message = 'Opening Application'; + const launchResult = await native.launch_from_cache({ + cacheRoot, + hash: event_file_obj.hash_sha256, + tempRoot, + filename: event_file_obj.filename + }); + + if (!launchResult.success) { + open_file_status = 'error'; + open_file_status_message = `Failed to open: ${launchResult.error}`; + } + + setTimeout(() => open_file_clicked = false, 5000); + } + // 2. ONSITE MODE (Browser with Modified Extensions) + else if ($events_loc.launcher.app_mode === 'onsite') { open_file_clicked = true; - await tick(); - setTimeout(() => { - console.log('Finished waiting to hide the message.'); - open_file_clicked = false; - }, 15000); + open_file_status = 'downloading_onsite'; + open_file_status_message = 'Downloading (Onsite Mode)...'; + + let filename = event_file_obj.filename; + if ((event_file_obj.extension === 'ppt' || event_file_obj.extension === 'pptx') && event_file_obj.open_in_os === 'win') { + filename = event_file_obj.filename + 'win'; + } + + ae_promises[event_file_id] = api.download_hosted_file({ + api_cfg: $ae_api, + hosted_file_id: event_file_obj.hosted_file_id, + return_file: true, + filename: filename, + auto_download: true, + log_lvl: 1 + }); + + setTimeout(() => open_file_clicked = false, 5000); + } + // 3. DEFAULT MODE (Standard Browser) + else { + open_file_clicked = true; + open_file_status = 'downloading_default'; + open_file_status_message = 'Downloading...'; + + ae_promises[event_file_id] = api.download_hosted_file({ + api_cfg: $ae_api, + hosted_file_id: event_file_obj.hosted_file_id, + return_file: true, + filename: event_file_obj.filename, + auto_download: true, + log_lvl: 1 + }); + + if ($events_loc.launcher.controller == 'local_push') { + $events_sess.launcher.controller_cmd = `ae_download:hosted_file=${event_file_obj.hosted_file_id}:${event_file_obj.filename}:${event_file_obj.extension}`; + $events_sess.launcher.controller_trigger_send = true; + } + + setTimeout(() => open_file_clicked = false, 5000); } } - // function handle_open_method_poster() { - // console.log('*** handle_open_method_poster() ***'); - - // $events_loc.launcher.show_modal_event_poster = true; - // if (event_file_obj.extension == 'png' || event_file_obj.extension == 'jpg') { - // $events_loc.launcher.event_poster_file_type = 'image'; - // } else if (event_file_obj.extension == 'mp4' || event_file_obj.extension == 'mov') { - // $events_loc.launcher.event_poster_file_type = 'video'; - // } - - // $events_loc.launcher.poster_src = `/event/file/${event_file_id}/download`; - // $events_loc.launcher.modal_title = modal_title; - // } - function preventDefault(fn: (event: T) => void) { return function (event: T) { event.preventDefault(); @@ -334,338 +193,81 @@
{#if open_file_clicked} -
- {#if $events_loc.launcher.app_mode == 'native'} - {#if open_file_status} -
- *** {open_file_status_message} *** -
- {/if} - - -

Most files will automatically be opened full screen.

-

PowerPoint or KeyNote will attempt to display in presenter view.

-

PDFs, videos, and images will attempt to be displayed mirrored.

-

Please close the file when finished.

- {:else if $events_loc.launcher.app_mode == 'onsite'} - *** Please wait while this file loads... *** -

Most files will automatically be opened full screen.

-

PowerPoint or KeyNote will attempt to display in presenter view.

-

PDFs, videos, and images will attempt to be displayed mirrored.

-

Please close the file when finished.

- {:else} - *** Please wait while this file downloads... *** -

Onsite in the Speaker Ready Room and conference session rooms:

-
    -
  • Most files will automatically be opened full screen.
  • -
  • PowerPoint or KeyNote will attempt to display in presenter view.
  • -
  • PDFs, videos, and images will attempt to be displayed mirrored.
  • -
  • Please close the file when finished.
  • -
- {/if} +
+
+ *** {open_file_status_message || 'Please wait while this file downloads...'} *** +
+

Most files will automatically be opened full screen.

+

PowerPoint or KeyNote will attempt to display in presenter view.

+

Please close the file when finished.

{/if} - - + {#if session_type == 'poster' || open_method == 'modal'} - - - - - {:else if $events_loc.launcher.app_mode == 'native'} - { - handle_open_file(); - })} - data-hash_sha256={event_file_obj.hash_sha256} - data-filename={event_file_obj.filename} - title={`${event_file_obj.filename} [A] -- SHA256 hash: ${event_file_obj.hash_sha256.slice(0, 10)}...`} - > - - {ae_util.shorten_filename({ - filename: event_file_obj.filename, - max_length: max_filename_length - })} - - - - - - {:else} {/if} - - - + { - console.log(`ae_crud_updated:`, e.detail); - - events_func.load_ae_obj_id__event_file({ - api_cfg: $ae_api, - event_file_id: event_file_obj?.event_file_id, - log_lvl: log_lvl - }); + on:ae_crud_updated={() => { + events_func.load_ae_obj_id__event_file({ api_cfg: $ae_api, event_file_id: event_file_obj?.event_file_id, log_lvl }); }} > - - + - - - {ae_util.iso_datetime_formatter(event_file_obj.created_on, 'date_short')} - - - - {ae_util.iso_datetime_formatter(event_file_obj.created_on, 'time_12_short')} - - + {ae_util.iso_datetime_formatter(event_file_obj.created_on, 'date_short')} - + {#if event_file_obj.file_size}{ae_util.format_bytes(event_file_obj.file_size)}{/if} - - - - - - - - - -
- - +
\ No newline at end of file diff --git a/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte b/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte index e0a7fcf2..6e776564 100644 --- a/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte +++ b/src/routes/events/[event_id]/(launcher)/launcher_session_view.svelte @@ -83,7 +83,7 @@ // Check if results are different than the current $events_slct.event_file_obj_li if ($events_slct.event_file_obj_li && results) { if (JSON.stringify($events_slct.event_file_obj_li) !== JSON.stringify(results)) { - $events_slct.event_file_obj_li = { ...results }; + $events_slct.event_file_obj_li = [...results]; if (log_lvl) { console.log( `$events_slct.event_file_obj_li has changed for event_session_id: ${slct__event_session_id}`, @@ -97,6 +97,8 @@ ); } } + } else if (results) { + $events_slct.event_file_obj_li = [...results]; } return results; diff --git a/src/routes/journals/ae_comp__modal_journal_export.svelte b/src/routes/journals/ae_comp__modal_journal_export.svelte index 9ae7323f..e8beac59 100644 --- a/src/routes/journals/ae_comp__modal_journal_export.svelte +++ b/src/routes/journals/ae_comp__modal_journal_export.svelte @@ -131,10 +131,10 @@
-