diff --git a/routers/web/repo/preview.go b/routers/web/repo/preview.go index dd77f40f2b..60e2d8ed65 100644 --- a/routers/web/repo/preview.go +++ b/routers/web/repo/preview.go @@ -8,7 +8,6 @@ import ( "io" "net/http" "os" - "sync" "code.gitea.io/gitea/modules/git" "code.gitea.io/gitea/modules/log" @@ -18,19 +17,8 @@ import ( "code.gitea.io/gitea/services/context" ) -// previewCache tracks known blob SHAs on S3 to skip Stat calls. -// Key: S3 object path, Value: blob SHA that was uploaded. -var previewCache sync.Map - -// HTMLPreview serves an HTML file via S3 with stale-while-revalidate caching. -// -// Flow: -// 1. Resolve the git blob for the requested file path -// 2. Build the S3 object key: preview/{owner}/{repo}/{path} -// 3. Check in-memory cache for the blob SHA -// 4. HIT+fresh (SHA matches current blob) → redirect to S3 presigned URL -// 5. HIT+stale (SHA mismatch) → redirect to S3 immediately, revalidate async -// 6. MISS → upload synchronously, then redirect +// HTMLPreview serves an HTML file via S3. +// Always uploads the current blob (overwrite), then redirects to presigned S3 URL. func HTMLPreview(ctx *context.Context) { if !setting.HTMLPreview.Enabled { ctx.NotFound(fmt.Errorf("HTML preview is not enabled")) @@ -43,7 +31,6 @@ func HTMLPreview(ctx *context.Context) { return } - // Get the blob entry, err := ctx.Repo.Commit.GetTreeEntryByPath(treePath) if err != nil { if git.IsErrNotExist(err) { @@ -59,34 +46,9 @@ func HTMLPreview(ctx *context.Context) { } blob := entry.Blob() - blobSHA := blob.ID.String() s3Key := buildPreviewS3Key(ctx) - // Check in-memory cache - if cachedSHA, ok := previewCache.Load(s3Key); ok { - if cachedSHA.(string) == blobSHA { - // Cache HIT + fresh — redirect directly - redirectToS3(ctx, s3Key, treePath) - return - } - // Cache HIT + stale — redirect to old version, revalidate in background - go revalidatePreview(s3Key, blob, blobSHA) - redirectToS3(ctx, s3Key, treePath) - return - } - - // Cache MISS — check if S3 has it (cold start) - _, err = storage.HTMLPreview.Stat(s3Key) - if err == nil { - // S3 has a version — serve it and revalidate in background - previewCache.Store(s3Key, "") // mark as known but unknown SHA - go revalidatePreview(s3Key, blob, blobSHA) - redirectToS3(ctx, s3Key, treePath) - return - } - - // No cached version — upload synchronously - if err := uploadPreview(s3Key, blob, blobSHA); err != nil { + if err := uploadPreview(s3Key, blob); err != nil { ctx.ServerError("uploadPreview", err) return } @@ -111,16 +73,16 @@ func HTMLPreviewByID(ctx *context.Context) { } blobSHA := blob.ID.String() - // For blob-by-SHA, use a content-addressed key (immutable) s3Key := fmt.Sprintf("blobs/%s", blobSHA) + // Content-addressed: same SHA = same content, skip if exists _, err = storage.HTMLPreview.Stat(s3Key) if err == nil { redirectToS3(ctx, s3Key, blobSHA+".html") return } - if err := uploadPreview(s3Key, blob, blobSHA); err != nil { + if err := uploadPreview(s3Key, blob); err != nil { ctx.ServerError("uploadPreview", err) return } @@ -140,7 +102,6 @@ func redirectToS3(ctx *context.Context, s3Key, name string) { ContentType: "text/html; charset=utf-8", }) if err != nil { - // Fallback: serve directly through Gitea servePreviewDirect(ctx, s3Key) return } @@ -171,7 +132,7 @@ func servePreviewDirect(ctx *context.Context, s3Key string) { _, _ = io.Copy(ctx.Resp, obj) } -func uploadPreview(s3Key string, blob *git.Blob, blobSHA string) error { +func uploadPreview(s3Key string, blob *git.Blob) error { dataRc, err := blob.DataAsync() if err != nil { return fmt.Errorf("blob DataAsync: %w", err) @@ -183,15 +144,8 @@ func uploadPreview(s3Key string, blob *git.Blob, blobSHA string) error { return fmt.Errorf("S3 save: %w", err) } - previewCache.Store(s3Key, blobSHA) - log.Debug("HTMLPreview: uploaded %s (blob %s)", s3Key, blobSHA[:8]) + log.Debug("HTMLPreview: uploaded %s (%d bytes)", s3Key, blob.Size()) return nil } -func revalidatePreview(s3Key string, blob *git.Blob, blobSHA string) { - if err := uploadPreview(s3Key, blob, blobSHA); err != nil { - log.Error("HTMLPreview revalidate failed for %s: %v", s3Key, err) - } -} - // isHTMLTreePath is defined in view_file.go