refactor(previews): streamline preview generation and update image variant to 400px

Remove temporary working directory usage and in-place file writes from preview
generation, simplifying logic for both PDF and EPUB previews. Change default
preview image variant from 480px to 400px width, updating related constants and
cache schema version to maintain consistency across client and server code.
This commit is contained in:
Richard R 2026-05-18 12:37:26 -06:00
parent 8af4e857b7
commit 90e7caac43
3 changed files with 14 additions and 31 deletions

View file

@ -8,7 +8,7 @@ import { documentPreviewFallbackUrl, documentPreviewPresignUrl } from '@/lib/cli
const inMemoryPreviewUrlCache = new Map<string, string>();
const inFlightPreviewPrime = new Map<string, Promise<string | null>>();
const PREVIEW_CACHE_SCHEMA_VERSION = 3;
const PREVIEW_CACHE_SCHEMA_VERSION = 4;
function revokeIfBlobUrl(url: string | null | undefined): void {
if (!url) return;

View file

@ -10,10 +10,10 @@ import { getS3Client, getS3Config, getS3ProxyClient } from '@/lib/server/storage
const SAFE_NAMESPACE_REGEX = /^[a-zA-Z0-9._-]{1,128}$/;
const DEFAULT_NAMESPACE_SEGMENT = '_default';
export const DOCUMENT_PREVIEW_VARIANT = 'card-480-jpeg';
export const DOCUMENT_PREVIEW_FILE_NAME = 'card-480.jpg';
export const DOCUMENT_PREVIEW_VARIANT = 'card-400-jpeg';
export const DOCUMENT_PREVIEW_FILE_NAME = 'card-400.jpg';
export const DOCUMENT_PREVIEW_CONTENT_TYPE = 'image/jpeg';
export const DOCUMENT_PREVIEW_WIDTH = 480;
export const DOCUMENT_PREVIEW_WIDTH = 400;
function sanitizeNamespace(namespace: string | null): string | null {
if (!namespace) return null;

View file

@ -1,7 +1,4 @@
import { randomUUID } from 'crypto';
import { mkdtemp, rm, writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import { join } from 'path';
import { and, eq, inArray, lt, or, sql } from 'drizzle-orm';
import { db } from '@/db';
import { documentPreviews } from '@/db/schema';
@ -290,31 +287,17 @@ async function markPreviewFailed(docId: string, namespaceKey: string, error: unk
}
async function generateAndStorePreview(doc: PreviewSourceDocument, namespace: string | null): Promise<void> {
let workDir: string | null = null;
try {
const sourceBytes = await getDocumentBlob(doc.id, namespace);
workDir = await mkdtemp(join(tmpdir(), 'openreader-preview-'));
const sourcePath = join(workDir, 'source');
await writeFile(sourcePath, sourceBytes);
let rendered;
if (doc.type === 'pdf') {
rendered = await renderPdfFirstPageToJpeg(sourceBytes, DOCUMENT_PREVIEW_WIDTH);
} else if (doc.type === 'epub') {
rendered = await renderEpubCoverToJpeg(sourceBytes, DOCUMENT_PREVIEW_WIDTH);
} else {
throw new Error(`Unsupported preview type: ${doc.type}`);
}
// Replace-in-place semantics: clear old preview objects for this document
// prefix before writing the current variant so stale files don't linger.
await deleteDocumentPreviewArtifacts(doc.id, namespace).catch(() => undefined);
await putDocumentPreviewBuffer(doc.id, rendered.bytes, namespace);
} finally {
if (workDir) {
await rm(workDir, { recursive: true, force: true }).catch(() => {});
}
const sourceBytes = await getDocumentBlob(doc.id, namespace);
let rendered;
if (doc.type === 'pdf') {
rendered = await renderPdfFirstPageToJpeg(sourceBytes, DOCUMENT_PREVIEW_WIDTH);
} else if (doc.type === 'epub') {
rendered = await renderEpubCoverToJpeg(sourceBytes, DOCUMENT_PREVIEW_WIDTH);
} else {
throw new Error(`Unsupported preview type: ${doc.type}`);
}
// Hot path: overwrite current variant key directly to avoid prefix list/delete latency.
await putDocumentPreviewBuffer(doc.id, rendered.bytes, namespace);
}
function pendingResult(status: PreviewStatus, lastError: string | null): EnsureDocumentPreviewResult {