diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue index 8f4bb8ba..5e945be7 100644 --- a/frontend/src/components/files/ListingItem.vue +++ b/frontend/src/components/files/ListingItem.vue @@ -78,7 +78,11 @@ export default { }, thumbnailUrl () { const path = this.url.replace(/^\/files\//, '') - return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true` + + // reload the image when the file is replaced + const key = Date.parse(this.modified) + + return `${baseURL}/api/preview/thumb/${path}?auth=${this.jwt}&inline=true&k=${key}` }, isThumbsEnabled () { return enableThumbs diff --git a/frontend/src/views/files/Preview.vue b/frontend/src/views/files/Preview.vue index 21cb8ccd..495f1244 100644 --- a/frontend/src/views/files/Preview.vue +++ b/frontend/src/views/files/Preview.vue @@ -102,10 +102,13 @@ export default { return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}` }, previewUrl () { + // reload the image when the file is replaced + const key = Date.parse(this.req.modified) + if (this.req.type === 'image' && !this.fullSize) { - return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}` + return `${baseURL}/api/preview/big${url.encodePath(this.req.path)}?auth=${this.jwt}&k=${key}` } - return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}` + return `${baseURL}/api/raw${url.encodePath(this.req.path)}?auth=${this.jwt}&k=${key}` }, raw () { return `${this.previewUrl}&inline=true` diff --git a/http/http.go b/http/http.go index 47073653..80c7e92c 100644 --- a/http/http.go +++ b/http/http.go @@ -54,7 +54,7 @@ func NewHandler( api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET") api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE") - api.PathPrefix("/resources").Handler(monkey(resourcePostHandler, "/api/resources")).Methods("POST") + api.PathPrefix("/resources").Handler(monkey(resourcePostHandler(fileCache), "/api/resources")).Methods("POST") api.PathPrefix("/resources").Handler(monkey(resourcePutHandler, "/api/resources")).Methods("PUT") api.PathPrefix("/resources").Handler(monkey(resourcePatchHandler, "/api/resources")).Methods("PATCH") diff --git a/http/preview.go b/http/preview.go index 0d956a51..562562ff 100644 --- a/http/preview.go +++ b/http/preview.go @@ -79,7 +79,7 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic return errToStatus(err), err } - cacheKey := previewCacheKey(file.Path, previewSize) + cacheKey := previewCacheKey(file.Path, file.ModTime.Unix(), previewSize) cachedFile, ok, err := fileCache.Load(r.Context(), cacheKey) if err != nil { return errToStatus(err), err @@ -133,6 +133,6 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic return 0, nil } -func previewCacheKey(fPath string, previewSize PreviewSize) string { - return fPath + previewSize.String() +func previewCacheKey(fPath string, fTime int64, previewSize PreviewSize) string { + return fmt.Sprintf("%x%x%x", fPath, fTime, previewSize) } diff --git a/http/resource.go b/http/resource.go index 16be5d89..84a5abf8 100644 --- a/http/resource.go +++ b/http/resource.go @@ -1,6 +1,7 @@ package http import ( + "context" "fmt" "io" "io/ioutil" @@ -71,11 +72,9 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc { } // delete thumbnails - for _, previewSizeName := range PreviewSizeNames() { - size, _ := ParsePreviewSize(previewSizeName) - if err := fileCache.Delete(r.Context(), previewCacheKey(file.Path, size)); err != nil { //nolint:govet - return errToStatus(err), err - } + err = delThumbs(r.Context(), fileCache, file) + if err != nil { + return errToStatus(err), err } err = d.RunHook(func() error { @@ -90,41 +89,56 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc { }) } -var resourcePostHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - if !d.user.Perm.Create { - return http.StatusForbidden, nil - } - - defer func() { - _, _ = io.Copy(ioutil.Discard, r.Body) - }() - - // Directories creation on POST. - if strings.HasSuffix(r.URL.Path, "/") { - err := d.user.Fs.MkdirAll(r.URL.Path, 0775) - return errToStatus(err), err - } - - if r.URL.Query().Get("override") != "true" { - if _, err := d.user.Fs.Stat(r.URL.Path); err == nil { - return http.StatusConflict, nil +func resourcePostHandler(fileCache FileCache) handleFunc { + return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + if !d.user.Perm.Create { + return http.StatusForbidden, nil } - } - err := d.RunHook(func() error { - info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body) + defer func() { + _, _ = io.Copy(ioutil.Discard, r.Body) + }() - etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size()) - w.Header().Set("ETag", etag) - return nil - }, "upload", r.URL.Path, "", d.user) + // Directories creation on POST. + if strings.HasSuffix(r.URL.Path, "/") { + err := d.user.Fs.MkdirAll(r.URL.Path, 0775) + return errToStatus(err), err + } - if err != nil { - _ = d.user.Fs.RemoveAll(r.URL.Path) - } + file, err := files.NewFileInfo(files.FileOptions{ + Fs: d.user.Fs, + Path: r.URL.Path, + Modify: d.user.Perm.Modify, + Expand: true, + ReadHeader: d.server.TypeDetectionByHeader, + Checker: d, + }) + if err == nil { + if r.URL.Query().Get("override") != "true" { + return http.StatusConflict, nil + } - return errToStatus(err), err -}) + err = delThumbs(r.Context(), fileCache, file) + if err != nil { + return errToStatus(err), err + } + } + + err = d.RunHook(func() error { + info, _ := writeFile(d.user.Fs, r.URL.Path, r.Body) + + etag := fmt.Sprintf(`"%x%x"`, info.ModTime().UnixNano(), info.Size()) + w.Header().Set("ETag", etag) + return nil + }, "upload", r.URL.Path, "", d.user) + + if err != nil { + _ = d.user.Fs.RemoveAll(r.URL.Path) + } + + return errToStatus(err), err + }) +} var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { if !d.user.Perm.Modify { @@ -264,3 +278,14 @@ func writeFile(fs afero.Fs, dst string, in io.Reader) (os.FileInfo, error) { return info, nil } + +func delThumbs(ctx context.Context, fileCache FileCache, file *files.FileInfo) error { + for _, previewSizeName := range PreviewSizeNames() { + size, _ := ParsePreviewSize(previewSizeName) + if err := fileCache.Delete(ctx, previewCacheKey(file.Path, file.ModTime.Unix(), size)); err != nil { + return err + } + } + + return nil +}