Compare commits

..

2 Commits

Author SHA1 Message Date
Administrator
a7ac8c85f3 release: v3.3.37 2026-06-04 22:08:55 +02:00
Administrator
ca35c2a6a4 fix(log): persist BARE log path (no compounded daily/session stamps)
User report: in session mode the upload-log lines split across two files — the
first few before the auto-persist fallback fired, the rest after into a path
with the session-stamp DOUBLED.

Root cause in main.js _persistFallbackLogPath:
1. The strip was gated on the legacy `sessionLog` boolean, which 3.3.35 retired
   in favour of `logMode`. So in session/daily mode the gate was false and the
   resolved path got persisted with its stamp intact.
2. Even when the gate triggered, its regex matched only the daily YYYY-MM-DD
   suffix, not the session "session-YYYY-MM-DD_HH-MM-SS-pid" suffix.

The next getLogFilePath() call read that saved path as the "base", treated the
already-stamped filename as the base name, and re-applied another stamp on top.
First flush hit the original session file; everything after hit a doubly-
stamped one — exactly the symptom (top file: 2 lines, bottom file: the rest).

- lib/log-mode.js: new pure stripModeStampFromFileName helper that removes both
  the daily and the session suffix patterns. Anchored to $, no nested
  quantifiers (linear).
- main.js: gate on logMode (not sessionLog) and call the helper for daily AND
  session, so logFilePath always persists as a bare base.
- Tests: 4 new — strip behaviour + an idempotence regression that locks in
  "resolve → strip → resolve = same path" so this can't silently come back. 204/200.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-04 22:08:15 +02:00
4 changed files with 78 additions and 10 deletions

View File

