From 62d1cd88a139442cfe23d6871973b33f145d17fb Mon Sep 17 00:00:00 2001 From: Graham Steffaniak <42989099+gtsteffaniak@users.noreply.github.com> Date: Mon, 16 Sep 2024 16:01:16 -0500 Subject: [PATCH] V0.2.9 release (#205) --- .github/workflows/main.yaml | 6 +- .github/workflows/pr.yaml | 4 +- .github/workflows/release_dev.yaml | 4 +- CHANGELOG.md | 19 + README.md | 8 +- backend/auth/hook.go | 2 +- backend/benchmark_results.txt | 42 +- backend/cmd/root.go | 10 +- backend/cmd/rules.go | 5 - backend/cmd/users_rm.go | 4 +- backend/cmd/version.go | 2 +- backend/files/file.go | 131 +++--- backend/files/file_test.go | 76 +++ backend/files/indexing.go | 22 +- backend/files/sync.go | 31 +- backend/files/sync_test.go | 220 +++++++++ backend/go.mod | 2 +- backend/go.sum | 39 +- backend/http/auth.go | 14 +- backend/http/public.go | 6 +- backend/http/raw.go | 3 +- backend/http/resource.go | 42 +- backend/http/users.go | 17 +- backend/run_tests.sh | 2 +- backend/runner/runner.go | 4 +- backend/settings/config.go | 33 +- backend/settings/structs.go | 22 +- backend/users/storage.go | 2 - backend/users/users.go | 67 +-- frontend/public/index.html | 6 +- frontend/public/manifest.json | 4 +- frontend/src/api/files.js | 260 ++++++----- frontend/src/api/pub.js | 3 +- frontend/src/api/search.js | 36 +- frontend/src/api/users.js | 85 ++-- frontend/src/api/utils.js | 4 +- .../src/components/{header => }/Action.vue | 0 frontend/src/components/Breadcrumbs.vue | 15 +- frontend/src/components/ContextMenu.vue | 230 ++++++++++ frontend/src/components/FileSelection.vue | 168 ------- frontend/src/components/Search.vue | 15 +- .../src/components/files/ExtendedImage.vue | 7 +- frontend/src/components/files/ListingItem.vue | 58 +-- frontend/src/components/prompts/Copy.vue | 4 +- frontend/src/components/prompts/Delete.vue | 8 +- .../src/components/prompts/DeleteUser.vue | 12 +- frontend/src/components/prompts/FileList.vue | 3 +- frontend/src/components/prompts/Info.vue | 9 +- frontend/src/components/prompts/Move.vue | 5 +- frontend/src/components/prompts/NewDir.vue | 17 +- frontend/src/components/prompts/NewFile.vue | 8 +- frontend/src/components/prompts/Rename.vue | 16 +- frontend/src/components/prompts/Share.vue | 62 +-- frontend/src/components/prompts/Upload.vue | 15 +- frontend/src/components/settings/UserForm.vue | 69 +-- frontend/src/components/sidebar/General.vue | 59 +-- frontend/src/components/sidebar/Settings.vue | 16 +- frontend/src/components/sidebar/Sidebar.vue | 3 +- frontend/src/css/base.css | 18 +- frontend/src/css/dark.css | 4 + frontend/src/css/listing.css | 25 +- frontend/src/css/login.css | 2 + frontend/src/i18n/en.json | 1 + frontend/src/i18n/tr.json | 2 +- frontend/src/notify/index.ts | 6 +- frontend/src/notify/message.js | 29 +- frontend/src/router/index.ts | 8 + frontend/src/store/getters.js | 5 +- frontend/src/store/mutations.js | 62 ++- frontend/src/utils/auth.js | 4 +- frontend/src/utils/constants.js | 12 +- frontend/src/utils/download.js | 30 ++ frontend/src/utils/upload.js | 5 +- frontend/src/views/Errors.vue | 20 +- frontend/src/views/Layout.vue | 13 +- frontend/src/views/Login.vue | 5 +- frontend/src/views/Settings.vue | 34 +- frontend/src/views/Share.vue | 41 +- frontend/src/views/bars/Default.vue | 9 +- frontend/src/views/bars/EditorBar.vue | 8 +- frontend/src/views/bars/ListingBar.vue | 9 +- frontend/src/views/files/Editor.vue | 42 +- frontend/src/views/files/ListingView.vue | 431 ++++++++++++++---- frontend/src/views/files/Preview.vue | 37 +- frontend/src/views/settings/Global.vue | 6 +- frontend/src/views/settings/Profile.vue | 14 +- frontend/src/views/settings/Shares.vue | 10 +- frontend/src/views/settings/User.vue | 43 +- frontend/src/views/settings/UserColumn.vue | 106 ----- frontend/src/views/settings/UserDefaults.vue | 87 ---- frontend/src/views/settings/Users.vue | 20 +- frontend/tests/auth.spec.ts | 6 +- roadmap.md | 8 +- 93 files changed, 1843 insertions(+), 1355 deletions(-) create mode 100644 backend/files/file_test.go create mode 100644 backend/files/sync_test.go rename frontend/src/components/{header => }/Action.vue (100%) create mode 100644 frontend/src/components/ContextMenu.vue delete mode 100644 frontend/src/components/FileSelection.vue create mode 100644 frontend/src/utils/download.js delete mode 100644 frontend/src/views/settings/UserColumn.vue delete mode 100644 frontend/src/views/settings/UserDefaults.vue diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index 1ebb49d3..babbc1d5 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -12,6 +12,10 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + - name: Find latest tag + run: | + echo "LATEST_TAG=$(git describe --tags `git rev-list --tags --max-count=1`)" >> $GITHUB_ENV + echo "latest tag is $LATEST_TAG" - name: Set up QEMU uses: docker/setup-qemu-action@v3.0.0 - name: Set up Docker Buildx @@ -31,7 +35,7 @@ jobs: with: context: . build-args: | - VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} + VERSION=${{ env.LATEST_TAG }} REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} platforms: linux/amd64,linux/arm64,linux/arm/v7 file: ./Dockerfile diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml index 92fac860..a091f989 100644 --- a/.github/workflows/pr.yaml +++ b/.github/workflows/pr.yaml @@ -52,5 +52,5 @@ jobs: tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | - version=${{ steps.meta.outputs.version }} - commitSHA=${{ steps.meta.outputs.revision }} \ No newline at end of file + VERSION=${{ steps.meta.outputs.version }} + REVISION=${{ steps.meta.outputs.revision }} \ No newline at end of file diff --git a/.github/workflows/release_dev.yaml b/.github/workflows/release_dev.yaml index c0e16e05..9464aabc 100644 --- a/.github/workflows/release_dev.yaml +++ b/.github/workflows/release_dev.yaml @@ -35,7 +35,7 @@ jobs: JSON="${{ steps.meta.outputs.tags }}" # Use jq to remove 'v' from the version field JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/') - echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT + echo "CLEANED_TAG=$JSON" >> $GITHUB_ENV - name: Build and push uses: docker/build-push-action@v6 with: @@ -46,5 +46,5 @@ jobs: platforms: linux/amd64 file: ./Dockerfile push: true - tags: ${{ steps.modify-json.outputs.cleaned_tag }} + tags: ${{ env.CLEANED_TAG }} labels: ${{ steps.meta.outputs.labels }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 8af26442..2af53cea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,25 @@ All notable changes to this project will be documented in this file. For commit guidelines, please refer to [Standard Version](https://github.com/conventional-changelog/standard-version). +## v0.2.9 + + This release focused on UI navigation experience. Improving keyboard navigation and adds right click context menu. + + **New Features**: + - listing view items are middle-clickable on selected listing or when in single-click mode. + - listing view items can be navigated via arrow keys. + - listing view can jump to items using letters and number keys to cycle through files that start with that character. + - You can use the enter key and backspace key to navigate backwards and forwards on selected items. + - ctr-space will open/close the search (leaving ctr-f to browser default find prompt) + - Added right-click context menu to replace the file selection prompt. + + **Bugfixes**: + - Fixed drag to upload not working. + - Fixed shared video link issues. + - Fixed user edit bug related to other user. + - Fixed password reset bug. + - Fixed loading state getting stuck. + ## v0.2.8 - **Feature**: New gallary view scaling options (closes [#141](https://github.com/gtsteffaniak/filebrowser/issues/141)) diff --git a/README.md b/README.md index dd384bc6..a31c33ff 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@

-

Filebrowser Quantum - A modern web-based file manager

+

FileBrowser Quantum - A modern web-based file manager

@@ -15,7 +15,7 @@ > Starting with v0.2.4 *ALL* share links need to be re-created (due to > security fix). -Filebrowser Quantum is a fork of the filebrowser opensource project with the +FileBrowser Quantum is a fork of the filebrowser opensource project with the following changes: 1. [x] Enhanced lightning fast indexed search @@ -33,7 +33,7 @@ following changes: ## About -Filebrowser Quantum provides a file managing interface within a specified directory +FileBrowser Quantum provides a file managing interface within a specified directory and can be used to upload, delete, preview, rename, and edit your files. It allows the creation of multiple users and each user can have its directory. @@ -44,7 +44,7 @@ aesthetics and performance. Improved search, simplified ui (without removing features) and more secure and up-to-date build are just a few examples. -Filebrowser Quantum differs significantly to the original. +FileBrowser Quantum differs significantly to the original. There are hundreds of thousands of lines changed and they are generally no longer compatible with each other. This has been intentional -- the focus of this fork is on a few key principles: diff --git a/backend/auth/hook.go b/backend/auth/hook.go index 66d68c38..3351d507 100644 --- a/backend/auth/hook.go +++ b/backend/auth/hook.go @@ -187,7 +187,7 @@ func (a *HookAuth) SaveUser() (*users.User, error) { func (a *HookAuth) GetUser(d *users.User) *users.User { // adds all permissions when user is admin isAdmin := d.Perm.Admin - perms := users.Permissions{ + perms := settings.Permissions{ Admin: isAdmin, Execute: isAdmin || d.Perm.Execute, Create: isAdmin || d.Perm.Create, diff --git a/backend/benchmark_results.txt b/backend/benchmark_results.txt index 3c1d0b4b..04a3563c 100644 --- a/backend/benchmark_results.txt +++ b/backend/benchmark_results.txt @@ -5,49 +5,37 @@ ? github.com/gtsteffaniak/filebrowser/auth [no test files] ? github.com/gtsteffaniak/filebrowser/cmd [no test files] PASS -ok github.com/gtsteffaniak/filebrowser/diskcache 0.003s +ok github.com/gtsteffaniak/filebrowser/diskcache 0.004s ? github.com/gtsteffaniak/filebrowser/errors [no test files] goos: linux goarch: amd64 pkg: github.com/gtsteffaniak/filebrowser/files cpu: 11th Gen Intel(R) Core(TM) i5-11320H @ 3.20GHz -BenchmarkFillIndex-8 10 3587120 ns/op 273640 B/op 2013 allocs/op -BenchmarkSearchAllIndexes-8 10 31291180 ns/op 19500700 B/op 298636 allocs/op +BenchmarkFillIndex-8 10 3559830 ns/op 274639 B/op 2026 allocs/op +BenchmarkSearchAllIndexes-8 10 31912612 ns/op 20545741 B/op 312477 allocs/op PASS -ok github.com/gtsteffaniak/filebrowser/files 0.408s +ok github.com/gtsteffaniak/filebrowser/files 0.417s PASS -ok github.com/gtsteffaniak/filebrowser/fileutils 0.003s -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 h: 401 -2024/02/07 07:16:43 h: 401 -2024/02/07 07:16:43 h: 401 -2024/02/07 07:16:43 h: 401 -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 Saving new user: publicUser -2024/02/07 07:16:43 h: 401 -2024/02/07 07:16:43 h: 401 +ok github.com/gtsteffaniak/filebrowser/fileutils 0.002s +2024/08/27 16:16:13 h: 401 +2024/08/27 16:16:13 h: 401 +2024/08/27 16:16:13 h: 401 +2024/08/27 16:16:13 h: 401 +2024/08/27 16:16:13 h: 401 +2024/08/27 16:16:13 h: 401 PASS -ok github.com/gtsteffaniak/filebrowser/http 0.202s +ok github.com/gtsteffaniak/filebrowser/http 0.100s PASS -ok github.com/gtsteffaniak/filebrowser/img 0.125s +ok github.com/gtsteffaniak/filebrowser/img 0.124s PASS ok github.com/gtsteffaniak/filebrowser/rules 0.002s PASS ok github.com/gtsteffaniak/filebrowser/runner 0.003s PASS -ok github.com/gtsteffaniak/filebrowser/settings 0.005s +ok github.com/gtsteffaniak/filebrowser/settings 0.004s ? github.com/gtsteffaniak/filebrowser/share [no test files] ? github.com/gtsteffaniak/filebrowser/storage [no test files] ? github.com/gtsteffaniak/filebrowser/storage/bolt [no test files] PASS -ok github.com/gtsteffaniak/filebrowser/users 0.003s +ok github.com/gtsteffaniak/filebrowser/users 0.002s ? github.com/gtsteffaniak/filebrowser/version [no test files] diff --git a/backend/cmd/root.go b/backend/cmd/root.go index 38772297..9214947e 100644 --- a/backend/cmd/root.go +++ b/backend/cmd/root.go @@ -26,6 +26,7 @@ import ( "github.com/gtsteffaniak/filebrowser/img" "github.com/gtsteffaniak/filebrowser/settings" "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/version" ) //go:embed dist/* @@ -47,7 +48,7 @@ func init() { // Bind the flags to the pflag command line parser pflag.CommandLine.AddGoFlagSet(flag.CommandLine) pflag.Parse() - log.Println("Initializing with config file:", *configFlag) + log.Printf("Initializing FileBrowser Quantum (%v) with config file: %v \n", version.Version, *configFlag) log.Println("Embeded Frontend:", !nonEmbededFS) settings.Initialize(*configFlag) } @@ -162,8 +163,7 @@ func quickSetup(d pythonData) { checkErr("d.store.Settings.Save", err) err = d.store.Settings.SaveServer(&settings.Config.Server) checkErr("d.store.Settings.SaveServer", err) - user := &users.User{} - settings.Config.UserDefaults.Apply(user) + user := users.ApplyDefaults(users.User{}) user.Username = settings.Config.Auth.AdminUsername user.Password = settings.Config.Auth.AdminPassword user.Perm.Admin = true @@ -171,7 +171,7 @@ func quickSetup(d pythonData) { user.DarkMode = true user.ViewMode = "normal" user.LockPassword = false - user.Perm = users.Permissions{ + user.Perm = settings.Permissions{ Create: true, Rename: true, Modify: true, @@ -180,6 +180,6 @@ func quickSetup(d pythonData) { Download: true, Admin: true, } - err = d.store.Users.Save(user) + err = d.store.Users.Save(&user) checkErr("d.store.Users.Save", err) } diff --git a/backend/cmd/rules.go b/backend/cmd/rules.go index 094b95c0..7638dd8d 100644 --- a/backend/cmd/rules.go +++ b/backend/cmd/rules.go @@ -67,11 +67,6 @@ func getUserIdentifier(flags *pflag.FlagSet) interface{} { } func printRules(rulez []rules.Rule, id interface{}) { - if id == nil { - fmt.Printf("Global Rules:\n\n") - } else { - fmt.Printf("Rules for user %v:\n\n", id) - } for id, rule := range rulez { fmt.Printf("(%d) ", id) diff --git a/backend/cmd/users_rm.go b/backend/cmd/users_rm.go index a4132e15..63beb900 100644 --- a/backend/cmd/users_rm.go +++ b/backend/cmd/users_rm.go @@ -1,7 +1,7 @@ package cmd import ( - "fmt" + "log" "github.com/spf13/cobra" ) @@ -26,6 +26,6 @@ var usersRmCmd = &cobra.Command{ } checkErr("usersRmCmd", err) - fmt.Println("user deleted successfully") + log.Println("user deleted successfully") }, pythonConfig{}), } diff --git a/backend/cmd/version.go b/backend/cmd/version.go index 2e085103..cc37f9b5 100644 --- a/backend/cmd/version.go +++ b/backend/cmd/version.go @@ -16,6 +16,6 @@ var versionCmd = &cobra.Command{ Use: "version", Short: "Print the version number", Run: func(cmd *cobra.Command, args []string) { - fmt.Println("File Browser v" + version.Version + "/" + version.CommitSHA) + fmt.Println("File Browser " + version.Version + "/" + version.CommitSHA) }, } diff --git a/backend/files/file.go b/backend/files/file.go index 5cfa358f..2cf30832 100644 --- a/backend/files/file.go +++ b/backend/files/file.go @@ -1,8 +1,8 @@ package files import ( - "crypto/md5" //nolint:gosec - "crypto/sha1" //nolint:gosec + "crypto/md5" + "crypto/sha1" "crypto/sha256" "crypto/sha512" "encoding/hex" @@ -52,6 +52,7 @@ type FileInfo struct { // FileOptions are the options when getting a file info. type FileOptions struct { Path string // realpath + IsDir bool Modify bool Expand bool ReadHeader bool @@ -83,7 +84,7 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) { if !opts.Checker.Check(opts.Path) { return nil, os.ErrPermission } - file, err := stat(opts.Path, opts) // Pass opts.Path here + file, err := stat(opts) if err != nil { return nil, err } @@ -101,7 +102,6 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) { } return file, err } - func FileInfoFaster(opts FileOptions) (*FileInfo, error) { // Lock access for the specific path pathMutex := getMutex(opts.Path) @@ -111,71 +111,65 @@ func FileInfoFaster(opts FileOptions) (*FileInfo, error) { return nil, os.ErrPermission } index := GetIndex(rootPath) - trimmed := strings.TrimPrefix(opts.Path, "/") - if trimmed == "" { - trimmed = "/" + adjustedPath := index.makeIndexPath(opts.Path, opts.IsDir) + if opts.IsDir { + info, exists := index.GetMetadataInfo(adjustedPath) + if exists && !opts.Content { + // Let's not refresh if less than a second has passed + if time.Since(info.CacheTime) > time.Second { + go RefreshFileInfo(opts) //nolint:errcheck + } + // refresh cache after + return &info, nil + } + } + // don't bother caching content + if opts.Content { + file, err := NewFileInfo(opts) + return file, err + } + err := RefreshFileInfo(opts) + if err != nil { + file, err := NewFileInfo(opts) + return file, err } - adjustedPath := makeIndexPath(trimmed, index.Root) - var info FileInfo info, exists := index.GetMetadataInfo(adjustedPath) - if exists && !opts.Content { - // Check if the cache time is less than 1 second - if time.Since(info.CacheTime) > time.Second { - go RefreshFileInfo(opts) - } - // refresh cache after - return &info, nil - } else { - // don't bother caching content - if opts.Content { - file, err := NewFileInfo(opts) - return file, err - } - updated := RefreshFileInfo(opts) - if !updated { - file, err := NewFileInfo(opts) - return file, err - } - info, exists = index.GetMetadataInfo(adjustedPath) - if !exists || info.Name == "" { - return &FileInfo{}, errors.ErrEmptyKey - } - return &info, nil + if !exists || info.Name == "" { + return &FileInfo{}, errors.ErrEmptyKey } + return &info, nil + } -func RefreshFileInfo(opts FileOptions) bool { +func RefreshFileInfo(opts FileOptions) error { if !opts.Checker.Check(opts.Path) { - return false + return fmt.Errorf("permission denied: %s", opts.Path) } index := GetIndex(rootPath) - trimmed := strings.TrimPrefix(opts.Path, "/") - if trimmed == "" { - trimmed = "/" - } - adjustedPath := makeIndexPath(trimmed, index.Root) - file, err := stat(opts.Path, opts) // Pass opts.Path here + adjustedPath := index.makeIndexPath(opts.Path, opts.IsDir) + file, err := stat(opts) if err != nil { - return false + return fmt.Errorf("File/folder does not exist to refresh data: %s", opts.Path) } - _ = file.detectType(adjustedPath, true, opts.Content, opts.ReadHeader) + _ = file.detectType(opts.Path, true, opts.Content, opts.ReadHeader) if file.IsDir { err := file.readListing(opts.Path, opts.Checker, opts.ReadHeader) if err != nil { - return false + return fmt.Errorf("Dir info could not be read: %s", opts.Path) } - return index.UpdateFileMetadata(adjustedPath, *file) - } else { - return index.UpdateFileMetadata(adjustedPath, *file) } + result := index.UpdateFileMetadata(adjustedPath, *file) + if !result { + return fmt.Errorf("File/folder does not exist in metadata: %s", adjustedPath) + } + return nil } -func stat(path string, opts FileOptions) (*FileInfo, error) { - info, err := os.Lstat(path) +func stat(opts FileOptions) (*FileInfo, error) { + info, err := os.Lstat(opts.Path) if err != nil { return nil, err } - file := &FileInfo{ Path: opts.Path, Name: info.Name(), @@ -185,13 +179,12 @@ func stat(path string, opts FileOptions) (*FileInfo, error) { Extension: filepath.Ext(info.Name()), Token: opts.Token, } - if info.IsDir() { file.IsDir = true } if info.Mode()&os.ModeSymlink != 0 { file.IsSymlink = true - targetInfo, err := os.Stat(path) + targetInfo, err := os.Stat(opts.Path) if err == nil { file.Size = targetInfo.Size() file.IsDir = targetInfo.IsDir() @@ -248,20 +241,19 @@ func (i *FileInfo) RealPath() string { return i.Path } -func GetRealPath(relativePath ...string) (string, error) { +func GetRealPath(relativePath ...string) (string, bool, error) { combined := []string{settings.Config.Server.Root} for _, path := range relativePath { combined = append(combined, strings.TrimPrefix(path, settings.Config.Server.Root)) } joinedPath := filepath.Join(combined...) - // Convert relative path to absolute path absolutePath, err := filepath.Abs(joinedPath) if err != nil { - return "", err + return "", false, err } if !Exists(absolutePath) { - return absolutePath, nil // return without error + return absolutePath, false, nil // return without error } // Resolve symlinks and get the real path return resolveSymlinks(absolutePath) @@ -272,10 +264,9 @@ func DeleteFiles(absPath string, opts FileOptions) error { if err != nil { return err } - parentDir := filepath.Dir(absPath) - opts.Path = parentDir - updated := RefreshFileInfo(opts) - if !updated { + opts.Path = filepath.Dir(absPath) + err = RefreshFileInfo(opts) + if err != nil { return errors.ErrEmptyKey } return nil @@ -288,16 +279,14 @@ func WriteDirectory(opts FileOptions) error { return err } opts.Path = filepath.Dir(opts.Path) - updated := RefreshFileInfo(opts) - if !updated { + err = RefreshFileInfo(opts) + if err != nil { return errors.ErrEmptyKey } - return nil } func WriteFile(opts FileOptions, in io.Reader) error { - fmt.Println("writing file", opts.Path) dst := opts.Path parentDir := filepath.Dir(dst) // Split the directory from the destination path @@ -321,23 +310,21 @@ func WriteFile(opts FileOptions, in io.Reader) error { if err != nil { return err } - fmt.Println("refreshing info for ", parentDir) opts.Path = parentDir - updated := RefreshFileInfo(opts) - if !updated { + err = RefreshFileInfo(opts) + if err != nil { return errors.ErrEmptyKey } - return nil } // resolveSymlinks resolves symlinks in the given path -func resolveSymlinks(path string) (string, error) { +func resolveSymlinks(path string) (string, bool, error) { for { // Get the file info info, err := os.Lstat(path) if err != nil { - return "", err + return "", false, err } // Check if it's a symlink @@ -345,14 +332,14 @@ func resolveSymlinks(path string) (string, error) { // Read the symlink target target, err := os.Readlink(path) if err != nil { - return "", err + return "", false, err } // Resolve the target relative to the symlink's directory path = filepath.Join(filepath.Dir(path), target) } else { - // Not a symlink, so we are done - return path, nil + // Not a symlink, so return the resolved path and check if it's a directory + return path, info.IsDir(), nil } } } diff --git a/backend/files/file_test.go b/backend/files/file_test.go new file mode 100644 index 00000000..281d06dd --- /dev/null +++ b/backend/files/file_test.go @@ -0,0 +1,76 @@ +package files + +import ( + "os" + "path/filepath" + "strings" + "testing" +) + +func Test_GetRealPath(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + return + } + trimPrefix := filepath.Dir(filepath.Dir(cwd)) + "/" + tests := []struct { + name string + paths []string + want struct { + path string + isDir bool + } + }{ + { + name: "current directory", + paths: []string{ + "./", + }, + want: struct { + path string + isDir bool + }{ + path: "backend/files", + isDir: true, + }, + }, + { + name: "current directory", + paths: []string{ + "./file.go", + }, + want: struct { + path string + isDir bool + }{ + path: "backend/files/file.go", + isDir: false, + }, + }, + { + name: "other test case", + paths: []string{ + "/mnt/doesnt/exist", + }, + want: struct { + path string + isDir bool + }{ + path: "/mnt/doesnt/exist", + isDir: false, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + realPath, isDir, err := GetRealPath(tt.paths...) + adjustedRealPath := strings.TrimPrefix(realPath, trimPrefix) + if tt.want.path != adjustedRealPath || tt.want.isDir != isDir { + t.Errorf("expected %v:%v but got: %v:%v", tt.want.path, tt.want.isDir, adjustedRealPath, isDir) + } + if err != nil { + t.Error("got error", err) + } + }) + } +} diff --git a/backend/files/indexing.go b/backend/files/indexing.go index 023627cd..55a35909 100644 --- a/backend/files/indexing.go +++ b/backend/files/indexing.go @@ -4,6 +4,7 @@ import ( "bytes" "log" "os" + "path/filepath" "strings" "sync" "time" @@ -15,6 +16,7 @@ type Directory struct { Metadata map[string]FileInfo Files string } + type File struct { Name string IsDir bool @@ -80,8 +82,7 @@ func indexingScheduler(intervalMinutes uint32) { // Define a function to recursively index files and directories func (si *Index) indexFiles(path string) error { // Check if the current directory has been modified since the last indexing - path = strings.TrimSuffix(path, "/") - adjustedPath := makeIndexPath(path, si.Root) + adjustedPath := si.makeIndexPath(path, true) dir, err := os.Open(path) if err != nil { // Directory must have been deleted, remove it from the index @@ -114,7 +115,7 @@ func (si *Index) indexFiles(path string) error { } func (si *Index) InsertFiles(path string) { - adjustedPath := makeIndexPath(path, si.Root) + adjustedPath := si.makeIndexPath(path, false) subDirectory := Directory{} buffer := bytes.Buffer{} @@ -130,9 +131,9 @@ func (si *Index) InsertFiles(path string) { } func (si *Index) InsertDirs(path string) { - adjustedPath := makeIndexPath(path, si.Root) for _, f := range si.GetQuickList() { if f.IsDir { + adjustedPath := si.makeIndexPath(path, true) if _, exists := si.Directories[adjustedPath]; exists { si.UpdateCount("dirs") // Add or update the directory in the map @@ -154,14 +155,21 @@ func (si *Index) InsertDirs(path string) { } } -func makeIndexPath(path string, root string) string { - if path == root { +func (si *Index) makeIndexPath(subPath string, isDir bool) string { + if si.Root == subPath { return "/" } - adjustedPath := strings.TrimPrefix(path, root+"/") + // clean path + subPath = strings.TrimSuffix(subPath, "/") + // remove index prefix + adjustedPath := strings.TrimPrefix(subPath, si.Root) + // remove trailing slash adjustedPath = strings.TrimSuffix(adjustedPath, "/") + // add leading slash for root of index if adjustedPath == "" { adjustedPath = "/" + } else if !isDir { + adjustedPath = filepath.Dir(adjustedPath) } return adjustedPath } diff --git a/backend/files/sync.go b/backend/files/sync.go index 982003f6..ec3095f9 100644 --- a/backend/files/sync.go +++ b/backend/files/sync.go @@ -8,24 +8,6 @@ import ( "github.com/gtsteffaniak/filebrowser/settings" ) -// GetFileMetadata retrieves the FileInfo from the specified directory in the index. -func (si *Index) GetFileMetadata(adjustedPath string) (FileInfo, bool) { - si.mu.RLock() - dir, exists := si.Directories[adjustedPath] - si.mu.RUnlock() - if exists { - // Initialize the Metadata map if it is nil - if dir.Metadata == nil { - dir.Metadata = make(map[string]FileInfo) - si.SetDirectoryInfo(adjustedPath, dir) - return FileInfo{}, false - } else { - return dir.Metadata[adjustedPath], true - } - } - return FileInfo{}, false -} - // UpdateFileMetadata updates the FileInfo for the specified directory in the index. func (si *Index) UpdateFileMetadata(adjustedPath string, info FileInfo) bool { si.mu.Lock() @@ -45,7 +27,6 @@ func (si *Index) UpdateFileMetadata(adjustedPath string, info FileInfo) bool { // SetFileMetadata sets the FileInfo for the specified directory in the index. // internal use only func (si *Index) SetFileMetadata(adjustedPath string, info FileInfo) bool { - _, exists := si.Directories[adjustedPath] if !exists { return false @@ -57,6 +38,7 @@ func (si *Index) SetFileMetadata(adjustedPath string, info FileInfo) bool { // GetMetadataInfo retrieves the FileInfo from the specified directory in the index. func (si *Index) GetMetadataInfo(adjustedPath string) (FileInfo, bool) { + fi := FileInfo{} si.mu.RLock() dir, exists := si.Directories[adjustedPath] si.mu.RUnlock() @@ -65,11 +47,11 @@ func (si *Index) GetMetadataInfo(adjustedPath string) (FileInfo, bool) { if dir.Metadata == nil { dir.Metadata = make(map[string]FileInfo) si.SetDirectoryInfo(adjustedPath, dir) + } else { + fi = dir.Metadata[adjustedPath] } - info, metadataExists := dir.Metadata[adjustedPath] - return info, metadataExists } - return FileInfo{}, false + return fi, exists } // SetDirectoryInfo sets the directory information in the index. @@ -84,10 +66,7 @@ func (si *Index) GetDirectoryInfo(adjustedPath string) (Directory, bool) { si.mu.RLock() dir, exists := si.Directories[adjustedPath] si.mu.RUnlock() - if exists { - return dir, true - } - return Directory{}, false + return dir, exists } func (si *Index) RemoveDirectory(path string) { diff --git a/backend/files/sync_test.go b/backend/files/sync_test.go new file mode 100644 index 00000000..ef1f6ac4 --- /dev/null +++ b/backend/files/sync_test.go @@ -0,0 +1,220 @@ +package files + +import ( + "io/fs" + "os" + "testing" + "time" +) + +// Mock for fs.FileInfo +type mockFileInfo struct { + name string + isDir bool +} + +func (m mockFileInfo) Name() string { return m.name } +func (m mockFileInfo) Size() int64 { return 0 } +func (m mockFileInfo) Mode() os.FileMode { return 0 } +func (m mockFileInfo) ModTime() time.Time { return time.Now() } +func (m mockFileInfo) IsDir() bool { return m.isDir } +func (m mockFileInfo) Sys() interface{} { return nil } + +var testIndex Index + +// Test for GetFileMetadata +//func TestGetFileMetadata(t *testing.T) { +// t.Parallel() +// tests := []struct { +// name string +// adjustedPath string +// fileName string +// expectedName string +// expectedExists bool +// }{ +// { +// name: "testpath exists", +// adjustedPath: "/testpath", +// fileName: "testfile.txt", +// expectedName: "testfile.txt", +// expectedExists: true, +// }, +// { +// name: "testpath not exists", +// adjustedPath: "/testpath", +// fileName: "nonexistent.txt", +// expectedName: "", +// expectedExists: false, +// }, +// { +// name: "File exists in /anotherpath", +// adjustedPath: "/anotherpath", +// fileName: "afile.txt", +// expectedName: "afile.txt", +// expectedExists: true, +// }, +// { +// name: "File does not exist in /anotherpath", +// adjustedPath: "/anotherpath", +// fileName: "nonexistentfile.txt", +// expectedName: "", +// expectedExists: false, +// }, +// { +// name: "Directory does not exist", +// adjustedPath: "/nonexistentpath", +// fileName: "testfile.txt", +// expectedName: "", +// expectedExists: false, +// }, +// } +// +// for _, tt := range tests { +// t.Run(tt.name, func(t *testing.T) { +// fileInfo, exists := testIndex.GetFileMetadata(tt.adjustedPath) +// if exists != tt.expectedExists || fileInfo.Name != tt.expectedName { +// t.Errorf("expected %v:%v but got: %v:%v", tt.expectedName, tt.expectedExists, //fileInfo.Name, exists) +// } +// }) +// } +//} + +// Test for UpdateFileMetadata +func TestUpdateFileMetadata(t *testing.T) { + index := &Index{ + Directories: map[string]Directory{ + "/testpath": { + Metadata: map[string]FileInfo{ + "testfile.txt": {Name: "testfile.txt"}, + "anotherfile.txt": {Name: "anotherfile.txt"}, + }, + }, + }, + } + + info := FileInfo{Name: "testfile.txt"} + + success := index.UpdateFileMetadata("/testpath", info) + if !success { + t.Fatalf("expected UpdateFileMetadata to succeed") + } + + dir, exists := index.Directories["/testpath"] + if !exists || dir.Metadata["testfile.txt"].Name != "testfile.txt" { + t.Fatalf("expected testfile.txt to be updated in the directory metadata") + } +} + +// Test for GetDirMetadata +func TestGetDirMetadata(t *testing.T) { + t.Parallel() + _, exists := testIndex.GetMetadataInfo("/testpath") + if !exists { + t.Fatalf("expected GetDirMetadata to return initialized metadata map") + } + + _, exists = testIndex.GetMetadataInfo("/nonexistent") + if exists { + t.Fatalf("expected GetDirMetadata to return false for nonexistent directory") + } +} + +// Test for SetDirectoryInfo +func TestSetDirectoryInfo(t *testing.T) { + index := &Index{ + Directories: map[string]Directory{ + "/testpath": { + Metadata: map[string]FileInfo{ + "testfile.txt": {Name: "testfile.txt"}, + "anotherfile.txt": {Name: "anotherfile.txt"}, + }, + }, + }, + } + dir := Directory{Metadata: map[string]FileInfo{"testfile.txt": {Name: "testfile.txt"}}} + index.SetDirectoryInfo("/newPath", dir) + storedDir, exists := index.Directories["/newPath"] + if !exists || storedDir.Metadata["testfile.txt"].Name != "testfile.txt" { + t.Fatalf("expected SetDirectoryInfo to store directory info correctly") + } +} + +// Test for GetDirectoryInfo +func TestGetDirectoryInfo(t *testing.T) { + t.Parallel() + dir, exists := testIndex.GetDirectoryInfo("/testpath") + if !exists || dir.Metadata["testfile.txt"].Name != "testfile.txt" { + t.Fatalf("expected GetDirectoryInfo to return correct directory info") + } + + _, exists = testIndex.GetDirectoryInfo("/nonexistent") + if exists { + t.Fatalf("expected GetDirectoryInfo to return false for nonexistent directory") + } +} + +// Test for RemoveDirectory +func TestRemoveDirectory(t *testing.T) { + index := &Index{ + Directories: map[string]Directory{ + "/testpath": {}, + }, + } + index.RemoveDirectory("/testpath") + _, exists := index.Directories["/testpath"] + if exists { + t.Fatalf("expected directory to be removed") + } +} + +// Test for UpdateCount +func TestUpdateCount(t *testing.T) { + index := &Index{} + index.UpdateCount("files") + if index.NumFiles != 1 { + t.Fatalf("expected NumFiles to be 1 after UpdateCount('files')") + } + if index.NumFiles != 1 { + t.Fatalf("expected NumFiles to be 1 after UpdateCount('files')") + } + index.UpdateCount("dirs") + if index.NumDirs != 1 { + t.Fatalf("expected NumDirs to be 1 after UpdateCount('dirs')") + } + index.UpdateCount("unknown") + // Just ensure it does not panic or update any counters + if index.NumFiles != 1 || index.NumDirs != 1 { + t.Fatalf("expected counts to remain unchanged for unknown type") + } + index.resetCount() + if index.NumFiles != 0 || index.NumDirs != 0 || !index.inProgress { + t.Fatalf("expected resetCount to reset counts and set inProgress to true") + } +} + +func init() { + testIndex = Index{ + NumFiles: 10, + NumDirs: 5, + inProgress: false, + Directories: map[string]Directory{ + "/testpath": { + Metadata: map[string]FileInfo{ + "testfile.txt": {Name: "testfile.txt"}, + "anotherfile.txt": {Name: "anotherfile.txt"}, + }, + }, + "/anotherpath": { + Metadata: map[string]FileInfo{ + "afile.txt": {Name: "afile.txt"}, + }, + }, + }, + } + + files := []fs.FileInfo{ + mockFileInfo{name: "file1.txt", isDir: false}, + mockFileInfo{name: "dir1", isDir: true}, + } + testIndex.UpdateQuickList(files) +} diff --git a/backend/go.mod b/backend/go.mod index 05ce5d2c..bfa86b2a 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -47,7 +47,7 @@ require ( github.com/ulikunitz/xz v0.5.12 // indirect github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect - go.etcd.io/bbolt v1.3.10 // indirect + go.etcd.io/bbolt v1.3.11 // indirect golang.org/x/net v0.28.0 // indirect golang.org/x/sys v0.24.0 // indirect golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 // indirect diff --git a/backend/go.sum b/backend/go.sum index 12b52a1a..1aaf45c9 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -2,7 +2,6 @@ github.com/DataDog/zstd v1.4.1 h1:3oxKN3wbHibqx897utPC2LTQU4J+IHWWJO+glkAkpFM= github.com/DataDog/zstd v1.4.1/go.mod h1:1jcaCB/ufaK+sKp1NBhlGmpz41jOoPQ35bpF36t7BBo= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwvZMJY1tzqBvQgpaZiQRuIDD40jM= github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863/go.mod h1:D0JMgToj/WdxCgd30Kc1UcA9E+WdZoJqeVOuYW7iTBM= -github.com/andybalholm/brotli v1.0.1 h1:KqhlKozYbRtJvsPrrEeXcO+N2l6NYT5A2QAFmSULpEc= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= @@ -33,8 +32,6 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003142440-7a1927d49d9d/go.mod h1:LV github.com/dsoprea/go-utility/v2 v2.0.0-20221003160719-7bc88537c05e/go.mod h1:VZ7cB0pTjm1ADBWhJUOHESu4ZYy9JN+ZPqjfiW09EPU= github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje0z+3UQ5YjYiSRRzVdtamFpvBQXKwMglWqw= github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= -github.com/fatih/color v1.10.0 h1:s36xzo75JdqLaaWoiEHk767eHiwo0598uUxyfiPkDsg= -github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4= github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= @@ -42,11 +39,9 @@ github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q= github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= github.com/go-errors/errors v1.1.1/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWEp4lssFEs= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -62,7 +57,6 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20200319012246-673a6f80352d/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= -github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo= github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= github.com/golang/geo v0.0.0-20230421003525-6adc56603217 h1:HKlyj6in2JV6wVkmQ4XmG/EIm+SCYlPZ+V4GWit7Z+I= github.com/golang/geo v0.0.0-20230421003525-6adc56603217/go.mod h1:8wI0hitZ3a1IxZfeH3/5I97CI8i5cLGsYe7xNhQGs9U= @@ -71,7 +65,6 @@ github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5y github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golang/snappy v0.0.2 h1:aeE13tS0IiQgFjYdoL8qN3K1N2bXXtI6Vi51/y7BpMw= github.com/golang/snappy v0.0.2/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -85,12 +78,10 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.11.4 h1:kz40R/YWls3iqT9zX9AHN3WoVsrAWVyui5sxuLqiXqU= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA= github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= -github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= github.com/klauspost/pgzip v1.2.6 h1:8RXeL5crjEUFnR2/Sn6GJNWtSQ3Dk8pq4CL3jvdDyjU= github.com/klauspost/pgzip v1.2.6/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= @@ -103,29 +94,21 @@ github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tpx6DJGM= github.com/marusama/semaphore/v2 v2.5.0/go.mod h1:z9nMiNUekt/LTpTUQdpp+4sJeYqUGpwMHfW0Z8V8fnQ= -github.com/mattn/go-colorable v0.1.8 h1:c1ghPdyEDarC70ftn0y+A/Ee++9zz8ljHG1b13eJ0s8= -github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mholt/archiver/v3 v3.5.1 h1:rDjOBX9JSF5BvoJGvjqK479aL70qh9DIpZCl+k7Clwo= github.com/mholt/archiver/v3 v3.5.1/go.mod h1:e3dqJ7H78uzsRSEACH1joayhuSyhnonssnDhppzS1L4= -github.com/nwaples/rardecode v1.1.0 h1:vSxaY8vQhOcVr4mm5e8XllHWTiM4JF507A0Katqw7MQ= github.com/nwaples/rardecode v1.1.0/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= github.com/nwaples/rardecode v1.1.3 h1:cWCaZwfM5H7nAD6PyEdcVnczzV8i/JtotnyW/dD9lEc= github.com/nwaples/rardecode v1.1.3/go.mod h1:5DzqNKiOdpKKBH87u8VlvAnPZMXcGRhxWkRpHbbfGS0= -github.com/pierrec/lz4/v4 v4.1.2 h1:qvY3YFXRQE/XB8MlLzJH7mSzBs74eA2gg52YTk6jUPM= github.com/pierrec/lz4/v4 v4.1.2/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw= -github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= @@ -143,7 +126,6 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce h1:fb190+cK2Xz/dvi9Hv8eCYJYvIGUTN2/KLq1pT6CjEc= github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoik09Xen7gje4m9ERNah1d1PPsVq1VEx9vE4= github.com/ulikunitz/xz v0.5.8/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ulikunitz/xz v0.5.9 h1:RsKRIA2MO8x56wkkcd3LbtcE/uMszhb6DpRf+3uwa3I= github.com/ulikunitz/xz v0.5.9/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= github.com/ulikunitz/xz v0.5.12 h1:37Nm15o69RwBkXM0J6A5OlE67RZTfzUxTj8fB3dfcsc= github.com/ulikunitz/xz v0.5.12/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= @@ -153,18 +135,13 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofm github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= -go.etcd.io/bbolt v1.3.4 h1:hi1bXHMVrlQh6WwxAy+qZCV/SYIlqo+Ushwdpa4tAKg= go.etcd.io/bbolt v1.3.4/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/bbolt v1.3.10 h1:+BqfJTcCzTItrop8mq/lbzL8wSGtj94UO/3U31shqG0= -go.etcd.io/bbolt v1.3.10/go.mod h1:bK3UQLPJZly7IlNmV7uVHJDxfe5aK9Ll93e/74Y9oEQ= +go.etcd.io/bbolt v1.3.11 h1:yGEzV1wPz2yVCLsD8ZAiGHhHVlczyC9d1rP43/VCRJ0= +go.etcd.io/bbolt v1.3.11/go.mod h1:dksAq7YMXoljX0xu6VF5DMZGbhYYoLUalEiSySYAS4I= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.18.0 h1:jGzIakQa/ZXI1I0Fxvaa9W7yP25TqT6cHIHn+6CqvSQ= -golang.org/x/image v0.18.0/go.mod h1:4yyo5vMFQjVjUcVk4jEQcU9MGy/rulF5WvUILseCM2E= golang.org/x/image v0.19.0 h1:D9FX4QWkLfkeqaC62SonffIIuYdOk/UE2XKUBgRIBIQ= golang.org/x/image v0.19.0/go.mod h1:y0zrRqlQRWQ5PXaYCOMLTW2fpsxZ8Qh9I/ohnInJEys= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= @@ -174,15 +151,13 @@ golang.org/x/net v0.0.0-20200320220750-118fecf932d8/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= +golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -192,22 +167,16 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220928140112-f11e5e49a4ec/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9 h1:LLhsEBxRTBLuKlQxFBYUOU8xyFgXv6cOTp2HASDlsDk= golang.org/x/xerrors v0.0.0-20240716161551-93cc26a95ae9/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= diff --git a/backend/http/auth.go b/backend/http/auth.go index 67efaad2..694cb97f 100644 --- a/backend/http/auth.go +++ b/backend/http/auth.go @@ -2,7 +2,6 @@ package http import ( "encoding/json" - "fmt" "log" "net/http" "os" @@ -127,11 +126,10 @@ var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, return http.StatusBadRequest, nil } - user := &users.User{ - Username: info.Username, - Password: info.Password, - } - settings.Config.UserDefaults.Apply(user) + user := users.ApplyDefaults(users.User{}) + user.Username = info.Username + user.Password = info.Password + userHome, err := d.settings.MakeUserDir(user.Username, user.Scope, d.server.Root) if err != nil { log.Printf("create user: failed to mkdir user home dir: [%s]", userHome) @@ -139,8 +137,7 @@ var signupHandler = func(w http.ResponseWriter, r *http.Request, d *data) (int, } user.Scope = userHome log.Printf("new user: %s, home dir: [%s].", user.Username, userHome) - settings.Config.UserDefaults.Apply(user) - err = d.store.Users.Save(user) + err = d.store.Users.Save(&user) if err == errors.ErrExist { return http.StatusConflict, err } else if err != nil { @@ -157,7 +154,6 @@ var renewHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data func printToken(w http.ResponseWriter, _ *http.Request, d *data, user *users.User) (int, error) { duration, err := time.ParseDuration(settings.Config.Auth.TokenExpirationTime) if err != nil { - fmt.Println("Error parsing duration:", err) duration = time.Hour * 2 } claims := &authToken{ diff --git a/backend/http/public.go b/backend/http/public.go index ac6075c0..9a0fd1e8 100644 --- a/backend/http/public.go +++ b/backend/http/public.go @@ -19,25 +19,25 @@ import ( var withHashFile = func(fn handleFunc) handleFunc { return func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { id, path := ifPathWithName(r) - fmt.Println(id, path) link, err := d.store.Share.GetByHash(id) if err != nil { return errToStatus(err), err } if link.Hash != "" { var status int - status, err = authenticateShareRequest(r, link) // Assign to the existing `err` variable + status, err = authenticateShareRequest(r, link) if err != nil || status != 0 { return status, err } } d.user = &users.PublicUser - realPath, err := files.GetRealPath(d.user.Scope, link.Path, path) + realPath, isDir, err := files.GetRealPath(d.user.Scope, link.Path, path) if err != nil { return http.StatusNotFound, err } file, err := files.FileInfoFaster(files.FileOptions{ Path: realPath, + IsDir: isDir, Modify: d.user.Perm.Modify, Expand: true, ReadHeader: d.server.TypeDetectionByHeader, diff --git a/backend/http/raw.go b/backend/http/raw.go index ad44178f..18f84aa1 100644 --- a/backend/http/raw.go +++ b/backend/http/raw.go @@ -81,12 +81,13 @@ var rawHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) if !d.user.Perm.Download { return http.StatusAccepted, nil } - realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path) + realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path) if err != nil { return http.StatusInternalServerError, err } file, err := files.FileInfoFaster(files.FileOptions{ Path: realPath, + IsDir: isDir, Modify: d.user.Perm.Modify, Expand: false, ReadHeader: d.server.TypeDetectionByHeader, diff --git a/backend/http/resource.go b/backend/http/resource.go index 9fb79050..589e624e 100644 --- a/backend/http/resource.go +++ b/backend/http/resource.go @@ -18,13 +18,13 @@ import ( ) var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path) + realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path) if err != nil { - fmt.Println("unable to get real path", d.user.Scope, r.URL.Path) return http.StatusNotFound, err } file, err := files.FileInfoFaster(files.FileOptions{ Path: realPath, + IsDir: isDir, Modify: d.user.Perm.Modify, Expand: true, ReadHeader: d.server.TypeDetectionByHeader, @@ -34,19 +34,16 @@ var resourceGetHandler = withUser(func(w http.ResponseWriter, r *http.Request, d if err != nil { return errToStatus(err), err } - if file.IsDir { - file.Listing.Sorting = d.user.Sorting - return renderJSON(w, r, file) - } - if checksum := r.URL.Query().Get("checksum"); checksum != "" { - err := file.Checksum(checksum) - if err == errors.ErrInvalidOption { - return http.StatusBadRequest, nil - } else if err != nil { - return http.StatusInternalServerError, err + if !file.IsDir { + if checksum := r.URL.Query().Get("checksum"); checksum != "" { + err := file.Checksum(checksum) + if err == errors.ErrInvalidOption { + return http.StatusBadRequest, nil + } else if err != nil { + return http.StatusInternalServerError, err + } } } - return renderJSON(w, r, file) }) @@ -55,12 +52,13 @@ func resourceDeleteHandler(fileCache FileCache) handleFunc { if r.URL.Path == "/" || !d.user.Perm.Delete { return http.StatusForbidden, nil } - realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path) + realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path) if err != nil { return http.StatusNotFound, err } fileOpts := files.FileOptions{ Path: realPath, + IsDir: isDir, Modify: d.user.Perm.Modify, Expand: false, ReadHeader: d.server.TypeDetectionByHeader, @@ -90,12 +88,13 @@ func resourcePostHandler(fileCache FileCache) handleFunc { if !d.user.Perm.Create || !d.Check(r.URL.Path) { return http.StatusForbidden, nil } - realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path) + realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path) if err != nil { return http.StatusNotFound, err } fileOpts := files.FileOptions{ Path: realPath, + IsDir: isDir, Modify: d.user.Perm.Modify, Expand: false, ReadHeader: d.server.TypeDetectionByHeader, @@ -109,7 +108,6 @@ func resourcePostHandler(fileCache FileCache) handleFunc { } return http.StatusOK, nil } - file, err := files.FileInfoFaster(fileOpts) if err == nil { if r.URL.Query().Get("override") != "true" { @@ -141,12 +139,13 @@ var resourcePutHandler = withUser(func(w http.ResponseWriter, r *http.Request, d return http.StatusMethodNotAllowed, nil } - realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path) + realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path) if err != nil { return http.StatusNotFound, err } fileOpts := files.FileOptions{ Path: realPath, + IsDir: isDir, Modify: d.user.Perm.Modify, Expand: false, ReadHeader: d.server.TypeDetectionByHeader, @@ -187,7 +186,6 @@ func resourcePatchHandler(fileCache FileCache) handleFunc { return http.StatusForbidden, nil } err = d.RunHook(func() error { - fmt.Println("hook", src, dst) return patchAction(r.Context(), action, src, dst, d, fileCache) }, action, src, dst, d.user) @@ -237,16 +235,17 @@ func patchAction(ctx context.Context, action, src, dst string, d *data, fileCach } src = path.Clean("/" + src) dst = path.Clean("/" + dst) - realDest, err := files.GetRealPath(d.user.Scope, dst) + realDest, _, err := files.GetRealPath(d.user.Scope, dst) if err != nil { return err } - realSrc, err := files.GetRealPath(d.user.Scope, src) + realSrc, isDir, err := files.GetRealPath(d.user.Scope, src) if err != nil { return err } file, err := files.FileInfoFaster(files.FileOptions{ Path: realSrc, + IsDir: isDir, Modify: d.user.Perm.Modify, Expand: false, ReadHeader: false, @@ -274,12 +273,13 @@ type DiskUsageResponse struct { } var diskUsage = withUser(func(w http.ResponseWriter, r *http.Request, d *data) (int, error) { - realPath, err := files.GetRealPath(d.user.Scope, r.URL.Path) + realPath, isDir, err := files.GetRealPath(d.user.Scope, r.URL.Path) if err != nil { return http.StatusNotFound, err } file, err := files.FileInfoFaster(files.FileOptions{ Path: realPath, + IsDir: isDir, Modify: d.user.Perm.Modify, Expand: false, ReadHeader: false, diff --git a/backend/http/users.go b/backend/http/users.go index 89e3bddc..16e8182a 100644 --- a/backend/http/users.go +++ b/backend/http/users.go @@ -2,7 +2,6 @@ package http import ( "encoding/json" - "fmt" "log" "net/http" "reflect" @@ -131,19 +130,21 @@ var userPostHandler = withAdmin(func(w http.ResponseWriter, r *http.Request, d * return http.StatusBadRequest, errors.ErrEmptyPassword } + newUser := users.ApplyDefaults(*req.Data) + userHome, err := d.settings.MakeUserDir(req.Data.Username, req.Data.Scope, d.server.Root) if err != nil { log.Printf("create user: failed to mkdir user home dir: [%s]", userHome) return http.StatusInternalServerError, err } - req.Data.Scope = userHome + newUser.Scope = userHome log.Printf("user: %s, home dir: [%s].", req.Data.Username, userHome) - _, err = files.GetRealPath(d.server.Root, req.Data.Scope) + _, _, err = files.GetRealPath(d.server.Root, req.Data.Scope) if err != nil { - fmt.Println("user path is not valid", req.Data.Scope) + log.Println("user path is not valid", req.Data.Scope) return http.StatusBadRequest, nil } - err = d.store.Users.Save(req.Data) + err = d.store.Users.Save(&newUser) if err != nil { return http.StatusInternalServerError, err } @@ -161,7 +162,7 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request if req.Data.ID != d.raw.(uint) { return http.StatusBadRequest, nil } - _, err = files.GetRealPath(d.server.Root, req.Data.Scope) + _, _, err = files.GetRealPath(d.server.Root, req.Data.Scope) if err != nil { return http.StatusBadRequest, nil } @@ -175,7 +176,9 @@ var userPutHandler = withSelfOrAdmin(func(w http.ResponseWriter, r *http.Request t := v.Type() for i := 0; i < t.NumField(); i++ { field := t.Field(i) - if field.Name != "Password" && field.Name != "Fs" { + if field.Name == "Password" && req.Data.Password != "" { + req.Which = append(req.Which, field.Name) + } else if field.Name != "Password" && field.Name != "Fs" { req.Which = append(req.Which, field.Name) } } diff --git a/backend/run_tests.sh b/backend/run_tests.sh index fc278bb2..4a391d1b 100755 --- a/backend/run_tests.sh +++ b/backend/run_tests.sh @@ -9,7 +9,7 @@ checkExit() { if command -v go &> /dev/null then printf "\n == Running tests == \n" - go test -race -v ./... + go test -race -parallel -v ./... checkExit else echo "ERROR: unable to perform tests" diff --git a/backend/runner/runner.go b/backend/runner/runner.go index 8206e925..369232c7 100644 --- a/backend/runner/runner.go +++ b/backend/runner/runner.go @@ -20,8 +20,8 @@ type Runner struct { // RunHook runs the hooks for the before and after event. func (r *Runner) RunHook(fn func() error, evt, path, dst string, user *users.User) error { - path, _ = files.GetRealPath(user.Scope, path) - dst, _ = files.GetRealPath(user.Scope, dst) + path, _, _ = files.GetRealPath(user.Scope, path) + dst, _, _ = files.GetRealPath(user.Scope, dst) if r.Enabled { if val, ok := r.Commands["before_"+evt]; ok { diff --git a/backend/settings/config.go b/backend/settings/config.go index 7c856b76..0cb12e24 100644 --- a/backend/settings/config.go +++ b/backend/settings/config.go @@ -3,10 +3,9 @@ package settings import ( "log" "os" - "strings" + "path/filepath" "github.com/goccy/go-yaml" - "github.com/gtsteffaniak/filebrowser/users" ) var Config Settings @@ -19,7 +18,16 @@ func Initialize(configFile string) { log.Fatalf("Error unmarshaling YAML data: %v", err) } Config.UserDefaults.Perm = Config.UserDefaults.Permissions - Config.Server.Root = strings.TrimSuffix(Config.Server.Root, "/") + // Convert relative path to absolute path + realRoot, err := filepath.Abs(Config.Server.Root) + if err != nil { + log.Fatalf("Error getting root path: %v", err) + } + _, err = os.Stat(realRoot) + if err != nil { + log.Fatalf("ERROR: Configured Root Path does not exist! %v", err) + } + Config.Server.Root = realRoot } func loadConfigFile(configFile string) []byte { @@ -77,8 +85,9 @@ func setDefaults() Settings { HideDotfiles: true, DarkMode: false, DisableSettings: false, + ViewMode: "normal", Locale: "en", - Permissions: users.Permissions{ + Permissions: Permissions{ Create: false, Rename: false, Modify: false, @@ -90,19 +99,3 @@ func setDefaults() Settings { }, } } - -// Apply applies the default options to a user. -func (d *UserDefaults) Apply(u *users.User) { - u.StickySidebar = d.StickySidebar - u.DisableSettings = d.DisableSettings - u.DarkMode = d.DarkMode - u.Scope = d.Scope - u.Locale = d.Locale - u.ViewMode = d.ViewMode - u.SingleClick = d.SingleClick - u.Perm = d.Perm - u.Sorting = d.Sorting - u.Commands = d.Commands - u.HideDotfiles = d.HideDotfiles - u.DateFormat = d.DateFormat -} diff --git a/backend/settings/structs.go b/backend/settings/structs.go index 156c6ddf..f9a003a3 100644 --- a/backend/settings/structs.go +++ b/backend/settings/structs.go @@ -2,7 +2,6 @@ package settings import ( "github.com/gtsteffaniak/filebrowser/rules" - "github.com/gtsteffaniak/filebrowser/users" ) type Settings struct { @@ -82,9 +81,20 @@ type UserDefaults struct { By string `json:"by"` Asc bool `json:"asc"` } `json:"sorting"` - Perm users.Permissions `json:"perm"` - Permissions users.Permissions `json:"permissions"` - Commands []string `json:"commands,omitempty"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` + Perm Permissions `json:"perm"` + Permissions Permissions `json:"permissions"` + Commands []string `json:"commands,omitempty"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` +} + +type Permissions struct { + Admin bool `json:"admin"` + Execute bool `json:"execute"` + Create bool `json:"create"` + Rename bool `json:"rename"` + Modify bool `json:"modify"` + Delete bool `json:"delete"` + Share bool `json:"share"` + Download bool `json:"download"` } diff --git a/backend/users/storage.go b/backend/users/storage.go index d7925de4..01439089 100644 --- a/backend/users/storage.go +++ b/backend/users/storage.go @@ -1,7 +1,6 @@ package users import ( - "log" "sync" "time" @@ -123,7 +122,6 @@ func (s *Storage) DeleteRule(userID string, ruleID string) error { // Save saves the user in a storage. func (s *Storage) Save(user *User) error { - log.Println("Saving new user:", user.Username) return s.back.Save(user) } diff --git a/backend/users/users.go b/backend/users/users.go index e12d2cee..2bcd378e 100644 --- a/backend/users/users.go +++ b/backend/users/users.go @@ -4,19 +4,9 @@ import ( "regexp" "github.com/gtsteffaniak/filebrowser/rules" + "github.com/gtsteffaniak/filebrowser/settings" ) -type Permissions struct { - Admin bool `json:"admin"` - Execute bool `json:"execute"` - Create bool `json:"create"` - Rename bool `json:"rename"` - Modify bool `json:"modify"` - Delete bool `json:"delete"` - Share bool `json:"share"` - Download bool `json:"download"` -} - // SortingSettings represents the sorting settings. type Sorting struct { By string `json:"by"` @@ -25,24 +15,24 @@ type Sorting struct { // User describes a user. type User struct { - StickySidebar bool `json:"stickySidebar"` - DarkMode bool `json:"darkMode"` - DisableSettings bool `json:"disableSettings"` - ID uint `storm:"id,increment" json:"id"` - Username string `storm:"unique" json:"username"` - Password string `json:"password"` - Scope string `json:"scope"` - Locale string `json:"locale"` - LockPassword bool `json:"lockPassword"` - ViewMode string `json:"viewMode"` - SingleClick bool `json:"singleClick"` - Perm Permissions `json:"perm"` - Commands []string `json:"commands"` - Sorting Sorting `json:"sorting"` - Rules []rules.Rule `json:"rules"` - HideDotfiles bool `json:"hideDotfiles"` - DateFormat bool `json:"dateFormat"` - GallerySize int `json:"gallerySize"` + StickySidebar bool `json:"stickySidebar"` + DarkMode bool `json:"darkMode"` + DisableSettings bool `json:"disableSettings"` + ID uint `storm:"id,increment" json:"id"` + Username string `storm:"unique" json:"username"` + Password string `json:"password"` + Scope string `json:"scope"` + Locale string `json:"locale"` + LockPassword bool `json:"lockPassword"` + ViewMode string `json:"viewMode"` + SingleClick bool `json:"singleClick"` + Perm settings.Permissions `json:"perm"` + Commands []string `json:"commands"` + Sorting Sorting `json:"sorting"` + Rules []rules.Rule `json:"rules"` + HideDotfiles bool `json:"hideDotfiles"` + DateFormat bool `json:"dateFormat"` + GallerySize int `json:"gallerySize"` } var PublicUser = User{ @@ -51,7 +41,7 @@ var PublicUser = User{ Scope: "./", ViewMode: "normal", LockPassword: true, - Perm: Permissions{ + Perm: settings.Permissions{ Create: false, Rename: false, Modify: false, @@ -81,3 +71,20 @@ func (u *User) CanExecute(command string) bool { return false } + +// Apply applies the default options to a user. +func ApplyDefaults(u User) User { + u.StickySidebar = settings.Config.UserDefaults.StickySidebar + u.DisableSettings = settings.Config.UserDefaults.DisableSettings + u.DarkMode = settings.Config.UserDefaults.DarkMode + u.Scope = settings.Config.UserDefaults.Scope + u.Locale = settings.Config.UserDefaults.Locale + u.ViewMode = settings.Config.UserDefaults.ViewMode + u.SingleClick = settings.Config.UserDefaults.SingleClick + u.Perm = settings.Config.UserDefaults.Perm + u.Sorting = settings.Config.UserDefaults.Sorting + u.Commands = settings.Config.UserDefaults.Commands + u.HideDotfiles = settings.Config.UserDefaults.HideDotfiles + u.DateFormat = settings.Config.UserDefaults.DateFormat + return u +} diff --git a/frontend/public/index.html b/frontend/public/index.html index 2e8afb29..e973325a 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -9,7 +9,7 @@ [{[ end ]}] - [{[ if .Name -]}][{[ .Name ]}][{[ else ]}]File Browser[{[ end ]}] + [{[ if .Name -]}][{[ .Name ]}][{[ else ]}]FileBrowser Quantum[{[ end ]}] @@ -33,8 +33,8 @@ var fullStaticURL = window.location.origin + window.FileBrowser.StaticURL; var dynamicManifest = { - "name": window.FileBrowser.Name || 'File Browser', - "short_name": window.FileBrowser.Name || 'File Browser', + "name": window.FileBrowser.Name || 'FileBrowser Quantum', + "short_name": window.FileBrowser.Name || 'FileBrowser', "icons": [ { "src": fullStaticURL + "/img/icons/android-chrome-256x256.png", diff --git a/frontend/public/manifest.json b/frontend/public/manifest.json index 33efca79..1686a426 100644 --- a/frontend/public/manifest.json +++ b/frontend/public/manifest.json @@ -1,6 +1,6 @@ { - "name": "File Browser", - "short_name": "File Browser", + "name": "FileBrowser", + "short_name": "FileBrowser", "icons": [ { "src": "./img/icons/android-chrome-192x192.png", diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js index acbbef6c..cd7ac10a 100644 --- a/frontend/src/api/files.js +++ b/frontend/src/api/files.js @@ -1,122 +1,151 @@ import { createURL, fetchURL, removePrefix } from "./utils"; import { baseURL } from "@/utils/constants"; import { state } from "@/store"; +import { notify } from "@/notify"; -export async function fetch(url,content=false) { - url = removePrefix(url); +// Notify if errors occur +export async function fetch(url, content = false) { + try { + url = removePrefix(url); - const res = await fetchURL(`/api/resources${url}?content=${content}`, {}); + const res = await fetchURL(`/api/resources${url}?content=${content}`, {}); + const data = await res.json(); + data.url = `/files${url}`; - let data = await res.json(); - data.url = `/files${url}`; + if (data.isDir) { + if (!data.url.endsWith("/")) data.url += "/"; + data.items = data.items.map((item, index) => { + item.index = index; + item.url = `${data.url}${encodeURIComponent(item.name)}`; - if (data.isDir) { - if (!data.url.endsWith("/")) data.url += "/"; - data.items = data.items.map((item, index) => { - item.index = index; - item.url = `${data.url}${encodeURIComponent(item.name)}`; + if (item.isDir) { + item.url += "/"; + } - if (item.isDir) { - item.url += "/"; - } + return item; + }); + } - return item; - }); + return data; + } catch (err) { + notify.showError(err.message || "Error fetching data"); + throw err; } - - return data; } async function resourceAction(url, method, content) { - url = removePrefix(url); + try { + url = removePrefix(url); - let opts = { method }; + let opts = { method }; - if (content) { - opts.body = content; + if (content) { + opts.body = content; + } + + const res = await fetchURL(`/api/resources${url}`, opts); + return res; + } catch (err) { + notify.showError(err.message || "Error performing resource action"); + throw err; } - - const res = await fetchURL(`/api/resources${url}`, opts); - - return res; } export async function remove(url) { - return resourceAction(url, "DELETE"); + try { + return await resourceAction(url, "DELETE"); + } catch (err) { + notify.showError(err.message || "Error deleting resource"); + throw err; + } } export async function put(url, content = "") { - return resourceAction(url, "PUT", content); + try { + return await resourceAction(url, "PUT", content); + } catch (err) { + notify.showError(err.message || "Error putting resource"); + throw err; + } } export function download(format, ...files) { - let url = `${baseURL}/api/raw`; + try { + let url = `${baseURL}/api/raw`; - if (files.length === 1) { - url += removePrefix(files[0]) + "?"; - } else { - let arg = ""; + if (files.length === 1) { + url += removePrefix(files[0]) + "?"; + } else { + let arg = ""; - for (let file of files) { - arg += removePrefix(file) + ","; + for (let file of files) { + arg += removePrefix(file) + ","; + } + + arg = arg.substring(0, arg.length - 1); + arg = encodeURIComponent(arg); + url += `/?files=${arg}&`; } - arg = arg.substring(0, arg.length - 1); - arg = encodeURIComponent(arg); - url += `/?files=${arg}&`; - } + if (format) { + url += `algo=${format}&`; + } - if (format) { - url += `algo=${format}&`; - } + if (state.jwt) { + url += `auth=${state.jwt}&`; + } - if (state.jwt) { - url += `auth=${state.jwt}&`; + window.open(url); + } catch (err) { + notify.showError(err.message || "Error downloading files"); } - - window.open(url); } export async function post(url, content = "", overwrite = false, onupload) { - url = removePrefix(url); + try { + url = removePrefix(url); - let bufferContent; - if ( - content instanceof Blob && - !["http:", "https:"].includes(window.location.protocol) - ) { - bufferContent = await new Response(content).arrayBuffer(); - } - - return new Promise((resolve, reject) => { - let request = new XMLHttpRequest(); - request.open( - "POST", - `${baseURL}/api/resources${url}?override=${overwrite}`, - true - ); - request.setRequestHeader("X-Auth", state.jwt); - - if (typeof onupload === "function") { - request.upload.onprogress = onupload; + let bufferContent; + if ( + content instanceof Blob && + !["http:", "https:"].includes(window.location.protocol) + ) { + bufferContent = await new Response(content).arrayBuffer(); } - request.onload = () => { - if (request.status === 200) { - resolve(request.responseText); - } else if (request.status === 409) { - reject(request.status); - } else { - reject(request.responseText); + return new Promise((resolve, reject) => { + let request = new XMLHttpRequest(); + request.open( + "POST", + `${baseURL}/api/resources${url}?override=${overwrite}`, + true + ); + request.setRequestHeader("X-Auth", state.jwt); + + if (typeof onupload === "function") { + request.upload.onprogress = onupload; } - }; - request.onerror = () => { - reject(new Error("001 Connection aborted")); - }; + request.onload = () => { + if (request.status === 200) { + resolve(request.responseText); + } else if (request.status === 409) { + reject(request.status); + } else { + reject(request.responseText); + } + }; - request.send(bufferContent || content); - }); + request.onerror = () => { + reject(new Error("001 Connection aborted")); + }; + + request.send(bufferContent || content); + }); + } catch (err) { + notify.showError(err.message || "Error posting resource"); + throw err; + } } function moveCopy(items, copy = false, overwrite = false, rename = false) { @@ -131,7 +160,10 @@ function moveCopy(items, copy = false, overwrite = false, rename = false) { promises.push(resourceAction(url, "PATCH")); } - return Promise.all(promises); + return Promise.all(promises).catch((err) => { + notify.showError(err.message || "Error moving/copying resources"); + throw err; + }); } export function move(items, overwrite = false, rename = false) { @@ -143,44 +175,68 @@ export function copy(items, overwrite = false, rename = false) { } export async function checksum(url, algo) { - const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); - return (await data.json()).checksums[algo]; + try { + const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); + return (await data.json()).checksums[algo]; + } catch (err) { + notify.showError(err.message || "Error fetching checksum"); + throw err; + } } export function getDownloadURL(file, inline) { - const params = { - ...(inline && { inline: "true" }), - }; + try { + const params = { + ...(inline && { inline: "true" }), + }; - return createURL("api/raw" + file.path, params); + return createURL("api/raw" + file.path, params); + } catch (err) { + notify.showError(err.message || "Error getting download URL"); + throw err; + } } export function getPreviewURL(file, size) { - const params = { - inline: "true", - key: Date.parse(file.modified), - }; + try { + const params = { + inline: "true", + key: Date.parse(file.modified), + }; - return createURL("api/preview/" + size + file.path, params); + return createURL("api/preview/" + size + file.path, params); + } catch (err) { + notify.showError(err.message || "Error getting preview URL"); + throw err; + } } export function getSubtitlesURL(file) { - const params = { - inline: "true", - }; + try { + const params = { + inline: "true", + }; - const subtitles = []; - for (const sub of file.subtitles) { - subtitles.push(createURL("api/raw" + sub, params)); + const subtitles = []; + for (const sub of file.subtitles) { + subtitles.push(createURL("api/raw" + sub, params)); + } + + return subtitles; + } catch (err) { + notify.showError(err.message || "Error fetching subtitles URL"); + throw err; } - - return subtitles; } export async function usage(url) { - url = removePrefix(url); + try { + url = removePrefix(url); - const res = await fetchURL(`/api/usage${url}`, {}); - - return await res.json(); + const res = await fetchURL(`/api/usage${url}`, {}); + return await res.json(); + } catch (err) { + notify.showError(err.message || "Error fetching usage data"); + throw err; + } } diff --git a/frontend/src/api/pub.js b/frontend/src/api/pub.js index a23a7e44..d7eaca3f 100644 --- a/frontend/src/api/pub.js +++ b/frontend/src/api/pub.js @@ -84,5 +84,6 @@ export function getDownloadURL(share, inline = false) { if (share.path == undefined) { share.path = "" } - return createURL("api/public/dl/" + share.hash + "/"+share.path, params, false); + const path = share.path.replace("/share/"+share.hash +"/","") + return createURL("api/public/dl/" + share.hash + "/"+path, params, false); } diff --git a/frontend/src/api/search.js b/frontend/src/api/search.js index d9acacea..1e13fde7 100644 --- a/frontend/src/api/search.js +++ b/frontend/src/api/search.js @@ -1,22 +1,28 @@ import { fetchURL, removePrefix } from "./utils"; import url from "../utils/url"; +import { notify } from "@/notify"; // Import notify for error handling export default async function search(base, query) { - base = removePrefix(base); - query = encodeURIComponent(query); + try { + base = removePrefix(base); + query = encodeURIComponent(query); - if (!base.endsWith("/")) { - base += "/"; + if (!base.endsWith("/")) { + base += "/"; + } + + const res = await fetchURL(`/api/search${base}?query=${query}`, {}); + + let data = await res.json(); + + data = data.map((item) => { + item.url = `/files${base}` + url.encodePath(item.path); + return item; + }); + + return data; + } catch (err) { + notify.showError(err.message || "Error occurred during search"); + throw err; } - - let res = await fetchURL(`/api/search${base}?query=${query}`, {}); - - let data = await res.json(); - - data = data.map((item) => { - item.url = `/files${base}` + url.encodePath(item.path); - return item; - }); - - return data; } diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js index 78a97015..0c93d621 100644 --- a/frontend/src/api/users.js +++ b/frontend/src/api/users.js @@ -1,47 +1,76 @@ import { fetchURL, fetchJSON } from "@/api/utils"; +import { notify } from "@/notify"; // Import notify for error handling export async function getAllUsers() { - return await fetchJSON(`/api/users`, {}); + try { + return await fetchJSON(`/api/users`, {}); + } catch (err) { + notify.showError(err.message || "Failed to fetch users"); + throw err; // Re-throw to handle further if needed + } } export async function get(id) { - return fetchJSON(`/api/users/${id}`, {}); + try { + return await fetchJSON(`/api/users/${id}`, {}); + } catch (err) { + notify.showError(err.message || `Failed to fetch user with ID: ${id}`); + throw err; + } } export async function create(user) { - const res = await fetchURL(`/api/users`, { - method: "POST", - body: JSON.stringify({ - what: "user", - which: [], - data: user, - }), - }); + try { + const res = await fetchURL(`/api/users`, { + method: "POST", + body: JSON.stringify({ + what: "user", + which: [], + data: user, + }), + }); - if (res.status === 201) { - return res.headers.get("Location"); + if (res.status === 201) { + return res.headers.get("Location"); + } else { + throw new Error("Failed to create user"); + } + } catch (err) { + notify.showError(err.message || "Error creating user"); + throw err; } } export async function update(user, which = ["all"]) { - if (which[0] != "password") { - user.password = ""; + try { + // List of keys to exclude from the "which" array + const excludeKeys = ["id", "name"]; + // Filter out the keys from "which" + which = which.filter(item => !excludeKeys.includes(item)); + if (user.username === "publicUser") { + return; + } + await fetchURL(`/api/users/${user.id}`, { + method: "PUT", + body: JSON.stringify({ + what: "user", + which: which, + data: user, + }), + }); + } catch (err) { + notify.showError(err.message || `Failed to update user with ID: ${user.id}`); + throw err; } - if (user.username == "publicUser") { - return - } - await fetchURL(`/api/users/${user.id}`, { - method: "PUT", - body: JSON.stringify({ - what: "user", - which: which, - data: user, - }), - }); } export async function remove(id) { - await fetchURL(`/api/users/${id}`, { - method: "DELETE", - }); + try { + await fetchURL(`/api/users/${id}`, { + method: "DELETE", + }); + } catch (err) { + notify.showError(err.message || `Failed to delete user with ID: ${id}`); + throw err; + } } diff --git a/frontend/src/api/utils.js b/frontend/src/api/utils.js index f05a123b..1900acb1 100644 --- a/frontend/src/api/utils.js +++ b/frontend/src/api/utils.js @@ -2,7 +2,7 @@ import { state } from "@/store"; import { renew, logout } from "@/utils/auth"; import { baseURL } from "@/utils/constants"; import { encodePath } from "@/utils/url"; -import { showError } from "@/notify"; +import { notify } from "@/notify"; export async function fetchURL(url, opts, auth = true) { opts = opts || {}; @@ -51,7 +51,7 @@ export async function fetchJSON(url, opts) { if (res.status === 200) { return res.json(); } else { - showError("unable to fetch : " + url + "status" + res.status); + notify.showError("unable to fetch : " + url + "status" + res.status); throw new Error(res.status); } } diff --git a/frontend/src/components/header/Action.vue b/frontend/src/components/Action.vue similarity index 100% rename from frontend/src/components/header/Action.vue rename to frontend/src/components/Action.vue diff --git a/frontend/src/components/Breadcrumbs.vue b/frontend/src/components/Breadcrumbs.vue index c69214dd..2e1bc2a9 100644 --- a/frontend/src/components/Breadcrumbs.vue +++ b/frontend/src/components/Breadcrumbs.vue @@ -14,7 +14,7 @@ {{ link.name }} -
+
Size: import { state, mutations, getters } from "@/store"; // Import mutations as well -import Action from "@/components/header/Action.vue"; +import Action from "@/components/Action.vue"; export default { name: "breadcrumbs", @@ -51,8 +51,8 @@ export default { }, props: ["base", "noLink"], computed: { - isResizableView() { - return getters.isResizableView(); + isCardView() { + return getters.isCardView(); }, items() { const relativePath = state.route.path.replace(this.base, ""); @@ -107,11 +107,6 @@ export default { return state.user?.perm && state.user?.perm.share; // Access from state directly }, }, - methods: { - // Example of a method using mutations - updateUserPermissions(newPerms) { - mutations.updateUser({ perm: newPerms }); - }, - }, + methods: { }, }; diff --git a/frontend/src/components/ContextMenu.vue b/frontend/src/components/ContextMenu.vue new file mode 100644 index 00000000..d61b5a19 --- /dev/null +++ b/frontend/src/components/ContextMenu.vue @@ -0,0 +1,230 @@ + + + + + diff --git a/frontend/src/components/FileSelection.vue b/frontend/src/components/FileSelection.vue deleted file mode 100644 index e13db4f5..00000000 --- a/frontend/src/components/FileSelection.vue +++ /dev/null @@ -1,168 +0,0 @@ - - - - diff --git a/frontend/src/components/Search.vue b/frontend/src/components/Search.vue index cda36b85..35433eb7 100644 --- a/frontend/src/components/Search.vue +++ b/frontend/src/components/Search.vue @@ -16,6 +16,7 @@ search { resultList.classList.add("active"); + document.getElementById("main-input").focus(); }, 100); }, value() { @@ -394,11 +399,9 @@ export default { } let path = state.route.path; this.ongoing = true; - try { - this.results = await search(path, searchTypesFull + this.value); - } catch (error) { - showError(error); - } + + this.results = await search(path, searchTypesFull + this.value); + this.ongoing = false; if (this.results.length == 0) { this.noneMessage = "No results found in indexed search."; diff --git a/frontend/src/components/files/ExtendedImage.vue b/frontend/src/components/files/ExtendedImage.vue index 7bd7edce..a819df2b 100644 --- a/frontend/src/components/files/ExtendedImage.vue +++ b/frontend/src/components/files/ExtendedImage.vue @@ -24,9 +24,9 @@ \ No newline at end of file + diff --git a/frontend/src/components/sidebar/General.vue b/frontend/src/components/sidebar/General.vue index a76973b4..ad18c81b 100644 --- a/frontend/src/components/sidebar/General.vue +++ b/frontend/src/components/sidebar/General.vue @@ -44,35 +44,6 @@