diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index d33db5a9..a0c11be4 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -61,7 +61,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_OUTPUT - name: Build and push uses: docker/build-push-action@v6 with: @@ -72,5 +72,5 @@ jobs: platforms: linux/amd64,linux/arm64,linux/arm/v7 file: ./Dockerfile push: true - tags: ${{ steps.modify-json.outputs.cleaned_tag }} + tags: ${{ steps.modify-json.outputs.CLEANED_TAG }} labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/release_dev.yaml b/.github/workflows/release_dev.yaml index 9464aabc..c965d5b5 100644 --- a/.github/workflows/release_dev.yaml +++ b/.github/workflows/release_dev.yaml @@ -10,7 +10,7 @@ permissions: jobs: push_release_to_registry: - name: Push dev release + name: Push release runs-on: ubuntu-latest steps: - name: Checkout @@ -35,16 +35,16 @@ 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_ENV + echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT - name: Build and push uses: docker/build-push-action@v6 with: + context: . build-args: | VERSION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.version'] }} REVISION=${{ fromJSON(steps.meta.outputs.json).labels['org.opencontainers.image.revision'] }} - context: . platforms: linux/amd64 file: ./Dockerfile push: true - tags: ${{ env.CLEANED_TAG }} + tags: ${{ steps.modify-json.outputs.cleaned_tag }} labels: ${{ steps.meta.outputs.labels }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fb5a668..1730aeb9 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.3.3 + + **New Features** + - Navigating remembers your previous scroll position when opening items and then navigating backwards. + - New Icons with larger selection of file types + - file "type" is shown on item info page. + - added optional non-root "filebrowser" user for docker image. See https://github.com/gtsteffaniak/filebrowser/issues/251 + - File preview supports more file types: + - images: jpg, bmp, gif, tiff, png, svg, heic, webp + + **Notes**: + - The file "type" is now either "directory" or a specific mimetype such as "text/xml". + - update safari styling + + **Bugfixes**: + - Delete/move file/folders sometimes wouldn't work. + - Possible fix for context menu not showing issue. See https://github.com/gtsteffaniak/filebrowser/issues/251 + - Fixed drag/drop not refreshing immediately to reflect changes. + ## v0.3.2 **New Features** @@ -186,7 +205,7 @@ This change focuses on minimizing and simplifying build process. - The shell feature has been deprecated. - Custom commands can be executed within the Docker container if needed. - The JSON config file is no longer used. - - All configurations are now performed via the advanced `filebrowser.yaml`. + - All configurations are now performed via the advanced `config.yaml`. - The only allowed flag is specifying the config file. - Removed old code for migrating database versions. - Eliminated all unused `cmd` code. diff --git a/Dockerfile b/Dockerfile index f6137d1e..0eb5aa55 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,8 +20,12 @@ RUN npm run build-docker FROM alpine:latest ENV FILEBROWSER_NO_EMBEDED="true" RUN apk --no-cache add ca-certificates mailcap -COPY --from=base /app/filebrowser* ./ +WORKDIR /home/filebrowser +RUN adduser -D -s /bin/true -u 1000 filebrowser +USER filebrowser +COPY --from=base --chown=filebrowser:1000 /app/filebrowser* ./ +COPY --from=nbuild --chown=filebrowser:1000 /app/dist/ ./http/dist/ +USER root # exposing default port for auto discovery. EXPOSE 80 -COPY --from=nbuild /app/dist/ ./http/dist/ ENTRYPOINT [ "./filebrowser" ] diff --git a/Dockerfile.playwright b/Dockerfile.playwright index a792362e..37af3608 100644 --- a/Dockerfile.playwright +++ b/Dockerfile.playwright @@ -8,7 +8,7 @@ WORKDIR /app COPY ./frontend/package.json ./ RUN npm i --maxsockets 1 RUN npx playwright install --with-deps firefox -COPY [ "backend/filebrowser.yaml", "./" ] +COPY [ "backend/config.yaml", "./" ] COPY ./frontend/ ./frontend WORKDIR /app/frontend RUN npm run build-docker diff --git a/README.md b/README.md index dcd3a508..ac0ebf41 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@

