promises, link

This commit is contained in:
2026-09-13 15:40:54 +05:30
parent 13c24e9db3
commit d8b42ece79
6 changed files with 315 additions and 110 deletions
+72 -27
View File
@@ -1,33 +1,78 @@
import { rm } from "node:fs/promises";
type Job = {
dir: string;
zipPath: string;
createdAt: number;
};
import { mkdir, readdir, rm, stat } from "node:fs/promises";
import { tmpdir } from "node:os";
import path from "node:path";
const JOB_TTL_MS = 15 * 60 * 1000;
const jobs = new Map<string, Job>();
const JOBS_ROOT = path.join(tmpdir(), "kk-card-jobs");
const JOB_ID_RE =
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
export function isJobId(id: string): boolean {
return JOB_ID_RE.test(id);
}
export function jobDir(id: string): string {
return path.join(JOBS_ROOT, id);
}
export function jobZipPath(id: string): string {
return path.join(jobDir(id), "ids.zip");
}
export async function sweepExpiredJobs(): Promise<void> {
let names: string[] = [];
try {
names = await readdir(JOBS_ROOT);
} catch {
return;
}
function sweepExpiredJobs(): void {
const cutoff = Date.now() - JOB_TTL_MS;
for (const [id, job] of jobs) {
if (job.createdAt > cutoff) continue;
jobs.delete(id);
void rm(job.dir, { recursive: true, force: true });
await Promise.all(
names.map(async (name) => {
const dir = path.join(JOBS_ROOT, name);
try {
const info = await stat(dir);
if (info.mtimeMs >= cutoff) return;
await rm(dir, { recursive: true, force: true });
} catch {
// ignore missing or locked job folders
}
}),
);
}
export async function createJobDir(): Promise<{
id: string;
dir: string;
pngDir: string;
imagePath: string;
zipPath: string;
}> {
await sweepExpiredJobs();
const id = crypto.randomUUID();
const dir = jobDir(id);
const pngDir = path.join(dir, "png");
await mkdir(pngDir, { recursive: true });
return {
id,
dir,
pngDir,
imagePath: path.join(dir, "base"),
zipPath: jobZipPath(id),
};
}
export async function getJobZip(
id: string,
): Promise<{ zipPath: string; size: number } | null> {
if (!isJobId(id)) return null;
const zipPath = jobZipPath(id);
try {
const info = await stat(zipPath);
if (!info.isFile() || info.size === 0) return null;
return { zipPath, size: info.size };
} catch {
return null;
}
}
export function saveJob(dir: string, zipPath: string): string {
sweepExpiredJobs();
const id = crypto.randomUUID();
jobs.set(id, { dir, zipPath, createdAt: Date.now() });
return id;
}
export function takeJob(id: string): Job | null {
const job = jobs.get(id);
if (!job) return null;
jobs.delete(id);
return job;
}