diff --git a/files/file.go b/files/file.go index f181f185..bd7f6988 100644 --- a/files/file.go +++ b/files/file.go @@ -95,6 +95,37 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) { return file, err } +func NewFolderInfo(opts FileOptions) (*FileInfo, error) { + file, err := NewFileInfo(opts) + if err != nil { + return nil, err + } + size, err := GetFolderSize(file.RealPath()) + if err != nil { + return nil, err + } + file.Size = size + return file, nil +} + +func GetFolderSize(path string) (int64, error) { + var size int64 + err := filepath.WalkDir(path, func(_ string, d os.DirEntry, err error) error { + if err != nil { + return err + } + if !d.IsDir() { + info, err := d.Info() + if err != nil { + return err + } + size += info.Size() + } + return err + }) + return size, err +} + func stat(opts FileOptions) (*FileInfo, error) { var file *FileInfo diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js index 1c2ea80a..46549696 100644 --- a/frontend/src/api/files.js +++ b/frontend/src/api/files.js @@ -42,6 +42,16 @@ async function resourceAction(url, method, content) { return res; } +async function resourceSizeAction(url, method, content) { + url = removePrefix(url); + let opts = { method }; + if (content) { + opts.body = content; + } + const res = await fetchURL(`/api/resources/size${url}`, opts); + return res; +} + export async function remove(url) { return resourceAction(url, "DELETE"); } @@ -163,6 +173,11 @@ export async function checksum(url, algo) { return (await data.json()).checksums[algo]; } +export async function foldersize(url) { + const data = await resourceSizeAction(`${url}`, "GET"); + return (await data.json()).size; +} + export function getDownloadURL(file, inline) { const params = { ...(inline && { inline: "true" }), diff --git a/frontend/src/components/prompts/Info.vue b/frontend/src/components/prompts/Info.vue index 03bbfb58..871a0c1a 100644 --- a/frontend/src/components/prompts/Info.vue +++ b/frontend/src/components/prompts/Info.vue @@ -13,11 +13,20 @@ {{ $t("prompts.displayName") }} {{ name }}

-

+

{{ $t("prompts.size") }}: {{ humanSize }}

+

+ Size: {{ + $t("prompts.show") + }} +

+
{{ $t("prompts.resolution") }}: {{ resolution.width }} x {{ resolution.height }} @@ -166,6 +175,26 @@ export default { this.$showError(e); } }, + foldersize: async function (event) { + event.preventDefault(); + + try { + let totalSize = 0; + + for (let selected of this.selected) { + let item = this.req.items[selected] + if (!item.isDir) { + totalSize += item.size; + } else { + totalSize += await api.foldersize(item.url) + } + } + + event.target.innerHTML = filesize(totalSize); + } catch (e) { + this.$showError(e); + } + }, }, }; diff --git a/http/http.go b/http/http.go index 8d0c4af7..18ecd21a 100644 --- a/http/http.go +++ b/http/http.go @@ -60,6 +60,7 @@ func NewHandler( users.Handle("/{id:[0-9]+}", monkey(userGetHandler, "")).Methods("GET") users.Handle("/{id:[0-9]+}", monkey(userDeleteHandler, "")).Methods("DELETE") + api.PathPrefix("/resources/size").Handler(monkey(resourceGetSizeHandler, "/api/resources/size")).Methods("GET") 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(fileCache), "/api/resources")).Methods("POST") diff --git a/http/resource.go b/http/resource.go index 3a12538a..8107e262 100644 --- a/http/resource.go +++ b/http/resource.go @@ -19,6 +19,23 @@ import ( "github.com/filebrowser/filebrowser/v2/fileutils" ) +var resourceGetSizeHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { + folder, err := files.NewFolderInfo(files.FileOptions{ + Fs: d.user.Fs, + Path: r.URL.Path, + Modify: d.user.Perm.Modify, + Expand: true, + ReadHeader: d.server.TypeDetectionByHeader, + Checker: d, + Content: true, + }) + if err != nil { + return errToStatus(err), err + } + + return renderJSON(w, r, folder) +}) + var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { file, err := files.NewFileInfo(files.FileOptions{ Fs: d.user.Fs,