const fs = require('fs'); const path = require('path'); function ensureDirForFile(filePath) { const dir = path.dirname(filePath); if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } } function cloneDefault(defaultValue) { return JSON.parse(JSON.stringify(defaultValue)); } function fsyncPath(filePath) { let fd = null; try { fd = fs.openSync(filePath, 'r'); fs.fsyncSync(fd); } catch (error) { if (error.code !== 'EINVAL' && error.code !== 'EBADF') { throw error; } } finally { if (fd !== null) { fs.closeSync(fd); } } } function readJsonFile(filePath, defaultValue, validate = () => true) { ensureDirForFile(filePath); if (!fs.existsSync(filePath)) { writeJsonFile(filePath, defaultValue, { backup: false }); return cloneDefault(defaultValue); } try { const raw = fs.readFileSync(filePath, 'utf8'); const parsed = JSON.parse(raw); if (validate(parsed)) { return parsed; } throw new Error('JSON shape validation failed'); } catch (error) { const backupPath = `${filePath}.bak`; if (fs.existsSync(backupPath)) { try { const backup = JSON.parse(fs.readFileSync(backupPath, 'utf8')); if (validate(backup)) { console.warn(`[JSON-STORE] Nutze Backup fuer ${filePath}: ${error.message}`); return backup; } } catch (backupError) { console.warn(`[JSON-STORE] Backup fuer ${filePath} ist unbrauchbar: ${backupError.message}`); } } throw error; } } function writeJsonFile(filePath, payload, { backup = true } = {}) { ensureDirForFile(filePath); const dir = path.dirname(filePath); const tmpPath = path.join( dir, `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp` ); const serialized = JSON.stringify(payload, null, 2); fs.writeFileSync(tmpPath, serialized); fsyncPath(tmpPath); if (backup && fs.existsSync(filePath)) { fs.copyFileSync(filePath, `${filePath}.bak`); } fs.renameSync(tmpPath, filePath); try { fsyncPath(dir); } catch (error) { console.warn(`[JSON-STORE] Directory fsync fuer ${dir} fehlgeschlagen: ${error.message}`); } } module.exports = { readJsonFile, writeJsonFile };