mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-13 10:49:41 +02:00
fix(uploads): list upload dirs bounded and async, and keep uploads inside the data dir collected
The hourly sweep called lstatSync and realpathSync on every live session's working directory, so a linked case on a mount that stopped answering blocked the event loop 30 s after boot and then every hour; the old sweep was fully async. uploadDirs() now probes the workspace with probePathKind() first and skips an unknown path without touching it, then uses fs.promises for the lstat and realpath. The sweep keeps the probe's stall cap; cleanupSession(), acting on one path at the user's request, passes pastCap and removes the directories with fs.rm. Refusing an upload dir that sits strictly inside the data dir protected nothing (it only ever holds uploads, and a link is already excluded by lstat) while the route kept writing there, so uploads under a workspace like the ~/.codeman/app that install.sh clones were never swept and never removed. Only the two cases that matter stay refused: the upload dir being the data dir, or containing it. Also: the ignore file's take-back after a failed write no longer replaces the error that caused it with its own, and the shared-dir test's header no longer names the fixed port it stopped using.
This commit is contained in:
@@ -10,7 +10,7 @@
|
||||
* closes sessions unattended, which turns that from an occasional loss into a
|
||||
* routine one.
|
||||
*
|
||||
* Port: 3188
|
||||
* Port: 0 (ephemeral, read from `server.boundPort`)
|
||||
*/
|
||||
import { existsSync, lstatSync, mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
@@ -18,6 +18,12 @@ import { join } from 'node:path';
|
||||
import { afterAll, beforeAll, describe, expect, it, vi } from 'vitest';
|
||||
import { WebServer } from '../src/web/server.js';
|
||||
import { pasteImageDirInUseByOtherSession } from '../src/web/paste-image-gc.js';
|
||||
import { probePathKind } from '../src/utils/bounded-path-probe.js';
|
||||
|
||||
vi.mock('../src/utils/bounded-path-probe.js', async (importOriginal) => {
|
||||
const real = await importOriginal<typeof import('../src/utils/bounded-path-probe.js')>();
|
||||
return { ...real, probePathKind: vi.fn(real.probePathKind) };
|
||||
});
|
||||
|
||||
describe('pasteImageDirInUseByOtherSession', () => {
|
||||
const none = new Set<string>();
|
||||
@@ -148,9 +154,14 @@ describe('deleting a session that shares its working directory', () => {
|
||||
expect(existsSync(join(uploadDir, 'paste-1.png'))).toBe(true);
|
||||
expect(existsSync(join(legacyDir, 'paste-0.png'))).toBe(true);
|
||||
|
||||
// The delete acts on one path at the user's request, so its probe goes past
|
||||
// the bulk stall cap (#516); the hourly sweep keeps the cap. Cleared first:
|
||||
// the create path probes the same workspace past the cap too.
|
||||
vi.mocked(probePathKind).mockClear();
|
||||
expect((await remove(second)).status).toBe(200);
|
||||
expect(existsSync(uploadDir)).toBe(false);
|
||||
expect(existsSync(legacyDir)).toBe(false);
|
||||
expect(probePathKind).toHaveBeenCalledWith(workingDir, { pastCap: true });
|
||||
});
|
||||
|
||||
it('does not follow a planted symlink at the upload dir into another case when the last session closes', async () => {
|
||||
|
||||
Reference in New Issue
Block a user