diff --git a/files/file.go b/files/file.go
index ad78ad06..830d9061 100644
--- a/files/file.go
+++ b/files/file.go
@@ -39,6 +39,8 @@ type FileInfo struct {
Content string `json:"content,omitempty"`
Checksums map[string]string `json:"checksums,omitempty"`
Token string `json:"token,omitempty"`
+ DiskUsage int64 `json:"diskUsage,omitempty"`
+ Inodes int64 `json:"inodes,omitempty"`
}
// FileOptions are the options when getting a file info.
diff --git a/fileutils/dir.go b/fileutils/dir.go
index 07a3528e..80a2aea8 100644
--- a/fileutils/dir.go
+++ b/fileutils/dir.go
@@ -2,6 +2,7 @@ package fileutils
import (
"errors"
+ "path/filepath"
"github.com/spf13/afero"
)
@@ -60,3 +61,46 @@ func CopyDir(fs afero.Fs, source, dest string) error {
return nil
}
+
+func DiskUsage(fs afero.Fs, path string, maxDepth int) (size, inodes int64, err error) {
+ info, err := fs.Stat(path)
+ if err != nil {
+ return 0, 0, err
+ }
+
+ size = info.Size()
+ inodes = int64(1)
+
+ if !info.IsDir() {
+ return size, inodes, err
+ }
+
+ if maxDepth < 1 {
+ return size, inodes, err
+ }
+
+ dir, err := fs.Open(path)
+ if err != nil {
+ return size, inodes, err
+ }
+ defer dir.Close()
+
+ fis, err := dir.Readdir(-1)
+ if err != nil {
+ return size, inodes, err
+ }
+
+ for _, fi := range fis {
+ if fi.Name() == "." || fi.Name() == ".." {
+ continue
+ }
+ s, i, e := DiskUsage(fs, filepath.Join(path, fi.Name()), maxDepth-1)
+ if e != nil {
+ return size, inodes, e
+ }
+ size += s
+ inodes += i
+ }
+
+ return size, inodes, err
+}
diff --git a/fileutils/dir_test.go b/fileutils/dir_test.go
new file mode 100644
index 00000000..dcd669cc
--- /dev/null
+++ b/fileutils/dir_test.go
@@ -0,0 +1,120 @@
+package fileutils
+
+import (
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "testing"
+
+ "github.com/spf13/afero"
+ "github.com/stretchr/testify/require"
+)
+
+const RootTestDir string = "testing_dir_func"
+
+func createFileStructure() error {
+ childDir := filepath.Join(RootTestDir, "child_dir")
+
+ err := os.MkdirAll(childDir, 0755)
+ if err != nil {
+ return err
+ }
+
+ data := []byte("test_data")
+
+ err = ioutil.WriteFile(filepath.Join(childDir, "test_file"), data, 0600)
+ if err != nil {
+ return err
+ }
+
+ err = ioutil.WriteFile(filepath.Join(RootTestDir, "test_file"), data, 0600)
+ if err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func cleanupFileStructure() {
+ os.RemoveAll(RootTestDir)
+}
+
+func TestDiskUsageOnFile(t *testing.T) {
+ err := createFileStructure()
+ if err != nil {
+ t.Errorf("createFileStructure() failed: %s", err)
+ }
+ defer cleanupFileStructure()
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Errorf("Getwd() failed: %s", err)
+ }
+
+ fs := afero.NewBasePathFs(afero.NewOsFs(), cwd)
+ size, inodes, err := DiskUsage(fs, filepath.Join(RootTestDir, "test_file"), 100)
+
+ require.NoError(t, err)
+ require.Equal(t, int64(9), size)
+ require.Equal(t, int64(1), inodes)
+}
+
+func TestDiskUsageOnNestedDir(t *testing.T) {
+ err := createFileStructure()
+ if err != nil {
+ t.Errorf("createFileStructure() failed: %s", err)
+ }
+ defer cleanupFileStructure()
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Errorf("Getwd() failed: %s", err)
+ }
+
+ fs := afero.NewBasePathFs(afero.NewOsFs(), cwd)
+ size, inodes, err := DiskUsage(fs, filepath.Join(RootTestDir, "child_dir"), 100)
+
+ require.NoError(t, err)
+ require.Equal(t, int64(105), size)
+ require.Equal(t, int64(2), inodes)
+}
+
+func TestDiskUsageOnRootDir(t *testing.T) {
+ err := createFileStructure()
+ if err != nil {
+ t.Errorf("createFileStructure() failed: %s", err)
+ }
+ defer cleanupFileStructure()
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Errorf("Getwd() failed: %s", err)
+ }
+
+ fs := afero.NewBasePathFs(afero.NewOsFs(), cwd)
+ size, inodes, err := DiskUsage(fs, RootTestDir, 100)
+
+ require.NoError(t, err)
+ require.Equal(t, int64(242), size)
+ require.Equal(t, int64(4), inodes)
+}
+
+func TestDiskUsageOnRootDirStopsAtDepthLimit(t *testing.T) {
+ err := createFileStructure()
+ if err != nil {
+ t.Errorf("createFileStructure() failed: %s", err)
+ }
+ defer cleanupFileStructure()
+
+ cwd, err := os.Getwd()
+ if err != nil {
+ t.Errorf("Getwd() failed: %s", err)
+ }
+
+ fs := afero.NewBasePathFs(afero.NewOsFs(), cwd)
+ size, inodes, err := DiskUsage(fs, RootTestDir, 1)
+
+ require.NoError(t, err)
+ require.Equal(t, int64(233), size)
+ require.Equal(t, int64(3), inodes)
+}
diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js
index 7494e55b..23620e92 100644
--- a/frontend/src/api/files.js
+++ b/frontend/src/api/files.js
@@ -154,3 +154,34 @@ export async function checksum(url, algo) {
const data = await resourceAction(`${url}?checksum=${algo}`, "GET");
return (await data.json()).checksums[algo];
}
+
+export async function diskUsage(url) {
+ const data = await resourceAction(`${url}?disk_usage=true`, "GET");
+ return await data.json();
+}
+
+export async function archive(url, name, format, ...files) {
+ let arg = "";
+
+ for (let file of files) {
+ arg += file + ",";
+ }
+
+ arg = arg.substring(0, arg.length - 1);
+ arg = encodeURIComponent(arg);
+ url += `?files=${arg}&`;
+ url += `name=${encodeURIComponent(name)}&`;
+
+ if (format) {
+ url += `algo=${format}&`;
+ }
+
+ return post(url);
+}
+
+export async function unarchive(path, name, override) {
+ const to = encodeURIComponent(removePrefix(name));
+ const action = `unarchive`;
+ const url = `${path}?action=${action}&destination=${to}&override=${override}`;
+ return resourceAction(url, "PATCH");
+}
diff --git a/frontend/src/components/prompts/Archive.vue b/frontend/src/components/prompts/Archive.vue
new file mode 100644
index 00000000..53e9ae9b
--- /dev/null
+++ b/frontend/src/components/prompts/Archive.vue
@@ -0,0 +1,88 @@
+
+ {{ $t("prompts.archiveMessage") }}{{ $t("prompts.archive") }}
+
{{ $t("prompts.displayName") }} {{ name }}
-+
{{ $t("prompts.size") }}: {{ humanSize }}
+
+ {{ $t("prompts.size") }}:
+
+ {{ $t("prompts.show") }}
+
+
{{ $t("prompts.lastModified") }}: {{ humanTime }}
@@ -147,6 +153,38 @@ export default { this.$showError(e); } }, + diskUsage: async function (event) { + event.preventDefault(); + + // eslint-disable-next-line + event.target.innerHTML = this.$t('files.loading'); + + let links = []; + + if (this.selectedCount === 0 || !this.isListing) { + links.push(this.$route.path); + } else { + for (let selected of this.selected) { + links.push(this.req.items[selected].url); + } + } + + let size = 0; + let inodes = 0; + + for (let link of links) { + try { + let data = await api.diskUsage(link); + size += data.diskUsage; + inodes += data.inodes; + } catch (e) { + this.$showError(e); + } + } + + // eslint-disable-next-line + event.target.innerHTML = filesize(size) + " " + this.$t("prompts.inodeCount", { count: inodes }) + }, }, }; diff --git a/frontend/src/components/prompts/Prompts.vue b/frontend/src/components/prompts/Prompts.vue index 36cb0349..5e4e190b 100644 --- a/frontend/src/components/prompts/Prompts.vue +++ b/frontend/src/components/prompts/Prompts.vue @@ -12,6 +12,8 @@ import Delete from "./Delete"; import Rename from "./Rename"; import Download from "./Download"; import Move from "./Move"; +import Archive from "./Archive"; +import Unarchive from "./Unarchive"; import Copy from "./Copy"; import NewFile from "./NewFile"; import NewDir from "./NewDir"; @@ -31,6 +33,8 @@ export default { Rename, Download, Move, + Archive, + Unarchive, Copy, Share, NewFile, @@ -91,6 +95,8 @@ export default { "delete", "rename", "move", + "archive", + "unarchive", "copy", "newFile", "newDir", diff --git a/frontend/src/components/prompts/Unarchive.vue b/frontend/src/components/prompts/Unarchive.vue new file mode 100644 index 00000000..5deb6605 --- /dev/null +++ b/frontend/src/components/prompts/Unarchive.vue @@ -0,0 +1,77 @@ + +{{ $t("prompts.unarchiveMessage") }}
+ +