refactor: enhance StorageService with filesystem detection and improved error handling

- Simplified number validation and parsing methods for better clarity and performance.
- Introduced new methods for retrieving mount information and detecting mount points, improving filesystem handling.
- Replaced direct disk space command execution with a unified filesystem information retrieval approach.
- Enhanced error logging and removed unnecessary console outputs to streamline the service's operation.
This commit is contained in:
Daniel Luiz Alves
2025-07-07 00:00:34 -03:00
parent c466bba77a
commit 82842508ef

View File

@@ -13,28 +13,22 @@ export class StorageService {
private configService = new ConfigService(); private configService = new ConfigService();
private _ensureNumber(value: number, fallback: number = 0): number { private _ensureNumber(value: number, fallback: number = 0): number {
if (isNaN(value) || !isFinite(value)) { return Number.isNaN(value) || !Number.isFinite(value) || value < 0 ? fallback : value;
return fallback;
}
return value;
} }
private _safeParseInt(value: string): number { private _safeParseInt(value: string): number {
const parsed = parseInt(value); const parsed = parseInt(value, 10);
return this._ensureNumber(parsed, 0); return Number.isNaN(parsed) ? 0 : parsed;
} }
private async _tryDiskSpaceCommand(command: string): Promise<{ total: number; available: number } | null> { private async _tryDiskSpaceCommand(command: string): Promise<{ total: number; available: number } | null> {
try { try {
console.log(`Trying disk space command: ${command}`);
const { stdout, stderr } = await execAsync(command); const { stdout, stderr } = await execAsync(command);
if (stderr) { if (stderr) {
console.warn(`Command stderr: ${stderr}`); console.warn(`Command stderr: ${stderr}`);
} }
console.log(`Command stdout: ${stdout}`);
let total = 0; let total = 0;
let available = 0; let available = 0;
@@ -76,7 +70,6 @@ export class StorageService {
} }
if (total > 0 && available >= 0) { if (total > 0 && available >= 0) {
console.log(`Successfully parsed disk space: ${total} bytes total, ${available} bytes available`);
return { total, available }; return { total, available };
} else { } else {
console.warn(`Invalid values parsed: total=${total}, available=${available}`); console.warn(`Invalid values parsed: total=${total}, available=${available}`);
@@ -88,19 +81,128 @@ export class StorageService {
} }
} }
/**
* Gets detailed mount information for debugging
*/
private async _getMountInfo(path: string): Promise<{ filesystem: string; mountPoint: string; type: string } | null> {
try {
if (!fs.existsSync("/proc/mounts")) {
return null;
}
const mountsContent = await fs.promises.readFile("/proc/mounts", "utf8");
const lines = mountsContent.split("\n").filter((line) => line.trim());
let bestMatch = null;
let bestMatchLength = 0;
for (const line of lines) {
const parts = line.split(/\s+/);
if (parts.length >= 3) {
const [filesystem, mountPoint, type] = parts;
if (path.startsWith(mountPoint) && mountPoint.length > bestMatchLength) {
bestMatch = { filesystem, mountPoint, type };
bestMatchLength = mountPoint.length;
}
}
}
return bestMatch;
} catch (error) {
console.warn(`Could not get mount info for ${path}:`, error);
return null;
}
}
/**
* Detects if a path is a bind mount or mount point by checking /proc/mounts
* Returns the actual filesystem path for bind mounts
*/
private async _detectMountPoint(path: string): Promise<string | null> {
try {
if (!fs.existsSync("/proc/mounts")) {
return null;
}
const mountsContent = await fs.promises.readFile("/proc/mounts", "utf8");
const lines = mountsContent.split("\n").filter((line) => line.trim());
let bestMatch = null;
let bestMatchLength = 0;
for (const line of lines) {
const parts = line.split(/\s+/);
if (parts.length >= 2) {
const [, mountPoint] = parts;
if (path.startsWith(mountPoint) && mountPoint.length > bestMatchLength) {
bestMatch = mountPoint;
bestMatchLength = mountPoint.length;
}
}
}
if (bestMatch && bestMatch !== "/") {
return bestMatch;
}
return null;
} catch (error) {
console.warn(`Could not detect mount point for ${path}:`, error);
return null;
}
}
/**
* Gets filesystem information for a specific path, with bind mount detection
*/
private async _getFileSystemInfo(
path: string
): Promise<{ total: number; available: number; mountPoint?: string } | null> {
try {
const mountInfo = await this._getMountInfo(path);
if (mountInfo && mountInfo.mountPoint !== "/") {
console.log(`📁 Bind mount detected: ${path}${mountInfo.filesystem} (${mountInfo.type})`);
}
const mountPoint = await this._detectMountPoint(path);
const targetPath = mountPoint || path;
const commandsToTry =
process.platform === "win32"
? ["wmic logicaldisk get size,freespace,caption"]
: process.platform === "darwin"
? [`df -k "${targetPath}"`, `df "${targetPath}"`]
: [`df -B1 "${targetPath}"`, `df -k "${targetPath}"`, `df "${targetPath}"`];
for (const command of commandsToTry) {
const result = await this._tryDiskSpaceCommand(command);
if (result) {
return {
...result,
mountPoint: mountPoint || undefined,
};
}
}
return null;
} catch (error) {
console.warn(`Error getting filesystem info for ${path}:`, error);
return null;
}
}
private async _getDiskSpaceMultiplePaths(): Promise<{ total: number; available: number } | null> { private async _getDiskSpaceMultiplePaths(): Promise<{ total: number; available: number } | null> {
const pathsToTry = IS_RUNNING_IN_CONTAINER const pathsToTry = IS_RUNNING_IN_CONTAINER
? ["/app/server/uploads", "/app/server", "/app", "/"] ? ["/app/server/uploads", "/app/server", "/app", "/"]
: [".", "./uploads", process.cwd()]; : [".", "./uploads", process.cwd()];
for (const pathToCheck of pathsToTry) { for (const pathToCheck of pathsToTry) {
console.log(`Trying path: ${pathToCheck}`);
if (pathToCheck.includes("uploads")) { if (pathToCheck.includes("uploads")) {
try { try {
if (!fs.existsSync(pathToCheck)) { if (!fs.existsSync(pathToCheck)) {
fs.mkdirSync(pathToCheck, { recursive: true }); fs.mkdirSync(pathToCheck, { recursive: true });
console.log(`Created directory: ${pathToCheck}`);
} }
} catch (err) { } catch (err) {
console.warn(`Could not create path ${pathToCheck}:`, err); console.warn(`Could not create path ${pathToCheck}:`, err);
@@ -109,23 +211,16 @@ export class StorageService {
} }
if (!fs.existsSync(pathToCheck)) { if (!fs.existsSync(pathToCheck)) {
console.warn(`Path does not exist: ${pathToCheck}`);
continue; continue;
} }
const commandsToTry = // Use the new filesystem detection method
process.platform === "win32" const result = await this._getFileSystemInfo(pathToCheck);
? ["wmic logicaldisk get size,freespace,caption"] if (result) {
: process.platform === "darwin" if (result.mountPoint) {
? [`df -k "${pathToCheck}"`, `df "${pathToCheck}"`] console.log(`✅ Storage resolved via bind mount: ${result.mountPoint}`);
: [`df -B1 "${pathToCheck}"`, `df -k "${pathToCheck}"`, `df "${pathToCheck}"`];
for (const command of commandsToTry) {
const result = await this._tryDiskSpaceCommand(command);
if (result) {
console.log(`✅ Successfully got disk space for path: ${pathToCheck}`);
return result;
} }
return { total: result.total, available: result.available };
} }
} }
@@ -143,17 +238,10 @@ export class StorageService {
}> { }> {
try { try {
if (isAdmin) { if (isAdmin) {
console.log(`Running in container: ${IS_RUNNING_IN_CONTAINER}`);
const diskInfo = await this._getDiskSpaceMultiplePaths(); const diskInfo = await this._getDiskSpaceMultiplePaths();
if (!diskInfo) { if (!diskInfo) {
console.error("❌ CRITICAL: Could not determine disk space using any method!"); console.error("❌ Could not determine disk space - system configuration issue");
console.error("This indicates a serious system issue. Please check:");
console.error("1. File system permissions");
console.error("2. Available disk utilities (df, wmic)");
console.error("3. Container/system configuration");
throw new Error("Unable to determine actual disk space - system configuration issue"); throw new Error("Unable to determine actual disk space - system configuration issue");
} }
@@ -164,10 +252,6 @@ export class StorageService {
const diskUsedGB = this._ensureNumber(used / (1024 * 1024 * 1024), 0); const diskUsedGB = this._ensureNumber(used / (1024 * 1024 * 1024), 0);
const diskAvailableGB = this._ensureNumber(available / (1024 * 1024 * 1024), 0); const diskAvailableGB = this._ensureNumber(available / (1024 * 1024 * 1024), 0);
console.log(
`✅ Real disk space: ${diskSizeGB.toFixed(2)}GB total, ${diskUsedGB.toFixed(2)}GB used, ${diskAvailableGB.toFixed(2)}GB available`
);
return { return {
diskSizeGB: Number(diskSizeGB.toFixed(2)), diskSizeGB: Number(diskSizeGB.toFixed(2)),
diskUsedGB: Number(diskUsedGB.toFixed(2)), diskUsedGB: Number(diskUsedGB.toFixed(2)),
@@ -199,7 +283,6 @@ export class StorageService {
throw new Error("User ID is required for non-admin users"); throw new Error("User ID is required for non-admin users");
} catch (error) { } catch (error) {
console.error("❌ Error getting disk space:", error); console.error("❌ Error getting disk space:", error);
throw new Error( throw new Error(
`Failed to get disk space information: ${error instanceof Error ? error.message : String(error)}` `Failed to get disk space information: ${error instanceof Error ? error.message : String(error)}`
); );