mirror of
https://github.com/kyantech/Palmr.git
synced 2025-10-23 06:11:58 +00:00
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:
@@ -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)}`
|
||||||
);
|
);
|
||||||
|
Reference in New Issue
Block a user