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,
|
supported_file_type_icon,
|
||||||
type Inode,
|
type Inode,
|
||||||
get_file_primary_key,
|
get_file_primary_key,
|
||||||
type FileOnDisplay
|
type FileOnDisplay,
|
||||||
|
type FileLoadingData
|
||||||
} from '$lib/ts/types';
|
} from '$lib/ts/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -47,7 +48,8 @@
|
|||||||
| Observable<{
|
| Observable<{
|
||||||
is_loading: boolean;
|
is_loading: boolean;
|
||||||
total_percentage: number;
|
total_percentage: number;
|
||||||
display_data: { is_loading: boolean; percentage: number }[];
|
total_seconds_until_finish: number;
|
||||||
|
display_data: FileLoadingData[];
|
||||||
}>
|
}>
|
||||||
| undefined = $state();
|
| undefined = $state();
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
@@ -198,7 +200,8 @@
|
|||||||
): Promise<{
|
): Promise<{
|
||||||
is_loading: boolean;
|
is_loading: boolean;
|
||||||
total_percentage: number;
|
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
|
const file_on_display_data: FileOnDisplay[] = await db.files_on_display
|
||||||
.where('file_primary_key')
|
.where('file_primary_key')
|
||||||
@@ -209,30 +212,40 @@
|
|||||||
return {
|
return {
|
||||||
is_loading: true,
|
is_loading: true,
|
||||||
total_percentage: 0,
|
total_percentage: 0,
|
||||||
|
total_seconds_until_finish: -1,
|
||||||
display_data: []
|
display_data: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const display_data = [];
|
const display_data = [];
|
||||||
let is_loading = false;
|
let is_loading = false;
|
||||||
let percentage_sum = 0;
|
let percentage_sum = 0;
|
||||||
|
let total_seconds_until_finish = 0;
|
||||||
for (const fod of file_on_display_data) {
|
for (const fod of file_on_display_data) {
|
||||||
if (!is_loading) is_loading = fod.is_loading;
|
if (!!fod.loading_data) {
|
||||||
percentage_sum += fod.percentage;
|
if (!is_loading) is_loading = true;
|
||||||
display_data.push({
|
percentage_sum += fod.loading_data.percentage;
|
||||||
is_loading: fod.is_loading,
|
total_seconds_until_finish += fod.loading_data.seconds_until_finish;
|
||||||
percentage: fod.percentage
|
display_data.push(fod.loading_data);
|
||||||
});
|
} else {
|
||||||
|
percentage_sum += 100;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let total_percentage = percentage_sum / display_data.length;
|
let total_percentage = percentage_sum / display_data.length;
|
||||||
return {
|
return {
|
||||||
is_loading,
|
is_loading,
|
||||||
total_percentage,
|
total_percentage,
|
||||||
|
total_seconds_until_finish,
|
||||||
display_data
|
display_data
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
</script>
|
</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}
|
{#if !not_interactable}
|
||||||
<div class="h-{$current_height.file} aspect-square max-w-15 flex">
|
<div class="h-{$current_height.file} aspect-square max-w-15 flex">
|
||||||
<Button
|
<Button
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import {
|
|||||||
type FileTransferTask,
|
type FileTransferTask,
|
||||||
type Inode
|
type Inode
|
||||||
} from './types';
|
} 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;
|
let is_processing: boolean = false;
|
||||||
const tasks: FileTransferTask[] = [];
|
const tasks: FileTransferTask[] = [];
|
||||||
@@ -51,20 +51,27 @@ export async function add_upload(
|
|||||||
display_id,
|
display_id,
|
||||||
file_primary_key,
|
file_primary_key,
|
||||||
date_created: new Date(),
|
date_created: new Date(),
|
||||||
is_loading: true,
|
loading_data: {
|
||||||
percentage: 0
|
type: 'upload',
|
||||||
|
percentage: 0,
|
||||||
|
bytes_per_second: 0,
|
||||||
|
seconds_until_finish: -1
|
||||||
|
}
|
||||||
};
|
};
|
||||||
await db.files_on_display.put(db_file_on_display);
|
await db.files_on_display.put(db_file_on_display);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: get_uuid(),
|
data: {
|
||||||
type: 'upload' as const,
|
type: 'upload' as const,
|
||||||
display_id,
|
file
|
||||||
display_ip,
|
},
|
||||||
|
display: {
|
||||||
|
id: display_id,
|
||||||
|
ip: display_ip
|
||||||
|
},
|
||||||
path: current_file_path,
|
path: current_file_path,
|
||||||
file_name: file_name,
|
file_name: file_name,
|
||||||
file_primary_key,
|
file_primary_key,
|
||||||
file,
|
|
||||||
bytes_total: file.size
|
bytes_total: file.size
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -108,7 +115,7 @@ async function start_task_processing() {
|
|||||||
async function start_task_loop() {
|
async function start_task_loop() {
|
||||||
while (tasks.length > 0) {
|
while (tasks.length > 0) {
|
||||||
const current_task = tasks[0];
|
const current_task = tasks[0];
|
||||||
switch (current_task.type) {
|
switch (current_task.data.type) {
|
||||||
case 'upload':
|
case 'upload':
|
||||||
await upload(current_task);
|
await upload(current_task);
|
||||||
break;
|
break;
|
||||||
@@ -123,15 +130,16 @@ async function start_task_loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function upload(task: FileTransferTask): Promise<void> {
|
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) => {
|
return new Promise<void>((resolve) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open(
|
xhr.open(
|
||||||
'POST',
|
'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
|
true
|
||||||
);
|
);
|
||||||
xhr.setRequestHeader('content-type', 'application/octet-stream');
|
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 total = e.lengthComputable ? e.total : task.bytes_total;
|
||||||
const current_percentage = total > 0 ? Math.round((e.loaded / total) * 100) : 1;
|
const current_percentage = total > 0 ? Math.round((e.loaded / total) * 100) : 1;
|
||||||
const apply = async () => {
|
const apply = async () => {
|
||||||
await db.files_on_display.update([task.display_id, task.file_primary_key], {
|
const prognosed_data = get_prognosed_data(start_time, e.loaded, total);
|
||||||
percentage: current_percentage
|
|
||||||
|
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();
|
apply();
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = async (e: ProgressEvent) => {
|
xhr.onerror = async (e: ProgressEvent) => {
|
||||||
await show_error('upload', e, task);
|
await show_error(task, e);
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onreadystatechange = async () => {
|
xhr.onreadystatechange = async () => {
|
||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
await db.files_on_display.update([task.display_id, task.file_primary_key], {
|
await db.files_on_display.update([task.display.id, task.file_primary_key], {
|
||||||
percentage: 100,
|
loading_data: null
|
||||||
is_loading: false
|
}); // TODO: Stattdessen update machen und gucken, ob die Datei wirklich da ist
|
||||||
});
|
|
||||||
} else {
|
} else {
|
||||||
await show_error('upload', `HTTP ${xhr.status}`, task);
|
await show_error(task, `HTTP ${xhr.status}`);
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.send(task.file);
|
xhr.send(task_data.file);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Generate Thumbnail if not done already
|
// Generate Thumbnail if not done already
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
@@ -174,23 +188,39 @@ async function upload(task: FileTransferTask): Promise<void> {
|
|||||||
JSON.parse(task.file_primary_key) as [string, string, number, string]
|
JSON.parse(task.file_primary_key) as [string, string, number, string]
|
||||||
);
|
);
|
||||||
if (!!inode_element && inode_element.thumbnail === null) {
|
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);
|
}, 10);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function show_error(
|
|
||||||
type: 'upload' | 'download' | 'sync',
|
function get_prognosed_data(
|
||||||
error: ProgressEvent | string,
|
start_time: Date,
|
||||||
task: FileTransferTask
|
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(
|
notifications.push(
|
||||||
'error',
|
'error',
|
||||||
`Fehler beim ${type === 'upload' ? 'Dateiupload' : type === 'download' ? 'Dateidownload' : 'Sychronisieren von Dateien'}`,
|
`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}`
|
`Datei: "${task.file_name}", Display-IP: ${task.display.ip}\nFehler: ${error}`
|
||||||
);
|
);
|
||||||
if (type === 'download') return;
|
if (task_data.type === 'upload') {
|
||||||
await remove_file_from_display(task.display_id, task.file_primary_key);
|
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();
|
await remove_all_files_without_display();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -232,8 +232,7 @@ export async function update_folder_elements_recursively(
|
|||||||
const file_on_display: FileOnDisplay = {
|
const file_on_display: FileOnDisplay = {
|
||||||
display_id: display.id,
|
display_id: display.id,
|
||||||
file_primary_key: get_file_primary_key(new_element.folder_element),
|
file_primary_key: get_file_primary_key(new_element.folder_element),
|
||||||
is_loading: false,
|
loading_data: null,
|
||||||
percentage: 100,
|
|
||||||
date_created: new_element.date_created
|
date_created: new_element.date_created
|
||||||
};
|
};
|
||||||
await db.files_on_display.put(file_on_display);
|
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 = {
|
export type FileTransferTask = {
|
||||||
id: string;
|
data: FileTransferTaskData;
|
||||||
type: 'upload' | 'download' | 'sync';
|
display: ShortDisplay;
|
||||||
display_id: string;
|
|
||||||
display_ip: string;
|
|
||||||
path: string;
|
path: string;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
file_primary_key: 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)
|
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 = {
|
export type FileOnDisplay = {
|
||||||
display_id: string;
|
display_id: string;
|
||||||
file_primary_key: string; // JSON.stringify([string, string, number, string])
|
file_primary_key: string; // JSON.stringify([string, string, number, string])
|
||||||
date_created: Date;
|
date_created: Date;
|
||||||
is_loading: boolean;
|
loading_data: FileLoadingData | null; // null if not loading
|
||||||
percentage: number;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FileLoadingData = {
|
||||||
|
type: 'upload' | 'download' | 'sync_download' | 'sync_upload';
|
||||||
|
percentage: number;
|
||||||
|
bytes_per_second: number;
|
||||||
|
seconds_until_finish: number;
|
||||||
|
}
|
||||||
|
|
||||||
export type Inode = {
|
export type Inode = {
|
||||||
path: string;
|
path: string;
|
||||||
name: string;
|
name: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user