From 9e9109984d242cb08a7ebabc2df5fd13421f88eb Mon Sep 17 00:00:00 2001
From: Graham Steffaniak <42989099+gtsteffaniak@users.noreply.github.com>
Date: Sat, 24 Aug 2024 17:02:33 -0500
Subject: [PATCH] v0.2.8 release (#193)
---
.github/workflows/main.yaml | 20 +-
.github/workflows/pr.yaml | 32 +-
.github/workflows/regular-tests.yaml | 8 +-
.github/workflows/release.yaml | 17 +-
.github/workflows/release_dev.yaml | 50 ++
.github/workflows/stale.yaml | 24 -
.gitignore | 2 +
CHANGELOG.md | 10 +
Dockerfile | 20 +-
Dockerfile.playwright | 19 +
README.md | 92 +++-
backend/.golangci.yml | 232 +++++++++
backend/.goreleaser.yaml | 7 +-
backend/cmd/root.go | 16 +-
backend/cmd/users_import.go | 5 -
backend/diskcache/file_cache.go | 32 +-
backend/diskcache/file_cache_test.go | 34 +-
backend/files/file.go | 241 ++++++---
backend/fileutils/copy.go | 20 +-
backend/fileutils/dir.go | 25 +-
backend/fileutils/file.go | 62 +--
backend/go.mod | 42 +-
backend/go.sum | 46 ++
backend/http/__debug_bin2682048437 | Bin 18089038 -> 0 bytes
backend/http/preview.go | 4 +-
backend/http/public.go | 40 +-
backend/http/public_test.go | 5 -
backend/http/raw.go | 17 +-
backend/http/resource.go | 186 +++----
backend/http/settings.go | 12 +-
backend/http/static.go | 8 +-
backend/http/users.go | 12 +-
backend/runner/runner.go | 5 +-
backend/settings/dir.go | 10 +-
backend/settings/storage.go | 4 -
backend/settings/structs.go | 1 +
backend/users/storage.go | 20 +-
backend/users/users.go | 26 +-
backend/version/version.go | 7 +-
configuration.md | 8 -
frontend/package.json | 3 +-
frontend/playwright.config.ts | 43 ++
frontend/src/App.vue | 9 +-
frontend/src/components/Breadcrumbs.vue | 30 +-
frontend/src/components/ProgressBar.vue | 11 +
frontend/src/components/Sidebar.vue | 461 ------------------
.../src/components/files/ExtendedImage.vue | 103 ++--
frontend/src/components/files/ListingItem.vue | 4 +-
frontend/src/components/prompts/Delete.vue | 4 +-
frontend/src/components/prompts/Move.vue | 3 +-
frontend/src/components/prompts/Prompts.vue | 2 +-
.../src/components/prompts/ReplaceRename.vue | 11 +-
frontend/src/components/settings/UserForm.vue | 14 +-
frontend/src/components/sidebar/General.vue | 320 ++++++++++++
frontend/src/components/sidebar/Settings.vue | 42 ++
frontend/src/components/sidebar/Sidebar.vue | 171 +++++++
frontend/src/css/base.css | 1 +
frontend/src/css/dashboard.css | 3 +-
frontend/src/css/header.css | 7 +-
frontend/src/css/listing.css | 39 +-
frontend/src/router/index.ts | 44 --
frontend/src/store/getters.js | 106 +++-
frontend/src/store/mutations.js | 53 +-
frontend/src/store/state.js | 25 +-
frontend/src/utils/constants.js | 12 +-
frontend/src/views/Files.vue | 8 +-
frontend/src/views/Layout.vue | 17 +-
frontend/src/views/Settings.vue | 103 ++--
frontend/src/views/Share.vue | 6 +-
frontend/src/views/bars/Default.vue | 24 +-
frontend/src/views/bars/EditorBar.vue | 5 +-
frontend/src/views/files/ListingView.vue | 136 +++---
frontend/src/views/files/Preview.vue | 24 +-
frontend/src/views/settings/Global.vue | 299 ++++--------
frontend/src/views/settings/Profile.vue | 56 ++-
frontend/src/views/settings/Shares.vue | 118 ++---
frontend/src/views/settings/User.vue | 78 +--
frontend/src/views/settings/UserColumn.vue | 106 ++++
frontend/src/views/settings/UserDefaults.vue | 87 ++++
frontend/src/views/settings/Users.vue | 80 ++-
frontend/tests/auth.spec.ts | 31 ++
frontend/tests/fixtures/auth.ts | 40 ++
makefile | 20 +-
roadmap.md | 10 +-
84 files changed, 2505 insertions(+), 1685 deletions(-)
create mode 100644 .github/workflows/release_dev.yaml
delete mode 100644 .github/workflows/stale.yaml
create mode 100644 Dockerfile.playwright
create mode 100644 backend/.golangci.yml
delete mode 100755 backend/http/__debug_bin2682048437
create mode 100644 frontend/playwright.config.ts
delete mode 100644 frontend/src/components/Sidebar.vue
create mode 100644 frontend/src/components/sidebar/General.vue
create mode 100644 frontend/src/components/sidebar/Settings.vue
create mode 100644 frontend/src/components/sidebar/Sidebar.vue
create mode 100644 frontend/src/views/settings/UserColumn.vue
create mode 100644 frontend/src/views/settings/UserDefaults.vue
create mode 100644 frontend/tests/auth.spec.ts
create mode 100644 frontend/tests/fixtures/auth.ts
diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index c6a29c5e..13233b09 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -1,4 +1,4 @@
-name: main job
+name: main release
on:
push:
@@ -16,12 +16,6 @@ jobs:
uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0
- # Workaround to fix error:
- # failed to push: failed to copy: io: read/write on closed pipe
- # See https://github.com/docker/build-push-action/issues/761
- with:
- driver-opts: |
- image=moby/buildkit:v0.10.6
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
@@ -32,12 +26,22 @@ jobs:
uses: docker/metadata-action@v5
with:
images: gtstef/filebrowser
+ - name: Get latest release tag and commit SHA
+ id: get_tag_and_sha
+ run: |
+ latest_tag=$(git describe --tags `git rev-list --tags --max-count=1`)
+ latest_commit=$(git rev-list -n 1 $latest_tag)
+ echo "latest_tag=${latest_tag}" >> $GITHUB_ENV
+ echo "latest_commit=${latest_commit}" >> $GITHUB_ENV
- name: Build and push
uses: docker/build-push-action@v5
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'] }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
file: ./Dockerfile
push: true
tags: 'gtstef/filebrowser:latest'
- labels: ${{ steps.meta.outputs.labels }}
+ labels: ${{ steps.meta.outputs.labels }}
\ No newline at end of file
diff --git a/.github/workflows/pr.yaml b/.github/workflows/pr.yaml
index c7139ec9..92fac860 100644
--- a/.github/workflows/pr.yaml
+++ b/.github/workflows/pr.yaml
@@ -1,13 +1,28 @@
-name: pr-merge
+name: pr-request
on:
pull_request:
branches:
- "main"
- "v[0-9]+.[0-9]+.[0-9]+"
- - "dev_*"
jobs:
+ test_frontend:
+ name: Push release
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3.0.0
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3.0.0
+ - name: Build
+ uses: docker/build-push-action@v6
+ with:
+ context: .
+ file: ./Dockerfile.playwright
+ push: false
push_pr_to_registry:
name: Push PR
runs-on: ubuntu-latest
@@ -18,12 +33,6 @@ jobs:
uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0
- # Workaround to fix error:
- # failed to push: failed to copy: io: read/write on closed pipe
- # See https://github.com/docker/build-push-action/issues/761
- with:
- driver-opts: |
- image=moby/buildkit:v0.10.6
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
@@ -35,10 +44,13 @@ jobs:
with:
images: gtstef/filebrowser
- name: Build and push
- uses: docker/build-push-action@v5
+ uses: docker/build-push-action@v6
with:
context: .
file: ./Dockerfile
push: true
tags: ${{ steps.meta.outputs.tags }}
- labels: ${{ steps.meta.outputs.labels }}
\ No newline at end of file
+ labels: ${{ steps.meta.outputs.labels }}
+ build-args: |
+ version=${{ steps.meta.outputs.version }}
+ commitSHA=${{ steps.meta.outputs.revision }}
\ No newline at end of file
diff --git a/.github/workflows/regular-tests.yaml b/.github/workflows/regular-tests.yaml
index e042f395..7a29eaf2 100644
--- a/.github/workflows/regular-tests.yaml
+++ b/.github/workflows/regular-tests.yaml
@@ -1,4 +1,4 @@
-name: dev
+name: dev tests
on:
push:
@@ -21,10 +21,10 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
- go-version: 'stable'
- - uses: golangci/golangci-lint-action@v6
+ go-version: '1.22.5'
+ - uses: golangci/golangci-lint-action@v5
with:
- version: v1.59
+ version: v1.60
working-directory: backend
format-backend:
runs-on: ubuntu-latest
diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml
index 3f842c78..d33db5a9 100644
--- a/.github/workflows/release.yaml
+++ b/.github/workflows/release.yaml
@@ -1,4 +1,4 @@
-name: release
+name: version release
on:
push:
@@ -34,6 +34,7 @@ jobs:
draft: false
generate_release_notes: true
name: ${{ steps.extract_branch.outputs.branch }}
+
push_release_to_registry:
name: Push release
runs-on: ubuntu-latest
@@ -44,14 +45,7 @@ jobs:
uses: docker/setup-qemu-action@v3.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3.0.0
- # Workaround to fix error:
- # failed to push: failed to copy: io: read/write on closed pipe
- # See https://github.com/docker/build-push-action/issues/761
- with:
- driver-opts: |
- image=moby/buildkit:v0.10.6
- name: Login to Docker Hub
- # Only push to Docker Hub when making a release
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
@@ -69,11 +63,14 @@ jobs:
JSON=$(echo "$JSON" | sed 's/filebrowser:v/filebrowser:/')
echo "cleaned_tag=$JSON" >> $GITHUB_OUTPUT
- name: Build and push
- uses: docker/build-push-action@v5
+ 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'] }}
platforms: linux/amd64,linux/arm64,linux/arm/v7
file: ./Dockerfile
push: true
tags: ${{ steps.modify-json.outputs.cleaned_tag }}
- labels: ${{ steps.meta.outputs.labels }}
\ No newline at end of file
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/release_dev.yaml b/.github/workflows/release_dev.yaml
new file mode 100644
index 00000000..c0e16e05
--- /dev/null
+++ b/.github/workflows/release_dev.yaml
@@ -0,0 +1,50 @@
+name: dev release
+
+on:
+ push:
+ branches:
+ - "dev_v[0-9]+.[0-9]+.[0-9]+"
+
+permissions:
+ contents: write
+
+jobs:
+ push_release_to_registry:
+ name: Push dev release
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v4
+ - name: Set up QEMU
+ uses: docker/setup-qemu-action@v3.0.0
+ - name: Set up Docker Buildx
+ uses: docker/setup-buildx-action@v3.0.0
+ - name: Login to Docker Hub
+ uses: docker/login-action@v3
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+ - name: Extract metadata (tags, labels) for Docker
+ id: meta
+ uses: docker/metadata-action@v5
+ with:
+ images: gtstef/filebrowser
+ - name: Strip v from version number
+ id: modify-json
+ run: |
+ 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
+ - name: Build and push
+ uses: docker/build-push-action@v6
+ with:
+ 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: ${{ steps.modify-json.outputs.cleaned_tag }}
+ labels: ${{ steps.meta.outputs.labels }}
diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yaml
deleted file mode 100644
index c7b8f58f..00000000
--- a/.github/workflows/stale.yaml
+++ /dev/null
@@ -1,24 +0,0 @@
-name: 'Close stale issues and PRs'
-permissions:
- issues: write
- pull-requests: write
-
-on:
- schedule:
- - cron: '30 1 * * *'
-
-jobs:
- stale:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/stale@v5
- with:
- stale-pr-message: 'This PR is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
- close-pr-message: 'This PR was closed because it has been stalled for 5 days with no activity.'
- stale-issue-message: 'This issue is stale because it has been open 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.'
- close-issue-message: 'This issue was closed because it has been stalled for 30 days with no activity.'
- days-before-stale: 30
- days-before-close: 30
- exempt-issue-labels: 'feature ☘,enhancement ⚙,bug 🐞'
- exempt-pr-labels: 'need-help,wip'
- operations-per-run: 100
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 14907522..da3c6c32 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,10 +7,12 @@ rice-box.go
/filebrowser.exe
/frontend/dist
/frontend/pkg
+/frontend/test-results
/frontend/package-lock.json
/backend/vendor
/backend/*.cov
/backend/test_config.yaml
+/backend/srv
.DS_Store
node_modules
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b321a1ed..8af26442 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,16 @@
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.8
+
+- **Feature**: New gallary view scaling options (closes [#141](https://github.com/gtsteffaniak/filebrowser/issues/141))
+- **Change**: Refactored backend files functions
+- **Change**: Improved UI response to filesystem changes
+- **Change**: Added frontend tests for deployment integrity
+- **Fix**: move/replace file prompt issue
+- **Fix**: opening files from search
+- **Fix**: Display count issue when hideDotFile is enabled.
+
## v0.2.7
- **Change**: New sidebar style and behavior
diff --git a/Dockerfile b/Dockerfile
index 2567621c..49a31790 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,18 +1,22 @@
-FROM node:slim as nbuild
+FROM golang:1.22-alpine AS base
+ARG VERSION
+ARG REVISION
WORKDIR /app
-COPY ./frontend/package*.json ./
+COPY ./backend ./
+RUN go build -ldflags="-w -s \
+ -X 'github.com/gtsteffaniak/filebrowser/version.Version=${VERSION}' \
+ -X 'github.com/gtsteffaniak/filebrowser/version.CommitSHA=${REVISION}'" \
+ -o filebrowser .
+
+FROM node:slim AS nbuild
+WORKDIR /app
+COPY ./frontend/package.json ./
RUN npm i --maxsockets 1
COPY ./frontend/ ./
RUN npm run build-docker
-FROM golang:1.22-alpine as base
-WORKDIR /app
-COPY ./backend ./
-RUN go build -ldflags="-w -s" -o filebrowser .
-
FROM alpine:latest
ENV FILEBROWSER_NO_EMBEDED="true"
-ARG app="/app/filebrowser"
RUN apk --no-cache add ca-certificates mailcap
COPY --from=base /app/filebrowser* ./
COPY --from=nbuild /app/dist/ ./frontend/dist/
diff --git a/Dockerfile.playwright b/Dockerfile.playwright
new file mode 100644
index 00000000..c8ffaf2a
--- /dev/null
+++ b/Dockerfile.playwright
@@ -0,0 +1,19 @@
+FROM golang:1.22-alpine AS base
+WORKDIR /app
+COPY ./backend ./
+RUN go build -ldflags="-w -s" -o filebrowser .
+
+FROM node:slim
+WORKDIR /app
+COPY ./frontend/package.json ./
+RUN npm i --maxsockets 1
+RUN npx playwright install --with-deps firefox
+COPY [ "backend/filebrowser.yaml", "./" ]
+COPY ./frontend/ ./frontend
+WORKDIR /app/frontend
+RUN npm run build-docker
+WORKDIR /app
+COPY --from=base /app/filebrowser* ./
+RUN cp -R frontend/tests/ srv
+ENV FILEBROWSER_NO_EMBEDED="true"
+RUN ./filebrowser & sleep 2 && cd frontend && npx playwright test
diff --git a/README.md b/README.md
index 1415f4a1..dd384bc6 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,7 @@
-Filebrowser - A modern web-based file manager
+Filebrowser Quantum - A modern web-based file manager
@@ -15,38 +15,41 @@
> Starting with v0.2.4 *ALL* share links need to be re-created (due to
> security fix).
-This fork makes the following significant changes to filebrowser for
-origin:
+Filebrowser Quantum is a fork of the filebrowser opensource project with the
+following changes:
- 1. [x] Better search
- - Lightning fast
- - real-time results as you type
+ 1. [x] Enhanced lightning fast indexed search
+ - Real-time results as you type
- Works with more type filters
- - interactive results page.
+ - Enhanced interactive results page.
2. [x] Revamped and simplified GUI navbar and sidebar menu.
- Additional compact view mode as well as refreshed view mode
styles.
- 3. [x] Revamped configuration via `filebrowser.yml` config file.
- - More configurations possible at a per-user level
+ 3. [x] Revamped and simplified configuration via `filebrowser.yml` config file.
+ 4. [x] Faster listing browsing
+ - Switching view modes is instant
+ - Changing Sort order is instant
+ - The entire directory is loaded in 1/3 the time
## About
-Filebrowser 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.
-This repository is a fork, a collection of changes that make this program
-work better in terms of aesthetics and performance. Improved search,
-simplified ui (without removing features) and more secure and up-to-date
+This repository is a fork of the original [filebrowser](https://github.com/filebrowser/filebrowser)
+with a collection of changes that make this program work better in terms of
+aesthetics and performance. Improved search, simplified ui
+(without removing features) and more secure and up-to-date
build are just a few examples.
-This Implementation of filebrowser 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:
- Simplicity and improved user experience
- - Efficiency of operations and performance
+ - Improving performance and faster feedback when making changes.
- Minimize external dependencies and standard library usage.
- Of course -- adding much-needed features.
@@ -81,7 +84,7 @@ Using docker:
docker run -it -v /path/to/folder:/srv -p 80:80 gtstef/filebrowser
```
-1. docker-compose:
+1. docker compose:
- with local storage
@@ -140,8 +143,8 @@ configuration options and other help.
## Migration from filebrowser/filebrowser
-If you currently use filebrowser from the filebrowser/filebrowser
-repo but want to try using this. I recommend you start fresh without
+If you currently use the original opensource filebrowser
+but want to try using this. I recommend you start fresh without
reusing the database, but there are a few things you'll need to do if you
must migrate:
@@ -157,10 +160,61 @@ must migrate:
filebrowser.yml and have a valid filebrowser config.
-The filebrowser application should run with the same user and rules that
+The filebrowser Quantum application should run with the same user and rules that
you have from the original. But keep in mind the differences that are
mentioned at the top of this readme.
+
+## Comparison Chart
+
+ Application Name |
Quantum |
Filebrowser |
Filestash |
Nextcloud |
Google_Drive |
FileRun
+--- | --- | --- | --- | --- | --- | --- |
+Filesystem support | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
+Linux | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
+Windows | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
+Mac | ✅ | ✅ | ✅ | ❌ | ❌ | ❌ |
+Self hostable | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
+Has Stable Release? | ❌ | ✅ | ✅ | ✅ | ✅ | ✅ |
+S3 support | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
+webdav support | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
+ftp support | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
+Dedicated docs site? | ❌ | ✅ | ✅ | ✅ | ❌ | ✅ |
+Multiple sources at once | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
+Docker image size | 22 MB | 31 MB | 240 MB (main image) | 250 MB | ❌ | > 2 GB |
+Min. Memory Requirements | 128 MB | 128 MB | 128 MB (main image) | 128 MB | ❌ | 4 GB |
+has standalone binary | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ |
+price | free | free | free | free tier | free tier | $99+ |
+rich media preview | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+upload files from the web? | ✅ | ✅ | ✅ | ✅ | ✅ | ❌ |
+Advanced Search? | ✅ | ❌ | ❌ | configurable | ✅ | ✅ |
+Indexed Search? | ✅ | ❌ | ❌ | configurable | ✅ | ✅ |
+Content-aware search? | ❌ | ❌ | ❌ | configurable | ✅ | ✅ |
+Custom job support | ❌ | ✅ | ❌ | ✅ | ❌ | ✅ |
+Multiple users | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+Single sign-on support | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+LDAP sign-on support | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+2FA sign-on support | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+Long-live API key support | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
+Mobile App | ❌ | ❌ | ❌ | ❌ | ✅ | ❌ |
+open source? | ✅ | ✅ | ✅ | ✅ | ❌ | ✅ |
+tags support | ❌ | ❌ | ❌ | ✅ | ❌ | ✅ |
+sharable web links? | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+Event-based notifications | ❌ | ❌ | ❌ | ❌ | ❌ | ✅ |
+Metrics | ❌ | ❌ | ❌ | ❌ | ❌ | ❌ |
+file space quotas | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+text-based files editor | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
+office file support | ❌ | ❌ | ✅ | ✅ | ✅ | ✅ |
+Themes | ✅ | ✅ | ❌ | ❌ | ❌ | ✅ |
+Branding support | ❌ | ✅ | ❌ | ❌ | ❌ | ✅ |
+activity log | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+Comments support | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+collaboration on same file | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+trash support | ❌ | ❌ | ❌ | ✅ | ✅ | ✅ |
+Starred/pinned files | ❌ | ❌ | ❌ | ❌ | ✅ | ✅ |
+Content preview icons | ✅ | ✅ | ❌ | ❌ | ✅ | ✅ |
+Plugins support | ❌ | ❌ | ✅ | ✅ | ❌ | ✅ |
+Chromecast support | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
+
## Roadmap
see [Roadmap Page](./roadmap.md)
diff --git a/backend/.golangci.yml b/backend/.golangci.yml
new file mode 100644
index 00000000..37144512
--- /dev/null
+++ b/backend/.golangci.yml
@@ -0,0 +1,232 @@
+# This code is licensed under the terms of the MIT license https://opensource.org/license/mit
+# Copyright (c) 2021 Marat Reymers
+
+## Golden config for golangci-lint v1.59.1
+#
+# This is the best config for golangci-lint based on my experience and opinion.
+# It is very strict, but not extremely strict.
+# Feel free to adapt and change it for your needs.
+
+run:
+ # Timeout for analysis, e.g. 30s, 5m.
+ # Default: 1m
+ timeout: 3m
+
+
+# This file contains only configs which differ from defaults.
+# All possible options can be found here https://github.com/golangci/golangci-lint/blob/master/.golangci.reference.yml
+linters-settings:
+ cyclop:
+ # The maximal code complexity to report.
+ # Default: 10
+ max-complexity: 30
+ # The maximal average package complexity.
+ # If it's higher than 0.0 (float) the check is enabled
+ # Default: 0.0
+ package-average: 10.0
+
+ errcheck:
+ # Report about not checking of errors in type assertions: `a := b.(MyStruct)`.
+ # Such cases aren't reported by default.
+ # Default: false
+ check-type-assertions: true
+
+ exhaustive:
+ # Program elements to check for exhaustiveness.
+ # Default: [ switch ]
+ check:
+ - switch
+ - map
+
+ exhaustruct:
+ # List of regular expressions to exclude struct packages and their names from checks.
+ # Regular expressions must match complete canonical struct package/name/structname.
+ # Default: []
+ exclude:
+ # std libs
+ - "^net/http.Client$"
+ - "^net/http.Cookie$"
+ - "^net/http.Request$"
+ - "^net/http.Response$"
+ - "^net/http.Server$"
+ - "^net/http.Transport$"
+ - "^net/url.URL$"
+ - "^os/exec.Cmd$"
+ - "^reflect.StructField$"
+ # public libs
+ - "^github.com/Shopify/sarama.Config$"
+ - "^github.com/Shopify/sarama.ProducerMessage$"
+ - "^github.com/mitchellh/mapstructure.DecoderConfig$"
+ - "^github.com/prometheus/client_golang/.+Opts$"
+ - "^github.com/spf13/cobra.Command$"
+ - "^github.com/spf13/cobra.CompletionOptions$"
+ - "^github.com/stretchr/testify/mock.Mock$"
+ - "^github.com/testcontainers/testcontainers-go.+Request$"
+ - "^github.com/testcontainers/testcontainers-go.FromDockerfile$"
+ - "^golang.org/x/tools/go/analysis.Analyzer$"
+ - "^google.golang.org/protobuf/.+Options$"
+ - "^gopkg.in/yaml.v3.Node$"
+
+ funlen:
+ # Checks the number of lines in a function.
+ # If lower than 0, disable the check.
+ # Default: 60
+ lines: 100
+ # Checks the number of statements in a function.
+ # If lower than 0, disable the check.
+ # Default: 40
+ statements: 50
+ # Ignore comments when counting lines.
+ # Default false
+ ignore-comments: true
+
+ gocognit:
+ # Minimal code complexity to report.
+ # Default: 30 (but we recommend 10-20)
+ min-complexity: 20
+
+ gocritic:
+ # Settings passed to gocritic.
+ # The settings key is the name of a supported gocritic checker.
+ # The list of supported checkers can be find in https://go-critic.github.io/overview.
+ settings:
+ captLocal:
+ # Whether to restrict checker to params only.
+ # Default: true
+ paramsOnly: false
+ underef:
+ # Whether to skip (*x).method() calls where x is a pointer receiver.
+ # Default: true
+ skipRecvDeref: false
+
+ gomodguard:
+ blocked:
+ # List of blocked modules.
+ # Default: []
+ modules:
+ - github.com/golang/protobuf:
+ recommendations:
+ - google.golang.org/protobuf
+ reason: "see https://developers.google.com/protocol-buffers/docs/reference/go/faq#modules"
+ - github.com/satori/go.uuid:
+ recommendations:
+ - github.com/google/uuid
+ reason: "satori's package is not maintained"
+ - github.com/gofrs/uuid:
+ recommendations:
+ - github.com/gofrs/uuid/v5
+ reason: "gofrs' package was not go module before v5"
+
+ govet:
+ # Enable all analyzers.
+ # Default: false
+ enable-all: true
+ # Disable analyzers by name.
+ # Run `go tool vet help` to see all analyzers.
+ # Default: []
+ disable:
+ - fieldalignment # too strict
+ # Settings per analyzer.
+ settings:
+ shadow:
+ # Whether to be strict about shadowing; can be noisy.
+ # Default: false
+ strict: false
+
+ inamedparam:
+ # Skips check for interface methods with only a single parameter.
+ # Default: false
+ skip-single-param: true
+
+ mnd:
+ # List of function patterns to exclude from analysis.
+ # Values always ignored: `time.Date`,
+ # `strconv.FormatInt`, `strconv.FormatUint`, `strconv.FormatFloat`,
+ # `strconv.ParseInt`, `strconv.ParseUint`, `strconv.ParseFloat`.
+ # Default: []
+ ignored-functions:
+ - args.Error
+ - flag.Arg
+ - flag.Duration.*
+ - flag.Float.*
+ - flag.Int.*
+ - flag.Uint.*
+ - os.Chmod
+ - os.Mkdir.*
+ - os.OpenFile
+ - os.WriteFile
+ - prometheus.ExponentialBuckets.*
+ - prometheus.LinearBuckets
+
+ nakedret:
+ # Make an issue if func has more lines of code than this setting, and it has naked returns.
+ # Default: 30
+ max-func-lines: 0
+
+ nolintlint:
+ # Exclude following linters from requiring an explanation.
+ # Default: []
+ allow-no-explanation: [ funlen, gocognit, lll ]
+ # Enable to require an explanation of nonzero length after each nolint directive.
+ # Default: false
+ require-explanation: true
+ # Enable to require nolint directives to mention the specific linter being suppressed.
+ # Default: false
+ require-specific: true
+
+ perfsprint:
+ # Optimizes into strings concatenation.
+ # Default: true
+ strconcat: false
+
+ rowserrcheck:
+ # database/sql is always checked
+ # Default: []
+ packages:
+ - github.com/jmoiron/sqlx
+
+ sloglint:
+ # Enforce not using global loggers.
+ # Values:
+ # - "": disabled
+ # - "all": report all global loggers
+ # - "default": report only the default slog logger
+ # https://github.com/go-simpler/sloglint?tab=readme-ov-file#no-global
+ # Default: ""
+ no-global: "all"
+ # Enforce using methods that accept a context.
+ # Values:
+ # - "": disabled
+ # - "all": report all contextless calls
+ # - "scope": report only if a context exists in the scope of the outermost function
+ # https://github.com/go-simpler/sloglint?tab=readme-ov-file#context-only
+ # Default: ""
+ context: "scope"
+
+ tenv:
+ # The option `all` will run against whole test files (`_test.go`) regardless of method/function signatures.
+ # Otherwise, only methods that take `*testing.T`, `*testing.B`, and `testing.TB` as arguments are checked.
+ # Default: false
+ all: true
+
+
+issues:
+ # Maximum count of issues with the same text.
+ # Set to 0 to disable.
+ # Default: 3
+ max-same-issues: 50
+
+ exclude-rules:
+ - source: "(noinspection|TODO)"
+ linters: [ godot ]
+ - source: "//noinspection"
+ linters: [ gocritic ]
+ - path: "_test\\.go"
+ linters:
+ - bodyclose
+ - dupl
+ - funlen
+ - goconst
+ - gosec
+ - noctx
+ - wrapcheck
\ No newline at end of file
diff --git a/backend/.goreleaser.yaml b/backend/.goreleaser.yaml
index 0e4ff45d..8ae31067 100644
--- a/backend/.goreleaser.yaml
+++ b/backend/.goreleaser.yaml
@@ -5,8 +5,8 @@ version: 2
builds:
# Build configuration for darwin and linux
- id: default
- ldflags:
- - -s -w
+ ldflags: &ldflags
+ - -s -w -X github.com/gtsteffaniak/filebrowser/version.Version={{ .Version }} -X github.com/gtsteffaniak/filebrowser/version.CommitSHA={{ .ShortCommit }}
main: main.go
binary: filebrowser
goos:
@@ -25,8 +25,7 @@ builds:
# Build configuration for windows without arm
- id: windows
- ldflags:
- - -s -w
+ ldflags: *ldflags
main: main.go
binary: filebrowser
goos:
diff --git a/backend/cmd/root.go b/backend/cmd/root.go
index 91dde909..38772297 100644
--- a/backend/cmd/root.go
+++ b/backend/cmd/root.go
@@ -17,7 +17,6 @@ import (
"github.com/spf13/pflag"
- "github.com/spf13/afero"
"github.com/spf13/cobra"
"github.com/gtsteffaniak/filebrowser/auth"
@@ -64,13 +63,20 @@ var rootCmd = &cobra.Command{
log.Fatal("Image resize workers count could not be < 1")
}
imgSvc := img.New(serverConfig.NumImageProcessors)
- var fileCache diskcache.Interface = diskcache.NewNoOp()
+
cacheDir := "/tmp"
+ var fileCache diskcache.Interface
+
+ // Use file cache if cacheDir is specified
if cacheDir != "" {
- if err := os.MkdirAll(cacheDir, 0700); err != nil { //nolint:govet,gomnd
- log.Fatalf("can't make directory %s: %s", cacheDir, err)
+ var err error
+ fileCache, err = diskcache.NewFileCache(cacheDir)
+ if err != nil {
+ log.Fatalf("failed to create file cache: %v", err)
}
- fileCache = diskcache.New(afero.NewOsFs(), cacheDir)
+ } else {
+ // No-op cache if no cacheDir is specified
+ fileCache = diskcache.NewNoOp()
}
// initialize indexing and schedule indexing ever n minutes (default 5)
go files.InitializeIndex(serverConfig.IndexingInterval, serverConfig.Indexing)
diff --git a/backend/cmd/users_import.go b/backend/cmd/users_import.go
index 2df91d94..a588f19f 100644
--- a/backend/cmd/users_import.go
+++ b/backend/cmd/users_import.go
@@ -34,11 +34,6 @@ list or set it to 0.`,
err = unmarshal(args[0], &list)
checkErr("unmarshal", err)
- for _, user := range list {
- err = user.Clean("")
- checkErr("Clean", err)
- }
-
if mustGetBool(cmd.Flags(), "replace") {
oldUsers, err := d.store.Users.Gets("")
checkErr("d.store.Users.Gets", err)
diff --git a/backend/diskcache/file_cache.go b/backend/diskcache/file_cache.go
index d2284cfb..ea870b1a 100644
--- a/backend/diskcache/file_cache.go
+++ b/backend/diskcache/file_cache.go
@@ -10,13 +10,17 @@ import (
"os"
"path/filepath"
"sync"
-
- "github.com/spf13/afero"
)
-type FileCache struct {
- fs afero.Fs
+// Cache interface for caching operations
+type Cache interface {
+ Get(key string) ([]byte, error)
+ Set(key string, value []byte) error
+}
+// FileCache struct for file-based caching
+type FileCache struct {
+ dir string
// granular locks
scopedLocks struct {
sync.Mutex
@@ -25,10 +29,12 @@ type FileCache struct {
}
}
-func New(fs afero.Fs, root string) *FileCache {
- return &FileCache{
- fs: afero.NewBasePathFs(fs, root),
+// NewFileCache creates a new FileCache
+func NewFileCache(dir string) (*FileCache, error) {
+ if err := os.MkdirAll(dir, 0700); err != nil {
+ return nil, fmt.Errorf("can't make directory %s: %v", dir, err)
}
+ return &FileCache{dir: dir}, nil
}
func (f *FileCache) Store(ctx context.Context, key string, value []byte) error {
@@ -37,11 +43,11 @@ func (f *FileCache) Store(ctx context.Context, key string, value []byte) error {
defer mu.Unlock()
fileName := f.getFileName(key)
- if err := f.fs.MkdirAll(filepath.Dir(fileName), 0700); err != nil { //nolint:gomnd
+ if err := os.MkdirAll(filepath.Dir(fileName), 0700); err != nil {
return err
}
- if err := afero.WriteFile(f.fs, fileName, value, 0700); err != nil { //nolint:gomnd
+ if err := os.WriteFile(fileName, value, 0600); err != nil {
return err
}
@@ -68,15 +74,15 @@ func (f *FileCache) Delete(ctx context.Context, key string) error {
defer mu.Unlock()
fileName := f.getFileName(key)
- if err := f.fs.Remove(fileName); err != nil && !errors.Is(err, os.ErrNotExist) {
+ if err := os.Remove(fileName); err != nil && !errors.Is(err, os.ErrNotExist) {
return err
}
return nil
}
-func (f *FileCache) open(key string) (afero.File, bool, error) {
+func (f *FileCache) open(key string) (*os.File, bool, error) {
fileName := f.getFileName(key)
- file, err := f.fs.Open(fileName)
+ file, err := os.Open(fileName)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, false, nil
@@ -106,5 +112,5 @@ func (f *FileCache) getFileName(key string) string {
hasher := sha1.New() //nolint:gosec
_, _ = hasher.Write([]byte(key))
hash := hex.EncodeToString(hasher.Sum(nil))
- return fmt.Sprintf("%s/%s/%s", hash[:1], hash[1:3], hash)
+ return filepath.Join(f.dir, fmt.Sprintf("%s/%s/%s", hash[:1], hash[1:3], hash))
}
diff --git a/backend/diskcache/file_cache_test.go b/backend/diskcache/file_cache_test.go
index fdb3d119..5745b219 100644
--- a/backend/diskcache/file_cache_test.go
+++ b/backend/diskcache/file_cache_test.go
@@ -2,10 +2,10 @@ package diskcache
import (
"context"
+ "os"
"path/filepath"
"testing"
- "github.com/spf13/afero"
"github.com/stretchr/testify/require"
)
@@ -15,35 +15,39 @@ func TestFileCache(t *testing.T) {
key = "key"
value = "some text"
newValue = "new text"
- cacheRoot = "/cache"
+ cacheRoot = "cache"
cachedFilePath = "a/62/a62f2225bf70bfaccbc7f1ef2a397836717377de"
)
- fs := afero.NewMemMapFs()
- cache := New(fs, "/cache")
+ // Create temporary directory for the cache
+ cacheDir, err := os.MkdirTemp("", cacheRoot)
+ require.NoError(t, err)
+ defer os.RemoveAll(cacheDir) // Clean up
+
+ cache, err := NewFileCache(cacheDir)
+ require.NoError(t, err)
// store new key
- err := cache.Store(ctx, key, []byte(value))
+ err = cache.Store(ctx, key, []byte(value))
require.NoError(t, err)
- checkValue(t, ctx, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, value)
+ checkValue(t, ctx, cache, filepath.Join(cacheDir, cachedFilePath), key, value)
// update existing key
err = cache.Store(ctx, key, []byte(newValue))
require.NoError(t, err)
- checkValue(t, ctx, fs, filepath.Join(cacheRoot, cachedFilePath), cache, key, newValue)
+ checkValue(t, ctx, cache, filepath.Join(cacheDir, cachedFilePath), key, newValue)
// delete key
err = cache.Delete(ctx, key)
require.NoError(t, err)
- exists, err := afero.Exists(fs, filepath.Join(cacheRoot, cachedFilePath))
- require.NoError(t, err)
+ exists := fileExists(filepath.Join(cacheDir, cachedFilePath))
require.False(t, exists)
}
-func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath string, cache *FileCache, key, wantValue string) { //nolint:golint
+func checkValue(t *testing.T, ctx context.Context, cache *FileCache, fileFullPath string, key, wantValue string) {
t.Helper()
// check actual file content
- b, err := afero.ReadFile(fs, fileFullPath)
+ b, err := os.ReadFile(fileFullPath)
require.NoError(t, err)
require.Equal(t, wantValue, string(b))
@@ -53,3 +57,11 @@ func checkValue(t *testing.T, ctx context.Context, fs afero.Fs, fileFullPath str
require.True(t, ok)
require.Equal(t, wantValue, string(b))
}
+
+func fileExists(filename string) bool {
+ info, err := os.Stat(filename)
+ if os.IsNotExist(err) {
+ return false
+ }
+ return !info.IsDir()
+}
diff --git a/backend/files/file.go b/backend/files/file.go
index 9a5caa92..96b2e3f9 100644
--- a/backend/files/file.go
+++ b/backend/files/file.go
@@ -6,21 +6,21 @@ import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
+ "fmt"
"hash"
"io"
"mime"
"net/http"
"os"
- filepath "path/filepath"
+ "path/filepath"
"strings"
"sync"
"time"
"unicode/utf8"
- "github.com/spf13/afero"
-
"github.com/gtsteffaniak/filebrowser/errors"
"github.com/gtsteffaniak/filebrowser/rules"
+ "github.com/gtsteffaniak/filebrowser/settings"
"github.com/gtsteffaniak/filebrowser/users"
)
@@ -33,7 +33,6 @@ var (
// FileInfo describes a file.
type FileInfo struct {
*Listing
- Fs afero.Fs `json:"-"`
Path string `json:"path,omitempty"`
Name string `json:"name"`
Size int64 `json:"size"`
@@ -52,8 +51,7 @@ type FileInfo struct {
// FileOptions are the options when getting a file info.
type FileOptions struct {
- Fs afero.Fs
- Path string
+ Path string // realpath
Modify bool
Expand bool
ReadHeader bool
@@ -91,7 +89,7 @@ func NewFileInfo(opts FileOptions) (*FileInfo, error) {
}
if opts.Expand {
if file.IsDir {
- if err := file.readListing(opts.Path, opts.Checker, opts.ReadHeader); err != nil { //nolint:govet
+ if err = file.readListing(opts.Path, opts.Checker, opts.ReadHeader); err != nil {
return nil, err
}
return file, nil
@@ -166,60 +164,37 @@ func RefreshFileInfo(opts FileOptions) bool {
if err != nil {
return false
}
- //_, exists := index.GetFileMetadata(adjustedPath)
-
return index.UpdateFileMetadata(adjustedPath, *file)
} else {
- //_, exists := index.GetFileMetadata(adjustedPath)
return index.UpdateFileMetadata(adjustedPath, *file)
}
}
func stat(path string, opts FileOptions) (*FileInfo, error) {
- var file *FileInfo
- if lstaterFs, ok := opts.Fs.(afero.Lstater); ok {
- info, _, err := lstaterFs.LstatIfPossible(path)
- if err == nil {
- file = &FileInfo{
- Fs: opts.Fs,
- Path: opts.Path,
- Name: info.Name(),
- ModTime: info.ModTime(),
- Mode: info.Mode(),
- Size: info.Size(),
- Extension: filepath.Ext(info.Name()),
- Token: opts.Token,
- }
- if info.IsDir() {
- file.IsDir = true
- }
- if info.Mode()&os.ModeSymlink != 0 {
- file.IsSymlink = true
- }
- }
+ info, err := os.Lstat(path)
+ if err != nil {
+ return nil, err
}
- if file == nil || file.IsSymlink {
- info, err := opts.Fs.Stat(opts.Path)
- if err != nil {
- return nil, err
- }
- if file != nil && file.IsSymlink {
- file.Size = info.Size()
- file.IsDir = info.IsDir()
- return file, nil
- }
+ file := &FileInfo{
+ Path: opts.Path,
+ Name: info.Name(),
+ ModTime: info.ModTime(),
+ Mode: info.Mode(),
+ Size: info.Size(),
+ Extension: filepath.Ext(info.Name()),
+ Token: opts.Token,
+ }
- file = &FileInfo{
- Fs: opts.Fs,
- Path: opts.Path,
- Name: info.Name(),
- ModTime: info.ModTime(),
- Mode: info.Mode(),
- IsDir: info.IsDir(),
- Size: info.Size(),
- 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)
+ if err == nil {
+ file.Size = targetInfo.Size()
+ file.IsDir = targetInfo.IsDir()
}
}
@@ -237,7 +212,7 @@ func (i *FileInfo) Checksum(algo string) error {
i.Checksums = map[string]string{}
}
- reader, err := i.Fs.Open(i.Path)
+ reader, err := os.Open(i.Path)
if err != nil {
return err
}
@@ -266,23 +241,127 @@ func (i *FileInfo) Checksum(algo string) error {
// RealPath gets the real path for the file, resolving symlinks if supported.
func (i *FileInfo) RealPath() string {
- if realPathFs, ok := i.Fs.(interface {
- RealPath(name string) (fPath string, err error)
- }); ok {
- realPath, err := realPathFs.RealPath(i.Path)
- if err == nil {
- return realPath
- }
+ realPath, err := filepath.EvalSymlinks(i.Path)
+ if err == nil {
+ return realPath
+ }
+ return i.Path
+}
+
+func GetRealPath(relativePath ...string) (string, 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
+ }
+ if !Exists(absolutePath) {
+ return absolutePath, nil // return without error
+ }
+ // Resolve symlinks and get the real path
+ return resolveSymlinks(absolutePath)
+}
+
+func DeleteFiles(absPath string, opts FileOptions) error {
+ err := os.RemoveAll(absPath)
+ if err != nil {
+ return err
+ }
+ parentDir := filepath.Dir(absPath)
+ opts.Path = parentDir
+ updated := RefreshFileInfo(opts)
+ if !updated {
+ return errors.ErrEmptyKey
+ }
+ return nil
+}
+
+func WriteDirectory(opts FileOptions) error {
+ // Ensure the parent directories exist
+ err := os.MkdirAll(opts.Path, 0775)
+ if err != nil {
+ return err
+ }
+ opts.Path = filepath.Dir(opts.Path)
+ updated := RefreshFileInfo(opts)
+ if !updated {
+ return errors.ErrEmptyKey
}
- return i.Path
+ 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
+ dir := filepath.Dir(dst)
+
+ // Create the directory and all necessary parents
+ err := os.MkdirAll(dir, 0775)
+ if err != nil {
+ return err
+ }
+
+ // Open the file for writing (create if it doesn't exist, truncate if it does)
+ file, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775)
+ if err != nil {
+ return err
+ }
+ defer file.Close()
+
+ // Copy the contents from the reader to the file
+ _, err = io.Copy(file, in)
+ if err != nil {
+ return err
+ }
+ fmt.Println("refreshing info for ", parentDir)
+ opts.Path = parentDir
+ updated := RefreshFileInfo(opts)
+ if !updated {
+ return errors.ErrEmptyKey
+ }
+
+ return nil
+}
+
+// resolveSymlinks resolves symlinks in the given path
+func resolveSymlinks(path string) (string, error) {
+ for {
+ // Get the file info
+ info, err := os.Lstat(path)
+ if err != nil {
+ return "", err
+ }
+
+ // Check if it's a symlink
+ if info.Mode()&os.ModeSymlink != 0 {
+ // Read the symlink target
+ target, err := os.Readlink(path)
+ if err != nil {
+ return "", 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
+ }
+ }
}
// addContent reads and sets content based on the file type.
func (i *FileInfo) addContent(path string) error {
if !i.IsDir {
- afs := &afero.Afero{Fs: i.Fs}
- content, err := afs.ReadFile(path)
+ fmt.Println("getting content for ", path)
+ content, err := os.ReadFile(path)
if err != nil {
return err
}
@@ -301,6 +380,9 @@ func (i *FileInfo) addContent(path string) error {
// detectType detects the file type.
func (i *FileInfo) detectType(path string, modify, saveContent, readHeader bool) error {
+ if i.IsDir {
+ return nil
+ }
if IsNamedPipe(i.Mode) {
i.Type = "blob"
if saveContent {
@@ -345,7 +427,6 @@ func (i *FileInfo) detectType(path string, modify, saveContent, readHeader bool)
}
}
}
-
if i.Type == "" {
i.Type = "blob"
if saveContent {
@@ -358,15 +439,15 @@ func (i *FileInfo) detectType(path string, modify, saveContent, readHeader bool)
// readFirstBytes reads the first bytes of the file.
func (i *FileInfo) readFirstBytes() []byte {
- reader, err := i.Fs.Open(i.Path)
+ file, err := os.Open(i.Path)
if err != nil {
i.Type = "blob"
return nil
}
- defer reader.Close()
+ defer file.Close()
buffer := make([]byte, 512) //nolint:gomnd
- n, err := reader.Read(buffer)
+ n, err := file.Read(buffer)
if err != nil && err != io.EOF {
i.Type = "blob"
return nil
@@ -387,7 +468,8 @@ func (i *FileInfo) detectSubtitles(parentDir string) {
// Directory must have been deleted, remove it from the index
return
}
- // Read the directory contents
+ defer dir.Close() // Ensure directory handle is closed
+
files, err := dir.Readdir(-1)
if err != nil {
return
@@ -412,8 +494,13 @@ func (i *FileInfo) detectSubtitles(parentDir string) {
// readListing reads the contents of a directory and fills the listing.
func (i *FileInfo) readListing(path string, checker rules.Checker, readHeader bool) error {
- afs := &afero.Afero{Fs: i.Fs}
- dir, err := afs.ReadDir(i.Path)
+ dir, err := os.Open(i.Path)
+ if err != nil {
+ return err
+ }
+ defer dir.Close()
+
+ files, err := dir.Readdir(-1)
if err != nil {
return err
}
@@ -425,7 +512,7 @@ func (i *FileInfo) readListing(path string, checker rules.Checker, readHeader bo
NumFiles: 0,
}
- for _, f := range dir {
+ for _, f := range files {
name := f.Name()
fPath := filepath.Join(i.Path, name)
@@ -436,7 +523,7 @@ func (i *FileInfo) readListing(path string, checker rules.Checker, readHeader bo
isSymlink, isInvalidLink := false, false
if IsSymlink(f.Mode()) {
isSymlink = true
- info, err := i.Fs.Stat(fPath)
+ info, err := os.Stat(fPath)
if err == nil {
f = info
} else {
@@ -478,6 +565,7 @@ func (i *FileInfo) readListing(path string, checker rules.Checker, readHeader bo
i.Listing = listing
return nil
}
+
func IsNamedPipe(mode os.FileMode) bool {
return mode&os.ModeNamedPipe != 0
}
@@ -498,3 +586,14 @@ func getMutex(path string) *sync.Mutex {
return pathMutexes[path]
}
+
+func Exists(path string) bool {
+ _, err := os.Stat(path)
+ if err == nil {
+ return true
+ }
+ if os.IsNotExist(err) {
+ return false
+ }
+ return false
+}
diff --git a/backend/fileutils/copy.go b/backend/fileutils/copy.go
index 57c961da..6f3d09c7 100644
--- a/backend/fileutils/copy.go
+++ b/backend/fileutils/copy.go
@@ -2,23 +2,23 @@ package fileutils
import (
"os"
- "path"
-
- "github.com/spf13/afero"
+ "path/filepath"
)
// Copy copies a file or folder from one place to another.
-func Copy(fs afero.Fs, src, dst string) error {
- if src = path.Clean("/" + src); src == "" {
+func Copy(src, dst string) error {
+ src = filepath.Clean(src)
+ if src == "" {
return os.ErrNotExist
}
- if dst = path.Clean("/" + dst); dst == "" {
+ dst = filepath.Clean(dst)
+ if dst == "" {
return os.ErrNotExist
}
if src == "/" || dst == "/" {
- // Prohibit copying from or to the virtual root directory.
+ // Prohibit copying from or to the root directory.
return os.ErrInvalid
}
@@ -26,14 +26,14 @@ func Copy(fs afero.Fs, src, dst string) error {
return os.ErrInvalid
}
- info, err := fs.Stat(src)
+ info, err := os.Stat(src)
if err != nil {
return err
}
if info.IsDir() {
- return CopyDir(fs, src, dst)
+ return CopyDir(src, dst)
}
- return CopyFile(fs, src, dst)
+ return CopyFile(src, dst)
}
diff --git a/backend/fileutils/dir.go b/backend/fileutils/dir.go
index 07a3528e..a939d872 100644
--- a/backend/fileutils/dir.go
+++ b/backend/fileutils/dir.go
@@ -2,27 +2,32 @@ package fileutils
import (
"errors"
-
- "github.com/spf13/afero"
+ "os"
+ "path/filepath"
)
// CopyDir copies a directory from source to dest and all
// of its sub-directories. It doesn't stop if it finds an error
// during the copy. Returns an error if any.
-func CopyDir(fs afero.Fs, source, dest string) error {
+func CopyDir(source, dest string) error {
// Get properties of source.
- srcinfo, err := fs.Stat(source)
+ srcinfo, err := os.Stat(source)
if err != nil {
return err
}
// Create the destination directory.
- err = fs.MkdirAll(dest, srcinfo.Mode())
+ err = os.MkdirAll(dest, srcinfo.Mode())
if err != nil {
return err
}
- dir, _ := fs.Open(source)
+ dir, err := os.Open(source)
+ if err != nil {
+ return err
+ }
+ defer dir.Close()
+
obs, err := dir.Readdir(-1)
if err != nil {
return err
@@ -31,18 +36,18 @@ func CopyDir(fs afero.Fs, source, dest string) error {
var errs []error
for _, obj := range obs {
- fsource := source + "/" + obj.Name()
- fdest := dest + "/" + obj.Name()
+ fsource := filepath.Join(source, obj.Name())
+ fdest := filepath.Join(dest, obj.Name())
if obj.IsDir() {
// Create sub-directories, recursively.
- err = CopyDir(fs, fsource, fdest)
+ err = CopyDir(fsource, fdest)
if err != nil {
errs = append(errs, err)
}
} else {
// Perform the file copy.
- err = CopyFile(fs, fsource, fdest)
+ err = CopyFile(fsource, fdest)
if err != nil {
errs = append(errs, err)
}
diff --git a/backend/fileutils/file.go b/backend/fileutils/file.go
index 0f782cf5..fe43e3f6 100644
--- a/backend/fileutils/file.go
+++ b/backend/fileutils/file.go
@@ -5,24 +5,22 @@ import (
"os"
"path"
"path/filepath"
-
- "github.com/spf13/afero"
)
-// MoveFile moves file from src to dst.
-// By default the rename filesystem system call is used. If src and dst point to different volumes
-// the file copy is used as a fallback
-func MoveFile(fs afero.Fs, src, dst string) error {
- if fs.Rename(src, dst) == nil {
+// MoveFile moves a file from src to dst.
+// By default, the rename system call is used. If src and dst point to different volumes,
+// the file copy is used as a fallback.
+func MoveFile(src, dst string) error {
+ if os.Rename(src, dst) == nil {
return nil
}
// fallback
- err := CopyFile(fs, src, dst)
+ err := CopyFile(src, dst)
if err != nil {
- _ = fs.Remove(dst)
+ _ = os.Remove(dst)
return err
}
- if err := fs.Remove(src); err != nil {
+ if err := os.Remove(src); err != nil {
return err
}
return nil
@@ -30,23 +28,22 @@ func MoveFile(fs afero.Fs, src, dst string) error {
// CopyFile copies a file from source to dest and returns
// an error if any.
-func CopyFile(fs afero.Fs, source, dest string) error {
+func CopyFile(source, dest string) error {
// Open the source file.
- src, err := fs.Open(source)
+ src, err := os.Open(source)
if err != nil {
return err
}
defer src.Close()
- // Makes the directory needed to create the dst
- // file.
- err = fs.MkdirAll(filepath.Dir(dest), 0666) //nolint:gomnd
+ // Makes the directory needed to create the dst file.
+ err = os.MkdirAll(filepath.Dir(dest), 0775) //nolint:gomnd
if err != nil {
return err
}
// Create the destination file.
- dst, err := fs.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0775) //nolint:gomnd
+ dst, err := os.OpenFile(dest, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) //nolint:gomnd
if err != nil {
return err
}
@@ -58,12 +55,12 @@ func CopyFile(fs afero.Fs, source, dest string) error {
return err
}
- // Copy the mode
- info, err := fs.Stat(source)
+ // Copy the mode.
+ info, err := os.Stat(source)
if err != nil {
return err
}
- err = fs.Chmod(dest, info.Mode())
+ err = os.Chmod(dest, info.Mode())
if err != nil {
return err
}
@@ -71,7 +68,7 @@ func CopyFile(fs afero.Fs, source, dest string) error {
return nil
}
-// CommonPrefix returns common directory path of provided files
+// CommonPrefix returns the common directory path of provided files.
func CommonPrefix(sep byte, paths ...string) string {
// Handle special cases.
switch len(paths) {
@@ -81,30 +78,19 @@ func CommonPrefix(sep byte, paths ...string) string {
return path.Clean(paths[0])
}
- // Note, we treat string as []byte, not []rune as is often
- // done in Go. (And sep as byte, not rune). This is because
- // most/all supported OS' treat paths as string of non-zero
- // bytes. A filename may be displayed as a sequence of Unicode
- // runes (typically encoded as UTF-8) but paths are
- // not required to be valid UTF-8 or in any normalized form
- // (e.g. "é" (U+00C9) and "é" (U+0065,U+0301) are different
- // file names.
+ // Treat string as []byte, not []rune as is often done in Go.
c := []byte(path.Clean(paths[0]))
- // We add a trailing sep to handle the case where the
- // common prefix directory is included in the path list
- // (e.g. /home/user1, /home/user1/foo, /home/user1/bar).
- // path.Clean will have cleaned off trailing / separators with
- // the exception of the root directory, "/" (in which case we
- // make it "//", but this will get fixed up to "/" bellow).
+ // Add a trailing sep to handle the case where the common prefix directory
+ // is included in the path list.
c = append(c, sep)
- // Ignore the first path since it's already in c
+ // Ignore the first path since it's already in c.
for _, v := range paths[1:] {
- // Clean up each path before testing it
+ // Clean up each path before testing it.
v = path.Clean(v) + string(sep)
- // Find the first non-common byte and truncate c
+ // Find the first non-common byte and truncate c.
if len(v) < len(c) {
c = c[:len(v)]
}
@@ -116,7 +102,7 @@ func CommonPrefix(sep byte, paths ...string) string {
}
}
- // Remove trailing non-separator characters and the final separator
+ // Remove trailing non-separator characters and the final separator.
for i := len(c) - 1; i >= 0; i-- {
if c[i] == sep {
c = c[:i]
diff --git a/backend/go.mod b/backend/go.mod
index 112029de..05ce5d2c 100644
--- a/backend/go.mod
+++ b/backend/go.mod
@@ -19,38 +19,38 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.9.0
github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce
- golang.org/x/crypto v0.25.0
- golang.org/x/image v0.18.0
- golang.org/x/text v0.16.0
+ golang.org/x/crypto v0.26.0
+ golang.org/x/image v0.19.0
+ golang.org/x/text v0.17.0
)
require (
- github.com/andybalholm/brotli v1.0.1 // indirect
+ github.com/andybalholm/brotli v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dsnet/compress v0.0.2-0.20210315054119-f66993602bf5 // indirect
github.com/dsoprea/go-logging v0.0.0-20200710184922-b02d349568dd // indirect
github.com/dsoprea/go-utility/v2 v2.0.0-20221003172846-a3e1774ef349 // indirect
- github.com/fatih/color v1.10.0 // indirect
- github.com/go-errors/errors v1.4.2 // indirect
- github.com/go-ole/go-ole v1.2.6 // indirect
- github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 // indirect
- github.com/golang/snappy v0.0.2 // indirect
+ github.com/fatih/color v1.17.0 // indirect
+ github.com/go-errors/errors v1.5.1 // indirect
+ github.com/go-ole/go-ole v1.3.0 // indirect
+ github.com/golang/geo v0.0.0-20230421003525-6adc56603217 // indirect
+ github.com/golang/snappy v0.0.4 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
- github.com/klauspost/compress v1.11.4 // indirect
- github.com/klauspost/pgzip v1.2.5 // indirect
- github.com/mattn/go-colorable v0.1.8 // indirect
- github.com/mattn/go-isatty v0.0.12 // indirect
- github.com/nwaples/rardecode v1.1.0 // indirect
- github.com/pierrec/lz4/v4 v4.1.2 // indirect
+ github.com/klauspost/compress v1.17.9 // indirect
+ github.com/klauspost/pgzip v1.2.6 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/nwaples/rardecode v1.1.3 // indirect
+ github.com/pierrec/lz4/v4 v4.1.21 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
- github.com/ulikunitz/xz v0.5.9 // indirect
+ github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
+ 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.4 // indirect
- golang.org/x/net v0.21.0 // indirect
- golang.org/x/sys v0.22.0 // indirect
- golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
+ go.etcd.io/bbolt v1.3.10 // 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
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 2d41d70a..12b52a1a 100644
--- a/backend/go.sum
+++ b/backend/go.sum
@@ -4,6 +4,8 @@ github.com/Sereal/Sereal v0.0.0-20190618215532-0b8ac451a863 h1:BRrxwOZBolJN4gIwv
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=
github.com/asdine/storm/v3 v3.2.1 h1:I5AqhkPK6nBZ/qJXySdI7ot5BlXSZ7qvDY1zAn5ZJac=
github.com/asdine/storm/v3 v3.2.1/go.mod h1:LEpXwGt4pIqrE/XcTvCnZHT5MgZCV6Ub9q7yQzOFWr0=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@@ -33,6 +35,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/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=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/go-errors/errors v1.0.1/go.mod h1:f4zRHt4oKfwPJE5k8C9vpYG+aDHdBFUsgrm6/TyX73Q=
@@ -40,8 +44,12 @@ github.com/go-errors/errors v1.0.2/go.mod h1:psDX2osz5VnTOnFWbDeWwS7yejl+uV3FEWE
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=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
@@ -56,6 +64,8 @@ github.com/golang/geo v0.0.0-20190916061304-5b978397cfec/go.mod h1:QZ0nwyI2jOfgR
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=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
@@ -63,6 +73,8 @@ github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
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=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@@ -75,9 +87,13 @@ github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c
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=
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
@@ -89,18 +105,29 @@ github.com/marusama/semaphore/v2 v2.5.0 h1:o/1QJD9DBYOWRnDhPwDVAXQn6mQYD0gZaS1Tp
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=
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
@@ -118,6 +145,8 @@ github.com/tomasen/realip v0.0.0-20180522021738-f0c99a92ddce/go.mod h1:o8v6yHRoi
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=
github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI=
github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk=
github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo=
@@ -126,12 +155,18 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo
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=
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=
golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@@ -141,6 +176,8 @@ golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/
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/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=
@@ -151,19 +188,28 @@ golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c=
google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc=
diff --git a/backend/http/__debug_bin2682048437 b/backend/http/__debug_bin2682048437
deleted file mode 100755
index ff94807c67ba03c5fcc0a341bcf99513cd1d4e40..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 18089038
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