mirror of
https://codeberg.org/PLG-Development/PLG-MuDiCS
synced 2026-07-06 00:47:09 +00:00
chore(control + display): improve api urls, paths and file_names
This commit is contained in:
@@ -8,6 +8,7 @@ import {
|
|||||||
type TreeElement
|
type TreeElement
|
||||||
} from './types';
|
} from './types';
|
||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
|
import { get_sanitized_file_url } from './utils';
|
||||||
|
|
||||||
export async function get_screenshot(ip: string): Promise<Blob | null> {
|
export async function get_screenshot(ip: string): Promise<Blob | null> {
|
||||||
const options = { method: 'PATCH' };
|
const options = { method: 'PATCH' };
|
||||||
@@ -18,7 +19,7 @@ export async function get_screenshot(ip: string): Promise<Blob | null> {
|
|||||||
|
|
||||||
export async function open_file(ip: string, path_to_file: string): Promise<void> {
|
export async function open_file(ip: string, path_to_file: string): Promise<void> {
|
||||||
const options = { method: 'PATCH', headers: { 'content-type': 'application/octet-stream' } };
|
const options = { method: 'PATCH', headers: { 'content-type': 'application/octet-stream' } };
|
||||||
await request_display(ip, `/file${path_to_file}`, options);
|
await request_display(ip, get_sanitized_file_url(path_to_file), options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function send_keyboard_input(ip: string, key: string): Promise<void> {
|
export async function send_keyboard_input(ip: string, key: string): Promise<void> {
|
||||||
@@ -165,7 +166,7 @@ export async function show_blackscreen(ip: string): Promise<void> {
|
|||||||
export async function get_thumbnail_blob(ip: string, path_to_file: string): Promise<Blob | null> {
|
export async function get_thumbnail_blob(ip: string, path_to_file: string): Promise<Blob | null> {
|
||||||
const raw_response = await request_display(
|
const raw_response = await request_display(
|
||||||
ip,
|
ip,
|
||||||
`/file/preview${path_to_file}`,
|
get_sanitized_file_url(path_to_file, true),
|
||||||
{ method: 'GET' },
|
{ method: 'GET' },
|
||||||
[415]
|
[415]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { dev } from '$app/environment';
|
import { dev } from '$app/environment';
|
||||||
import { db } from './files_display.db';
|
import { db } from './files_display.db';
|
||||||
import { get_display_by_id } from './stores/displays';
|
import { get_display_by_id } from './stores/displays';
|
||||||
import { remove_all_files_without_display, remove_file_from_display } from './stores/files';
|
import {
|
||||||
|
get_current_folder_elements,
|
||||||
|
remove_all_files_without_display,
|
||||||
|
remove_file_from_display
|
||||||
|
} from './stores/files';
|
||||||
import { notifications } from './stores/notification';
|
import { notifications } from './stores/notification';
|
||||||
import { generate_thumbnail } from './stores/thumbnails';
|
import { generate_thumbnail } from './stores/thumbnails';
|
||||||
import {
|
import {
|
||||||
@@ -10,7 +14,7 @@ import {
|
|||||||
type FileTransferTask,
|
type FileTransferTask,
|
||||||
type Inode
|
type Inode
|
||||||
} from './types';
|
} from './types';
|
||||||
import { get_uuid, make_valid_name } from './utils';
|
import { get_sanitized_file_url, get_uuid, make_valid_name } from './utils';
|
||||||
|
|
||||||
let is_processing: boolean = false;
|
let is_processing: boolean = false;
|
||||||
const tasks: FileTransferTask[] = [];
|
const tasks: FileTransferTask[] = [];
|
||||||
@@ -22,8 +26,14 @@ export async function add_upload(
|
|||||||
) {
|
) {
|
||||||
if (file_list.length === 0) return;
|
if (file_list.length === 0) return;
|
||||||
|
|
||||||
|
const used_file_names: string[] = await (
|
||||||
|
await get_current_folder_elements(current_file_path, selected_display_ids)
|
||||||
|
).map((e) => e.name);
|
||||||
|
|
||||||
for (const file of file_list) {
|
for (const file of file_list) {
|
||||||
const file_name = make_valid_name(file.name);
|
const file_name = generate_valid_file_name(file.name, used_file_names);
|
||||||
|
used_file_names.push(file_name);
|
||||||
|
|
||||||
const db_file: Inode = {
|
const db_file: Inode = {
|
||||||
path: current_file_path,
|
path: current_file_path,
|
||||||
name: file_name,
|
name: file_name,
|
||||||
@@ -67,6 +77,28 @@ export async function add_upload(
|
|||||||
await start_task_processing();
|
await start_task_processing();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function generate_valid_file_name(original_file_name: string, used_file_names: string[]): string {
|
||||||
|
const regex = /\s\((\d{1,3})\)$/;
|
||||||
|
|
||||||
|
let name: string = make_valid_name(original_file_name);
|
||||||
|
while (used_file_names.includes(name)) {
|
||||||
|
const last_dot = name.lastIndexOf('.');
|
||||||
|
let name_without_extension = last_dot > 0 ? name.slice(0, last_dot) : name;
|
||||||
|
const extension = last_dot > 0 ? name.slice(last_dot) : '';
|
||||||
|
if (!regex.test(name_without_extension)) {
|
||||||
|
name_without_extension += ' (1)';
|
||||||
|
} else {
|
||||||
|
const match = name_without_extension.match(regex);
|
||||||
|
const current_number: number = match ? Number(match[1]) : 0;
|
||||||
|
console.log("current", current_number)
|
||||||
|
name_without_extension = name_without_extension.replace(regex, ` (${current_number + 1})`);
|
||||||
|
}
|
||||||
|
name = name_without_extension + extension;
|
||||||
|
console.log(name)
|
||||||
|
}
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
async function start_task_processing() {
|
async function start_task_processing() {
|
||||||
if (!is_processing) {
|
if (!is_processing) {
|
||||||
is_processing = tasks.length !== 0;
|
is_processing = tasks.length !== 0;
|
||||||
@@ -94,9 +126,6 @@ async function start_task_loop() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function file_url(ip: string, path: string, file_name: string) {
|
|
||||||
return `http://${ip}:1323/api/file${path}${encodeURIComponent(file_name)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function upload(task: FileTransferTask): Promise<void> {
|
async function upload(task: FileTransferTask): Promise<void> {
|
||||||
if (task.type !== 'upload' || !task.file) return;
|
if (task.type !== 'upload' || !task.file) return;
|
||||||
@@ -108,7 +137,7 @@ async function upload(task: FileTransferTask): Promise<void> {
|
|||||||
|
|
||||||
return new Promise<void>((resolve) => {
|
return new Promise<void>((resolve) => {
|
||||||
const xhr = new XMLHttpRequest();
|
const xhr = new XMLHttpRequest();
|
||||||
xhr.open('POST', file_url(task.display_ip, task.path, task.file_name), true);
|
xhr.open('POST', `http://${task.display_ip}:1323/api${get_sanitized_file_url(task.path + task.file_name)}`, true);
|
||||||
xhr.setRequestHeader('content-type', 'application/octet-stream');
|
xhr.setRequestHeader('content-type', 'application/octet-stream');
|
||||||
|
|
||||||
xhr.upload.onprogress = (e) => {
|
xhr.upload.onprogress = (e) => {
|
||||||
@@ -152,7 +181,7 @@ async function upload(task: FileTransferTask): Promise<void> {
|
|||||||
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);
|
||||||
}
|
}
|
||||||
}, 0);
|
}, 10);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ const supported_file_types: Record<string, SupportedFileType> = supported_file_t
|
|||||||
>;
|
>;
|
||||||
|
|
||||||
const invalid_first_regex = /^[^a-zA-ZäöüßÄÖÜ0-9_]/u;
|
const invalid_first_regex = /^[^a-zA-ZäöüßÄÖÜ0-9_]/u;
|
||||||
const invalid_rest_regex = /[^a-zA-ZäöüßÄÖÜ0-9 _\-()$${}.,+$€!?%&=/]/gu;
|
const invalid_rest_regex = /[^a-zA-ZäöüßÄÖÜ0-9 _\-()$${}.,+$€!=/]/gu;
|
||||||
|
|
||||||
export function is_valid_name(input: string): boolean {
|
export function is_valid_name(input: string): boolean {
|
||||||
return !invalid_rest_regex.test(input) && first_letter_is_valid(input);
|
return !invalid_rest_regex.test(input) && first_letter_is_valid(input);
|
||||||
@@ -19,10 +19,10 @@ export function first_letter_is_valid(input: string): boolean {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function make_valid_name(input: string): string {
|
export function make_valid_name(input: string): string {
|
||||||
const s = input.normalize("NFC");
|
const s = input.normalize('NFC');
|
||||||
if (s.length === 0) return 'Datei';
|
if (s.length === 0) return 'Datei';
|
||||||
let out = s.replace(invalid_rest_regex, "_");
|
let out = s.replace(invalid_rest_regex, '_');
|
||||||
out = out.replace(invalid_first_regex, "_");
|
out = out.replace(invalid_first_regex, '_');
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
@@ -44,7 +44,9 @@ export function get_file_type(file: Inode): SupportedFileType | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function get_accepted_file_type_string(): string {
|
export function get_accepted_file_type_string(): string {
|
||||||
return Object.values(supported_file_types).map((e) => e.mime_type).join(',');
|
return Object.values(supported_file_types)
|
||||||
|
.map((e) => e.mime_type)
|
||||||
|
.join(',');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function get_uuid(): string {
|
export function get_uuid(): string {
|
||||||
@@ -102,3 +104,12 @@ export function display_status_to_info(status: DisplayStatus): string {
|
|||||||
return '???';
|
return '???';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function get_sanitized_file_url(file_path: string, is_preview = false) {
|
||||||
|
const pathSegments = file_path
|
||||||
|
.split('/')
|
||||||
|
.filter(Boolean)
|
||||||
|
.map((seg) => encodeURIComponent(seg));
|
||||||
|
|
||||||
|
return `/file/${is_preview ? 'preview/' : ''}${[...pathSegments].join('/')}`;
|
||||||
|
}
|
||||||
|
|||||||
+12
-3
@@ -10,6 +10,7 @@ import (
|
|||||||
"log/slog"
|
"log/slog"
|
||||||
"net"
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -146,12 +147,20 @@ func qrRoute(c echo.Context) error {
|
|||||||
|
|
||||||
func extractFilePathMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
func extractFilePathMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
|
||||||
return func(ctx echo.Context) error {
|
return func(ctx echo.Context) error {
|
||||||
pathParam := ctx.Param("path")
|
raw := ctx.Param("path")
|
||||||
fullPath, exists, err := pkg.ResolveStorageFilePath(pathParam)
|
|
||||||
|
decoded, err := url.PathUnescape(raw)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
slog.Warn("Failed to validate file path", "path", pathParam, "error", err)
|
slog.Warn("Invalid path encoding", "path", raw, "error", err)
|
||||||
return ctx.JSON(http.StatusBadRequest, shared.ErrorResponse{Description: "Invalid file path"})
|
return ctx.JSON(http.StatusBadRequest, shared.ErrorResponse{Description: "Invalid file path"})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fullPath, exists, err := pkg.ResolveStorageFilePath(decoded)
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("Failed to validate file path", "path", decoded, "error", err)
|
||||||
|
return ctx.JSON(http.StatusBadRequest, shared.ErrorResponse{Description: "Invalid file path"})
|
||||||
|
}
|
||||||
|
|
||||||
ctx.Set("fullPath", fullPath)
|
ctx.Set("fullPath", fullPath)
|
||||||
ctx.Set("fileExists", exists)
|
ctx.Set("fileExists", exists)
|
||||||
return next(ctx)
|
return next(ctx)
|
||||||
|
|||||||
Reference in New Issue
Block a user