import { get, writable, type Writable } from 'svelte/store'; import type { Display, DisplayGroup, DisplayIdGroup, DisplayIdObject, DisplayStatus } from '../types'; import { is_selected, select, selected_display_ids } from './select'; import { get_uuid, image_content_hash } from '../utils'; import { get_screenshot } from '../api_handler'; import { delete_and_deselect_unique_files_from_display } from './files'; import { db } from '../database'; import { dev } from '$app/environment'; import { remove_display_from_loading_displays } from '../main'; import { preview_settings } from './ui_behavior'; import { liveQuery } from 'dexie'; export const online_displays: Writable = writable([]); export const online_displays_sub = liveQuery(() => db.displays.where('status').equals('app_online').toArray() ).subscribe((value) => { online_displays.set(value); const current_online_display_ids = value.map((d) => d.id); selected_online_display_ids.set(get(selected_display_ids).filter((id) => current_online_display_ids.includes(id))); }); export const selected_online_display_ids: Writable = writable([]); export const local_displays: Writable = writable([]); export async function is_display_name_taken(name: string): Promise { const exists = await db.displays.where('name').equals(name).first(); return !!exists; } export async function add_display( ip: string, mac: string | null, name: string, status: DisplayStatus ): Promise { if (await is_display_name_taken(name)) return null; const new_id = get_uuid(); const group = await db.display_groups.toCollection().first(); let group_id: string; if (group) { group_id = group.id; } else { group_id = get_uuid(); await db.display_groups.put({ id: group_id, position: 0 }); } const element_count_in_group = (await db.displays.where('group_id').equals(group_id).toArray()) .length; const display = { id: new_id, ip, mac, position: element_count_in_group, preview: { currently_updating: false, url: null }, group_id: group_id, name, status }; await db.displays.put(display); return display; } export async function edit_display_data( display_id: string, ip: string, mac: string | null, name: string ): Promise { let display = await db.displays.get(display_id); if (!display) return null; display = { ...display, ip: ip, mac: mac, name, preview: { currently_updating: false, url: null } }; await db.displays.put(display); // save screenshot_loop(display.id); return display; } export async function remove_display(display_id: string) { select(selected_display_ids, display_id, 'deselect'); await delete_and_deselect_unique_files_from_display(display_id); const group_id = (await db.displays.get(display_id))?.group_id; await db.displays.delete(display_id); if (group_id && (await db.displays.where('group_id').equals(group_id).toArray()).length === 0) { await db.display_groups.delete(group_id); // delete empty group } await remove_display_from_loading_displays(display_id); } export async function all_displays_of_group_selected( display_group_id: string, current_selected_displays: string[] ): Promise { const displays_of_group: Display[] = await db.displays .where('group_id') .equals(display_group_id) .toArray(); if (displays_of_group.length === 0) return false; for (const display of displays_of_group) { if (!is_selected(display.id, current_selected_displays)) { return false; } } return true; } export async function set_select_for_group(display_group_id: string, new_value: boolean) { const displays_of_group: Display[] = await db.displays .where('group_id') .equals(display_group_id) .toArray(); for (const display of displays_of_group) { let action: string; if (new_value === true) { action = 'select'; } else { action = 'deselect'; } select(selected_display_ids, display.id, action as 'toggle' | 'select' | 'deselect'); } } export async function get_display_by_id(display_id: string): Promise { return (await db.displays.get(display_id)) ?? null; } export function screenshot_loop(display_id: string) { setTimeout(async () => { let settings = get(preview_settings); if (settings.mode === 'never') return; const initial_retry_count = settings.retry_count.now; const retry_seconds = get(preview_settings).retry_seconds.now; const display = await db.displays.get(display_id); if (!display || display.preview.currently_updating) return; display.preview.currently_updating = true; await db.displays.update(display.id, { preview: display.preview }); let last_hash: number | null = null; let retry_count = initial_retry_count; while (retry_count > 0) { settings = get(preview_settings); if (settings.mode === 'never') break; if (settings.mode !== 'always') retry_count -= 1; const new_blob = await get_screenshot(display.ip); settings = get(preview_settings); if (settings.mode === 'never') break; if (!new_blob) { display.preview = { currently_updating: false, url: null }; await db.displays.update(display.id, { preview: display.preview }); return; } const new_hash = await image_content_hash(new_blob); if (last_hash !== new_hash) { if (display.preview.url) { URL.revokeObjectURL(display.preview.url); } last_hash = new_hash; display.preview.url = URL.createObjectURL(new_blob); await db.displays.update(display.id, { preview: display.preview }); retry_count = initial_retry_count; } await new Promise((resolve) => setTimeout(resolve, retry_seconds * 1000)); // sleep } if (settings.mode === 'never') { await db.displays.update(display.id, { preview: { currently_updating: false, url: null } }); } else { display.preview.currently_updating = false; await db.displays.update(display.id, { preview: display.preview }); } }, 0); } export async function run_on_all_selected_displays( run_function: (display: Display) => void | Promise, update_screenshot_afterwards: boolean = true, display_ids: string[] | null = null ) { if (!display_ids) display_ids = get(selected_online_display_ids); const maybe_displays: (Display | null)[] = await Promise.all( // fails when only a single promis fails display_ids.map(async (id) => await get_display_by_id(id)) ); const displays: Display[] = maybe_displays.filter((d): d is Display => d !== null); await Promise.all( displays.map(async (display) => { if (!display) return; await run_function(display); if (update_screenshot_afterwards) { screenshot_loop(display.id); } }) ); } export async function get_display_groups(): Promise { return await db.display_groups.orderBy('position').toArray(); } export async function update_local_displays() { const display_groups = await db.display_groups.orderBy('position').toArray(); const displays = await db.displays.orderBy('position').toArray(); const out: DisplayIdGroup[] = []; for (const group of display_groups) { out.push({ id: group.id, displays: (await displays).filter((d) => d.group_id === group.id).map((d) => ({ id: d.id })) }); } return out; } export async function update_db_displays() { local_displays.update((groups) => groups.filter((g) => g.displays.length !== 0)); const filtered_local_display_groups = get(local_displays); const db_display_group_ids = (await db.display_groups.toArray()).map((group) => group.id); const local_display_group_ids = filtered_local_display_groups.map((group) => group.id); const display_group_ids_to_delete = db_display_group_ids.filter( (group) => !local_display_group_ids.includes(group) ); await db.display_groups.where('id').anyOf(display_group_ids_to_delete).delete(); for (let i = 0; i < filtered_local_display_groups.length; i++) { const group = filtered_local_display_groups[i]; if (db_display_group_ids.includes(group.id)) { await db.display_groups.update(group.id, { position: i }); } else { await db.display_groups.put({ id: group.id, position: i }); } for (let j = 0; j < group.displays.length; j++) { const display_id = group.displays[j].id; await db.displays.update(display_id, { position: j, group_id: group.id }); } } } export function set_new_display_order(display_id_group_id: string, new_data: DisplayIdObject[]) { local_displays.update((local_displays: DisplayIdGroup[]) => { for (const display_id_group of local_displays) { if (display_id_group.id === display_id_group_id) { display_id_group.displays = new_data; } } return local_displays; }); } if (dev) { setTimeout(add_testing_displays, 0); async function add_testing_displays() { await add_display('127.0.0.1', '00:1A:2B:3C:4D:5E', 'PC', 'host_offline'); // await add_display("192.168.178.111", "D4:81:D7:C0:DF:3C", "Laptop", "host_offline"); } }