mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
chore(control): display transferring file correctly (still a few bugs)
This commit is contained in:
@@ -11,7 +11,7 @@
|
|||||||
type Inode,
|
type Inode,
|
||||||
get_file_primary_key,
|
get_file_primary_key,
|
||||||
type FileOnDisplay,
|
type FileOnDisplay,
|
||||||
type CompleteFileLoadingData
|
type FileTransferTask
|
||||||
} from '$lib/ts/types';
|
} from '$lib/ts/types';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -33,9 +33,12 @@
|
|||||||
import { get_thumbnail_url } from '$lib/ts/stores/thumbnails';
|
import { get_thumbnail_url } from '$lib/ts/stores/thumbnails';
|
||||||
import { liveQuery, type Observable } from 'dexie';
|
import { liveQuery, type Observable } from 'dexie';
|
||||||
import { db } from '$lib/ts/database';
|
import { db } from '$lib/ts/database';
|
||||||
|
import { file_transfer_tasks } from '$lib/ts/file_transfer_handler';
|
||||||
|
|
||||||
let { file, not_interactable = false }: { file: Inode; not_interactable?: boolean } = $props();
|
let { file, not_interactable = false }: { file: Inode; not_interactable?: boolean } = $props();
|
||||||
|
|
||||||
|
let file_primary_key = $derived(get_file_primary_key(file));
|
||||||
|
|
||||||
let missing_colliding_displays_ids:
|
let missing_colliding_displays_ids:
|
||||||
| Observable<{ missing: string[]; colliding: string[] }>
|
| Observable<{ missing: string[]; colliding: string[] }>
|
||||||
| undefined = $state();
|
| undefined = $state();
|
||||||
@@ -44,31 +47,26 @@
|
|||||||
missing_colliding_displays_ids = liveQuery(() => get_missing_colliding_display_ids(file, s));
|
missing_colliding_displays_ids = liveQuery(() => get_missing_colliding_display_ids(file, s));
|
||||||
});
|
});
|
||||||
|
|
||||||
let loading_data:
|
let file_transfer_task: FileTransferTask | null = $derived(
|
||||||
| Observable<CompleteFileLoadingData>
|
$file_transfer_tasks.hasOwnProperty(file_primary_key)
|
||||||
| undefined = $state();
|
? $file_transfer_tasks[file_primary_key]
|
||||||
$effect(() => {
|
: null
|
||||||
const d = $selected_display_ids;
|
);
|
||||||
loading_data = liveQuery(() => get_loading_data(get_file_primary_key(file), d));
|
|
||||||
});
|
|
||||||
|
|
||||||
let loading_finished = $state(false);
|
let loading_finished = $state(false);
|
||||||
|
let previous_loading_state = $state(false);
|
||||||
$effect(() => {
|
$effect(() => {
|
||||||
if (!loading_data) return;
|
const ftt = file_transfer_task;
|
||||||
let prev: boolean | undefined;
|
if (previous_loading_state && !ftt) {
|
||||||
const sub = loading_data.subscribe((v) => {
|
|
||||||
if (prev === true && v.is_loading === false) {
|
|
||||||
loading_finished = true;
|
loading_finished = true;
|
||||||
setTimeout(() => (loading_finished = false), 200);
|
setTimeout(() => (loading_finished = false), 200);
|
||||||
}
|
}
|
||||||
prev = v.is_loading;
|
previous_loading_state = !!ftt;
|
||||||
});
|
|
||||||
return () => sub.unsubscribe();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let thumbnail_url = liveQuery(() => get_thumbnail_url(get_file_primary_key(file)));
|
let thumbnail_url = liveQuery(() => get_thumbnail_url(file_primary_key));
|
||||||
let date_mapping: Observable<Record<string, Date>> = liveQuery(() =>
|
let date_mapping: Observable<Record<string, Date>> = liveQuery(() =>
|
||||||
get_date_mapping(get_file_primary_key(file))
|
get_date_mapping(file_primary_key)
|
||||||
);
|
);
|
||||||
|
|
||||||
const is_folder = file.type === 'inode/directory';
|
const is_folder = file.type === 'inode/directory';
|
||||||
@@ -130,7 +128,7 @@
|
|||||||
|
|
||||||
function get_grayed_out_text_color_strings(is_selected: boolean): string {
|
function get_grayed_out_text_color_strings(is_selected: boolean): string {
|
||||||
if (not_interactable) return 'text-stone-400';
|
if (not_interactable) return 'text-stone-400';
|
||||||
if ($loading_data?.is_loading) return 'text-white/20';
|
if (!!file_transfer_task) return 'text-white/20';
|
||||||
const color = is_selected ? 'text-stone-600' : 'text-stone-400';
|
const color = is_selected ? 'text-stone-600' : 'text-stone-400';
|
||||||
const factor = is_selected ? -1 : 1;
|
const factor = is_selected ? -1 : 1;
|
||||||
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
||||||
@@ -138,15 +136,15 @@
|
|||||||
|
|
||||||
function get_grayed_out_border_color_strings(is_selected: boolean): string {
|
function get_grayed_out_border_color_strings(is_selected: boolean): string {
|
||||||
if (not_interactable) return 'border-stone-550';
|
if (not_interactable) return 'border-stone-550';
|
||||||
if ($loading_data?.is_loading) return 'border-white/10';
|
if (!!file_transfer_task) return 'border-white/10';
|
||||||
const color = is_selected ? 'border-stone-450' : 'border-stone-550';
|
const color = is_selected ? 'border-stone-450' : 'border-stone-550';
|
||||||
const factor = is_selected ? 1 : 1;
|
const factor = is_selected ? 1 : 1;
|
||||||
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
return `${color} group-hover:${get_shifted_color(color, factor * 100)} group-active:${get_shifted_color(color, factor * 150)}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
function onclick(e: Event) {
|
function onclick(e: Event) {
|
||||||
if (not_interactable || $loading_data?.is_loading) return;
|
if (not_interactable || !!file_transfer_task) return;
|
||||||
select(selected_file_ids, get_file_primary_key(file), 'toggle');
|
select(selected_file_ids, file_primary_key, 'toggle');
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,11 +162,11 @@
|
|||||||
|
|
||||||
if (loading_finished) {
|
if (loading_finished) {
|
||||||
out += 'bg-stone-500 text-white/30';
|
out += 'bg-stone-500 text-white/30';
|
||||||
} else if ($loading_data?.is_loading) {
|
} else if (!!file_transfer_task) {
|
||||||
out += 'bg-stone-700 text-white/30';
|
out += 'bg-stone-700 text-white/30';
|
||||||
} else {
|
} else {
|
||||||
out += get_selectable_color_classes(
|
out += get_selectable_color_classes(
|
||||||
!not_interactable && is_selected(get_file_primary_key(file), $selected_file_ids),
|
!not_interactable && is_selected(file_primary_key, $selected_file_ids),
|
||||||
{
|
{
|
||||||
bg: true,
|
bg: true,
|
||||||
hover: !not_interactable,
|
hover: !not_interactable,
|
||||||
@@ -180,7 +178,7 @@
|
|||||||
|
|
||||||
if (not_interactable) {
|
if (not_interactable) {
|
||||||
out += ' rounded-lg';
|
out += ' rounded-lg';
|
||||||
} else if ($loading_data?.is_loading) {
|
} else if (!!file_transfer_task) {
|
||||||
out += ' rounded-r-lg';
|
out += ' rounded-r-lg';
|
||||||
} else {
|
} else {
|
||||||
out += ' rounded-r-lg cursor-pointer';
|
out += ' rounded-r-lg cursor-pointer';
|
||||||
@@ -189,54 +187,20 @@
|
|||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function get_loading_data(
|
function get_total_percentage(ftt: FileTransferTask): number {
|
||||||
file_primary_key: string,
|
let total_percentage: number;
|
||||||
selected_display_ids: string[]
|
if (ftt.data.type === 'upload') {
|
||||||
): Promise<CompleteFileLoadingData> {
|
total_percentage = ftt.loading_data.percentage;
|
||||||
const file_on_display_data: FileOnDisplay[] = await db.files_on_display
|
|
||||||
.where('file_primary_key')
|
|
||||||
.equals(file_primary_key)
|
|
||||||
.filter((e) => selected_display_ids.includes(e.display_id))
|
|
||||||
.toArray();
|
|
||||||
if (file_on_display_data.length === 0) {
|
|
||||||
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 (!!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;
|
|
||||||
const display = await get_display_by_id(fod.display_id);
|
|
||||||
if (!display) continue;
|
|
||||||
const display_data_element = {
|
|
||||||
loading_data: fod.loading_data,
|
|
||||||
display_name: display.name
|
|
||||||
};
|
|
||||||
if (fod.loading_data.type === 'sync_download') {
|
|
||||||
display_data.unshift(display_data_element); // insert sync_download element at beginning
|
|
||||||
} else {
|
} else {
|
||||||
display_data.push(display_data_element);
|
const percentage_array = ftt.data.destination_display_data.map(
|
||||||
|
(dd) => dd.loading_data.percentage
|
||||||
|
);
|
||||||
|
total_percentage =
|
||||||
|
(ftt.loading_data.percentage + percentage_array.reduce((total, n) => total + n, 0)) /
|
||||||
|
(1 + percentage_array.length);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
percentage_sum += 100;
|
return Math.min(total_percentage, 100);
|
||||||
}
|
|
||||||
}
|
|
||||||
let total_percentage = percentage_sum / display_data.length;
|
|
||||||
return {
|
|
||||||
is_loading,
|
|
||||||
total_percentage,
|
|
||||||
total_seconds_until_finish,
|
|
||||||
display_data
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -302,10 +266,10 @@
|
|||||||
{onclick}
|
{onclick}
|
||||||
class="{get_main_classes()} relative transition-colors duration-200 gap-4 flex flex-row justify-between group w-full min-w-0"
|
class="{get_main_classes()} relative transition-colors duration-200 gap-4 flex flex-row justify-between group w-full min-w-0"
|
||||||
>
|
>
|
||||||
{#if $loading_data?.is_loading}
|
{#if !!file_transfer_task}
|
||||||
<div
|
<div
|
||||||
class="absolute pointer-events-none inset-y-0 left-0 transition-[width] duration-200 bg-stone-600 rounded-r-lg"
|
class="absolute pointer-events-none inset-y-0 left-0 transition-[width] duration-400 bg-stone-600 rounded-r-lg"
|
||||||
style={`width: ${Math.min($loading_data.total_percentage, 100)}%;`}
|
style={`width: ${get_total_percentage(file_transfer_task)}%;`}
|
||||||
></div>
|
></div>
|
||||||
{/if}
|
{/if}
|
||||||
<div class="flex flex-row gap-2 min-w-0 w-full z-10">
|
<div class="flex flex-row gap-2 min-w-0 w-full z-10">
|
||||||
@@ -334,7 +298,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class=" p-1 flex flex-row items-center gap-1 pr-1 z-10 {get_grayed_out_text_color_strings(
|
class=" p-1 flex flex-row items-center gap-1 pr-1 z-10 {get_grayed_out_text_color_strings(
|
||||||
is_selected(get_file_primary_key(file), $selected_file_ids)
|
is_selected(file_primary_key, $selected_file_ids)
|
||||||
)} duration-200 transition-colors"
|
)} duration-200 transition-colors"
|
||||||
>
|
>
|
||||||
<!-- {#if get_display_ids_where_file_is_missing($current_file_path, file, $selected_display_ids, $all_files)[1].length !== 0}
|
<!-- {#if get_display_ids_where_file_is_missing($current_file_path, file, $selected_display_ids, $all_files)[1].length !== 0}
|
||||||
@@ -372,7 +336,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="h-[70%] border {get_grayed_out_border_color_strings(
|
class="h-[70%] border {get_grayed_out_border_color_strings(
|
||||||
is_selected(get_file_primary_key(file), $selected_file_ids)
|
is_selected(file_primary_key, $selected_file_ids)
|
||||||
)} duration-200 transition-colors my-1"
|
)} duration-200 transition-colors my-1"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
@@ -383,7 +347,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="h-[70%] border {get_grayed_out_border_color_strings(
|
class="h-[70%] border {get_grayed_out_border_color_strings(
|
||||||
is_selected(get_file_primary_key(file), $selected_file_ids)
|
is_selected(file_primary_key, $selected_file_ids)
|
||||||
)} duration-200 transition-colors"
|
)} duration-200 transition-colors"
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -23,8 +23,7 @@ export class FileDatabase extends Dexie {
|
|||||||
[display_id+file_primary_key],
|
[display_id+file_primary_key],
|
||||||
display_id,
|
display_id,
|
||||||
file_primary_key,
|
file_primary_key,
|
||||||
date_created,
|
date_created
|
||||||
loading_data
|
|
||||||
`,
|
`,
|
||||||
displays: `
|
displays: `
|
||||||
id,
|
id,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { get, writable, type Writable } from 'svelte/store';
|
||||||
import { db } from './database';
|
import { db } from './database';
|
||||||
import { get_display_by_id } from './stores/displays';
|
import { get_display_by_id } from './stores/displays';
|
||||||
import {
|
import {
|
||||||
@@ -19,8 +20,17 @@ import {
|
|||||||
} from './types';
|
} from './types';
|
||||||
import { get_sanitized_file_url, get_uuid, make_valid_name } from './utils';
|
import { get_sanitized_file_url, get_uuid, make_valid_name } from './utils';
|
||||||
|
|
||||||
|
const START_LOADING_DATA = {
|
||||||
|
percentage: 0,
|
||||||
|
bytes_per_second: 0,
|
||||||
|
seconds_until_finish: -1
|
||||||
|
};
|
||||||
|
|
||||||
|
export const file_transfer_tasks: Writable<Record<string, FileTransferTask>> = writable<
|
||||||
|
Record<string, FileTransferTask>
|
||||||
|
>({});
|
||||||
|
|
||||||
let is_processing: boolean = false;
|
let is_processing: boolean = false;
|
||||||
const tasks: FileTransferTask[] = [];
|
|
||||||
|
|
||||||
export async function add_upload(
|
export async function add_upload(
|
||||||
file_list: FileList,
|
file_list: FileList,
|
||||||
@@ -28,7 +38,6 @@ export async function add_upload(
|
|||||||
current_file_path: string
|
current_file_path: string
|
||||||
) {
|
) {
|
||||||
if (file_list.length === 0) return console.warn('Upload canceled: no selected files');
|
if (file_list.length === 0) return console.warn('Upload canceled: no selected files');
|
||||||
|
|
||||||
await create_path_on_all_selected_displays(current_file_path, selected_display_ids);
|
await create_path_on_all_selected_displays(current_file_path, selected_display_ids);
|
||||||
|
|
||||||
const used_file_names: string[] = await (
|
const used_file_names: string[] = await (
|
||||||
@@ -47,6 +56,10 @@ export async function add_upload(
|
|||||||
thumbnail: null
|
thumbnail: null
|
||||||
};
|
};
|
||||||
const file_primary_key = get_file_primary_key(db_file);
|
const file_primary_key = get_file_primary_key(db_file);
|
||||||
|
|
||||||
|
if (Object.prototype.hasOwnProperty.call(file_transfer_tasks, file_primary_key))
|
||||||
|
return show_already_in_tasks_error(db_file, 'upload'); // file is already in task
|
||||||
|
|
||||||
await db.files.put(db_file);
|
await db.files.put(db_file);
|
||||||
|
|
||||||
const upload_task_promises = selected_display_ids.map(async (display_id) => {
|
const upload_task_promises = selected_display_ids.map(async (display_id) => {
|
||||||
@@ -56,17 +69,11 @@ export async function add_upload(
|
|||||||
const db_file_on_display: FileOnDisplay = {
|
const db_file_on_display: FileOnDisplay = {
|
||||||
display_id,
|
display_id,
|
||||||
file_primary_key,
|
file_primary_key,
|
||||||
date_created: new Date(),
|
date_created: new Date()
|
||||||
loading_data: {
|
|
||||||
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 {
|
const new_task = {
|
||||||
data: {
|
data: {
|
||||||
type: 'upload' as const,
|
type: 'upload' as const,
|
||||||
file
|
file
|
||||||
@@ -77,13 +84,16 @@ export async function add_upload(
|
|||||||
},
|
},
|
||||||
path: current_file_path,
|
path: current_file_path,
|
||||||
file_name: file_name,
|
file_name: file_name,
|
||||||
file_primary_key,
|
loading_data: START_LOADING_DATA,
|
||||||
bytes_total: file.size
|
bytes_total: file.size
|
||||||
};
|
};
|
||||||
|
file_transfer_tasks.update((tasks) => ({
|
||||||
|
...tasks,
|
||||||
|
[file_primary_key]: new_task
|
||||||
|
}));
|
||||||
});
|
});
|
||||||
|
|
||||||
const upload_tasks = (await Promise.all(upload_task_promises)).filter((e) => !!e);
|
await Promise.all(upload_task_promises);
|
||||||
tasks.push(...upload_tasks);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
await start_task_processing();
|
await start_task_processing();
|
||||||
@@ -113,42 +123,36 @@ export async function add_sync_recursively(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (file_data.short_displays_without_file.length === 0) return; // file is present on all selected displays
|
if (file_data.short_displays_without_file.length === 0) return; // file is present on all selected displays
|
||||||
|
if (Object.prototype.hasOwnProperty.call(file_transfer_tasks, selected_file_id))
|
||||||
|
return show_already_in_tasks_error(file_data.file, 'sync'); // file is already in task
|
||||||
await create_path_on_all_selected_displays(file_data.file.path, selected_display_ids);
|
await create_path_on_all_selected_displays(file_data.file.path, selected_display_ids);
|
||||||
|
|
||||||
tasks.push({
|
const new_task: FileTransferTask = {
|
||||||
data: {
|
data: {
|
||||||
type: 'sync',
|
type: 'sync',
|
||||||
destination_displays: file_data.short_displays_without_file
|
destination_display_data: file_data.short_displays_without_file.map((display) => ({
|
||||||
|
display,
|
||||||
|
loading_data: START_LOADING_DATA
|
||||||
|
}))
|
||||||
},
|
},
|
||||||
display: file_data.short_display_with_file,
|
display: file_data.short_display_with_file,
|
||||||
path: file_data.file.path,
|
path: file_data.file.path,
|
||||||
file_name: file_data.file.name,
|
file_name: file_data.file.name,
|
||||||
file_primary_key: selected_file_id,
|
loading_data: START_LOADING_DATA,
|
||||||
bytes_total: file_data.file.size
|
bytes_total: file_data.file.size
|
||||||
});
|
|
||||||
|
|
||||||
await db.files_on_display.update([file_data.short_display_with_file.id, selected_file_id], {
|
|
||||||
loading_data: {
|
|
||||||
type: 'sync_download',
|
|
||||||
percentage: 0,
|
|
||||||
bytes_per_second: 0,
|
|
||||||
seconds_until_finish: -1
|
|
||||||
}
|
|
||||||
});
|
|
||||||
const display_ids_without_file = file_data.short_displays_without_file.map((d) => d.id);
|
|
||||||
const new_file_loading_data: FileLoadingData = {
|
|
||||||
type: 'sync_upload',
|
|
||||||
percentage: 0,
|
|
||||||
bytes_per_second: 0,
|
|
||||||
seconds_until_finish: -1
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
file_transfer_tasks.update((tasks) => ({
|
||||||
|
...tasks,
|
||||||
|
[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) => ({
|
const new_fods: FileOnDisplay[] = display_ids_without_file.map((display_id) => ({
|
||||||
display_id,
|
display_id,
|
||||||
file_primary_key: selected_file_id,
|
file_primary_key: selected_file_id,
|
||||||
date_created: new Date(),
|
date_created: new Date()
|
||||||
loading_data: new_file_loading_data
|
|
||||||
}));
|
}));
|
||||||
console.log("TEST", new_fods)
|
|
||||||
await db.files_on_display.bulkPut(new_fods);
|
await db.files_on_display.bulkPut(new_fods);
|
||||||
|
|
||||||
await start_task_processing();
|
await start_task_processing();
|
||||||
@@ -222,15 +226,15 @@ function generate_valid_file_name(original_file_name: string, used_file_names: s
|
|||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upload(task: FileTransferTask): Promise<void> {
|
async function upload(file_primary_key: string, task: FileTransferTask): Promise<void> {
|
||||||
const task_data = task.data;
|
const task_data = task.data;
|
||||||
if (task_data.type !== 'upload' || !task_data.file)
|
if (task_data.type !== 'upload' || !task_data.file)
|
||||||
return console.warn('Task cancelled: wrong task type:', task);
|
return console.warn('Task cancelled: wrong task type:', task);
|
||||||
|
|
||||||
await upload_file_via_xhr(task, task.display, task_data.file);
|
await upload_file_via_xhr(file_primary_key, task, task_data.file);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function sync(task: FileTransferTask) {
|
export async function sync(file_primary_key: string, task: FileTransferTask) {
|
||||||
if (task.data.type !== 'sync') return console.warn('Task cancelled: wrong task type:', task);
|
if (task.data.type !== 'sync') return console.warn('Task cancelled: wrong task type:', task);
|
||||||
|
|
||||||
const hasOPFS =
|
const hasOPFS =
|
||||||
@@ -238,7 +242,8 @@ export async function sync(task: FileTransferTask) {
|
|||||||
'storage' in navigator &&
|
'storage' in navigator &&
|
||||||
'getDirectory' in navigator.storage;
|
'getDirectory' in navigator.storage;
|
||||||
if (!hasOPFS) {
|
if (!hasOPFS) {
|
||||||
return show_error(
|
return show_general_error(
|
||||||
|
file_primary_key,
|
||||||
task,
|
task,
|
||||||
'OPFS (navigator.storage.getDirectory) nicht verfügbar – bitte Chromium/Edge/Chrome nutzen.'
|
'OPFS (navigator.storage.getDirectory) nicht verfügbar – bitte Chromium/Edge/Chrome nutzen.'
|
||||||
);
|
);
|
||||||
@@ -251,7 +256,7 @@ export async function sync(task: FileTransferTask) {
|
|||||||
const url = `http://${task.display.ip}:1323/api${get_sanitized_file_url(task.path + task.file_name)}`;
|
const url = `http://${task.display.ip}:1323/api${get_sanitized_file_url(task.path + task.file_name)}`;
|
||||||
const fetch_source = await fetch(url, { method: 'GET' });
|
const fetch_source = await fetch(url, { method: 'GET' });
|
||||||
if (!fetch_source.ok || !fetch_source.body)
|
if (!fetch_source.ok || !fetch_source.body)
|
||||||
return show_error(task, `HTTP ${fetch_source.status}`);
|
return show_general_error(file_primary_key, task, `HTTP ${fetch_source.status}`);
|
||||||
|
|
||||||
const dir = await navigator.storage.getDirectory();
|
const dir = await navigator.storage.getDirectory();
|
||||||
const file_handle = await dir.getFileHandle(temp_name, { create: true });
|
const file_handle = await dir.getFileHandle(temp_name, { create: true });
|
||||||
@@ -264,25 +269,23 @@ export async function sync(task: FileTransferTask) {
|
|||||||
if (done) break;
|
if (done) break;
|
||||||
if (!value) continue;
|
if (!value) continue;
|
||||||
|
|
||||||
await update_current_loading_data('sync_download', task, value.byteLength, start_time);
|
update_current_loading_data(file_primary_key, value.byteLength, start_time);
|
||||||
await writable.write(value);
|
await writable.write(value);
|
||||||
}
|
}
|
||||||
await writable.close();
|
await writable.close();
|
||||||
|
|
||||||
await db.files_on_display.update([task.display.id, task.file_primary_key], {
|
finish_loading_data(file_primary_key);
|
||||||
loading_data: null
|
|
||||||
});
|
|
||||||
|
|
||||||
// 02 - send downloaded file to every destination_display
|
// 02 - send downloaded file to every destination_display
|
||||||
const temp_file = await file_handle.getFile();
|
const temp_file = await file_handle.getFile();
|
||||||
|
|
||||||
for (const current_short_display of task.data.destination_displays) {
|
for (const current_short_display of task.data.destination_display_data) {
|
||||||
await upload_file_via_xhr(task, current_short_display, temp_file);
|
await upload_file_via_xhr(file_primary_key, task, temp_file, current_short_display.display);
|
||||||
}
|
}
|
||||||
|
|
||||||
await dir.removeEntry(temp_name);
|
await dir.removeEntry(temp_name);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
show_error(task, String(e));
|
show_general_error(file_primary_key, task, String(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,56 +318,62 @@ export async function download_file(selected_file_id: string, selected_display_i
|
|||||||
|
|
||||||
async function start_task_processing() {
|
async function start_task_processing() {
|
||||||
if (!is_processing) {
|
if (!is_processing) {
|
||||||
is_processing = tasks.length !== 0;
|
is_processing = Object.keys(get(file_transfer_tasks)).length !== 0;
|
||||||
await start_task_loop();
|
await start_task_loop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function start_task_loop() {
|
async function start_task_loop() {
|
||||||
while (tasks.length > 0) {
|
while (Object.keys(get(file_transfer_tasks)).length > 0) {
|
||||||
const current_task = tasks[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') {
|
if (current_task.data.type === 'upload') {
|
||||||
await upload(current_task);
|
await upload(current_file_id, current_task);
|
||||||
} else if (current_task.data.type === 'sync') {
|
} else if (current_task.data.type === 'sync') {
|
||||||
await sync(current_task);
|
await sync(current_file_id, current_task);
|
||||||
}
|
}
|
||||||
tasks.shift(); // Remove current_task from tasks
|
|
||||||
|
file_transfer_tasks.update((all_tasks) => {
|
||||||
|
const next = { ...all_tasks };
|
||||||
|
delete next[current_file_id];
|
||||||
|
return next;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
is_processing = false;
|
is_processing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function upload_file_via_xhr(
|
async function upload_file_via_xhr(
|
||||||
|
file_primary_key: string,
|
||||||
task: FileTransferTask,
|
task: FileTransferTask,
|
||||||
current_short_display: ShortDisplay,
|
current_file: File,
|
||||||
current_file: File
|
destination_short_display: ShortDisplay | null = null
|
||||||
) {
|
) {
|
||||||
const start_time = new Date();
|
const start_time = new Date();
|
||||||
const loading_type = task.data.type === 'upload' ? 'upload' : 'sync_upload';
|
|
||||||
|
|
||||||
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://${current_short_display.ip}:1323/api${get_sanitized_file_url(task.path + task.file_name)}`,
|
`http://${destination_short_display ? destination_short_display.ip : 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');
|
||||||
|
|
||||||
xhr.upload.onprogress = (e) => {
|
xhr.upload.onprogress = (e) => {
|
||||||
const apply = async () => {
|
const apply = async () => {
|
||||||
await update_current_loading_data(
|
update_current_loading_data(
|
||||||
loading_type,
|
file_primary_key,
|
||||||
task,
|
|
||||||
e.loaded,
|
e.loaded,
|
||||||
start_time,
|
start_time,
|
||||||
current_short_display.id
|
destination_short_display ? destination_short_display.id : null
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
apply();
|
apply();
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = async (e: ProgressEvent) => {
|
xhr.onerror = async (e: ProgressEvent) => {
|
||||||
await show_error(task, e);
|
await show_general_error(file_primary_key, task, e);
|
||||||
resolve();
|
resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -372,21 +381,21 @@ async function upload_file_via_xhr(
|
|||||||
if (xhr.readyState === 4) {
|
if (xhr.readyState === 4) {
|
||||||
if (xhr.status == 200) {
|
if (xhr.status == 200) {
|
||||||
// set loading_data to 100%
|
// set loading_data to 100%
|
||||||
await db.files_on_display.update([current_short_display.id, task.file_primary_key], {
|
finish_loading_data(
|
||||||
date_created: new Date(),
|
file_primary_key,
|
||||||
loading_data: null
|
destination_short_display ? destination_short_display.id : null
|
||||||
});
|
);
|
||||||
// Generate Thumbnail if not done already
|
// Generate Thumbnail if not done already
|
||||||
setTimeout(async () => {
|
setTimeout(async () => {
|
||||||
const inode_element: Inode | undefined = await db.files.get(
|
const inode_element: Inode | undefined = await db.files.get(
|
||||||
JSON.parse(task.file_primary_key) as [string, string, number, string]
|
JSON.parse(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);
|
||||||
} else {
|
} else {
|
||||||
await show_error(task, `HTTP ${xhr.status}`);
|
await show_general_error(file_primary_key, task, `HTTP ${xhr.status}`);
|
||||||
}
|
}
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
@@ -396,30 +405,74 @@ async function upload_file_via_xhr(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function update_current_loading_data(
|
function finish_loading_data(
|
||||||
type: FileLoadingData['type'],
|
file_primary_key: string,
|
||||||
task: FileTransferTask,
|
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
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
...tasks,
|
||||||
|
[file_primary_key]: get_updated_task(current_loading_data, destination_display_id, task)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function update_current_loading_data(
|
||||||
|
file_primary_key: string,
|
||||||
current_bytes: number,
|
current_bytes: number,
|
||||||
start_time: Date,
|
start_time: Date,
|
||||||
other_display_id: string | null = null
|
destination_display_id: string | null = null
|
||||||
) {
|
) {
|
||||||
|
file_transfer_tasks.update((tasks) => {
|
||||||
|
const task = tasks[file_primary_key];
|
||||||
|
if (!task) return tasks;
|
||||||
|
|
||||||
const current_percentage = Math.min(
|
const current_percentage = Math.min(
|
||||||
task.bytes_total > 0 ? Math.round((current_bytes / task.bytes_total) * 100) : 1,
|
task.bytes_total > 0 ? Math.round((current_bytes / task.bytes_total) * 100) : 1,
|
||||||
99
|
99
|
||||||
); // calculate percantage, but maximum value is 99%
|
);
|
||||||
|
|
||||||
const prognosed_data = get_prognosed_data(start_time, current_bytes, task.bytes_total);
|
const prognosed_data = get_prognosed_data(start_time, current_bytes, task.bytes_total);
|
||||||
|
|
||||||
await db.files_on_display.update(
|
const current_loading_data: FileLoadingData = {
|
||||||
[other_display_id ? other_display_id : task.display.id, task.file_primary_key],
|
|
||||||
{
|
|
||||||
loading_data: {
|
|
||||||
type,
|
|
||||||
percentage: current_percentage,
|
percentage: current_percentage,
|
||||||
bytes_per_second: prognosed_data.bytes_per_second,
|
bytes_per_second: prognosed_data.bytes_per_second,
|
||||||
seconds_until_finish: prognosed_data.seconds_until_finish
|
seconds_until_finish: prognosed_data.seconds_until_finish
|
||||||
}
|
};
|
||||||
}
|
|
||||||
|
return {
|
||||||
|
...tasks,
|
||||||
|
[file_primary_key]: get_updated_task(current_loading_data, destination_display_id, task)
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function get_updated_task(current_loading_data: FileLoadingData, destination_display_id: string | null, task: FileTransferTask): FileTransferTask {
|
||||||
|
if (destination_display_id && task.data.type === 'sync') {
|
||||||
|
const updatedDestinations = task.data.destination_display_data.map((dd) =>
|
||||||
|
dd.display.id === destination_display_id ? { ...dd, loading_data: current_loading_data } : dd
|
||||||
);
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...task,
|
||||||
|
data: {
|
||||||
|
...task.data,
|
||||||
|
destination_display_data: updatedDestinations
|
||||||
|
}
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
...task,
|
||||||
|
loading_data: current_loading_data
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function get_prognosed_data(
|
function get_prognosed_data(
|
||||||
@@ -434,7 +487,11 @@ function get_prognosed_data(
|
|||||||
return { bytes_per_second, seconds_until_finish };
|
return { bytes_per_second, seconds_until_finish };
|
||||||
}
|
}
|
||||||
|
|
||||||
async function show_error(task: FileTransferTask, error: ProgressEvent | string) {
|
async function show_general_error(
|
||||||
|
file_primary_key: string,
|
||||||
|
task: FileTransferTask,
|
||||||
|
error: ProgressEvent | string
|
||||||
|
) {
|
||||||
const task_data = task.data;
|
const task_data = task.data;
|
||||||
console.error('Error in File-Transfer-Handler:', error, task);
|
console.error('Error in File-Transfer-Handler:', error, task);
|
||||||
notifications.push(
|
notifications.push(
|
||||||
@@ -443,12 +500,20 @@ async function show_error(task: FileTransferTask, error: ProgressEvent | string)
|
|||||||
`Datei: "${task.file_name}", Display-IP: ${task.display.ip}\nFehler: ${error}`
|
`Datei: "${task.file_name}", Display-IP: ${task.display.ip}\nFehler: ${error}`
|
||||||
);
|
);
|
||||||
if (task_data.type === 'upload') {
|
if (task_data.type === 'upload') {
|
||||||
await remove_file_from_display_recusively(task.display.id, task.file_primary_key);
|
await remove_file_from_display_recusively(task.display.id, file_primary_key);
|
||||||
await remove_all_files_without_display();
|
await remove_all_files_without_display();
|
||||||
} else if (task_data.type === 'sync') {
|
} else if (task_data.type === 'sync') {
|
||||||
for (const display_id of task_data.destination_displays.map((e) => e.id)) {
|
for (const display_id of task_data.destination_display_data.map((e) => e.display.id)) {
|
||||||
await remove_file_from_display_recusively(display_id, task.file_primary_key);
|
await remove_file_from_display_recusively(display_id, file_primary_key);
|
||||||
}
|
}
|
||||||
await remove_all_files_without_display();
|
await remove_all_files_without_display();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function show_already_in_tasks_error(file: Inode, process: 'upload' | 'sync') {
|
||||||
|
notifications.push(
|
||||||
|
'error',
|
||||||
|
`Datei '${file.name}' konnte nicht ${process === 'upload' ? 'hochgeladen' : 'synchronisiert'} werden`,
|
||||||
|
`Eine Datei kann nicht in mehreren file_transfer_tasks enthalten sein. Bitte erneut versuchen, wenn die aktuellen Aktionen fertiggestellt wurden`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { is_selected, select, selected_display_ids, selected_file_ids } from './
|
|||||||
import { create_path, get_file_data, get_file_tree_data } from '../api_handler';
|
import { create_path, get_file_data, get_file_tree_data } from '../api_handler';
|
||||||
import { deactivate_old_thumbnail_urls, generate_thumbnail } from './thumbnails';
|
import { deactivate_old_thumbnail_urls, generate_thumbnail } from './thumbnails';
|
||||||
import { db } from '../database';
|
import { db } from '../database';
|
||||||
|
import { file_transfer_tasks } from '../file_transfer_handler';
|
||||||
|
|
||||||
export const current_file_path: Writable<string> = writable<string>('/');
|
export const current_file_path: Writable<string> = writable<string>('/');
|
||||||
|
|
||||||
@@ -231,9 +232,20 @@ export async function update_folder_elements_recursively(
|
|||||||
const new_folder_elements = await get_file_data(display.ip, file_path);
|
const new_folder_elements = await get_file_data(display.ip, file_path);
|
||||||
if (!new_folder_elements) return;
|
if (!new_folder_elements) return;
|
||||||
|
|
||||||
|
const loading_file_ids = Object.keys(get(file_transfer_tasks));
|
||||||
|
const loading_file_keys_without_size = (
|
||||||
|
await db.files.bulkGet(
|
||||||
|
loading_file_ids.map((string_id) => JSON.parse(string_id) as [string, string, number, string])
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.filter((f) => !!f)
|
||||||
|
.map((f) => JSON.stringify([f.path, f.name, f.type]));
|
||||||
|
|
||||||
|
// Filter new files, which aren't currently uploading
|
||||||
const existing_files_on_display_in_path: FileOnDisplay[] = await db.files_on_display
|
const existing_files_on_display_in_path: FileOnDisplay[] = await db.files_on_display
|
||||||
.where('display_id')
|
.where('display_id')
|
||||||
.equals(display.id)
|
.equals(display.id)
|
||||||
|
.filter((f) => !loading_file_ids.includes(f.file_primary_key))
|
||||||
.toArray();
|
.toArray();
|
||||||
const existing_file_keys_on_display_in_path: [string, string, number, string][] =
|
const existing_file_keys_on_display_in_path: [string, string, number, string][] =
|
||||||
existing_files_on_display_in_path.map(
|
existing_files_on_display_in_path.map(
|
||||||
@@ -245,17 +257,14 @@ export async function update_folder_elements_recursively(
|
|||||||
.filter((e) => e.path === file_path)
|
.filter((e) => e.path === file_path)
|
||||||
.toArray();
|
.toArray();
|
||||||
|
|
||||||
const existing_files_with_loading_state: { folder_element: Inode; is_loading: boolean }[] =
|
const diff = get_folder_elements_difference(existing_files, new_folder_elements);
|
||||||
existing_files.map((folder_element) => ({
|
|
||||||
folder_element,
|
|
||||||
is_loading: !!existing_files_on_display_in_path.find(
|
|
||||||
(e) => e.file_primary_key === get_file_primary_key(folder_element)
|
|
||||||
)?.loading_data
|
|
||||||
}));
|
|
||||||
|
|
||||||
const diff = get_folder_elements_difference(
|
// Filter new files, which aren't currently uploading -> don't compare size
|
||||||
existing_files_with_loading_state,
|
diff.new = diff.new.filter(
|
||||||
new_folder_elements
|
(e) =>
|
||||||
|
!loading_file_keys_without_size.includes(
|
||||||
|
JSON.stringify([e.folder_element.path, e.folder_element.name, e.folder_element.type])
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if (diff.new.length > 0) {
|
if (diff.new.length > 0) {
|
||||||
@@ -265,7 +274,6 @@ 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),
|
||||||
loading_data: null,
|
|
||||||
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);
|
||||||
@@ -295,15 +303,13 @@ export async function update_folder_elements_recursively(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function get_folder_elements_difference(
|
function get_folder_elements_difference(
|
||||||
old_elements: { folder_element: Inode; is_loading: boolean }[],
|
old_elements: Inode[],
|
||||||
new_elements: { folder_element: Inode; date_created: Date }[]
|
new_elements: { folder_element: Inode; date_created: Date }[]
|
||||||
): { deleted: Inode[]; new: { folder_element: Inode; date_created: Date }[] } {
|
): { deleted: Inode[]; new: { folder_element: Inode; date_created: Date }[] } {
|
||||||
const old_keys = new Set(old_elements.map((e) => get_file_primary_key(e.folder_element)));
|
const old_keys = new Set(old_elements.map((e) => get_file_primary_key(e)));
|
||||||
const new_keys = new Set(new_elements.map((e) => get_file_primary_key(e.folder_element)));
|
const new_keys = new Set(new_elements.map((e) => get_file_primary_key(e.folder_element)));
|
||||||
|
|
||||||
const only_in_old = old_elements
|
const only_in_old = old_elements.filter((e) => !new_keys.has(get_file_primary_key(e)));
|
||||||
.filter((e) => !new_keys.has(get_file_primary_key(e.folder_element)) && !e.is_loading)
|
|
||||||
.map((e) => e.folder_element);
|
|
||||||
const only_in_new = new_elements.filter(
|
const only_in_new = new_elements.filter(
|
||||||
(e) => !old_keys.has(get_file_primary_key(e.folder_element))
|
(e) => !old_keys.has(get_file_primary_key(e.folder_element))
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export type FileTransferTask = {
|
|||||||
display: ShortDisplay;
|
display: ShortDisplay;
|
||||||
path: string;
|
path: string;
|
||||||
file_name: string;
|
file_name: string;
|
||||||
file_primary_key: string;
|
loading_data: FileLoadingData;
|
||||||
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)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -44,9 +44,18 @@ export type FileTransferTaskData =
|
|||||||
}
|
}
|
||||||
| {
|
| {
|
||||||
type: 'sync';
|
type: 'sync';
|
||||||
destination_displays: ShortDisplay[];
|
destination_display_data: {
|
||||||
|
display: ShortDisplay;
|
||||||
|
loading_data: FileLoadingData
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type FileLoadingData = {
|
||||||
|
percentage: number;
|
||||||
|
bytes_per_second: number;
|
||||||
|
seconds_until_finish: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ShortDisplay = {
|
export type ShortDisplay = {
|
||||||
id: string;
|
id: string;
|
||||||
ip: string;
|
ip: string;
|
||||||
@@ -56,21 +65,6 @@ 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;
|
||||||
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 CompleteFileLoadingData = {
|
|
||||||
is_loading: boolean;
|
|
||||||
total_percentage: number;
|
|
||||||
total_seconds_until_finish: number;
|
|
||||||
display_data: { loading_data: FileLoadingData; display_name: string }[];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Inode = {
|
export type Inode = {
|
||||||
|
|||||||
Reference in New Issue
Block a user