mirror of
https://github.com/Termix-SSH/Termix.git
synced 2026-07-16 21:33:41 +00:00
8b6037b7ff
- ssh/ renamed to hosts/ (it covers SSH, RDP, VNC, Telnet, Docker, metrics) - serial/serial.ts and guacamole/ moved inside hosts/ - dashboard.ts and homepage.ts moved to services/ - swagger.ts moved to utils/ with adjusted scan globs Import paths and the generate:openapi script updated; ports and endpoints unchanged.
194 lines
4.7 KiB
TypeScript
194 lines
4.7 KiB
TypeScript
import type { TransferPlatform } from "./transfer-paths.js";
|
||
|
||
export type TransferMethodPreference = "auto" | "tar" | "item_sftp";
|
||
|
||
export interface TransferScanSummary {
|
||
fileCount: number;
|
||
totalBytes: number;
|
||
largestFileBytes: number;
|
||
/** Share of total bytes in likely incompressible file types (0–1). */
|
||
incompressibleRatio: number;
|
||
}
|
||
|
||
const INCOMPRESSIBLE_EXT =
|
||
/\.(zip|gz|bz2|xz|7z|rar|tar|tgz|jpg|jpeg|png|gif|webp|mp3|mp4|mkv|avi|mov|wmv|iso|dmg|deb|rpm|pdf|db|sqlite|wasm|vmdk|qcow2)$/i;
|
||
|
||
const GB = 1024 * 1024 * 1024;
|
||
const MB = 1024 * 1024;
|
||
|
||
export function isLikelyIncompressiblePath(path: string): boolean {
|
||
return INCOMPRESSIBLE_EXT.test(path);
|
||
}
|
||
|
||
export function buildTransferScanSummary(
|
||
items: Array<{ sourcePath: string; size: number }>,
|
||
): TransferScanSummary {
|
||
let totalBytes = 0;
|
||
let largestFileBytes = 0;
|
||
let incompressibleBytes = 0;
|
||
|
||
for (const item of items) {
|
||
totalBytes += item.size;
|
||
largestFileBytes = Math.max(largestFileBytes, item.size);
|
||
if (isLikelyIncompressiblePath(item.sourcePath)) {
|
||
incompressibleBytes += item.size;
|
||
}
|
||
}
|
||
|
||
return {
|
||
fileCount: items.length,
|
||
totalBytes,
|
||
largestFileBytes,
|
||
incompressibleRatio: totalBytes > 0 ? incompressibleBytes / totalBytes : 0,
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Choose tar vs per-item SFTP for directory / multi-file transfers.
|
||
* Single-file stream transfers bypass this entirely.
|
||
*/
|
||
export function resolveArchiveTransferMethod(
|
||
preference: TransferMethodPreference,
|
||
summary: TransferScanSummary,
|
||
sourcePlatform: TransferPlatform,
|
||
destPlatform: TransferPlatform,
|
||
sourceHasTar: boolean,
|
||
destHasTar: boolean,
|
||
): "tar" | "item_sftp" {
|
||
if (sourcePlatform === "windows" || destPlatform === "windows") {
|
||
return "item_sftp";
|
||
}
|
||
|
||
if (preference === "item_sftp") {
|
||
return "item_sftp";
|
||
}
|
||
|
||
if (preference === "tar") {
|
||
return sourceHasTar && destHasTar ? "tar" : "item_sftp";
|
||
}
|
||
|
||
// auto
|
||
if (!sourceHasTar || !destHasTar) {
|
||
return "item_sftp";
|
||
}
|
||
|
||
const { fileCount, totalBytes, largestFileBytes, incompressibleRatio } =
|
||
summary;
|
||
|
||
if (fileCount === 0) {
|
||
return "item_sftp";
|
||
}
|
||
|
||
if (fileCount === 1 && largestFileBytes >= 2 * GB) {
|
||
return "item_sftp";
|
||
}
|
||
|
||
// Multi-item sets with a large file: tar only when data is likely compressible
|
||
// (mostly video/zip → per-file SFTP is simpler and avoids pack/unpack time).
|
||
if (
|
||
fileCount > 1 &&
|
||
largestFileBytes >= 2 * GB &&
|
||
totalBytes >= 500 * MB &&
|
||
incompressibleRatio < 0.7
|
||
) {
|
||
return "tar";
|
||
}
|
||
|
||
if (fileCount === 1 && largestFileBytes >= 5 * GB) {
|
||
return "item_sftp";
|
||
}
|
||
|
||
if (totalBytes >= 10 * GB && incompressibleRatio >= 0.5) {
|
||
return "item_sftp";
|
||
}
|
||
|
||
if (incompressibleRatio >= 0.85) {
|
||
return "item_sftp";
|
||
}
|
||
|
||
if (fileCount >= 100) {
|
||
return "tar";
|
||
}
|
||
|
||
if (fileCount >= 20 && totalBytes >= 50 * MB && incompressibleRatio < 0.5) {
|
||
return "tar";
|
||
}
|
||
|
||
if (fileCount > 5 && incompressibleRatio < 0.7) {
|
||
return "tar";
|
||
}
|
||
|
||
return "item_sftp";
|
||
}
|
||
|
||
export type ArchiveTransferReasonKey =
|
||
| "user_item_sftp"
|
||
| "user_tar"
|
||
| "tar_unavailable"
|
||
| "windows_host"
|
||
| "auto_multi_large"
|
||
| "auto_single_large_in_archive"
|
||
| "auto_many_incompressible"
|
||
| "auto_many_files"
|
||
| "auto_default";
|
||
|
||
/** i18n key suffix under transfer.methodReason.* */
|
||
export function getArchiveTransferReasonKey(
|
||
preference: TransferMethodPreference,
|
||
resolvedMethod: "tar" | "item_sftp",
|
||
summary: TransferScanSummary,
|
||
sourcePlatform: TransferPlatform,
|
||
destPlatform: TransferPlatform,
|
||
sourceHasTar: boolean,
|
||
destHasTar: boolean,
|
||
): ArchiveTransferReasonKey {
|
||
if (preference === "item_sftp") return "user_item_sftp";
|
||
if (preference === "tar") {
|
||
return sourceHasTar && destHasTar ? "user_tar" : "tar_unavailable";
|
||
}
|
||
|
||
if (sourcePlatform === "windows" || destPlatform === "windows") {
|
||
return "windows_host";
|
||
}
|
||
if (!sourceHasTar || !destHasTar) {
|
||
return "tar_unavailable";
|
||
}
|
||
|
||
const { fileCount, totalBytes, largestFileBytes, incompressibleRatio } =
|
||
summary;
|
||
|
||
if (
|
||
fileCount > 1 &&
|
||
largestFileBytes >= 2 * GB &&
|
||
totalBytes >= 500 * MB &&
|
||
incompressibleRatio < 0.7
|
||
) {
|
||
return "auto_multi_large";
|
||
}
|
||
|
||
if (fileCount === 1 && largestFileBytes >= 2 * GB) {
|
||
return "auto_single_large_in_archive";
|
||
}
|
||
|
||
if (totalBytes >= 10 * GB && incompressibleRatio >= 0.5) {
|
||
return "auto_many_incompressible";
|
||
}
|
||
if (incompressibleRatio >= 0.85) {
|
||
return "auto_many_incompressible";
|
||
}
|
||
|
||
if (
|
||
fileCount >= 100 ||
|
||
(fileCount >= 20 && totalBytes >= 50 * MB && incompressibleRatio < 0.5) ||
|
||
(fileCount > 5 && incompressibleRatio < 0.7)
|
||
) {
|
||
return "auto_many_files";
|
||
}
|
||
|
||
if (resolvedMethod === "tar") {
|
||
return "auto_many_files";
|
||
}
|
||
|
||
return "auto_default";
|
||
}
|