> [!Note] -> Starting with `v0.3.0` API routes have been slightly altered for friendly usage outside of the UI. The resources API returns items in separate `files` and `folder` objects now. +> Starting with v0.3.3, configuration file mapping is different to support non-root user. Now, the default config file name is `config.yaml` and in docker the path is `/home/filebrowser/config.yaml` and `/home/filebrowser/`. Please read the usage below to properly update your config to point the new config location. > [!WARNING] > - There is no stable version yet. Always check release notes for bug fixes on functionality that may have been changed. If you notice any unexpected behavior -- please open an issue to have it fixed soon. @@ -22,18 +22,18 @@ FileBrowser Quantum is a fork of the file browser opensource project with the fo - Real-time search results as you type - Search supports file/folder sizes and many file type filters. - Enhanced interactive results that show file/folder sizes. - 1. [x] Revamped and simplified GUI navbar and sidebar menu. + 2. [x] Revamped and simplified GUI navbar and sidebar menu. - Additional compact view mode as well as refreshed view mode styles. - Many graphical and user experience improvements. - right-click context menu - 1. [x] Revamped and simplified configuration via `filebrowser.yml` config file. - 1. [x] Better listing browsing + 3. [x] Revamped and simplified configuration via `filebrowser.yml` config file. + 4. [x] Better listing browsing - Switching view modes is instant - Folder sizes are shown as well - Changing Sort order is instant - The entire directory is loaded in 1/3 the time - 1. [x] Developer API support + 5. [x] Developer API support - Can create long-live API Tokens. - Helpful Swagger page available at `/swagger` endpoint. @@ -94,7 +94,13 @@ Using docker: 1. docker run (no persistent db): ``` -docker run -it -v /path/to/folder:/srv -p 80:80 gtstef/filebrowser +docker run -it -v /path/to/folder:/srv -v $(pwd)/config.yaml:/home/filebrowser/config.yaml -p 80:80 gtstef/filebrowser +``` + +or optionally, as non-root filebrowser user: + +``` +docker run -u filebrowser -it -v $(pwd)/config.yaml:/home/filebrowser/config.yaml -v /path/to/folder:/srv -p 80:80 gtstef/filebrowser ``` 1. docker compose: @@ -106,11 +112,14 @@ services: filebrowser: volumes: - '/path/to/folder:/srv' # required (for now not configurable) - - './database:/database' # optional if you want db to persist - configure a path under "database" dir in config file. - - './filebrowser.yaml:/filebrowser.yaml' # required + # optional if you want db to persist - configure a path under "database" dir in config file. + - './database:/home/filebrowser/database' + - './config.yaml:/home/filebrowser/config.yaml' ports: - '80:80' image: gtstef/filebrowser + # optionally run as non-root filebrowser user + #user: filebrowser restart: always ``` @@ -121,8 +130,9 @@ services: filebrowser: volumes: - 'storage:/srv' # required (for now not configurable) - - './database:/database' # optional if you want db to persist - configure a path under "database" dir in config file. - - './filebrowser.yaml:/filebrowser.yaml' # required + # optional if you want db to persist - configure a path under "database" dir in config file. + - './database:/home/filebrowser/database' + - './config.yaml:/home/filebrowser/config.yaml' ports: - '80:80' image: gtstef/filebrowser @@ -139,14 +149,14 @@ volumes: Not using docker (not recommended), download your binary from releases and run with your custom config file: ``` -./filebrowser -c +./filebrowser -c ``` ## Command Line Usage There are very few commands available. There are 3 actions done via the command line: -1. Running the program, as shown in the install step. The only argument used is the config file if you choose to override the default "filebrowser.yaml" +1. Running the program, as shown in the install step. The only argument used is the config file if you choose to override the default "config.yaml" 2. Checking the version info via `./filebrowser version` 3. Updating the DB, which currently only supports adding users via `./filebrowser set -u username,password [-a] [-s "example/scope"]` @@ -172,8 +182,8 @@ Failed Request ## Configuration All configuration is now done via a single configuration file: -`filebrowser.yaml`, here is an example of minimal [configuration -file](./backend/filebrowser.yaml). +`config.yaml`, here is an example of minimal [configuration +file](./backend/config.yaml). View the [Configuration Help Page](./docs/configuration.md) for available configuration options and other help. diff --git a/backend/auth/auth.go b/backend/auth/auth.go index 3df1a955..8a2250ee 100644 --- a/backend/auth/auth.go +++ b/backend/auth/auth.go @@ -3,7 +3,7 @@ package auth import ( "net/http" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/users" ) // Auther is the authentication interface. diff --git a/backend/auth/hook.go b/backend/auth/hook.go index 75297b95..85178d98 100644 --- a/backend/auth/hook.go +++ b/backend/auth/hook.go @@ -9,9 +9,9 @@ import ( "os/exec" "strings" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/users" ) type hookCred struct { diff --git a/backend/auth/json.go b/backend/auth/json.go index cc082597..54e23d9b 100644 --- a/backend/auth/json.go +++ b/backend/auth/json.go @@ -7,8 +7,8 @@ import ( "os" "strings" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/users" ) type jsonCred struct { diff --git a/backend/auth/none.go b/backend/auth/none.go index bc2a53c4..ca4aeb4b 100644 --- a/backend/auth/none.go +++ b/backend/auth/none.go @@ -3,8 +3,8 @@ package auth import ( "net/http" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/users" ) // MethodNoAuth is used to identify no auth. diff --git a/backend/auth/proxy.go b/backend/auth/proxy.go index a5e6cd1a..86183a1e 100644 --- a/backend/auth/proxy.go +++ b/backend/auth/proxy.go @@ -4,10 +4,10 @@ import ( "net/http" "os" - "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/backend/settings" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/users" ) // MethodProxyAuth is used to identify no auth. diff --git a/backend/auth/storage.go b/backend/auth/storage.go index 0af743df..ee51dc2c 100644 --- a/backend/auth/storage.go +++ b/backend/auth/storage.go @@ -1,7 +1,7 @@ package auth import ( - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/users" ) // StorageBackend is a storage backend for auth storage. diff --git a/backend/cmd/root.go b/backend/cmd/root.go index fda40a7d..5132ebde 100644 --- a/backend/cmd/root.go +++ b/backend/cmd/root.go @@ -7,17 +7,17 @@ import ( "os" "strings" - "github.com/gtsteffaniak/filebrowser/diskcache" - "github.com/gtsteffaniak/filebrowser/files" - fbhttp "github.com/gtsteffaniak/filebrowser/http" - "github.com/gtsteffaniak/filebrowser/img" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/swagger/docs" + "github.com/gtsteffaniak/filebrowser/backend/diskcache" + "github.com/gtsteffaniak/filebrowser/backend/files" + fbhttp "github.com/gtsteffaniak/filebrowser/backend/http" + "github.com/gtsteffaniak/filebrowser/backend/img" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/swagger/docs" "github.com/swaggo/swag" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/version" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/version" ) func getStore(config string) (*storage.Storage, bool) { @@ -47,7 +47,7 @@ func StartFilebrowser() { var help bool // Override the default usage output to use generalUsage() flag.Usage = generalUsage - flag.StringVar(&configPath, "c", "filebrowser.yaml", "Path to the config file.") + flag.StringVar(&configPath, "c", "config.yaml", "Path to the config file, default: config.yaml") flag.BoolVar(&help, "h", false, "Get help about commands") // Parse global flags (before subcommands) @@ -67,7 +67,7 @@ func StartFilebrowser() { setCmd.StringVar(&user, "u", "", "Comma-separated username and password: \"set -u ,\"") setCmd.BoolVar(&asAdmin, "a", false, "Create user as admin user, used in combination with -u") setCmd.StringVar(&scope, "s", "", "Specify a user scope, otherwise default user config scope is used") - setCmd.StringVar(&dbConfig, "c", "filebrowser.yaml", "Path to the config file.") + setCmd.StringVar(&dbConfig, "c", "config.yaml", "Path to the config file, default: config.yaml") // Parse subcommand flags only if a subcommand is specified if len(os.Args) > 1 { diff --git a/backend/cmd/rule_rm.go b/backend/cmd/rule_rm.go index 90b3f787..8e12068c 100644 --- a/backend/cmd/rule_rm.go +++ b/backend/cmd/rule_rm.go @@ -5,10 +5,10 @@ import ( "github.com/spf13/cobra" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func init() { diff --git a/backend/cmd/rules.go b/backend/cmd/rules.go index dec58a68..5249bc7a 100644 --- a/backend/cmd/rules.go +++ b/backend/cmd/rules.go @@ -6,10 +6,10 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func init() { diff --git a/backend/cmd/rules_add.go b/backend/cmd/rules_add.go index 27d8ba36..2ae17b9b 100644 --- a/backend/cmd/rules_add.go +++ b/backend/cmd/rules_add.go @@ -5,10 +5,10 @@ import ( "github.com/spf13/cobra" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func init() { diff --git a/backend/cmd/rules_ls.go b/backend/cmd/rules_ls.go index a29f6098..e55db473 100644 --- a/backend/cmd/rules_ls.go +++ b/backend/cmd/rules_ls.go @@ -1,7 +1,7 @@ package cmd import ( - "github.com/gtsteffaniak/filebrowser/storage" + "github.com/gtsteffaniak/filebrowser/backend/storage" "github.com/spf13/cobra" ) diff --git a/backend/cmd/users.go b/backend/cmd/users.go index 9d8e4f40..fb471d5f 100644 --- a/backend/cmd/users.go +++ b/backend/cmd/users.go @@ -8,7 +8,7 @@ import ( "github.com/spf13/cobra" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/users" ) var usersCmd = &cobra.Command{ diff --git a/backend/cmd/users_add.go b/backend/cmd/users_add.go index ccf9a119..bcf5f729 100644 --- a/backend/cmd/users_add.go +++ b/backend/cmd/users_add.go @@ -3,9 +3,9 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func init() { diff --git a/backend/cmd/users_export.go b/backend/cmd/users_export.go index d62cddfb..bd4cfc52 100644 --- a/backend/cmd/users_export.go +++ b/backend/cmd/users_export.go @@ -1,8 +1,8 @@ package cmd import ( - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/utils" "github.com/spf13/cobra" ) diff --git a/backend/cmd/users_find.go b/backend/cmd/users_find.go index 43ac9a5f..0bb40112 100644 --- a/backend/cmd/users_find.go +++ b/backend/cmd/users_find.go @@ -3,9 +3,9 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func init() { diff --git a/backend/cmd/users_import.go b/backend/cmd/users_import.go index b2984296..6ac73e95 100644 --- a/backend/cmd/users_import.go +++ b/backend/cmd/users_import.go @@ -8,9 +8,9 @@ import ( "github.com/spf13/cobra" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func init() { diff --git a/backend/cmd/users_rm.go b/backend/cmd/users_rm.go index 6cc89868..4d7176d0 100644 --- a/backend/cmd/users_rm.go +++ b/backend/cmd/users_rm.go @@ -3,8 +3,8 @@ package cmd import ( "log" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/utils" "github.com/spf13/cobra" ) diff --git a/backend/cmd/users_update.go b/backend/cmd/users_update.go index 7dc75fe4..b2824d16 100644 --- a/backend/cmd/users_update.go +++ b/backend/cmd/users_update.go @@ -3,9 +3,9 @@ package cmd import ( "github.com/spf13/cobra" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func init() { diff --git a/backend/cmd/utils.go b/backend/cmd/utils.go index 43ac168b..94a57cdb 100644 --- a/backend/cmd/utils.go +++ b/backend/cmd/utils.go @@ -10,8 +10,8 @@ import ( "github.com/spf13/cobra" "github.com/spf13/pflag" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func mustGetString(flags *pflag.FlagSet, flag string) string { diff --git a/backend/filebrowser.yaml b/backend/config.yaml similarity index 95% rename from backend/filebrowser.yaml rename to backend/config.yaml index 1991969a..6fea8512 100644 --- a/backend/filebrowser.yaml +++ b/backend/config.yaml @@ -1,7 +1,7 @@ server: port: 80 baseURL: "/" - root: "./srv" + root: "/srv" auth: method: password signup: false diff --git a/backend/filebrowser b/backend/filebrowser deleted file mode 100755 index 81ec32af..00000000 Binary files a/backend/filebrowser and /dev/null differ diff --git a/backend/files/conditions.go b/backend/files/conditions.go index 29860183..17b08050 100644 --- a/backend/files/conditions.go +++ b/backend/files/conditions.go @@ -24,11 +24,24 @@ var documentTypes = []string{ ".pdf", // Portable Document Format ".odt", // OpenDocument Text ".rtf", // Rich Text Format + ".conf", + ".bash_history", + ".gitignore", + ".htpasswd", + ".profile", + ".dockerignore", + ".editorconfig", // Presentation Formats ".ppt", ".pptx", // Microsoft PowerPoint ".odp", // OpenDocument Presentation + // google docs + ".gdoc", + + // google sheet + ".gsheet", + // Spreadsheet Formats ".xls", ".xlsx", // Microsoft Excel ".ods", // OpenDocument Spreadsheet @@ -102,6 +115,16 @@ type SearchOptions struct { Terms []string } +func extendedMimeTypeCheck(extension string) string { + if isDoc(extension) { + return "application/document" + } + if isText(extension) { + return "text/plain" + } + return "blob" +} + func ParseSearch(value string) SearchOptions { opts := SearchOptions{ Conditions: map[string]bool{ @@ -199,8 +222,6 @@ func IsMatchingType(extension string, matchType string) bool { switch matchType { case "doc": return isDoc(extension) - case "pdf": - return extension == ".pdf" case "text": return isText(extension) case "archive": diff --git a/backend/files/conditions_test.go b/backend/files/conditions_test.go index 6bd08610..454a9c1e 100644 --- a/backend/files/conditions_test.go +++ b/backend/files/conditions_test.go @@ -22,7 +22,7 @@ func TestIsMatchingType(t *testing.T) { extension string expectedType string }{ - {".pdf", "pdf"}, + {".pdf", "doc"}, {".doc", "doc"}, {".docx", "doc"}, {".json", "text"}, diff --git a/backend/files/file.go b/backend/files/file.go index edfbf946..a3cba8ab 100644 --- a/backend/files/file.go +++ b/backend/files/file.go @@ -11,6 +11,7 @@ import ( "io" "mime" "net/http" + "os" "path/filepath" "sort" @@ -20,11 +21,11 @@ import ( "time" "unicode/utf8" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/fileutils" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/fileutils" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) var ( @@ -201,7 +202,8 @@ func DeleteFiles(absPath string, opts FileOptions) error { return err } index := GetIndex(rootPath) - err = index.RefreshFileInfo(opts) + refreshConfig := FileOptions{Path: filepath.Dir(opts.Path), IsDir: true} + err = index.RefreshFileInfo(refreshConfig) if err != nil { return err } @@ -341,74 +343,29 @@ func getContent(path string) (string, error) { return stringContent, nil } -// detectType detects the file type. -func (i *ItemInfo) detectType(path string, modify, saveContent, readHeader bool) error { +// DetectType detects the MIME type of a file and updates the ItemInfo struct. +func (i *ItemInfo) DetectType(path string, saveContent bool) { name := i.Name - var contentErr error - ext := filepath.Ext(name) - var buffer []byte - if readHeader { - buffer = i.readFirstBytes(path) - mimetype := mime.TypeByExtension(ext) - if mimetype == "" { - http.DetectContentType(buffer) - } - } - for _, fileType := range AllFiletypeOptions { - if IsMatchingType(ext, fileType) { - i.Type = fileType - } - switch i.Type { - case "text": - if !modify { - i.Type = "textImmutable" - } - if saveContent { - return contentErr - } - case "video": - // TODO add back somewhere else, not during metadata fetch - //parentDir := strings.TrimRight(path, name) - //i.detectSubtitles(parentDir) - case "doc": - if ext == ".pdf" { - i.Type = "pdf" - return nil - } - if saveContent { - return nil - } - } - } + // Attempt MIME detection by file extension + i.Type = strings.Split(mime.TypeByExtension(ext), ";")[0] if i.Type == "" { - i.Type = "blob" - if saveContent { - return contentErr + i.Type = extendedMimeTypeCheck(ext) + } + if i.Type == "blob" { + realpath, _, _ := GetRealPath(path) + // Read only the first 512 bytes for efficient MIME detection + file, err := os.Open(realpath) + if err != nil { + + } else { + defer file.Close() + buffer := make([]byte, 512) + n, _ := file.Read(buffer) // Ignore errors from Read + i.Type = strings.Split(http.DetectContentType(buffer[:n]), ";")[0] } } - - return nil -} - -// readFirstBytes reads the first bytes of the file. -func (i *ItemInfo) readFirstBytes(path string) []byte { - file, err := os.Open(path) - if err != nil { - i.Type = "blob" - return nil - } - defer file.Close() - - buffer := make([]byte, 512) //nolint:gomnd - n, err := file.Read(buffer) - if err != nil && err != io.EOF { - i.Type = "blob" - return nil - } - - return buffer[:n] } // TODO add subtitles back diff --git a/backend/files/indexingFiles.go b/backend/files/indexingFiles.go index 497e3eef..5a47eace 100644 --- a/backend/files/indexingFiles.go +++ b/backend/files/indexingFiles.go @@ -9,8 +9,8 @@ import ( "sync" "time" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) type Index struct { @@ -132,7 +132,7 @@ func (si *Index) indexDirectory(adjustedPath string, quick, recursive bool) erro dirInfos = append(dirInfos, *itemInfo) si.NumDirs++ } else { - _ = itemInfo.detectType(combinedPath+file.Name(), true, false, false) + itemInfo.DetectType(combinedPath+file.Name(), false) itemInfo.Size = file.Size() fileInfos = append(fileInfos, *itemInfo) totalSize += itemInfo.Size diff --git a/backend/files/indexingSchedule.go b/backend/files/indexingSchedule.go index d7613b37..442e24de 100644 --- a/backend/files/indexingSchedule.go +++ b/backend/files/indexingSchedule.go @@ -4,7 +4,7 @@ import ( "log" "time" - "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/backend/settings" ) // schedule in minutes diff --git a/backend/files/indexing_test.go b/backend/files/indexing_test.go index 4d15abb0..6e71df97 100644 --- a/backend/files/indexing_test.go +++ b/backend/files/indexing_test.go @@ -7,7 +7,7 @@ import ( "testing" "time" - "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/backend/settings" ) func BenchmarkFillIndex(b *testing.B) { diff --git a/backend/files/search.go b/backend/files/search.go index df855fce..4f5986cc 100644 --- a/backend/files/search.go +++ b/backend/files/search.go @@ -6,7 +6,7 @@ import ( "strings" "sync" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) var ( @@ -14,19 +14,19 @@ var ( maxSearchResults = 100 ) -type searchResult struct { +type SearchResult struct { Path string `json:"path"` Type string `json:"type"` Size int64 `json:"size"` } -func (si *Index) Search(search string, scope string, sourceSession string) []searchResult { +func (si *Index) Search(search string, scope string, sourceSession string) []SearchResult { // Remove slashes scope = si.makeIndexPath(scope) runningHash := utils.GenerateRandomHash(4) sessionInProgress.Store(sourceSession, runningHash) // Store the value in the sync.Map searchOptions := ParseSearch(search) - results := make(map[string]searchResult, 0) + results := make(map[string]SearchResult, 0) count := 0 var directories []string cachedDirs, ok := utils.SearchResultsCache.Get(si.Root + scope).([]string) @@ -62,7 +62,7 @@ func (si *Index) Search(search string, scope string, sourceSession string) []sea } matches := reducedDir.containsSearchTerm(searchTerm, searchOptions) if matches { - results[scopedPath] = searchResult{Path: scopedPath, Type: "directory", Size: dir.Size} + results[scopedPath] = SearchResult{Path: scopedPath, Type: "directory", Size: dir.Size} count++ } // search files first @@ -75,14 +75,14 @@ func (si *Index) Search(search string, scope string, sourceSession string) []sea value, found := sessionInProgress.Load(sourceSession) if !found || value != runningHash { si.mu.Unlock() - return []searchResult{} + return []SearchResult{} } if count > maxSearchResults { break } matches := item.containsSearchTerm(searchTerm, searchOptions) if matches { - results[scopedPath] = searchResult{Path: scopedPath, Type: item.Type, Size: item.Size} + results[scopedPath] = SearchResult{Path: scopedPath, Type: item.Type, Size: item.Size} count++ } } @@ -91,7 +91,7 @@ func (si *Index) Search(search string, scope string, sourceSession string) []sea } // Sort keys based on the number of elements in the path after splitting by "/" - sortedKeys := make([]searchResult, 0, len(results)) + sortedKeys := make([]SearchResult, 0, len(results)) for _, v := range results { sortedKeys = append(sortedKeys, v) } diff --git a/backend/files/search_test.go b/backend/files/search_test.go index 2ebbcfe2..968b56ab 100644 --- a/backend/files/search_test.go +++ b/backend/files/search_test.go @@ -119,12 +119,12 @@ func TestSearchIndexes(t *testing.T) { tests := []struct { search string scope string - expectedResult []searchResult + expectedResult []SearchResult }{ { search: "audio", scope: "/new/", - expectedResult: []searchResult{ + expectedResult: []SearchResult{ { Path: "test/audio.wav", Type: "audio", @@ -135,7 +135,7 @@ func TestSearchIndexes(t *testing.T) { { search: "test", scope: "/", - expectedResult: []searchResult{ + expectedResult: []SearchResult{ { Path: "test/", Type: "directory", @@ -151,7 +151,7 @@ func TestSearchIndexes(t *testing.T) { { search: "archive", scope: "/", - expectedResult: []searchResult{ + expectedResult: []SearchResult{ { Path: "firstDir/archive.zip", Type: "archive", @@ -167,7 +167,7 @@ func TestSearchIndexes(t *testing.T) { { search: "arch", scope: "/firstDir", - expectedResult: []searchResult{ + expectedResult: []SearchResult{ { Path: "archive.zip", Type: "archive", @@ -178,7 +178,7 @@ func TestSearchIndexes(t *testing.T) { { search: "isdir", scope: "/", - expectedResult: []searchResult{ + expectedResult: []SearchResult{ { Path: "firstDir/thisIsDir/", Type: "directory", @@ -189,7 +189,7 @@ func TestSearchIndexes(t *testing.T) { { search: "IsDir type:largerThan=1", scope: "/", - expectedResult: []searchResult{ + expectedResult: []SearchResult{ { Path: "firstDir/thisIsDir/", Type: "directory", @@ -200,7 +200,7 @@ func TestSearchIndexes(t *testing.T) { { search: "video", scope: "/", - expectedResult: []searchResult{ + expectedResult: []SearchResult{ { Path: "new/test/video.MP4", Type: "video", diff --git a/backend/files/sync.go b/backend/files/sync.go index 9205fe1d..5619826a 100644 --- a/backend/files/sync.go +++ b/backend/files/sync.go @@ -3,7 +3,7 @@ package files import ( "path/filepath" - "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/backend/settings" ) // UpdateFileMetadata updates the FileInfo for the specified directory in the index. diff --git a/backend/go.mod b/backend/go.mod index 56efcfb6..57993455 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -1,4 +1,4 @@ -module github.com/gtsteffaniak/filebrowser +module github.com/gtsteffaniak/filebrowser/backend go 1.22.5 @@ -7,7 +7,8 @@ require ( github.com/disintegration/imaging v1.6.2 github.com/dsoprea/go-exif/v3 v3.0.1 github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 - github.com/goccy/go-yaml v1.15.6 + github.com/gabriel-vasile/mimetype v1.4.7 + github.com/goccy/go-yaml v1.15.7 github.com/golang-jwt/jwt/v4 v4.5.1 github.com/google/go-cmp v0.6.0 github.com/shirou/gopsutil/v3 v3.24.5 @@ -17,9 +18,9 @@ require ( github.com/stretchr/testify v1.9.0 github.com/swaggo/http-swagger v1.3.4 github.com/swaggo/swag v1.16.4 - golang.org/x/crypto v0.29.0 - golang.org/x/image v0.22.0 - golang.org/x/text v0.20.0 + golang.org/x/crypto v0.30.0 + golang.org/x/image v0.23.0 + golang.org/x/text v0.21.0 ) require ( @@ -43,9 +44,9 @@ require ( github.com/swaggo/files v1.0.1 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect go.etcd.io/bbolt v1.3.11 // indirect - golang.org/x/net v0.31.0 // indirect - golang.org/x/sys v0.27.0 // indirect - golang.org/x/tools v0.27.0 // indirect + golang.org/x/net v0.32.0 // indirect + golang.org/x/sys v0.28.0 // indirect + golang.org/x/tools v0.28.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/backend/go.sum b/backend/go.sum index e8b769a5..3175e34a 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -30,6 +30,8 @@ github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 h1:DilThiXje github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349/go.mod h1:4GC5sXji84i/p+irqghpPFZBF8tRN/Q7+700G0/DLe8= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568 h1:BHsljHzVlRcyQhjrss6TZTdY2VfCqZPbv5k3iBFa2ZQ= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/gabriel-vasile/mimetype v1.4.7 h1:SKFKl7kD0RiPdbht0s7hFtjl489WcQ1VyPW8ZzUMYCA= +github.com/gabriel-vasile/mimetype v1.4.7/go.mod h1:GDlAgAyIRT27BhFl53XNAFtfjzOkLaF35JdEG0P7LtU= 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= @@ -47,8 +49,8 @@ github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9Z github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk= github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/goccy/go-yaml v1.15.6 h1:gy5kf1yjMia3/c3wWD+u1z3lU5XlhpT8FZGaLJU9cOA= -github.com/goccy/go-yaml v1.15.6/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= +github.com/goccy/go-yaml v1.15.7 h1:L7XuKpd/A66X4w/dlk08lVfiIADdy79a1AzRoIefC98= +github.com/goccy/go-yaml v1.15.7/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/golang-jwt/jwt/v4 v4.5.1 h1:JdqV9zKUdtaa9gdPlywC3aeoEsR681PlKC+4F5gQgeo= github.com/golang-jwt/jwt/v4 v4.5.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI= @@ -114,11 +116,11 @@ 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.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ= -golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg= +golang.org/x/crypto v0.30.0 h1:RwoQn3GkWiMkzlX562cLB7OxWvjH1L8xutO2WoJcRoY= +golang.org/x/crypto v0.30.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/image v0.22.0 h1:UtK5yLUzilVrkjMAZAZ34DXGpASN8i8pj8g+O+yd10g= -golang.org/x/image v0.22.0/go.mod h1:9hPFhljd4zZ1GNSIZJ49sqbp45GKK9t6w+iXvGqZUz4= +golang.org/x/image v0.23.0 h1:HseQ7c2OpPKTPVzNjG5fwJsOTCiiwS4QdsYi5XU6H68= +golang.org/x/image v0.23.0/go.mod h1:wJJBTdLfCCf3tiHa1fNxpZmUI4mmoZvwMCPP0ddoNKY= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= @@ -133,12 +135,12 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.0.0-20221002022538-bcab6841153b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo= -golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM= +golang.org/x/net v0.32.0 h1:ZqPmj8Kzc+Y6e0+skZsuACbx+wzMgo5MQsJh9Qd6aYI= +golang.org/x/net v0.32.0/go.mod h1:CwU0IoeOlnQQWJ6ioyFrfRuomB8GKF6KbYXZVyeXNfs= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ= -golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.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-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -153,8 +155,8 @@ golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/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.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= -golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -163,13 +165,13 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug= -golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.27.0 h1:qEKojBykQkQ4EynWy4S8Weg69NumxKdn40Fce3uc/8o= -golang.org/x/tools v0.27.0/go.mod h1:sUi0ZgbwW9ZPAq26Ekut+weQPR5eIM6GQLQ1Yjm1H0Q= +golang.org/x/tools v0.28.0 h1:WuB6qZ4RPCQo5aP3WdKZS7i595EdWqWR8vqJTlwTVK8= +golang.org/x/tools v0.28.0/go.mod h1:dcIOrVd3mfQKTgrDVQHqCPMWy6lnhfhtX3hLXYVLfRw= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= diff --git a/backend/http/api.go b/backend/http/api.go index 6f12acfb..64ae9524 100644 --- a/backend/http/api.go +++ b/backend/http/api.go @@ -7,7 +7,7 @@ import ( "strings" "time" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/users" ) func createApiKeyHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) { diff --git a/backend/http/auth.go b/backend/http/auth.go index 1b01d4b7..cabf36c8 100644 --- a/backend/http/auth.go +++ b/backend/http/auth.go @@ -16,11 +16,11 @@ import ( "github.com/golang-jwt/jwt/v4/request" "golang.org/x/crypto/bcrypt" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/share" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/share" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) var ( diff --git a/backend/http/middleware.go b/backend/http/middleware.go index cf0abf00..b2764e53 100644 --- a/backend/http/middleware.go +++ b/backend/http/middleware.go @@ -11,10 +11,10 @@ import ( "time" "github.com/golang-jwt/jwt/v4" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/runner" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/runner" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/users" ) type requestContext struct { diff --git a/backend/http/middleware_test.go b/backend/http/middleware_test.go index a14a53fc..283bb7f5 100644 --- a/backend/http/middleware_test.go +++ b/backend/http/middleware_test.go @@ -8,15 +8,15 @@ import ( "time" "github.com/asdine/storm/v3" - "github.com/gtsteffaniak/filebrowser/diskcache" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/img" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/share" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/storage/bolt" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/diskcache" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/img" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/share" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/storage/bolt" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) func setupTestEnv(t *testing.T) { diff --git a/backend/http/preview.go b/backend/http/preview.go index 8cb082e2..08e24423 100644 --- a/backend/http/preview.go +++ b/backend/http/preview.go @@ -8,9 +8,10 @@ import ( "net/http" "os" "path/filepath" + "strings" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/img" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/img" ) type ImgService interface { @@ -64,7 +65,7 @@ func previewHandler(w http.ResponseWriter, r *http.Request, d *requestContext) ( return http.StatusBadRequest, fmt.Errorf("can't create preview for directory") } setContentDisposition(w, r, fileInfo.Name) - if fileInfo.Type != "image" { + if !strings.HasPrefix(fileInfo.Type, "image") { return http.StatusNotImplemented, fmt.Errorf("can't create preview for %s type", fileInfo.Type) } diff --git a/backend/http/public.go b/backend/http/public.go index 748137b4..f68192a3 100644 --- a/backend/http/public.go +++ b/backend/http/public.go @@ -6,11 +6,11 @@ import ( "net/http" "strings" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/users" - _ "github.com/gtsteffaniak/filebrowser/swagger/docs" + _ "github.com/gtsteffaniak/filebrowser/backend/swagger/docs" ) func publicShareHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) { diff --git a/backend/http/raw.go b/backend/http/raw.go index 24e937db..5116f0c8 100644 --- a/backend/http/raw.go +++ b/backend/http/raw.go @@ -5,6 +5,7 @@ import ( "archive/zip" "compress/gzip" "errors" + "fmt" "io" "log" "net/http" @@ -13,7 +14,7 @@ import ( "path/filepath" "strings" - "github.com/gtsteffaniak/filebrowser/files" + "github.com/gtsteffaniak/filebrowser/backend/files" ) func setContentDisposition(w http.ResponseWriter, r *http.Request, fileName string) { @@ -44,7 +45,12 @@ func rawHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, if !d.user.Perm.Download { return http.StatusAccepted, nil } - files := r.URL.Query().Get("files") + encodedFiles := r.URL.Query().Get("files") + // Decode the URL-encoded path + files, err := url.QueryUnescape(encodedFiles) + if err != nil { + return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err) + } return rawFilesHandler(w, r, d, strings.Split(files, ",")) } diff --git a/backend/http/resource.go b/backend/http/resource.go index e38569f8..cf51d611 100644 --- a/backend/http/resource.go +++ b/backend/http/resource.go @@ -13,9 +13,9 @@ import ( "github.com/shirou/gopsutil/v3/disk" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) // resourceGetHandler retrieves information about a resource. @@ -33,9 +33,13 @@ import ( // @Failure 500 {object} map[string]string "Internal server error" // @Router /api/resources [get] func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) { - // TODO source := r.URL.Query().Get("source") - path := r.URL.Query().Get("path") + encodedPath := r.URL.Query().Get("path") + // Decode the URL-encoded path + path, err := url.QueryUnescape(encodedPath) + if err != nil { + return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err) + } fileInfo, err := files.FileInfoFaster(files.FileOptions{ Path: filepath.Join(d.user.Scope, path), Modify: d.user.Perm.Modify, @@ -78,7 +82,12 @@ func resourceGetHandler(w http.ResponseWriter, r *http.Request, d *requestContex // @Router /api/resources [delete] func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) { // TODO source := r.URL.Query().Get("source") - path := r.URL.Query().Get("path") + encodedPath := r.URL.Query().Get("path") + // Decode the URL-encoded path + path, err := url.QueryUnescape(encodedPath) + if err != nil { + return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err) + } if path == "/" || !d.user.Perm.Delete { return http.StatusForbidden, nil } @@ -87,7 +96,7 @@ func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestCon return http.StatusNotFound, err } fileOpts := files.FileOptions{ - Path: filepath.Join(d.user.Scope, path), + Path: realPath, IsDir: isDir, Modify: d.user.Perm.Modify, Expand: false, @@ -130,7 +139,12 @@ func resourceDeleteHandler(w http.ResponseWriter, r *http.Request, d *requestCon // @Router /api/resources [post] func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) { // TODO source := r.URL.Query().Get("source") - path := r.URL.Query().Get("path") + encodedPath := r.URL.Query().Get("path") + // Decode the URL-encoded path + path, err := url.QueryUnescape(encodedPath) + if err != nil { + return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err) + } if !d.user.Perm.Create || !d.user.Check(path) { return http.StatusForbidden, nil } @@ -142,7 +156,7 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte } // Directories creation on POST. if strings.HasSuffix(path, "/") { - err := files.WriteDirectory(fileOpts) + err = files.WriteDirectory(fileOpts) if err != nil { return errToStatus(err), err } @@ -188,7 +202,13 @@ func resourcePostHandler(w http.ResponseWriter, r *http.Request, d *requestConte // @Router /api/resources [put] func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) { // TODO source := r.URL.Query().Get("source") - path := r.URL.Query().Get("path") + // TODO source := r.URL.Query().Get("source") + encodedPath := r.URL.Query().Get("path") + // Decode the URL-encoded path + path, err := url.QueryUnescape(encodedPath) + if err != nil { + return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err) + } if !d.user.Perm.Modify || !d.user.Check(path) { return http.StatusForbidden, nil } @@ -233,16 +253,21 @@ func resourcePutHandler(w http.ResponseWriter, r *http.Request, d *requestContex // @Router /api/resources [patch] func resourcePatchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) { // TODO source := r.URL.Query().Get("source") - src := r.URL.Query().Get("from") - dst := r.URL.Query().Get("destination") action := r.URL.Query().Get("action") - dst, err := url.QueryUnescape(dst) - if !d.user.Check(src) || !d.user.Check(dst) { - return http.StatusForbidden, nil + encodedFrom := r.URL.Query().Get("from") + // Decode the URL-encoded path + src, err := url.QueryUnescape(encodedFrom) + if err != nil { + return http.StatusBadRequest, fmt.Errorf("invalid path encoding: %v", err) } + dst := r.URL.Query().Get("destination") + dst, err = url.QueryUnescape(dst) if err != nil { return errToStatus(err), err } + if !d.user.Check(src) || !d.user.Check(dst) { + return http.StatusForbidden, nil + } if dst == "/" || src == "/" { return http.StatusForbidden, nil } @@ -298,13 +323,14 @@ func delThumbs(ctx context.Context, fileCache FileCache, file *files.FileInfo) e func patchAction(ctx context.Context, action, src, dst string, d *requestContext, fileCache FileCache, isSrcDir bool) error { switch action { - // TODO: use enum case "copy": if !d.user.Perm.Create { return errors.ErrPermissionDenied } - return files.CopyResource(src, dst, isSrcDir) + err := files.CopyResource(src, dst, isSrcDir) + return err case "rename", "move": + if !d.user.Perm.Rename { return errors.ErrPermissionDenied } @@ -378,7 +404,9 @@ func diskUsage(w http.ResponseWriter, r *http.Request, d *requestContext) (int, } func inspectIndex(w http.ResponseWriter, r *http.Request) { - path := r.URL.Query().Get("path") + encodedPath := r.URL.Query().Get("path") + // Decode the URL-encoded path + path, _ := url.QueryUnescape(encodedPath) isDir := r.URL.Query().Get("isDir") == "true" index := files.GetIndex(config.Server.Root) info, _ := index.GetReducedMetadata(path, isDir) diff --git a/backend/http/router.go b/backend/http/router.go index 30c14907..6a4fdfbc 100644 --- a/backend/http/router.go +++ b/backend/http/router.go @@ -10,9 +10,9 @@ import ( "os" "text/template" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/version" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/version" httpSwagger "github.com/swaggo/http-swagger" // http-swagger middleware ) diff --git a/backend/http/search.go b/backend/http/search.go index 35487338..22eb5942 100644 --- a/backend/http/search.go +++ b/backend/http/search.go @@ -4,8 +4,8 @@ import ( "net/http" "strings" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/settings" ) // searchHandler handles search requests for files based on the provided query. @@ -49,7 +49,7 @@ import ( // @Param query query string true "Search query" // @Param scope query string false "path within user scope to search, for example '/first/second' to search within the second directory only" // @Param SessionId header string false "User session ID, add unique value to prevent collisions" -// @Success 200 {array} files.searchResult "List of search results" +// @Success 200 {array} files.SearchResult "List of search results" // @Failure 400 {object} map[string]string "Bad Request" // @Router /api/search [get] func searchHandler(w http.ResponseWriter, r *http.Request, d *requestContext) (int, error) { diff --git a/backend/http/settings.go b/backend/http/settings.go index b2f7efc7..c6bb1587 100644 --- a/backend/http/settings.go +++ b/backend/http/settings.go @@ -4,8 +4,8 @@ import ( "encoding/json" "net/http" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/users" ) type settingsData struct { diff --git a/backend/http/share.go b/backend/http/share.go index e04d470b..ab9e6684 100644 --- a/backend/http/share.go +++ b/backend/http/share.go @@ -12,8 +12,8 @@ import ( "golang.org/x/crypto/bcrypt" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/share" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/share" ) // shareListHandler returns a list of all share links. diff --git a/backend/http/static.go b/backend/http/static.go index c0a055ef..065bed43 100644 --- a/backend/http/static.go +++ b/backend/http/static.go @@ -12,9 +12,9 @@ import ( "strings" "text/template" - "github.com/gtsteffaniak/filebrowser/auth" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/version" + "github.com/gtsteffaniak/filebrowser/backend/auth" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/version" ) var templateRenderer *TemplateRenderer diff --git a/backend/http/users.go b/backend/http/users.go index bcae5319..d42709e5 100644 --- a/backend/http/users.go +++ b/backend/http/users.go @@ -12,10 +12,10 @@ import ( "golang.org/x/text/cases" "golang.org/x/text/language" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/storage" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/storage" + "github.com/gtsteffaniak/filebrowser/backend/users" ) var ( diff --git a/backend/http/utils.go b/backend/http/utils.go index 0aec1c9f..a410c148 100644 --- a/backend/http/utils.go +++ b/backend/http/utils.go @@ -5,7 +5,7 @@ import ( "net/http" "os" - libErrors "github.com/gtsteffaniak/filebrowser/errors" + libErrors "github.com/gtsteffaniak/filebrowser/backend/errors" ) func errToStatus(err error) int { diff --git a/backend/img/service.go b/backend/img/service.go index be3a7fd3..f699c0a6 100644 --- a/backend/img/service.go +++ b/backend/img/service.go @@ -261,3 +261,22 @@ func getEmbeddedThumbnail(in io.Reader) ([]byte, io.Reader, error) { thm, err := ifd.Thumbnail() return thm, wrappedReader, err } + +// CreateThumbnail takes raw image data and creates a thumbnail image. +func CreateThumbnail(rawData io.Reader, width, height int) (image.Image, error) { + // Decode the raw image to get an image.Image. + img, _, err := image.Decode(rawData) + if err != nil { + return nil, fmt.Errorf("failed to decode image: %w", err) + } + + // Resize the image to create a thumbnail using the specified dimensions. + thumb := imaging.Fit(img, width, height, imaging.Lanczos) + + // Optionally, convert the thumbnail to grayscale if needed. + // Uncomment the line below if you want the result to be grayscale. + // thumb = imaging.Grayscale(thumb) + + // Return the resized thumbnail image. + return thumb, nil +} diff --git a/backend/main.go b/backend/main.go index 9f606708..77172ca2 100644 --- a/backend/main.go +++ b/backend/main.go @@ -1,7 +1,7 @@ package main import ( - "github.com/gtsteffaniak/filebrowser/cmd" + "github.com/gtsteffaniak/filebrowser/backend/cmd" ) func main() { diff --git a/backend/runner/parser.go b/backend/runner/parser.go index 84357cc5..73941730 100644 --- a/backend/runner/parser.go +++ b/backend/runner/parser.go @@ -3,7 +3,7 @@ package runner import ( "os/exec" - "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/backend/settings" ) // ParseCommand parses the command taking in account if the current diff --git a/backend/runner/runner.go b/backend/runner/runner.go index 369232c7..c70f30bb 100644 --- a/backend/runner/runner.go +++ b/backend/runner/runner.go @@ -7,9 +7,9 @@ import ( "os/exec" "strings" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/users" ) // Runner is a commands runner. diff --git a/backend/settings/config.go b/backend/settings/config.go index bfb4a5a9..e8f10d2f 100644 --- a/backend/settings/config.go +++ b/backend/settings/config.go @@ -7,7 +7,7 @@ import ( "strings" "github.com/goccy/go-yaml" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/users" ) var Config Settings diff --git a/backend/settings/dir_test.go b/backend/settings/dir_test.go index f55e0074..627f660c 100644 --- a/backend/settings/dir_test.go +++ b/backend/settings/dir_test.go @@ -3,7 +3,7 @@ package settings import ( "testing" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/users" ) func TestSettings_MakeUserDir(t *testing.T) { diff --git a/backend/settings/settings.go b/backend/settings/settings.go index b5bedecf..dfd50ac0 100644 --- a/backend/settings/settings.go +++ b/backend/settings/settings.go @@ -3,7 +3,7 @@ package settings import ( "crypto/rand" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/users" ) const DefaultUsersHomeBasePath = "/users" diff --git a/backend/settings/storage.go b/backend/settings/storage.go index 8d0f6f37..47d638d4 100644 --- a/backend/settings/storage.go +++ b/backend/settings/storage.go @@ -1,8 +1,8 @@ package settings import ( - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/users" ) // StorageBackend is a settings storage backend. diff --git a/backend/settings/structs.go b/backend/settings/structs.go index 78d3304a..16377433 100644 --- a/backend/settings/structs.go +++ b/backend/settings/structs.go @@ -1,7 +1,7 @@ package settings import ( - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/users" ) type Settings struct { diff --git a/backend/share/storage.go b/backend/share/storage.go index 41424242..89b77adc 100644 --- a/backend/share/storage.go +++ b/backend/share/storage.go @@ -3,7 +3,7 @@ package share import ( "time" - "github.com/gtsteffaniak/filebrowser/errors" + "github.com/gtsteffaniak/filebrowser/backend/errors" ) // StorageBackend is the interface to implement for a share storage. diff --git a/backend/storage/bolt/auth.go b/backend/storage/bolt/auth.go index c843d825..5ddd5fcb 100644 --- a/backend/storage/bolt/auth.go +++ b/backend/storage/bolt/auth.go @@ -2,8 +2,8 @@ package bolt import ( "github.com/asdine/storm/v3" - "github.com/gtsteffaniak/filebrowser/auth" - "github.com/gtsteffaniak/filebrowser/errors" + "github.com/gtsteffaniak/filebrowser/backend/auth" + "github.com/gtsteffaniak/filebrowser/backend/errors" ) type authBackend struct { diff --git a/backend/storage/bolt/bolt.go b/backend/storage/bolt/bolt.go index f771785f..55148e9d 100644 --- a/backend/storage/bolt/bolt.go +++ b/backend/storage/bolt/bolt.go @@ -3,10 +3,10 @@ package bolt import ( "github.com/asdine/storm/v3" - "github.com/gtsteffaniak/filebrowser/auth" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/share" - "github.com/gtsteffaniak/filebrowser/users" + "github.com/gtsteffaniak/filebrowser/backend/auth" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/share" + "github.com/gtsteffaniak/filebrowser/backend/users" ) // NewStorage creates a storage.Storage based on Bolt DB. diff --git a/backend/storage/bolt/config.go b/backend/storage/bolt/config.go index 93e896e9..d41cdb65 100644 --- a/backend/storage/bolt/config.go +++ b/backend/storage/bolt/config.go @@ -2,7 +2,7 @@ package bolt import ( "github.com/asdine/storm/v3" - "github.com/gtsteffaniak/filebrowser/settings" + "github.com/gtsteffaniak/filebrowser/backend/settings" ) type settingsBackend struct { diff --git a/backend/storage/bolt/share.go b/backend/storage/bolt/share.go index b08b2dc4..e054139b 100644 --- a/backend/storage/bolt/share.go +++ b/backend/storage/bolt/share.go @@ -4,8 +4,8 @@ import ( "github.com/asdine/storm/v3" "github.com/asdine/storm/v3/q" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/share" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/share" ) type shareBackend struct { diff --git a/backend/storage/bolt/users.go b/backend/storage/bolt/users.go index 4ec2ea76..02d77f1d 100644 --- a/backend/storage/bolt/users.go +++ b/backend/storage/bolt/users.go @@ -6,9 +6,9 @@ import ( "github.com/asdine/storm/v3" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) type usersBackend struct { diff --git a/backend/storage/bolt/utils.go b/backend/storage/bolt/utils.go index 40e3a1eb..66c61031 100644 --- a/backend/storage/bolt/utils.go +++ b/backend/storage/bolt/utils.go @@ -3,7 +3,7 @@ package bolt import ( "github.com/asdine/storm/v3" - "github.com/gtsteffaniak/filebrowser/errors" + "github.com/gtsteffaniak/filebrowser/backend/errors" ) func get(db *storm.DB, name string, to interface{}) error { diff --git a/backend/storage/storage.go b/backend/storage/storage.go index 5a83a6d3..62eb6af1 100644 --- a/backend/storage/storage.go +++ b/backend/storage/storage.go @@ -7,14 +7,14 @@ import ( "path/filepath" "github.com/asdine/storm/v3" - "github.com/gtsteffaniak/filebrowser/auth" - "github.com/gtsteffaniak/filebrowser/errors" - "github.com/gtsteffaniak/filebrowser/files" - "github.com/gtsteffaniak/filebrowser/settings" - "github.com/gtsteffaniak/filebrowser/share" - "github.com/gtsteffaniak/filebrowser/storage/bolt" - "github.com/gtsteffaniak/filebrowser/users" - "github.com/gtsteffaniak/filebrowser/utils" + "github.com/gtsteffaniak/filebrowser/backend/auth" + "github.com/gtsteffaniak/filebrowser/backend/errors" + "github.com/gtsteffaniak/filebrowser/backend/files" + "github.com/gtsteffaniak/filebrowser/backend/settings" + "github.com/gtsteffaniak/filebrowser/backend/share" + "github.com/gtsteffaniak/filebrowser/backend/storage/bolt" + "github.com/gtsteffaniak/filebrowser/backend/users" + "github.com/gtsteffaniak/filebrowser/backend/utils" ) // Storage is a storage powered by a Backend which makes the necessary diff --git a/backend/swagger/docs/docs.go b/backend/swagger/docs/docs.go index c90cd8aa..4ae596d9 100644 --- a/backend/swagger/docs/docs.go +++ b/backend/swagger/docs/docs.go @@ -579,7 +579,7 @@ const docTemplate = `{ "schema": { "type": "array", "items": { - "$ref": "#/definitions/files.searchResult" + "$ref": "#/definitions/files.SearchResult" } } }, @@ -1186,7 +1186,7 @@ const docTemplate = `{ } } }, - "files.searchResult": { + "files.SearchResult": { "type": "object", "properties": { "path": { diff --git a/backend/swagger/docs/swagger.json b/backend/swagger/docs/swagger.json index 2c86b105..438ec6d3 100644 --- a/backend/swagger/docs/swagger.json +++ b/backend/swagger/docs/swagger.json @@ -568,7 +568,7 @@ "schema": { "type": "array", "items": { - "$ref": "#/definitions/files.searchResult" + "$ref": "#/definitions/files.SearchResult" } } }, @@ -1175,7 +1175,7 @@ } } }, - "files.searchResult": { + "files.SearchResult": { "type": "object", "properties": { "path": { diff --git a/backend/swagger/docs/swagger.yaml b/backend/swagger/docs/swagger.yaml index ff532c06..25345ba6 100644 --- a/backend/swagger/docs/swagger.yaml +++ b/backend/swagger/docs/swagger.yaml @@ -31,7 +31,7 @@ definitions: type: type: string type: object - files.searchResult: + files.SearchResult: properties: path: type: string @@ -658,7 +658,7 @@ paths: description: List of search results schema: items: - $ref: '#/definitions/files.searchResult' + $ref: '#/definitions/files.SearchResult' type: array "400": description: Bad Request diff --git a/backend/users/storage.go b/backend/users/storage.go index 9b9029bb..ca5457cf 100644 --- a/backend/users/storage.go +++ b/backend/users/storage.go @@ -4,7 +4,7 @@ import ( "sync" "time" - "github.com/gtsteffaniak/filebrowser/errors" + "github.com/gtsteffaniak/filebrowser/backend/errors" ) // StorageBackend is the interface to implement for a users storage. diff --git a/docs/roadmap.md b/docs/roadmap.md index f18e136d..9c6f985c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -1,18 +1,17 @@ # Planned Roadmap upcoming 0.3.x releases, ordered by priority: - -- More filetype icons and refreshed icons. -- more filetype previews - eg. office, photoshop, vector, 3d files. -- Enable mobile search with same features as desktop -- Enable mobile search with same features as desktop -- Theme configuration from settings -- introduce jobs as replacement to runners. - - Add Job status to the sidebar - - index status. - - Job status from users - - upload status -- opentelemetry metrics + - more indexing flexability + - option not to index hidden files/folders + - options folders to include/exclude from indexing + - implement more indexing runners for more efficienct filesystem watching + - more filetype previews: eg. raw img, office, photoshop, vector, 3d files. + - introduce jobs as replacement to runners. + - Add Job status to the sidebar + - index status. + - Job status from users + - upload status + - opentelemetry metrics Unplanned Future releases: - multiple sources https://github.com/filebrowser/filebrowser/issues/2514 diff --git a/frontend/package.json b/frontend/package.json index 92c2128a..988da94a 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -25,6 +25,7 @@ "css-vars-ponyfill": "^2.4.3", "file-loader": "^6.2.0", "material-icons": "^1.10.5", + "material-symbols": "^0.27.2", "normalize.css": "^8.0.1", "qrcode.vue": "^3.4.1", "vue": "^3.4.21", diff --git a/frontend/src/api/files.js b/frontend/src/api/files.js index 19438d2f..a5a2ae48 100644 --- a/frontend/src/api/files.js +++ b/frontend/src/api/files.js @@ -6,7 +6,7 @@ import { notify } from "@/notify"; // Notify if errors occur export async function fetchFiles(url, content = false) { try { - let path = removePrefix(url, "files"); + let path = encodeURIComponent(removePrefix(url, "files")); const apiPath = getApiPath("api/resources",{path: path, content: content}); const res = await fetchURL(apiPath); const data = await res.json(); @@ -24,7 +24,8 @@ async function resourceAction(url, method, content) { if (content) { opts.body = content; } - const apiPath = getApiPath("api/resources", { path: url }); + let path = encodeURIComponent(removePrefix(url, "files")); + const apiPath = getApiPath("api/resources", { path: path }); const res = await fetchURL(apiPath, opts); return res; } catch (err) { @@ -35,7 +36,8 @@ async function resourceAction(url, method, content) { export async function remove(url) { try { - return await resourceAction(url, "DELETE"); + let path = encodeURIComponent(removePrefix(url, "files")); + return await resourceAction(path, "DELETE"); } catch (err) { notify.showError(err.message || "Error deleting resource"); throw err; @@ -44,7 +46,8 @@ export async function remove(url) { export async function put(url, content = "") { try { - return await resourceAction(url, "PUT", content); + let path = encodeURIComponent(removePrefix(url, "files")); + return await resourceAction(path, "PUT", content); } catch (err) { notify.showError(err.message || "Error putting resource"); throw err; @@ -58,14 +61,14 @@ export function download(format, files) { try { let fileargs = ""; if (files.length === 1) { - fileargs = removePrefix(files[0], "files") + fileargs = decodeURI(removePrefix(files[0], "files")) } else { for (let file of files) { - fileargs += removePrefix(file,"files") + ","; + fileargs += decodeURI(removePrefix(file,"files")) + ","; } fileargs = fileargs.substring(0, fileargs.length - 1); } - const apiPath = getApiPath("api/raw", { files: fileargs, algo: format }); + const apiPath = getApiPath("api/raw", { files: encodeURIComponent(fileargs), algo: format }); const url = window.origin+apiPath window.open(url); } catch (err) { @@ -130,9 +133,11 @@ export async function moveCopy(items, action = "copy", overwrite = false, rename } try { for (let item of items) { + let toPath = encodeURIComponent(removePrefix(decodeURI(item.to), "files")); + let fromPath = encodeURIComponent(removePrefix(decodeURI(item.from), "files")); let localParams = { ...params }; - localParams.destination = item.to; - localParams.from = item.from; + localParams.destination = toPath; + localParams.from = fromPath; const apiPath = getApiPath("api/resources", localParams); promises.push(fetch(apiPath, { method: "PATCH" })); } @@ -157,7 +162,7 @@ export async function checksum(url, algo) { export function getDownloadURL(path, inline) { try { const params = { - files: removePrefix(path,"files"), + files: encodeURIComponent(removePrefix(decodeURI(path),"files")), ...(inline && { inline: "true" }), }; const apiPath = getApiPath("api/raw", params); @@ -171,7 +176,7 @@ export function getDownloadURL(path, inline) { export function getPreviewURL(path, size, modified) { try { const params = { - path: path, + path: encodeURIComponent(removePrefix(decodeURI(path),"files")), size: size, key: Date.parse(modified), inline: "true", @@ -190,7 +195,7 @@ export function getSubtitlesURL(file) { for (const sub of file.subtitles) { const params = { inline: "true", - path: sub + path: encodeURIComponent(removePrefix(sub,"files")) }; const apiPath = getApiPath("api/raw", params); return window.origin+apiPath diff --git a/frontend/src/api/utils.js b/frontend/src/api/utils.js index 97813696..335f2034 100644 --- a/frontend/src/api/utils.js +++ b/frontend/src/api/utils.js @@ -61,7 +61,6 @@ export async function fetchJSON(url, opts) { export function adjustedData(data, url) { data.url = url; - if (data.type === "directory") { if (!data.url.endsWith("/")) data.url += "/"; diff --git a/frontend/src/components/ContextMenu.vue b/frontend/src/components/ContextMenu.vue index d21d4020..ee410c1d 100644 --- a/frontend/src/components/ContextMenu.vue +++ b/frontend/src/components/ContextMenu.vue @@ -8,7 +8,7 @@ left: `${left}px`, }" class="button" - :class="{ 'dark-mode': isDarkMode, mobile: isMobile }" + :class="{ 'dark-mode': isDarkMode, centered: centered }" >
{{ selectedCount }} selected @@ -108,8 +108,8 @@ export default { user() { return state.user; }, - isMobile() { - return getters.isMobile(); + centered() { + return getters.isMobile() || ( !this.posX || !this.posY ); }, showContext() { if (getters.currentPromptName() == "ContextMenu" && state.prompts != []) { @@ -158,7 +158,6 @@ export default { return mutations.showHover(value); }, setPositions() { - console.log("Setting positions"); const contextProps = getters.currentPrompt().props; let tempX = contextProps.posX; let tempY = contextProps.posY; @@ -204,7 +203,7 @@ export default { justify-content: center; } -#context-menu.mobile { +#context-menu.centered { top: 50% !important; left: 50% !important; -webkit-transform: translate(-50%, -50%); diff --git a/frontend/src/components/Icon.vue b/frontend/src/components/Icon.vue new file mode 100644 index 00000000..5381beb6 --- /dev/null +++ b/frontend/src/components/Icon.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/frontend/src/components/Search.vue b/frontend/src/components/Search.vue index 6e4beb1c..e0d2b19f 100644 --- a/frontend/src/components/Search.vue +++ b/frontend/src/components/Search.vue @@ -3,15 +3,30 @@
- search - +
@@ -21,29 +36,52 @@
- +
- - + +

Smaller Than:

- +

MB

Larger Than:

- +

MB

-
@@ -78,28 +116,14 @@
@@ -112,6 +136,7 @@ import ButtonGroup from "./ButtonGroup.vue"; import { search } from "@/api"; import { getters, mutations, state } from "@/store"; import { getHumanReadableFilesize } from "@/utils/filesizes"; +import Icon from "@/components/Icon.vue"; var boxes = { folder: { label: "folders", icon: "folder" }, @@ -126,6 +151,7 @@ var boxes = { export default { components: { ButtonGroup, + Icon, }, name: "search", data: function () { @@ -147,9 +173,7 @@ export default { { label: "Documents", value: "type:doc" }, { label: "Archives", value: "type:archive" }, ], - toggleOptionButton: [ - { label: "Show Options" }, - ], + toggleOptionButton: [{ label: "Show Options" }], value: "", ongoing: false, results: [], @@ -192,7 +216,7 @@ export default { }, computed: { showOptions() { - return !this.hiddenOptions || !this.isMobile + return !this.hiddenOptions || !this.isMobile; }, isMobile() { return state.isMobile; @@ -241,17 +265,19 @@ export default { }, }, methods: { + getRelative(path) { + return window.location.href + "/" + path; + }, + getIcon(mimetype) { + return getMaterialIconForType(mimetype); + }, enableOptions() { - this.hiddenOptions = false - this.toggleOptionButton = [ - { label: "Hide Options" }, - ]; + this.hiddenOptions = false; + this.toggleOptionButton = [{ label: "Hide Options" }]; }, disableOptions() { - this.hiddenOptions = true - this.toggleOptionButton = [ - { label: "Show Options" }, - ]; + this.hiddenOptions = true; + this.toggleOptionButton = [{ label: "Show Options" }]; }, humanSize(size) { return getHumanReadableFilesize(size); @@ -372,7 +398,7 @@ export default { word-wrap: break-word; } -#results>#result-list { +#results > #result-list { max-height: 80vh; width: 35em; overflow: scroll; @@ -518,7 +544,7 @@ body.rtl #search #result { direction: ltr; } -#search #result>div>*:first-child { +#search #result > div > *:first-child { margin-top: 0; } @@ -528,7 +554,7 @@ body.rtl #search #result { } /* Search Results */ -body.rtl #search #result ul>* { +body.rtl #search #result ul > * { direction: ltr; text-align: left; } diff --git a/frontend/src/components/files/ListingItem.vue b/frontend/src/components/files/ListingItem.vue index 32301607..80973098 100644 --- a/frontend/src/components/files/ListingItem.vue +++ b/frontend/src/components/files/ListingItem.vue @@ -1,11 +1,11 @@ diff --git a/frontend/src/views/Share.vue b/frontend/src/views/Share.vue index c2cc3408..7d52abc3 100644 --- a/frontend/src/views/Share.vue +++ b/frontend/src/views/Share.vue @@ -149,6 +149,8 @@ import QrcodeVue from "qrcode.vue"; import Item from "@/components/files/ListingItem.vue"; import Clipboard from "clipboard"; import { state, getters, mutations } from "@/store"; +import { url } from "@/utils"; +import { getTypeInfo } from "@/utils/mimetype"; export default { name: "share", @@ -209,12 +211,11 @@ export default { }, icon() { if (state.req.type == "directory") return "folder"; - if (state.req.type === "image") return "insert_photo"; - if (state.req.type === "audio") return "volume_up"; - if (state.req.type === "video") return "movie"; + if (getTypeInfo(state.req.type).simpleType == "image") return "insert_photo"; + if (getTypeInfo(state.req.type).simpleType == "audio") return "volume_up"; + if (getTypeInfo(state.req.type).simpleType == "video") return "movie"; return "insert_drive_file"; }, - humanSize() { if (state.req.type == "directory") { return state.req.items.length; @@ -229,10 +230,13 @@ export default { return new Date(Date.parse(state.req.modified)).toLocaleString(); }, isImage() { - return state.req.type === "image"; + return getTypeInfo(state.req.type).simpleType === "image"; }, isMedia() { - return state.req.type === "video" || state.req.type === "audio"; + return ( + getTypeInfo(state.req.type).simpleType === "video" || + getTypeInfo(state.req.type).simpleType === "audio" + ); }, }, methods: { @@ -245,7 +249,7 @@ export default { }); }, base64(name) { - return window.btoa(unescape(encodeURIComponent(name))); + return url.base64Encode(name); }, async fetchData() { let urlPath = getters.routePath("share"); @@ -298,7 +302,7 @@ export default { download() { if (getters.isSingleFileSelected()) { const share = { - path: his.subPath, + path: this.subPath, hash: this.hash, token: this.token, format: null, diff --git a/frontend/src/views/bars/Default.vue b/frontend/src/views/bars/Default.vue index ba83cf8f..055f4694 100644 --- a/frontend/src/views/bars/Default.vue +++ b/frontend/src/views/bars/Default.vue @@ -15,7 +15,7 @@