@ -75,7 +75,29 @@
return `${base}${ext}`;
}
const api = { normalizeLogMode, resolveLogFileName, formatDateStamp, formatSessionStamp, VALID_MODES };
/**
* Reverse of resolveLogFileName: given a full filename like
* "fileuploader-2026-06-03.log" or
* "fileuploader-session-2026-06-03_18-16-20-8132.log", strip the mode-stamp
* so the bare base ("fileuploader.log") remains. Used when persisting an
* auto-resolved fallback path back into config otherwise the saved path
* would keep growing a new stamp on every reload.
*/
function stripModeStampFromFileName(fileName) {
if (!fileName || typeof fileName !== 'string') return fileName;
// Order matters: session first (longer, more specific) before daily.
// Both regexes are anchored to $ with no nested/ambiguous quantifiers, so
// matching is linear — the eslint security warning is precautionary.
// eslint-disable-next-line security/detect-unsafe-regex
const sessionRe = /-session-\d{4}-\d{2}-\d{2}_\d{2}-\d{2}-\d{2}(?:-\d+)?(\.[^.]+)?$/;
// eslint-disable-next-line security/detect-unsafe-regex
const dailyRe = /-\d{4}-\d{2}-\d{2}(\.[^.]+)?$/;
let out = fileName.replace(sessionRe, (m, ext) => ext || '');
out = out.replace(dailyRe, (m, ext) => ext || '');
return out;
}
const api = { normalizeLogMode, resolveLogFileName, formatDateStamp, formatSessionStamp, stripModeStampFromFileName, VALID_MODES };
if (typeof module !== 'undefined' && module.exports) {
module.exports = api;

18
main.js
View File

@ -245,7 +245,7 @@ function getBaseLogFilePath() {
// given session lands in the same file. A close→reopen of the app starts a new
// main process, so a new SESSION_ID, so a new session file. PID is appended as
// a cheap hedge against same-second restart collisions.
const { resolveLogFileName, formatSessionStamp, formatDateStamp } = require('./lib/log-mode');
const { resolveLogFileName, formatSessionStamp, formatDateStamp, stripModeStampFromFileName } = require('./lib/log-mode');
const SESSION_ID = formatSessionStamp(new Date(), process.pid);
let _activeLogKey = null; // remembers (mode + date-or-session) so cache rolls correctly
let _activeLogPath = null;
@ -398,16 +398,18 @@ function _persistFallbackLogPath(workingPath) {
try {
const cfg = configStore.load();
const gs = cfg.globalSettings || {};
// If daily-log is on, workingPath has a date suffix (fileuploader-YYYY-MM-DD.log).
// Strip that before saving so the base path rolls forward to tomorrow's
// file correctly — otherwise the next day's getLogFilePath would append
// another date onto the already-dated base.
const mode = gs.logMode || 'single';
// Strip the mode-specific suffix so logFilePath stores the BARE base path.
// Otherwise daily would compound into "...-2026-06-03-2026-06-04.log" and
// session would compound a second session-stamp onto the first — which split
// a session's lines across two files (the first few before _persistFallback
// ran, the rest after, into the doubly-stamped path). gated on logMode (the
// legacy `sessionLog` field is no longer the source of truth).
let toSave = workingPath;
if (gs.sessionLog) {
if (mode === 'daily' || mode === 'session') {
const dir = path.dirname(workingPath);
const base = path.basename(workingPath);
const stripped = base.replace(/-\d{4}-\d{2}-\d{2}(\.[^.]+)$/, '$1');
toSave = path.join(dir, stripped);
toSave = path.join(dir, stripModeStampFromFileName(base));
}
if (gs.logFilePath === toSave) return;
gs.logFilePath = toSave;

View File

@ -1,6 +1,6 @@
{
"name": "multi-hoster-uploader",
"version": "3.3.36",
"version": "3.3.37",
"description": "Upload files to doodstream, voe, vidmoly, byse simultaneously",
"main": "main.js",
"scripts": {

View File

@ -78,6 +78,50 @@ test('resolveLogFileName: unknown mode is treated as single', () => {
);
});
// --- stripModeStampFromFileName ---
const { stripModeStampFromFileName } = require('../lib/log-mode');
test('stripModeStampFromFileName: leaves bare names alone', () => {
assert.equal(stripModeStampFromFileName('fileuploader.log'), 'fileuploader.log');
assert.equal(stripModeStampFromFileName('fileuploader'), 'fileuploader');
});
test('stripModeStampFromFileName: strips a daily YYYY-MM-DD suffix', () => {
assert.equal(stripModeStampFromFileName('fileuploader-2026-06-03.log'), 'fileuploader.log');
});
test('stripModeStampFromFileName: strips a session-stamp suffix (with and without pid)', () => {
assert.equal(
stripModeStampFromFileName('fileuploader-session-2026-06-03_18-16-20-8132.log'),
'fileuploader.log'
);
assert.equal(
stripModeStampFromFileName('fileuploader-session-2026-06-03_18-16-20.log'),
'fileuploader.log'
);
});
test('regression: resolveLogFileName(stripModeStampFromFileName(...)) is idempotent — persisting then re-resolving never compounds stamps', () => {
// This is the exact bug shape: persist the resolved path, then on next call
// re-resolve from the saved base — must produce the same file, not a doubled
// session-stamped one. The fix is the strip; this test guards against
// regressing _persistFallbackLogPath into the 3.3.35 bug.
const sessionId = '2026-06-03_18-16-20-8132';
const dailyDate = new Date(2026, 5, 3);
for (const mode of ['daily', 'session']) {
const date = mode === 'daily' ? dailyDate : new Date();
const initial = resolveLogFileName({ baseName: 'fileuploader', ext: '.log', mode, date, sessionId });
const stripped = stripModeStampFromFileName(initial);
// After strip, the base should be back to the bare name.
assert.equal(stripped, 'fileuploader.log', `${mode}: strip should produce bare base`);
// Re-resolving from the bare base gives the same final filename — no doubling.
const reBase = stripped.replace(/\.log$/, '');
const second = resolveLogFileName({ baseName: reBase, ext: '.log', mode, date, sessionId });
assert.equal(second, initial, `${mode}: round-trip must be idempotent`);
}
});
// --- format helpers ---
test('formatDateStamp: zero-pads month and day', () => {