feat(launcher): implement device heartbeat and background sync engine
- Hardened 'find_object_id' in Dexie to support 'event_' prefixed IDs. - Implemented singleton 'LauncherBackgroundSync' in root launcher layout. - Added V3-compliant heartbeat and room refresh cycles. - Fixed redundant component imports and Skeleton UI prop errors. - Added 'inc_file_li' support to event API loader.
This commit is contained in:
@@ -13,7 +13,14 @@ function find_object_id(
|
|||||||
table_name: string,
|
table_name: string,
|
||||||
log_lvl: number
|
log_lvl: number
|
||||||
): string | number | undefined {
|
): string | number | undefined {
|
||||||
const potential_keys = ['id', 'id_random', `${table_name}_id`, `${table_name}_id_random`];
|
const potential_keys = [
|
||||||
|
'id',
|
||||||
|
'id_random',
|
||||||
|
`${table_name}_id`,
|
||||||
|
`${table_name}_id_random`,
|
||||||
|
`event_${table_name}_id`,
|
||||||
|
`event_${table_name}_id_random`
|
||||||
|
];
|
||||||
|
|
||||||
for (const key of potential_keys) {
|
for (const key of potential_keys) {
|
||||||
if (obj[key]) {
|
if (obj[key]) {
|
||||||
@@ -27,10 +34,12 @@ function find_object_id(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(
|
if (log_lvl) {
|
||||||
`Object is missing a valid ID for table "${table_name}". It will be skipped.`,
|
console.error(
|
||||||
obj
|
`Object is missing a valid ID for table "${table_name}". It will be skipped. Checked keys: ${potential_keys.join(', ')}`,
|
||||||
);
|
obj
|
||||||
|
);
|
||||||
|
}
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export async function load_ae_obj_id__event({
|
|||||||
event_id,
|
event_id,
|
||||||
view = 'default',
|
view = 'default',
|
||||||
inc_device_li = false,
|
inc_device_li = false,
|
||||||
|
inc_file_li = false,
|
||||||
inc_location_li = false,
|
inc_location_li = false,
|
||||||
inc_session_li = false,
|
inc_session_li = false,
|
||||||
inc_template_li = false,
|
inc_template_li = false,
|
||||||
@@ -30,6 +31,7 @@ export async function load_ae_obj_id__event({
|
|||||||
event_id: string;
|
event_id: string;
|
||||||
view?: string;
|
view?: string;
|
||||||
inc_device_li?: boolean;
|
inc_device_li?: boolean;
|
||||||
|
inc_file_li?: boolean;
|
||||||
inc_location_li?: boolean;
|
inc_location_li?: boolean;
|
||||||
inc_session_li?: boolean;
|
inc_session_li?: boolean;
|
||||||
inc_template_li?: boolean;
|
inc_template_li?: boolean;
|
||||||
@@ -45,7 +47,7 @@ export async function load_ae_obj_id__event({
|
|||||||
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
|
if (log_lvl) console.log('Browser is offline. Skipping API and attempting cache load.');
|
||||||
ae_promises.load__event_obj = await db_events.event.get(event_id);
|
ae_promises.load__event_obj = await db_events.event.get(event_id);
|
||||||
if (ae_promises.load__event_obj) {
|
if (ae_promises.load__event_obj) {
|
||||||
return await _handle_nested_loads(ae_promises.load__event_obj, { api_cfg, inc_device_li, inc_location_li, inc_session_li, inc_template_li, log_lvl });
|
return await _handle_nested_loads(ae_promises.load__event_obj, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_template_li, log_lvl });
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -97,13 +99,13 @@ export async function load_ae_obj_id__event({
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return await _handle_nested_loads(ae_promises.load__event_obj, { api_cfg, inc_device_li, inc_location_li, inc_session_li, inc_template_li, log_lvl });
|
return await _handle_nested_loads(ae_promises.load__event_obj, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_template_li, log_lvl });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shared logic for loading nested child collections
|
* Shared logic for loading nested child collections
|
||||||
*/
|
*/
|
||||||
async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, inc_location_li, inc_session_li, inc_template_li, log_lvl }: any) {
|
async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, inc_file_li, inc_location_li, inc_session_li, inc_template_li, log_lvl }: any) {
|
||||||
const current_event_id = event_obj.event_id_random || event_obj.event_id || event_obj.id;
|
const current_event_id = event_obj.event_id_random || event_obj.event_id || event_obj.id;
|
||||||
|
|
||||||
if (inc_device_li) {
|
if (inc_device_li) {
|
||||||
@@ -114,6 +116,16 @@ async function _handle_nested_loads(event_obj: any, { api_cfg, inc_device_li, in
|
|||||||
log_lvl
|
log_lvl
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if (inc_file_li) {
|
||||||
|
event_obj.event_file_li = await load_ae_obj_li__event_file({
|
||||||
|
api_cfg,
|
||||||
|
for_obj_type: 'event',
|
||||||
|
for_obj_id: current_event_id,
|
||||||
|
enabled: 'all',
|
||||||
|
limit: 100,
|
||||||
|
log_lvl
|
||||||
|
});
|
||||||
|
}
|
||||||
if (inc_location_li) {
|
if (inc_location_li) {
|
||||||
event_obj.event_location_obj_li = await load_ae_obj_li__event_location({
|
event_obj.event_location_obj_li = await load_ae_obj_li__event_location({
|
||||||
api_cfg,
|
api_cfg,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ 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';
|
import { load_ae_obj_li__event_file } from '$lib/ae_events/ae_events__event_file';
|
||||||
|
import { load_ae_obj_li__event_session } from '$lib/ae_events/ae_events__event_session';
|
||||||
|
|
||||||
const ae_promises: key_val = {};
|
const ae_promises: key_val = {};
|
||||||
|
|
||||||
|
|||||||
@@ -43,7 +43,7 @@
|
|||||||
class: classes = ''
|
class: classes = ''
|
||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
let editor_element: HTMLDivElement = $state();
|
let editor_element: HTMLDivElement | undefined = $state();
|
||||||
// let editorView: any = $state(); // Removed redundant declaration
|
// let editorView: any = $state(); // Removed redundant declaration
|
||||||
let cm_modules: any = $state(); // To hold the dynamically loaded CodeMirror modules
|
let cm_modules: any = $state(); // To hold the dynamically loaded CodeMirror modules
|
||||||
let editor_extensions: any[] = $state([]);
|
let editor_extensions: any[] = $state([]);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
let { content = $bindable(''), placeholder = 'Start typing...' }: Props = $props();
|
let { content = $bindable(''), placeholder = 'Start typing...' }: Props = $props();
|
||||||
|
|
||||||
let editor_container: HTMLDivElement = $state();
|
let editor_container: HTMLDivElement | undefined = $state();
|
||||||
let editor_view: any;
|
let editor_view: any;
|
||||||
let cm: any; // Declare cm at the top level
|
let cm: any; // Declare cm at the top level
|
||||||
|
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
events_trigger,
|
events_trigger,
|
||||||
events_trig
|
events_trig
|
||||||
} from '$lib/stores/ae_events_stores';
|
} from '$lib/stores/ae_events_stores';
|
||||||
// import { events_func } from '$lib/ae_events_functions';
|
import { events_func } from '$lib/ae_events_functions';
|
||||||
|
|
||||||
import Launcher_cfg from '../launcher_cfg.svelte';
|
import Launcher_cfg from '../launcher_cfg.svelte';
|
||||||
import Launcher_menu from '../launcher_menu.svelte';
|
import Launcher_menu from '../launcher_menu.svelte';
|
||||||
@@ -1222,7 +1222,6 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
bgColor="bg-blue"
|
|
||||||
class="bg-orange-100 opacity-90 hover:opacity-97 transition-all duration-1000 border border-gray-300 dark:border-gray-600"
|
class="bg-orange-100 opacity-90 hover:opacity-97 transition-all duration-1000 border border-gray-300 dark:border-gray-600"
|
||||||
placement="left"
|
placement="left"
|
||||||
transitionType="fly"
|
transitionType="fly"
|
||||||
@@ -1231,7 +1230,6 @@
|
|||||||
duration: 200,
|
duration: 200,
|
||||||
easing: sineIn
|
easing: sineIn
|
||||||
}}
|
}}
|
||||||
width={'w-md'}
|
|
||||||
bind:hidden={$events_loc.launcher.hide_drawer__cfg}
|
bind:hidden={$events_loc.launcher.hide_drawer__cfg}
|
||||||
id="sidebar1"
|
id="sidebar1"
|
||||||
>
|
>
|
||||||
@@ -1276,7 +1274,6 @@
|
|||||||
|
|
||||||
<Drawer
|
<Drawer
|
||||||
activateClickOutside={false}
|
activateClickOutside={false}
|
||||||
backdrop={false}
|
|
||||||
class="bg-red-100 opacity-75 hover:opacity-95 transition-all duration-1000"
|
class="bg-red-100 opacity-75 hover:opacity-95 transition-all duration-1000"
|
||||||
placement="bottom"
|
placement="bottom"
|
||||||
transitionType="fly"
|
transitionType="fly"
|
||||||
@@ -1285,7 +1282,6 @@
|
|||||||
duration: 200,
|
duration: 200,
|
||||||
easing: sineIn
|
easing: sineIn
|
||||||
}}
|
}}
|
||||||
width={'min-h-96 h-1/2'}
|
|
||||||
bind:hidden={$events_loc.launcher.hide_drawer__debug}
|
bind:hidden={$events_loc.launcher.hide_drawer__debug}
|
||||||
id="sidebar2"
|
id="sidebar2"
|
||||||
>
|
>
|
||||||
@@ -1321,7 +1317,6 @@
|
|||||||
open={$events_sess.launcher?.modal__open_event_file_id}
|
open={$events_sess.launcher?.modal__open_event_file_id}
|
||||||
autoclose={false}
|
autoclose={false}
|
||||||
placement="top-center"
|
placement="top-center"
|
||||||
size=""
|
|
||||||
class="
|
class="
|
||||||
bg-gray-500/90 dark:bg-gray-800/90 text-gray-800 dark:text-gray-200
|
bg-gray-500/90 dark:bg-gray-800/90 text-gray-800 dark:text-gray-200
|
||||||
rounded-lg border-gray-200 dark:border-gray-700
|
rounded-lg border-gray-200 dark:border-gray-700
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
get_device_info
|
get_device_info
|
||||||
} from '$lib/electron/electron_relay';
|
} from '$lib/electron/electron_relay';
|
||||||
|
|
||||||
import LauncherBackgroundSync from '../../launcher_background_sync.svelte';
|
// import LauncherBackgroundSync from '../../launcher_background_sync.svelte';
|
||||||
|
|
||||||
// import Event_launcher_menu from '../../launcher_menu.svelte';
|
// import Event_launcher_menu from '../../launcher_menu.svelte';
|
||||||
// import Event_launcher_session_view from '../../launcher_session_view.svelte';
|
// import Event_launcher_session_view from '../../launcher_session_view.svelte';
|
||||||
|
|||||||
@@ -6,6 +6,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 } from '$lib/stores/ae_events_stores';
|
||||||
|
import { events_func } from '$lib/ae_events_functions';
|
||||||
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';
|
||||||
|
|
||||||
@@ -14,6 +15,7 @@
|
|||||||
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_stats = $state({ total: 0, cached: 0, missing: 0 });
|
let sync_stats = $state({ total: 0, cached: 0, missing: 0 });
|
||||||
|
let last_heartbeat: string | null = $state(null);
|
||||||
|
|
||||||
// Loop Timings (Visible in UI)
|
// Loop Timings (Visible in UI)
|
||||||
let loop_info = $state({
|
let loop_info = $state({
|
||||||
@@ -24,10 +26,10 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Timer Handles
|
// Timer Handles
|
||||||
let timer__event: any = null;
|
let timer__event: any = $state(null);
|
||||||
let timer__device: any = null;
|
let timer__device: any = $state(null);
|
||||||
let timer__location: any = null;
|
let timer__location: any = $state(null);
|
||||||
let timer__session: any = null;
|
let timer__session: any = $state(null);
|
||||||
|
|
||||||
let is_syncing = false;
|
let is_syncing = false;
|
||||||
let show_monitor = $state(false);
|
let show_monitor = $state(false);
|
||||||
@@ -44,12 +46,14 @@
|
|||||||
loop_info.session = dev.check_event_session_loop_period || 10000;
|
loop_info.session = dev.check_event_session_loop_period || 10000;
|
||||||
|
|
||||||
timer__event = setInterval(() => run_sync_cycle(), loop_info.event);
|
timer__event = setInterval(() => run_sync_cycle(), loop_info.event);
|
||||||
timer__device = setInterval(() => { /* TODO: Heartbeat */ }, loop_info.device);
|
timer__device = setInterval(() => run_device_heartbeat(), loop_info.device);
|
||||||
timer__location = setInterval(() => { /* TODO: Refresh Loc */ }, loop_info.location);
|
timer__location = setInterval(() => refresh_location_config(), loop_info.location);
|
||||||
timer__session = setInterval(() => run_sync_cycle(), loop_info.session);
|
timer__session = setInterval(() => run_sync_cycle(), loop_info.session);
|
||||||
|
|
||||||
// Immediate first run
|
// Immediate first run
|
||||||
run_sync_cycle();
|
run_sync_cycle();
|
||||||
|
run_device_heartbeat();
|
||||||
|
refresh_location_config();
|
||||||
});
|
});
|
||||||
|
|
||||||
onDestroy(() => {
|
onDestroy(() => {
|
||||||
@@ -128,8 +132,15 @@
|
|||||||
* Gathers OS info and updates the device record in the cloud.
|
* Gathers OS info and updates the device record in the cloud.
|
||||||
*/
|
*/
|
||||||
async function run_device_heartbeat() {
|
async function run_device_heartbeat() {
|
||||||
const device_id = $ae_loc.native_device?.event_device_id || $ae_loc.native_device?.id;
|
const dev = $ae_loc.native_device;
|
||||||
if (!device_id) return;
|
const device_id = dev?.event_device_id_random || dev?.id_random || dev?.event_device_id || dev?.id;
|
||||||
|
|
||||||
|
if (!device_id) {
|
||||||
|
if (log_lvl) console.warn('Sync: Heartbeat skipped, no device_id found in $ae_loc.native_device.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (log_lvl > 1) console.log(`Sync: Running heartbeat for device: ${device_id}`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const info = await native.get_device_info();
|
const info = await native.get_device_info();
|
||||||
@@ -151,11 +162,12 @@
|
|||||||
|
|
||||||
await events_func.update_ae_obj__event_device({
|
await events_func.update_ae_obj__event_device({
|
||||||
api_cfg: $ae_api,
|
api_cfg: $ae_api,
|
||||||
event_device_id: device_id,
|
event_device_id: String(device_id),
|
||||||
data_kv: update_payload,
|
data_kv: update_payload,
|
||||||
log_lvl: 0
|
log_lvl: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
|
last_heartbeat = new Date().toLocaleTimeString();
|
||||||
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);
|
||||||
@@ -198,6 +210,11 @@
|
|||||||
|
|
||||||
<span class="opacity-70 text-primary-300">Prefix Len:</span>
|
<span class="opacity-70 text-primary-300">Prefix Len:</span>
|
||||||
<span class="text-right">{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>
|
<span class="text-right">{$ae_loc.native_device?.hash_prefix_length || 2} chars</span>
|
||||||
|
|
||||||
|
<span class="opacity-70 text-primary-300">Heartbeat:</span>
|
||||||
|
<span class="text-right {last_heartbeat ? 'text-success-500' : 'text-error-500'}">
|
||||||
|
{last_heartbeat || 'Pending...'}
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t border-white/10 pt-2 flex flex-col gap-1">
|
<div class="border-t border-white/10 pt-2 flex flex-col gap-1">
|
||||||
|
|||||||
@@ -422,7 +422,7 @@ max-w-max -->
|
|||||||
<!-- lg:bg-green-100
|
<!-- lg:bg-green-100
|
||||||
xl:bg-green-200 -->
|
xl:bg-green-200 -->
|
||||||
|
|
||||||
<Event_page_menu {data} {lq__event_obj} />
|
<Event_page_menu {lq__event_obj} />
|
||||||
|
|
||||||
{#if !$lq__event_obj}
|
{#if !$lq__event_obj}
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
Reference in New Issue
Block a user