Keep full folder scans recoverable under duplicate protection and classify every discovered file once before automatic admission. Separate manual previews from automatic capacity limits, serialize renderer intake, normalize automation counters and intervals, and make Main authoritative for runtime timestamps. Enforce persistent pause across startup, import, close recovery, resume failures, and reconciliation while preserving read-only paused scans and exactly one activation reconciliation. Restore failed asynchronous rotation chunks, complete automation localization and unlimited queue accessibility, and tighten hidden integration cleanup coverage.
166 lines
5.9 KiB
JavaScript
166 lines
5.9 KiB
JavaScript
const nodePath = require('path');
|
|
|
|
function createInternalLogPathResolver(options) {
|
|
const source = options && typeof options === 'object' ? options : {};
|
|
const fs = source.fs;
|
|
const path = source.path || nodePath;
|
|
const userDataPath = typeof source.userDataPath === 'string' ? source.userDataPath.trim() : '';
|
|
|
|
if (!fs || typeof fs.mkdirSync !== 'function' || !userDataPath) {
|
|
throw new TypeError('createInternalLogPathResolver requires fs and userDataPath');
|
|
}
|
|
|
|
const directories = [path.join(userDataPath, 'logs'), path.join(userDataPath, 'internal-logs')];
|
|
|
|
return function resolveInternalLogPath(fileName, excludedPaths = new Set()) {
|
|
if (typeof fileName !== 'string' || !fileName || path.basename(fileName) !== fileName) return null;
|
|
const excluded = excludedPaths instanceof Set
|
|
? excludedPaths
|
|
: new Set(Array.isArray(excludedPaths) ? excludedPaths : [excludedPaths]);
|
|
for (const directory of directories) {
|
|
const targetPath = path.join(directory, fileName);
|
|
if (excluded.has(targetPath)) continue;
|
|
try {
|
|
fs.mkdirSync(directory, { recursive: true });
|
|
return targetPath;
|
|
} catch {}
|
|
}
|
|
return null;
|
|
};
|
|
}
|
|
|
|
function createInternalLogWriter(options) {
|
|
const source = options && typeof options === 'object' ? options : {};
|
|
const fs = source.fs;
|
|
const path = source.path || nodePath;
|
|
const fileName = source.fileName;
|
|
const resolveInternalLogPath = source.resolveInternalLogPath;
|
|
const rotateLogFile = typeof source.rotateLogFile === 'function' ? source.rotateLogFile : () => {};
|
|
const reportError = typeof source.reportError === 'function' ? source.reportError : () => {};
|
|
const retryDelays = Array.isArray(source.retryDelays) && source.retryDelays.length > 0 ? source.retryDelays : [0, 100, 250];
|
|
const maxBytes = Number.isFinite(source.maxBytes) ? source.maxBytes : 10 * 1024 * 1024;
|
|
const maxBackups = Number.isFinite(source.maxBackups) ? source.maxBackups : 2;
|
|
let activePath = null;
|
|
|
|
if (!fs || !fs.promises || typeof fs.promises.appendFile !== 'function' || typeof fs.appendFileSync !== 'function' || typeof resolveInternalLogPath !== 'function' || typeof fileName !== 'string') {
|
|
throw new TypeError('createInternalLogWriter requires fs, fileName and resolveInternalLogPath');
|
|
}
|
|
|
|
async function append(line, label) {
|
|
const excludedPaths = new Set();
|
|
for (const delay of retryDelays) {
|
|
if (delay) await new Promise(resolve => setTimeout(resolve, delay));
|
|
const targetPath = resolveInternalLogPath(fileName, excludedPaths);
|
|
if (!targetPath) break;
|
|
excludedPaths.add(targetPath);
|
|
try {
|
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
rotateLogFile(targetPath, maxBytes, maxBackups);
|
|
await fs.promises.appendFile(targetPath, line, 'utf-8');
|
|
activePath = targetPath;
|
|
return true;
|
|
} catch (error) {
|
|
reportError(label, error);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function appendSync(line, label) {
|
|
const excludedPaths = new Set();
|
|
for (let attempt = 0; attempt < retryDelays.length; attempt++) {
|
|
const targetPath = resolveInternalLogPath(fileName, excludedPaths);
|
|
if (!targetPath) break;
|
|
excludedPaths.add(targetPath);
|
|
try {
|
|
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
rotateLogFile(targetPath, maxBytes, maxBackups);
|
|
fs.appendFileSync(targetPath, line, 'utf-8');
|
|
activePath = targetPath;
|
|
return true;
|
|
} catch (error) {
|
|
reportError(label, error);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function flushSync(buffer, label) {
|
|
if (!Array.isArray(buffer) || buffer.length === 0) return true;
|
|
if (!appendSync(buffer.join(''), label)) return false;
|
|
buffer.length = 0;
|
|
return true;
|
|
}
|
|
|
|
return {
|
|
append,
|
|
appendSync,
|
|
flushSync,
|
|
getActivePath: () => activePath,
|
|
getPath: () => activePath || resolveInternalLogPath(fileName)
|
|
};
|
|
}
|
|
|
|
function createUploadAuditWriter(options) {
|
|
return createInternalLogWriter({ ...options, fileName: 'upload-audit.log' });
|
|
}
|
|
|
|
function createBufferedInternalLogFlusher(options) {
|
|
const source = options && typeof options === 'object' ? options : {};
|
|
const buffer = source.buffer;
|
|
const writer = source.writer;
|
|
const schedule = typeof source.schedule === 'function' ? source.schedule : setImmediate;
|
|
const reportError = typeof source.reportError === 'function' ? source.reportError : () => {};
|
|
let writing = false;
|
|
|
|
if (!Array.isArray(buffer) || !writer || typeof writer.append !== 'function' || typeof writer.flushSync !== 'function') {
|
|
throw new TypeError('createBufferedInternalLogFlusher requires buffer and writer');
|
|
}
|
|
|
|
function restoreChunk(chunk) {
|
|
for (let end = chunk.length; end > 0;) {
|
|
const start = Math.max(0, end - 1024);
|
|
buffer.splice(0, 0, ...chunk.slice(start, end));
|
|
end = start;
|
|
}
|
|
}
|
|
|
|
async function flush(label) {
|
|
if (writing || buffer.length === 0) return null;
|
|
const chunk = buffer.splice(0);
|
|
writing = true;
|
|
let written = false;
|
|
try {
|
|
written = await writer.append(chunk.join(''), label);
|
|
} catch (error) {
|
|
reportError(label, error);
|
|
} finally {
|
|
writing = false;
|
|
}
|
|
if (!written) {
|
|
restoreChunk(chunk);
|
|
return false;
|
|
}
|
|
if (buffer.length > 0) {
|
|
try {
|
|
schedule(() => { void flush(label); });
|
|
} catch (error) {
|
|
reportError(label, error);
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
return {
|
|
flush,
|
|
flushSync: label => writer.flushSync(buffer, label),
|
|
isWriting: () => writing
|
|
};
|
|
}
|
|
|
|
function getLogOpenDirectory(targetPath, fallbackDirectory, pathApi = nodePath) {
|
|
return typeof targetPath === 'string' && targetPath ? pathApi.dirname(targetPath) : fallbackDirectory;
|
|
}
|
|
|
|
module.exports = { createInternalLogPathResolver, createInternalLogWriter, createUploadAuditWriter, createBufferedInternalLogFlusher, getLogOpenDirectory };
|