chore(control + display): improve api urls, paths and file_names

This commit is contained in:
E44
2026-01-04 23:22:59 +01:00
parent a2c1f385c1
commit 44290f46fe
4 changed files with 68 additions and 18 deletions
+3 -2
View File
@@ -8,6 +8,7 @@ import {
type TreeElement
} from './types';
import { dev } from '$app/environment';
import { get_sanitized_file_url } from './utils';
export async function get_screenshot(ip: string): Promise<Blob | null> {
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> {
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> {
@@ -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> {
const raw_response = await request_display(
ip,
`/file/preview${path_to_file}`,
get_sanitized_file_url(path_to_file, true),
{ method: 'GET' },
[415]
);
@@ -1,7 +1,11 @@
import { dev } from '$app/environment';
import { db } from './files_display.db';
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 { generate_thumbnail } from './stores/thumbnails';
import {
@@ -10,7 +14,7 @@ import {
type FileTransferTask,
type Inode
} 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;
const tasks: FileTransferTask[] = [];
@@ -22,8 +26,14 @@ export async function add_upload(
) {
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) {
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 = {
path: current_file_path,
name: file_name,
@@ -67,6 +77,28 @@ export async function add_upload(
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() {
if (!is_processing) {
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> {
if (task.type !== 'upload' || !task.file) return;
@@ -108,7 +137,7 @@ async function upload(task: FileTransferTask): Promise<void> {
return new Promise<void>((resolve) => {
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.upload.onprogress = (e) => {
@@ -152,7 +181,7 @@ async function upload(task: FileTransferTask): Promise<void> {
if (!!inode_element && inode_element.thumbnail === null) {
await generate_thumbnail(task.display_ip, task.path, inode_element);
}
}, 0);
}, 10);
});
}
+16 -5
View File
@@ -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_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 {
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 {
const s = input.normalize("NFC");
const s = input.normalize('NFC');
if (s.length === 0) return 'Datei';
let out = s.replace(invalid_rest_regex, "_");
out = out.replace(invalid_first_regex, "_");
let out = s.replace(invalid_rest_regex, '_');
out = out.replace(invalid_first_regex, '_');
return out;
}
@@ -44,7 +44,9 @@ export function get_file_type(file: Inode): SupportedFileType | null {
}
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 {
@@ -102,3 +104,12 @@ export function display_status_to_info(status: DisplayStatus): string {
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
View File
@@ -10,6 +10,7 @@ import (
"log/slog"
"net"
"net/http"
"net/url"
"os"
"os/exec"
"path/filepath"
@@ -146,12 +147,20 @@ func qrRoute(c echo.Context) error {
func extractFilePathMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
return func(ctx echo.Context) error {
pathParam := ctx.Param("path")
fullPath, exists, err := pkg.ResolveStorageFilePath(pathParam)
raw := ctx.Param("path")
decoded, err := url.PathUnescape(raw)
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"})
}
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("fileExists", exists)
return next(ctx)