From b6150fdab02e080e239b9101e1cfcf58b1b9e7a3 Mon Sep 17 00:00:00 2001 From: E44 <129310925+programmer-44@users.noreply.github.com> Date: Wed, 10 Jun 2026 21:57:36 +0200 Subject: [PATCH] fix(control): uploading file(s) to multiple displays closes #41 --- .../src/lib/components/InodeElement.svelte | 29 +++-- .../src/lib/ts/file_transfer_handler.ts | 111 ++++++++++++------ 2 files changed, 97 insertions(+), 43 deletions(-) diff --git a/control/frontend/src/lib/components/InodeElement.svelte b/control/frontend/src/lib/components/InodeElement.svelte index 8dd76c3..8fa90e6 100755 --- a/control/frontend/src/lib/components/InodeElement.svelte +++ b/control/frontend/src/lib/components/InodeElement.svelte @@ -60,7 +60,7 @@ file_size = liveQuery(() => get_size_recursively(f)); }); - let file_transfer_task: FileTransferTask | null = $derived( + let file_transfer_task_list: FileTransferTask[] | null = $derived( Object.hasOwn($file_transfer_tasks, file_primary_key) ? $file_transfer_tasks[file_primary_key] : null @@ -69,7 +69,7 @@ let loading_finished = $state(false); let previous_loading_state = $state(false); $effect(() => { - const ftt = file_transfer_task; + const ftt = file_transfer_task_list; if (previous_loading_state && !ftt) { loading_finished = true; setTimeout(() => (loading_finished = false), 200); @@ -141,7 +141,7 @@ function get_grayed_out_text_color_strings(is_selected: boolean): string { if (not_interactable) return 'text-stone-400'; - if (file_transfer_task) return 'text-white/20'; + if (file_transfer_task_list) return 'text-white/20'; const color = is_selected ? 'text-stone-600' : 'text-stone-400'; const factor = is_selected ? -1 : 1; return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`; @@ -149,14 +149,14 @@ function get_grayed_out_border_color_strings(is_selected: boolean): string { if (not_interactable) return 'border-stone-550'; - if (file_transfer_task) return 'border-white/10'; + if (file_transfer_task_list) return 'border-white/10'; const color = is_selected ? 'border-stone-450' : 'border-stone-550'; const factor = is_selected ? 1 : 1; return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`; } function onclick(e: Event) { - if (not_interactable || file_transfer_task) return; + if (not_interactable || file_transfer_task_list) return; select(selected_file_ids, file_primary_key, 'toggle'); e.stopPropagation(); } @@ -180,7 +180,7 @@ if (loading_finished) { out += 'bg-stone-500 text-white/30'; - } else if (file_transfer_task) { + } else if (file_transfer_task_list) { out += 'bg-stone-700 text-white/30'; } else { out += get_selectable_color_classes( @@ -196,7 +196,7 @@ if (not_interactable) { out += ' rounded-lg'; - } else if (file_transfer_task) { + } else if (file_transfer_task_list) { out += ' rounded-r-lg'; } else { out += ' rounded-r-lg cursor-pointer'; @@ -205,7 +205,16 @@ return out; } - function get_total_percentage(ftt: FileTransferTask): number { + function get_total_percentage(ftt_list: FileTransferTask[]): number { + let percentage_sum = 0; + for (const ftt of ftt_list) { + percentage_sum += get_percentage(ftt); + } + return Math.round(percentage_sum / ftt_list.length); + } + + + function get_percentage(ftt: FileTransferTask): number { let total_percentage: number; if (ftt.data.type === 'upload') { total_percentage = ftt.loading_data.percentage; @@ -312,10 +321,10 @@ {onclick} class="{get_main_classes()} relative transition-colors duration-200 gap-4 flex flex-row justify-between group w-full min-w-0" > - {#if !!file_transfer_task} + {#if !!file_transfer_task_list}
{/if}
diff --git a/control/frontend/src/lib/ts/file_transfer_handler.ts b/control/frontend/src/lib/ts/file_transfer_handler.ts index f807092..b608a79 100644 --- a/control/frontend/src/lib/ts/file_transfer_handler.ts +++ b/control/frontend/src/lib/ts/file_transfer_handler.ts @@ -28,12 +28,23 @@ const START_LOADING_DATA = { seconds_until_finish: -1 }; -export const file_transfer_tasks: Writable> = writable< - Record +export const file_transfer_tasks: Writable> = writable< + Record >({}); let is_processing: boolean = false; +function add_file_transfer_task(file_primary_key: string, new_task: FileTransferTask) { + file_transfer_tasks.update((tasks) => { + if (Object.hasOwn(tasks, file_primary_key)) { + tasks[file_primary_key].push(new_task); + return tasks; + } else { + return { ...tasks, [file_primary_key]: [new_task] }; + } + }); +} + export async function add_upload( file_list: FileList, selected_display_ids: string[], @@ -89,10 +100,7 @@ export async function add_upload( loading_data: START_LOADING_DATA, bytes_total: file.size }; - file_transfer_tasks.update((tasks) => ({ - ...tasks, - [file_primary_key]: new_task - })); + add_file_transfer_task(file_primary_key, new_task); }); await Promise.all(upload_task_promises); @@ -146,10 +154,7 @@ export async function add_sync_recursively( bytes_total: file_data.file.size }; - file_transfer_tasks.update((tasks) => ({ - ...tasks, - [selected_file_id]: new_task - })); + add_file_transfer_task(selected_file_id, new_task); const display_ids_without_file = file_data.short_displays_without_file.map((d) => d.id); const new_fods: FileOnDisplay[] = display_ids_without_file.map((display_id) => ({ @@ -230,15 +235,19 @@ function generate_valid_file_name(original_file_name: string, used_file_names: s return name; } -async function upload(file_primary_key: string, task: FileTransferTask): Promise { +async function upload( + file_primary_key: string, + task: FileTransferTask, + list_index: number +): Promise { const task_data = task.data; if (task_data.type !== 'upload' || !task_data.file) return console.warn('Task cancelled: wrong task type:', task); - await upload_file_via_xhr(file_primary_key, task, task_data.file); + await upload_file_via_xhr(file_primary_key, list_index, task, task_data.file); } -export async function sync(file_primary_key: string, task: FileTransferTask) { +export async function sync(file_primary_key: string, task: FileTransferTask, list_index: number) { if (task.data.type !== 'sync') return console.warn('Task cancelled: wrong task type:', task); const hasOPFS = @@ -273,18 +282,24 @@ export async function sync(file_primary_key: string, task: FileTransferTask) { if (done) break; if (!value) continue; - update_current_loading_data(file_primary_key, value.byteLength, start_time); + update_current_loading_data(file_primary_key, list_index, value.byteLength, start_time); await writable.write(value); } await writable.close(); - finish_loading_data(file_primary_key); + finish_loading_data(file_primary_key, list_index); // 02 - send downloaded file to every destination_display const temp_file = await file_handle.getFile(); for (const current_short_display of task.data.destination_display_data) { - await upload_file_via_xhr(file_primary_key, task, temp_file, current_short_display.display); + await upload_file_via_xhr( + file_primary_key, + list_index, + task, + temp_file, + current_short_display.display + ); } await dir.removeEntry(temp_name); @@ -292,7 +307,11 @@ export async function sync(file_primary_key: string, task: FileTransferTask) { // open file, if required if (task.data.open_file_afterwards_on_display_ids.length !== 0) { const path_to_file = task.path + task.file_name; - await run_on_all_selected_displays((d) => open_file(d.ip, path_to_file), true, task.data.open_file_afterwards_on_display_ids); + await run_on_all_selected_displays( + (d) => open_file(d.ip, path_to_file), + true, + task.data.open_file_afterwards_on_display_ids + ); } } catch (e) { show_general_error(file_primary_key, task, String(e)); @@ -336,24 +355,37 @@ async function start_task_loop() { while (Object.keys(get(file_transfer_tasks)).length > 0) { const tasks = get(file_transfer_tasks); const current_file_id = Object.keys(tasks)[0]; - const current_task = tasks[current_file_id]; - if (current_task.data.type === 'upload') { - await upload(current_file_id, current_task); - } else if (current_task.data.type === 'sync') { - await sync(current_file_id, current_task); - } + const current_task_list = tasks[current_file_id]; - file_transfer_tasks.update((all_tasks) => { - const next = { ...all_tasks }; - delete next[current_file_id]; - return next; - }); + for (const [list_index, current_task] of current_task_list.entries()) { + if (current_task.data.type === 'upload') { + await upload(current_file_id, current_task, list_index); + } else if (current_task.data.type === 'sync') { + await sync(current_file_id, current_task, list_index); + } + delete_current_task_if_needed(current_file_id); + } } is_processing = false; } +function delete_current_task_if_needed(current_file_id: string) { + file_transfer_tasks.update((all_tasks) => { + const next = { ...all_tasks }; + const current_tasks = next[current_file_id]; + if (current_tasks.length !== 1) { + if (current_tasks.find((t) => t.loading_data.percentage !== 100)) { + return next; // not all tasks are finished -> do nothing + } + } + delete next[current_file_id]; + return next; + }); +} + async function upload_file_via_xhr( file_primary_key: string, + list_index: number, task: FileTransferTask, current_file: File, destination_short_display: ShortDisplay | null = null @@ -373,6 +405,7 @@ async function upload_file_via_xhr( const apply = async () => { update_current_loading_data( file_primary_key, + list_index, e.loaded, start_time, destination_short_display ? destination_short_display.id : null @@ -392,6 +425,7 @@ async function upload_file_via_xhr( // set loading_data to 100% finish_loading_data( file_primary_key, + list_index, destination_short_display ? destination_short_display.id : null ); // Generate Thumbnail if not done already @@ -416,38 +450,43 @@ async function upload_file_via_xhr( function finish_loading_data( file_primary_key: string, + list_index: number, destination_display_id: string | null = null ) { file_transfer_tasks.update((tasks) => { - const task = tasks[file_primary_key]; const current_loading_data = { percentage: 100, bytes_per_second: 0, seconds_until_finish: 0 }; + const new_task_list = tasks[file_primary_key].map((task, index) => + index === list_index + ? get_updated_task(current_loading_data, destination_display_id, task) + : task + ); return { ...tasks, - [file_primary_key]: get_updated_task(current_loading_data, destination_display_id, task) + [file_primary_key]: new_task_list }; }); } function update_current_loading_data( file_primary_key: string, + list_index: number, current_bytes: number, start_time: Date, destination_display_id: string | null = null ) { file_transfer_tasks.update((tasks) => { - const task = tasks[file_primary_key]; + const task = tasks[file_primary_key][list_index]; if (!task) return tasks; const current_percentage = Math.min( task.bytes_total > 0 ? Math.round((current_bytes / task.bytes_total) * 100) : 1, 99 ); - const prognosed_data = get_prognosed_data(start_time, current_bytes, task.bytes_total); const current_loading_data: FileLoadingData = { @@ -456,9 +495,15 @@ function update_current_loading_data( seconds_until_finish: prognosed_data.seconds_until_finish }; + const new_task_list = tasks[file_primary_key].map((task, index) => + index === list_index + ? get_updated_task(current_loading_data, destination_display_id, task) + : task + ); + return { ...tasks, - [file_primary_key]: get_updated_task(current_loading_data, destination_display_id, task) + [file_primary_key]: new_task_list }; }); }