diff --git a/src/lib/client/cache/previews.ts b/src/lib/client/cache/previews.ts index d8ae889..736c273 100644 --- a/src/lib/client/cache/previews.ts +++ b/src/lib/client/cache/previews.ts @@ -8,7 +8,7 @@ import { documentPreviewFallbackUrl, documentPreviewPresignUrl } from '@/lib/cli const inMemoryPreviewUrlCache = new Map(); const inFlightPreviewPrime = new Map>(); -const PREVIEW_CACHE_SCHEMA_VERSION = 3; +const PREVIEW_CACHE_SCHEMA_VERSION = 4; function revokeIfBlobUrl(url: string | null | undefined): void { if (!url) return; diff --git a/src/lib/server/documents/previews-blobstore.ts b/src/lib/server/documents/previews-blobstore.ts index 01d9ecd..aa2d973 100644 --- a/src/lib/server/documents/previews-blobstore.ts +++ b/src/lib/server/documents/previews-blobstore.ts @@ -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; diff --git a/src/lib/server/documents/previews.ts b/src/lib/server/documents/previews.ts index 8f4414c..9e7000a 100644 --- a/src/lib/server/documents/previews.ts +++ b/src/lib/server/documents/previews.ts @@ -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 { - 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 {