diff --git a/internal/http/response/file.go b/internal/http/response/file.go index d0704a8de..d5329ea4a 100644 --- a/internal/http/response/file.go +++ b/internal/http/response/file.go @@ -9,10 +9,12 @@ import ( "github.com/go-shiori/shiori/internal/model" ) -// SendFile sends file to client with caching header -func SendFile(c *gin.Context, storageDomain model.StorageDomain, path string) { - c.Header("Cache-Control", "public, max-age=86400") +type SendFileOptions struct { + Headers []http.Header +} +// SendFile sends file to client with caching header +func SendFile(c *gin.Context, storageDomain model.StorageDomain, path string, options *SendFileOptions) { if !storageDomain.FileExists(path) { c.AbortWithStatus(http.StatusNotFound) return @@ -24,8 +26,17 @@ func SendFile(c *gin.Context, storageDomain model.StorageDomain, path string) { return } + c.Header("Cache-Control", "public, max-age=86400") c.Header("ETag", fmt.Sprintf("W/%x-%x", info.ModTime().Unix(), info.Size())) + if options != nil { + for _, header := range options.Headers { + for key, value := range header { + c.Header(key, value[0]) + } + } + } + // TODO: Find a better way to send the file to the client from the FS, probably making a // conversion between afero.Fs and http.FileSystem to use c.FileFromFS. fileContent, err := storageDomain.FS().Open(path) diff --git a/internal/http/routes/bookmark.go b/internal/http/routes/bookmark.go index ae2cc0905..e0073dcdd 100644 --- a/internal/http/routes/bookmark.go +++ b/internal/http/routes/bookmark.go @@ -165,7 +165,23 @@ func (r *BookmarkRoutes) bookmarkThumbnailHandler(c *gin.Context) { return } - response.SendFile(c, r.deps.Domains.Storage, model.GetThumbnailPath(bookmark)) + etag := "w/" + model.GetThumbnailPath(bookmark) + "-" + bookmark.ModifiedAt + + // Check if the client's ETag matches the current ETag + if c.GetHeader("If-None-Match") == etag { + c.Status(http.StatusNotModified) + return + } + + options := &response.SendFileOptions{ + Headers: []http.Header{ + {"Cache-Control": {"no-cache , must-revalidate"}}, + {"Last-Modified": {bookmark.ModifiedAt}}, + {"ETag": {etag}}, + }, + } + + response.SendFile(c, r.deps.Domains.Storage, model.GetThumbnailPath(bookmark), options) } func (r *BookmarkRoutes) bookmarkEbookHandler(c *gin.Context) { @@ -185,5 +201,5 @@ func (r *BookmarkRoutes) bookmarkEbookHandler(c *gin.Context) { // TODO: Potentially improve this c.Header("Content-Disposition", `attachment; filename="`+bookmark.Title+`.epub"`) - response.SendFile(c, r.deps.Domains.Storage, model.GetEbookPath(bookmark)) + response.SendFile(c, r.deps.Domains.Storage, model.GetEbookPath(bookmark), nil) } diff --git a/internal/view/assets/js/component/bookmark.js b/internal/view/assets/js/component/bookmark.js index 9b4c6a36c..952ab129f 100644 --- a/internal/view/assets/js/component/bookmark.js +++ b/internal/view/assets/js/component/bookmark.js @@ -51,6 +51,7 @@ export default { hasContent: Boolean, hasArchive: Boolean, hasEbook: Boolean, + modifiedAt: String, index: Number, ShowId: Boolean, editMode: Boolean, @@ -95,7 +96,7 @@ export default { }, thumbnailStyleURL() { return { - backgroundImage: `url("${this.imageURL}")`, + backgroundImage: `url("${this.imageURL}?modifiedAt=${this.modifiedAt}")`, }; }, eventItem() { diff --git a/internal/view/assets/js/page/home.js b/internal/view/assets/js/page/home.js index 05721268b..9d703df81 100644 --- a/internal/view/assets/js/page/home.js +++ b/internal/view/assets/js/page/home.js @@ -48,6 +48,7 @@ var template = ` :excerpt="book.excerpt" :public="book.public" :imageURL="book.imageURL" + :modifiedAt="book.modifiedAt" :hasContent="book.hasContent" :hasArchive="book.hasArchive" :hasEbook="book.hasEbook"