mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
refactor(control): improve file transfer backend data structures
This commit is contained in:
@@ -10,7 +10,8 @@
|
||||
supported_file_type_icon,
|
||||
type Inode,
|
||||
get_file_primary_key,
|
||||
type FileOnDisplay
|
||||
type FileOnDisplay,
|
||||
type FileLoadingData
|
||||
} from '$lib/ts/types';
|
||||
|
||||
import {
|
||||
@@ -47,7 +48,8 @@
|
||||
| Observable<{
|
||||
is_loading: boolean;
|
||||
total_percentage: number;
|
||||
display_data: { is_loading: boolean; percentage: number }[];
|
||||
total_seconds_until_finish: number;
|
||||
display_data: FileLoadingData[];
|
||||
}>
|
||||
| undefined = $state();
|
||||
$effect(() => {
|
||||
@@ -198,7 +200,8 @@
|
||||
): Promise<{
|
||||
is_loading: boolean;
|
||||
total_percentage: number;
|
||||
display_data: { is_loading: boolean; percentage: number }[];
|
||||
total_seconds_until_finish: number;
|
||||
display_data: FileLoadingData[];
|
||||
}> {
|
||||
const file_on_display_data: FileOnDisplay[] = await db.files_on_display
|
||||
.where('file_primary_key')
|
||||
@@ -209,30 +212,40 @@
|
||||
return {
|
||||
is_loading: true,
|
||||
total_percentage: 0,
|
||||
total_seconds_until_finish: -1,
|
||||
display_data: []
|
||||
};
|
||||
}
|
||||
const display_data = [];
|
||||
let is_loading = false;
|
||||
let percentage_sum = 0;
|
||||
let total_seconds_until_finish = 0;
|
||||
for (const fod of file_on_display_data) {
|
||||
if (!is_loading) is_loading = fod.is_loading;
|
||||
percentage_sum += fod.percentage;
|
||||
display_data.push({
|
||||
is_loading: fod.is_loading,
|
||||
percentage: fod.percentage
|
||||
});
|
||||
if (!!fod.loading_data) {
|
||||
if (!is_loading) is_loading = true;
|
||||
percentage_sum += fod.loading_data.percentage;
|
||||
total_seconds_until_finish += fod.loading_data.seconds_until_finish;
|
||||
display_data.push(fod.loading_data);
|
||||
} else {
|
||||
percentage_sum += 100;
|
||||
}
|
||||
}
|
||||
let total_percentage = percentage_sum / display_data.length;
|
||||
return {
|
||||
is_loading,
|
||||
total_percentage,
|
||||
total_seconds_until_finish,
|
||||
display_data
|
||||
};
|
||||
}
|
||||
</script>
|
||||
|
||||
<div data-testid="inode" class="flex flex-row h-{$current_height.file} w-full {loading_finished ? 'scale-105' : ''} transition-[scale] duration-300">
|
||||
<div
|
||||
data-testid="inode"
|
||||
class="flex flex-row h-{$current_height.file} w-full {loading_finished
|
||||
? 'scale-105'
|
||||
: ''} transition-[scale] duration-300"
|
||||
>
|
||||
{#if !not_interactable}
|
||||
<div class="h-{$current_height.file} aspect-square max-w-15 flex">
|
||||
<Button
|
||||
|
||||
@@ -13,7 +13,7 @@ import {
|
||||
type FileTransferTask,
|
||||
type Inode
|
||||
} from './types';
|
||||
import { get_sanitized_file_url, get_uuid, make_valid_name } from './utils';
|
||||
import { get_sanitized_file_url, make_valid_name } from './utils';
|
||||
|
||||
let is_processing: boolean = false;
|
||||
const tasks: FileTransferTask[] = [];
|
||||
@@ -51,20 +51,27 @@ export async function add_upload(
|
||||
display_id,
|
||||
file_primary_key,
|
||||
date_created: new Date(),
|
||||
is_loading: true,
|
||||
percentage: 0
|
||||
loading_data: {
|
||||
type: 'upload',
|
||||
percentage: 0,
|
||||
bytes_per_second: 0,
|
||||
seconds_until_finish: -1
|
||||
}
|
||||
};
|
||||
await db.files_on_display.put(db_file_on_display);
|
||||
|
||||
return {
|
||||
id: get_uuid(),
|
||||
type: 'upload' as const,
|
||||
display_id,
|
||||
display_ip,
|
||||
data: {
|
||||
type: 'upload' as const,
|
||||
file
|
||||
},
|
||||
display: {
|
||||
id: display_id,
|
||||
ip: display_ip
|
||||
},
|
||||
path: current_file_path,
|
||||
file_name: file_name,
|
||||
file_primary_key,
|
||||
file,
|
||||
bytes_total: file.size
|
||||
};
|
||||
});
|
||||
@@ -108,7 +115,7 @@ async function start_task_processing() {
|
||||
async function start_task_loop() {
|
||||
while (tasks.length > 0) {
|
||||
const current_task = tasks[0];
|
||||
switch (current_task.type) {
|
||||
switch (current_task.data.type) {
|
||||
case 'upload':
|
||||
await upload(current_task);
|
||||
break;
|
||||
@@ -123,15 +130,16 @@ async function start_task_loop() {
|
||||
}
|
||||
|
||||
async function upload(task: FileTransferTask): Promise<void> {
|
||||
if (task.type !== 'upload' || !task.file) return;
|
||||
const task_data = task.data;
|
||||
if (task_data.type !== 'upload' || !task_data.file) return;
|
||||
|
||||
await db.files_on_display.update([task.display_id, task.file_primary_key], { percentage: 1 });
|
||||
const start_time = new Date();
|
||||
|
||||
return new Promise<void>((resolve) => {
|
||||
const xhr = new XMLHttpRequest();
|
||||
xhr.open(
|
||||
'POST',
|
||||
`http://${task.display_ip}:1323/api${get_sanitized_file_url(task.path + task.file_name)}`,
|
||||
`http://${task.display.ip}:1323/api${get_sanitized_file_url(task.path + task.file_name)}`,
|
||||
true
|
||||
);
|
||||
xhr.setRequestHeader('content-type', 'application/octet-stream');
|
||||
@@ -140,33 +148,39 @@ async function upload(task: FileTransferTask): Promise<void> {
|
||||
const total = e.lengthComputable ? e.total : task.bytes_total;
|
||||
const current_percentage = total > 0 ? Math.round((e.loaded / total) * 100) : 1;
|
||||
const apply = async () => {
|
||||
await db.files_on_display.update([task.display_id, task.file_primary_key], {
|
||||
percentage: current_percentage
|
||||
const prognosed_data = get_prognosed_data(start_time, e.loaded, total);
|
||||
|
||||
await db.files_on_display.update([task.display.id, task.file_primary_key], {
|
||||
loading_data: {
|
||||
type: 'upload',
|
||||
percentage: current_percentage,
|
||||
bytes_per_second: prognosed_data.bytes_per_second,
|
||||
seconds_until_finish: prognosed_data.seconds_until_finish
|
||||
}
|
||||
});
|
||||
};
|
||||
apply();
|
||||
};
|
||||
|
||||
xhr.onerror = async (e: ProgressEvent) => {
|
||||
await show_error('upload', e, task);
|
||||
await show_error(task, e);
|
||||
resolve();
|
||||
};
|
||||
|
||||
xhr.onreadystatechange = async () => {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status == 200) {
|
||||
await db.files_on_display.update([task.display_id, task.file_primary_key], {
|
||||
percentage: 100,
|
||||
is_loading: false
|
||||
});
|
||||
await db.files_on_display.update([task.display.id, task.file_primary_key], {
|
||||
loading_data: null
|
||||
}); // TODO: Stattdessen update machen und gucken, ob die Datei wirklich da ist
|
||||
} else {
|
||||
await show_error('upload', `HTTP ${xhr.status}`, task);
|
||||
await show_error(task, `HTTP ${xhr.status}`);
|
||||
}
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send(task.file);
|
||||
xhr.send(task_data.file);
|
||||
}).then(() => {
|
||||
// Generate Thumbnail if not done already
|
||||
setTimeout(async () => {
|
||||
@@ -174,23 +188,39 @@ async function upload(task: FileTransferTask): Promise<void> {
|
||||
JSON.parse(task.file_primary_key) as [string, string, number, string]
|
||||
);
|
||||
if (!!inode_element && inode_element.thumbnail === null) {
|
||||
await generate_thumbnail(task.display_ip, task.path, inode_element);
|
||||
await generate_thumbnail(task.display.ip, task.path, inode_element);
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
}
|
||||
|
||||
async function show_error(
|
||||
type: 'upload' | 'download' | 'sync',
|
||||
error: ProgressEvent | string,
|
||||
task: FileTransferTask
|
||||
) {
|
||||
|
||||
function get_prognosed_data(
|
||||
start_time: Date,
|
||||
bytes_done: number,
|
||||
bytes_total: number
|
||||
): { bytes_per_second: number; seconds_until_finish: number } {
|
||||
const diff_seconds = (Date.now() - start_time.getTime()) / 1000;
|
||||
const bytes_per_second = bytes_done / diff_seconds;
|
||||
|
||||
const seconds_until_finish = bytes_total / bytes_per_second;
|
||||
return { bytes_per_second, seconds_until_finish };
|
||||
}
|
||||
|
||||
async function show_error(task: FileTransferTask, error: ProgressEvent | string) {
|
||||
const task_data = task.data;
|
||||
notifications.push(
|
||||
'error',
|
||||
`Fehler beim ${type === 'upload' ? 'Dateiupload' : type === 'download' ? 'Dateidownload' : 'Sychronisieren von Dateien'}`,
|
||||
`Datei: "${task.file_name}", Display-IP: ${task.display_ip}\nFehler: ${error}`
|
||||
`Fehler beim ${task_data.type === 'upload' ? 'Dateiupload' : task_data.type === 'download' ? 'Dateidownload' : 'Sychronisieren von Dateien'}`,
|
||||
`Datei: "${task.file_name}", Display-IP: ${task.display.ip}\nFehler: ${error}`
|
||||
);
|
||||
if (type === 'download') return;
|
||||
await remove_file_from_display(task.display_id, task.file_primary_key);
|
||||
await remove_all_files_without_display();
|
||||
if (task_data.type === 'upload') {
|
||||
await remove_file_from_display(task.display.id, task.file_primary_key);
|
||||
await remove_all_files_without_display();
|
||||
} else if (task_data.type === 'sync') {
|
||||
for (const display_id of task_data.destination_displays.map((e) => e.id)) {
|
||||
await remove_file_from_display(display_id, task.file_primary_key);
|
||||
}
|
||||
await remove_all_files_without_display();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,8 +232,7 @@ export async function update_folder_elements_recursively(
|
||||
const file_on_display: FileOnDisplay = {
|
||||
display_id: display.id,
|
||||
file_primary_key: get_file_primary_key(new_element.folder_element),
|
||||
is_loading: false,
|
||||
percentage: 100,
|
||||
loading_data: null,
|
||||
date_created: new_element.date_created
|
||||
};
|
||||
await db.files_on_display.put(file_on_display);
|
||||
|
||||
@@ -29,25 +29,45 @@ export const supported_file_type_icon: Record<string, typeof X> = {
|
||||
};
|
||||
|
||||
export type FileTransferTask = {
|
||||
id: string;
|
||||
type: 'upload' | 'download' | 'sync';
|
||||
display_id: string;
|
||||
display_ip: string;
|
||||
data: FileTransferTaskData;
|
||||
display: ShortDisplay;
|
||||
path: string;
|
||||
file_name: string;
|
||||
file_primary_key: string;
|
||||
file?: File; // only if type === 'upload'
|
||||
bytes_total: number; // if type === 'sync' -> bytes_total = file_size * 2 (1x download + 1x upload)
|
||||
};
|
||||
|
||||
export type FileTransferTaskData = {
|
||||
type: 'upload',
|
||||
file: File,
|
||||
} | {
|
||||
type: 'download',
|
||||
} | {
|
||||
type: "sync",
|
||||
destination_displays: ShortDisplay[],
|
||||
}
|
||||
|
||||
|
||||
export type ShortDisplay = {
|
||||
id: string;
|
||||
ip: string;
|
||||
}
|
||||
|
||||
|
||||
export type FileOnDisplay = {
|
||||
display_id: string;
|
||||
file_primary_key: string; // JSON.stringify([string, string, number, string])
|
||||
date_created: Date;
|
||||
is_loading: boolean;
|
||||
percentage: number;
|
||||
loading_data: FileLoadingData | null; // null if not loading
|
||||
};
|
||||
|
||||
export type FileLoadingData = {
|
||||
type: 'upload' | 'download' | 'sync_download' | 'sync_upload';
|
||||
percentage: number;
|
||||
bytes_per_second: number;
|
||||
seconds_until_finish: number;
|
||||
}
|
||||
|
||||
export type Inode = {
|
||||
path: string;
|
||||
name: string;
|
||||
|
||||
Reference in New Issue
Block a user