()
| 525 | } |
| 526 | |
| 527 | async getStorageStatus() { |
| 528 | const { execFile } = require('child_process'); |
| 529 | const util = require('util'); |
| 530 | const execFilePromise = util.promisify(execFile); |
| 531 | |
| 532 | try { |
| 533 | const targetPath = process.env.DATA_PATH || '/usr/src/app/data'; |
| 534 | |
| 535 | if (!targetPath) { |
| 536 | logger.warn('No YouTube output directory configured, cannot check storage status'); |
| 537 | return null; |
| 538 | } |
| 539 | |
| 540 | // Use execFile with array arguments to prevent shell injection |
| 541 | // -B 1 forces output in bytes for accurate calculations |
| 542 | const { stdout } = await execFilePromise('df', ['-B', '1', targetPath]); |
| 543 | const lines = stdout.trim().split('\n'); |
| 544 | |
| 545 | if (lines.length < 2) { |
| 546 | throw new Error('Unexpected df output'); |
| 547 | } |
| 548 | |
| 549 | // Parse the second line which contains the actual data |
| 550 | const parts = lines[1].split(/\s+/); |
| 551 | const total = parseInt(parts[1]); |
| 552 | const used = parseInt(parts[2]); |
| 553 | const available = parseInt(parts[3]); |
| 554 | const percentUsed = Math.round((used / total) * 100); |
| 555 | |
| 556 | return { |
| 557 | total, |
| 558 | used, |
| 559 | available, |
| 560 | percentUsed, |
| 561 | percentFree: 100 - percentUsed, |
| 562 | // Human readable versions |
| 563 | totalGB: (total / (1024 ** 3)).toFixed(1), |
| 564 | usedGB: (used / (1024 ** 3)).toFixed(1), |
| 565 | availableGB: (available / (1024 ** 3)).toFixed(1) |
| 566 | }; |
| 567 | } catch (error) { |
| 568 | logger.error({ err: error }, 'Error getting storage status'); |
| 569 | return null; |
| 570 | } |
| 571 | } |
| 572 | |
| 573 | /** |
| 574 | * Migrate notification settings from legacy formats to current format. |
no outgoing calls
no test coverage detected