diff --git a/diskcache/file_cache.go b/diskcache/file_cache.go index 419f155f..d299d2a0 100644 --- a/diskcache/file_cache.go +++ b/diskcache/file_cache.go @@ -68,7 +68,7 @@ func (f *FileCache) Delete(ctx context.Context, key string) error { defer mu.Unlock() fileName := f.getFileName(key) - if err := f.fs.Remove(fileName); err != nil && err != os.ErrNotExist { + if err := f.fs.Remove(fileName); err != nil && !errors.Is(err, os.ErrNotExist) { return err } return nil diff --git a/http/http.go b/http/http.go index 1633a9e2..a4b3c2d2 100644 --- a/http/http.go +++ b/http/http.go @@ -46,7 +46,7 @@ func NewHandler(imgSvc ImgService, fileCache FileCache, store *storage.Storage, users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE") api.PathPrefix("/resources").Handler(monkey(resourceGetHandler, "/api/resources")).Methods("GET") - api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler, "/api/resources")).Methods("DELETE") + api.PathPrefix("/resources").Handler(monkey(resourceDeleteHandler(fileCache), "/api/resources")).Methods("DELETE") api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/api/resources")).Methods("POST") api.PathPrefix("/resources").Handler(monkey(resourcePostPutHandler, "/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 bb3f1829..b6f8c256 100644 --- a/http/preview.go +++ b/http/preview.go @@ -1,3 +1,4 @@ +//go:generate go-enum --sql --marshal --names --file $GOFILE package http import ( @@ -13,10 +14,13 @@ import ( "github.com/filebrowser/filebrowser/v2/img" ) -const ( - sizeThumb = "thumb" - sizeBig = "big" +/* +ENUM( +thumb +big ) +*/ +type PreviewSize int type ImgService interface { FormatFromExtension(ext string) (img.Format, error) @@ -26,6 +30,7 @@ type ImgService interface { type FileCache interface { Store(ctx context.Context, key string, value []byte) error Load(ctx context.Context, key string) ([]byte, bool, error) + Delete(ctx context.Context, key string) error } func previewHandler(imgSvc ImgService, fileCache FileCache, enableThumbnails, resizePreview bool) handleFunc { @@ -34,9 +39,10 @@ func previewHandler(imgSvc ImgService, fileCache FileCache, enableThumbnails, re return http.StatusAccepted, nil } vars := mux.Vars(r) - size := vars["size"] - if size != sizeBig && size != sizeThumb { - return http.StatusNotImplemented, nil + + previewSize, err := ParsePreviewSize(vars["size"]) + if err != nil { + return http.StatusBadRequest, err } file, err := files.NewFileInfo(files.FileOptions{ @@ -54,7 +60,7 @@ func previewHandler(imgSvc ImgService, fileCache FileCache, enableThumbnails, re switch file.Type { case "image": - return handleImagePreview(w, r, imgSvc, fileCache, file, size, enableThumbnails, resizePreview) + return handleImagePreview(w, r, imgSvc, fileCache, file, previewSize, enableThumbnails, resizePreview) default: return http.StatusNotImplemented, fmt.Errorf("can't create preview for %s type", file.Type) } @@ -62,7 +68,7 @@ func previewHandler(imgSvc ImgService, fileCache FileCache, enableThumbnails, re } func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgService, fileCache FileCache, - file *files.FileInfo, size string, enableThumbnails, resizePreview bool) (int, error) { + file *files.FileInfo, previewSize PreviewSize, enableThumbnails, resizePreview bool) (int, error) { format, err := imgSvc.FormatFromExtension(file.Extension) if err != nil { // Unsupported extensions directly return the raw data @@ -72,7 +78,7 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic return errToStatus(err), err } - cacheKey := file.Path + size + cacheKey := previewCacheKey(file.Path, previewSize) cachedFile, ok, err := fileCache.Load(r.Context(), cacheKey) if err != nil { return errToStatus(err), err @@ -95,11 +101,11 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic ) switch { - case size == sizeBig && resizePreview && format != img.FormatGif: + case previewSize == PreviewSizeBig && resizePreview && format != img.FormatGif: width = 1080 height = 1080 options = append(options, img.WithMode(img.ResizeModeFit), img.WithQuality(img.QualityMedium)) - case size == sizeThumb && enableThumbnails: + case previewSize == PreviewSizeThumb && enableThumbnails: width = 128 height = 128 options = append(options, img.WithMode(img.ResizeModeFill), img.WithQuality(img.QualityLow), img.WithFormat(img.FormatJpeg)) @@ -125,3 +131,7 @@ func handleImagePreview(w http.ResponseWriter, r *http.Request, imgSvc ImgServic return 0, nil } + +func previewCacheKey(fPath string, previewSize PreviewSize) string { + return fPath + previewSize.String() +} diff --git a/http/preview_enum.go b/http/preview_enum.go new file mode 100644 index 00000000..50e3372c --- /dev/null +++ b/http/preview_enum.go @@ -0,0 +1,100 @@ +// Code generated by go-enum +// DO NOT EDIT! + +package http + +import ( + "database/sql/driver" + "fmt" + "strings" +) + +const ( + // PreviewSizeThumb is a PreviewSize of type Thumb + PreviewSizeThumb PreviewSize = iota + // PreviewSizeBig is a PreviewSize of type Big + PreviewSizeBig +) + +const _PreviewSizeName = "thumbbig" + +var _PreviewSizeNames = []string{ + _PreviewSizeName[0:5], + _PreviewSizeName[5:8], +} + +// PreviewSizeNames returns a list of possible string values of PreviewSize. +func PreviewSizeNames() []string { + tmp := make([]string, len(_PreviewSizeNames)) + copy(tmp, _PreviewSizeNames) + return tmp +} + +var _PreviewSizeMap = map[PreviewSize]string{ + 0: _PreviewSizeName[0:5], + 1: _PreviewSizeName[5:8], +} + +// String implements the Stringer interface. +func (x PreviewSize) String() string { + if str, ok := _PreviewSizeMap[x]; ok { + return str + } + return fmt.Sprintf("PreviewSize(%d)", x) +} + +var _PreviewSizeValue = map[string]PreviewSize{ + _PreviewSizeName[0:5]: 0, + _PreviewSizeName[5:8]: 1, +} + +// ParsePreviewSize attempts to convert a string to a PreviewSize +func ParsePreviewSize(name string) (PreviewSize, error) { + if x, ok := _PreviewSizeValue[name]; ok { + return x, nil + } + return PreviewSize(0), fmt.Errorf("%s is not a valid PreviewSize, try [%s]", name, strings.Join(_PreviewSizeNames, ", ")) +} + +// MarshalText implements the text marshaller method +func (x PreviewSize) MarshalText() ([]byte, error) { + return []byte(x.String()), nil +} + +// UnmarshalText implements the text unmarshaller method +func (x *PreviewSize) UnmarshalText(text []byte) error { + name := string(text) + tmp, err := ParsePreviewSize(name) + if err != nil { + return err + } + *x = tmp + return nil +} + +// Scan implements the Scanner interface. +func (x *PreviewSize) Scan(value interface{}) error { + var name string + + switch v := value.(type) { + case string: + name = v + case []byte: + name = string(v) + case nil: + *x = PreviewSize(0) + return nil + } + + tmp, err := ParsePreviewSize(name) + if err != nil { + return err + } + *x = tmp + return nil +} + +// Value implements the driver Valuer interface. +func (x PreviewSize) Value() (driver.Value, error) { + return x.String(), nil +} diff --git a/http/resource.go b/http/resource.go index 55a24a52..5d77a62f 100644 --- a/http/resource.go +++ b/http/resource.go @@ -50,21 +50,42 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d return renderJSON(w, r, file) }) -var resourceDeleteHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - if r.URL.Path == "/" || !d.user.Perm.Delete { - return http.StatusForbidden, nil - } +func resourceDeleteHandler(fileCache FileCache) handleFunc { + return withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + if r.URL.Path == "/" || !d.user.Perm.Delete { + return http.StatusForbidden, nil + } - err := d.RunHook(func() error { - return d.user.Fs.RemoveAll(r.URL.Path) - }, "delete", r.URL.Path, "", d.user) + file, err := files.NewFileInfo(files.FileOptions{ + Fs: d.user.Fs, + Path: r.URL.Path, + Modify: d.user.Perm.Modify, + Expand: true, + Checker: d, + }) + if err != nil { + return errToStatus(err), err + } - if err != nil { - return errToStatus(err), err - } + // delete thumbnails + for _, previewSizeName := range PreviewSizeNames() { + size, _ := ParsePreviewSize(previewSizeName) + if err := fileCache.Delete(r.Context(), previewCacheKey(file.Path, size)); err != nil { + return errToStatus(err), err + } + } - return http.StatusOK, nil -}) + err = d.RunHook(func() error { + return d.user.Fs.RemoveAll(r.URL.Path) + }, "delete", r.URL.Path, "", d.user) + + if err != nil { + return errToStatus(err), err + } + + return http.StatusOK, nil + }) +} var resourcePostPutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { if !d.user.Perm.Create && r.Method == http.MethodPost {