Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 81f4ce5e36 | |||
| eff831b142 |
@@ -15,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"warpbox.dev/backend/libs/helpers"
|
"warpbox.dev/backend/libs/helpers"
|
||||||
|
"warpbox.dev/backend/libs/jobs"
|
||||||
"warpbox.dev/backend/libs/services"
|
"warpbox.dev/backend/libs/services"
|
||||||
"warpbox.dev/backend/libs/web"
|
"warpbox.dev/backend/libs/web"
|
||||||
)
|
)
|
||||||
@@ -319,6 +320,17 @@ func (a *App) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
|||||||
|
|
||||||
object, err := a.uploadService.OpenThumbnailObject(r.Context(), box, file)
|
object, err := a.uploadService.OpenThumbnailObject(r.Context(), box, file)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
if thumbnail := a.generateMissingThumbnailForRequest(r, box, file); thumbnail != "" {
|
||||||
|
file.Thumbnail = thumbnail
|
||||||
|
object, err = a.uploadService.OpenThumbnailObject(r.Context(), box, file)
|
||||||
|
if err == nil {
|
||||||
|
defer object.Body.Close()
|
||||||
|
w.Header().Set("Content-Type", "image/jpeg")
|
||||||
|
w.Header().Set("Cache-Control", "public, max-age=604800, immutable")
|
||||||
|
http.ServeContent(w, r, file.ID+"-thumbnail.jpg", object.ModTime, readSeekCloser(object.Body))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
// The thumbnail isn't generated yet (background job pending). Serve the
|
// The thumbnail isn't generated yet (background job pending). Serve the
|
||||||
// placeholder but mark it non-cacheable, otherwise the browser would
|
// placeholder but mark it non-cacheable, otherwise the browser would
|
||||||
// keep showing the placeholder until a hard refresh once the real
|
// keep showing the placeholder until a hard refresh once the real
|
||||||
@@ -333,6 +345,30 @@ func (a *App) Thumbnail(w http.ResponseWriter, r *http.Request) {
|
|||||||
http.ServeContent(w, r, file.ID+"-thumbnail.jpg", object.ModTime, readSeekCloser(object.Body))
|
http.ServeContent(w, r, file.ID+"-thumbnail.jpg", object.ModTime, readSeekCloser(object.Body))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *App) generateMissingThumbnailForRequest(r *http.Request, box services.Box, file services.File) string {
|
||||||
|
if file.Thumbnail != "" || !jobs.NeedsThumbnail(file) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
thumbnail, err := jobs.GenerateThumbnailForFile(a.uploadService, box, file)
|
||||||
|
if err != nil || thumbnail == "" {
|
||||||
|
if err != nil {
|
||||||
|
a.logger.Warn("on-demand thumbnail generation failed", withRequestLogAttrs(r, "source", "thumbnail", "severity", "warn", "code", 4102, "box_id", box.ID, "file_id", file.ID, "error", err.Error())...)
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
for i := range box.Files {
|
||||||
|
if box.Files[i].ID == file.ID {
|
||||||
|
box.Files[i].Thumbnail = thumbnail
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := a.uploadService.SaveBox(box); err != nil {
|
||||||
|
a.logger.Warn("on-demand thumbnail metadata save failed", withRequestLogAttrs(r, "source", "thumbnail", "severity", "warn", "code", 4103, "box_id", box.ID, "file_id", file.ID, "error", err.Error())...)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return thumbnail
|
||||||
|
}
|
||||||
|
|
||||||
// servePlaceholderThumbnail serves the fallback image with no-store so the
|
// servePlaceholderThumbnail serves the fallback image with no-store so the
|
||||||
// browser re-requests on the next load and picks up the real thumbnail as soon
|
// browser re-requests on the next load and picks up the real thumbnail as soon
|
||||||
// as it has been generated.
|
// as it has been generated.
|
||||||
@@ -477,7 +513,7 @@ func (a *App) fileViewWithReactions(box services.Box, file services.File, reacti
|
|||||||
URL: fmt.Sprintf("/d/%s/f/%s", box.ID, file.ID),
|
URL: fmt.Sprintf("/d/%s/f/%s", box.ID, file.ID),
|
||||||
DownloadURL: fmt.Sprintf("/d/%s/f/%s/download", box.ID, file.ID),
|
DownloadURL: fmt.Sprintf("/d/%s/f/%s/download", box.ID, file.ID),
|
||||||
ThumbnailURL: fmt.Sprintf("/d/%s/thumb/%s", box.ID, file.ID),
|
ThumbnailURL: fmt.Sprintf("/d/%s/thumb/%s", box.ID, file.ID),
|
||||||
HasThumbnail: file.Thumbnail != "",
|
HasThumbnail: file.Thumbnail != "" || jobs.NeedsThumbnail(file),
|
||||||
IconURL: fileIconURL("standard", icon.Standard),
|
IconURL: fileIconURL("standard", icon.Standard),
|
||||||
IconRetroURL: fileIconURL("retro", icon.Retro),
|
IconRetroURL: fileIconURL("retro", icon.Retro),
|
||||||
ReactURL: fmt.Sprintf("/d/%s/f/%s/react", box.ID, file.ID),
|
ReactURL: fmt.Sprintf("/d/%s/f/%s/react", box.ID, file.ID),
|
||||||
|
|||||||
@@ -121,9 +121,12 @@ func TestSocialPreviewBotGetsCardForSingleNonMediaBox(t *testing.T) {
|
|||||||
t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
|
t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
|
||||||
}
|
}
|
||||||
body := response.Body.String()
|
body := response.Body.String()
|
||||||
if !strings.Contains(body, `property="og:image" content="http://example.test/d/`+payload.BoxID+`/f/`+payload.Files[0].ID+`/og-image.jpg"`) {
|
if !strings.Contains(body, `/d/`+payload.BoxID+`/f/`+payload.Files[0].ID+`/og-image.jpg"`) {
|
||||||
t.Fatalf("social preview bot did not receive file card metadata: %s", body)
|
t.Fatalf("social preview bot did not receive file card metadata: %s", body)
|
||||||
}
|
}
|
||||||
|
if !strings.Contains(body, `class="file-thumb" src="/d/`+payload.BoxID+`/thumb/`+payload.Files[0].ID+`"`) {
|
||||||
|
t.Fatalf("download page did not render text thumbnail image: %s", body)
|
||||||
|
}
|
||||||
if !strings.Contains(body, "Click to preview or download") && !strings.Contains(body, "click to preview or download") {
|
if !strings.Contains(body, "Click to preview or download") && !strings.Contains(body, "click to preview or download") {
|
||||||
t.Fatalf("social preview body missing preview/download description: %s", body)
|
t.Fatalf("social preview body missing preview/download description: %s", body)
|
||||||
}
|
}
|
||||||
@@ -145,7 +148,7 @@ func TestSocialPreviewBotGetsCardForNonMediaFilePreview(t *testing.T) {
|
|||||||
t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
|
t.Fatalf("status = %d, body = %s", response.Code, response.Body.String())
|
||||||
}
|
}
|
||||||
body := response.Body.String()
|
body := response.Body.String()
|
||||||
if !strings.Contains(body, `property="og:image" content="http://example.test/d/`+payload.BoxID+`/f/`+payload.Files[0].ID+`/og-image.jpg"`) {
|
if !strings.Contains(body, `/d/`+payload.BoxID+`/f/`+payload.Files[0].ID+`/og-image.jpg"`) {
|
||||||
t.Fatalf("social preview bot did not receive file card metadata: %s", body)
|
t.Fatalf("social preview bot did not receive file card metadata: %s", body)
|
||||||
}
|
}
|
||||||
if !strings.Contains(body, `name="twitter:card" content="summary_large_image"`) {
|
if !strings.Contains(body, `name="twitter:card" content="summary_large_image"`) {
|
||||||
|
|||||||
@@ -141,6 +141,14 @@ func needsThumbnail(file services.File) bool {
|
|||||||
return file.PreviewKind == "image" || file.PreviewKind == "video" || isTextThumbnailCandidate(file)
|
return file.PreviewKind == "image" || file.PreviewKind == "video" || isTextThumbnailCandidate(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NeedsThumbnail(file services.File) bool {
|
||||||
|
return needsThumbnail(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GenerateThumbnailForFile(uploadService *services.UploadService, box services.Box, file services.File) (string, error) {
|
||||||
|
return generateThumbnail(uploadService, box, file)
|
||||||
|
}
|
||||||
|
|
||||||
func generateThumbnail(uploadService *services.UploadService, box services.Box, file services.File) (string, error) {
|
func generateThumbnail(uploadService *services.UploadService, box services.Box, file services.File) (string, error) {
|
||||||
thumbnailName := "@thumb@" + file.ID + ".jpg"
|
thumbnailName := "@thumb@" + file.ID + ".jpg"
|
||||||
object, err := uploadService.OpenFileObject(context.Background(), box, file)
|
object, err := uploadService.OpenFileObject(context.Background(), box, file)
|
||||||
|
|||||||
Reference in New Issue
Block a user