mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-07-19 01:48:42 +08:00
Compare commits
12 Commits
renovate/g
...
beta
Author | SHA1 | Date | |
---|---|---|---|
648079ae24 | |||
e8d45398d6 | |||
0c461991f9 | |||
2a4c546a8b | |||
750d4eb3f6 | |||
cc01b410a4 | |||
e5fbe72581 | |||
283f3723d1 | |||
ad8c7b37a1 | |||
a84ffb96e9 | |||
19c6b6f930 | |||
eed3c0533c |
6
.github/workflows/beta_release.yml
vendored
6
.github/workflows/beta_release.yml
vendored
@ -14,12 +14,8 @@ permissions:
|
||||
|
||||
jobs:
|
||||
changelog:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
go-version: ["1.21"]
|
||||
name: Beta Release Changelog
|
||||
runs-on: ${{ matrix.platform }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
7
.github/workflows/build.yml
vendored
7
.github/workflows/build.yml
vendored
@ -1,8 +1,6 @@
|
||||
name: Test Build
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
workflow_dispatch:
|
||||
@ -15,7 +13,6 @@ jobs:
|
||||
build:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ubuntu-latest]
|
||||
target:
|
||||
- darwin-amd64
|
||||
- darwin-arm64
|
||||
@ -24,8 +21,8 @@ jobs:
|
||||
- linux-amd64-musl
|
||||
- windows-arm64
|
||||
- android-arm64
|
||||
name: Build
|
||||
runs-on: ${{ matrix.platform }}
|
||||
name: Build ${{ matrix.target }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
2
.github/workflows/changelog.yml
vendored
2
.github/workflows/changelog.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Automatic changelog
|
||||
name: Release Automatic changelog
|
||||
|
||||
on:
|
||||
push:
|
||||
|
101
.github/workflows/release.yml
vendored
101
.github/workflows/release.yml
vendored
@ -8,24 +8,34 @@ permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
# Set release to prerelease first
|
||||
prerelease:
|
||||
name: Set Prerelease
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Prerelease
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
id: ${{ github.event.release.id }}
|
||||
prerelease: true
|
||||
|
||||
# Main release job for all platforms
|
||||
release:
|
||||
needs: prerelease
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
build-type: [ 'standard', 'lite' ]
|
||||
target-platform: [ '', 'android', 'freebsd', 'linux_musl', 'linux_musl_arm' ]
|
||||
name: Release ${{ matrix.target-platform && format('{0} ', matrix.target-platform) || '' }}${{ matrix.build-type == 'lite' && 'Lite' || '' }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
if: matrix.target-platform == ''
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
@ -33,17 +43,10 @@ jobs:
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Prerelease
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
id: ${{ github.event.release.id }}
|
||||
prerelease: true
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
go-version: '1.24'
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
@ -51,6 +54,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies
|
||||
if: matrix.target-platform == ''
|
||||
run: |
|
||||
sudo snap install zig --classic --beta
|
||||
docker pull crazymax/xgo:latest
|
||||
@ -59,68 +63,7 @@ jobs:
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
||||
prerelease: false
|
||||
|
||||
release-lite:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release Lite
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Free Disk Space (Ubuntu)
|
||||
uses: jlumbroso/free-disk-space@main
|
||||
with:
|
||||
# this might remove tools that are actually needed,
|
||||
# if set to "true" but frees about 6 GB
|
||||
tool-cache: false
|
||||
|
||||
# all of these default to true, but feel free to set to
|
||||
# "false" if necessary for your workflow
|
||||
android: true
|
||||
dotnet: true
|
||||
haskell: true
|
||||
large-packages: true
|
||||
docker-images: true
|
||||
swap-storage: true
|
||||
|
||||
- name: Prerelease
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
id: ${{ github.event.release.id }}
|
||||
prerelease: true
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
sudo snap install zig --classic --beta
|
||||
docker pull crazymax/xgo:latest
|
||||
go install github.com/crazy-max/xgo@latest
|
||||
sudo apt install upx
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release lite
|
||||
bash build.sh release ${{ matrix.build-type == 'lite' && 'lite' || '' }} ${{ matrix.target-platform }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
69
.github/workflows/release_android.yml
vendored
69
.github/workflows/release_android.yml
vendored
@ -1,69 +0,0 @@
|
||||
name: Release builds (Android)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release_android:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release android
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
||||
|
||||
release_android_lite:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release lite android
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
43
.github/workflows/release_docker.yml
vendored
43
.github/workflows/release_docker.yml
vendored
@ -33,9 +33,6 @@ env:
|
||||
ARTIFACT_NAME_LITE: 'binaries_docker_release_lite'
|
||||
RELEASE_PLATFORMS: 'linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x,linux/ppc64le,linux/riscv64'
|
||||
IMAGE_PUSH: ${{ github.event_name == 'push' || github.event_name == 'workflow_dispatch' }}
|
||||
IMAGE_IS_PROD: ${{ github.ref_type == 'tag' || github.event.inputs.as_latest == 'true' }}
|
||||
IMAGE_TAGS_BETA: |
|
||||
type=raw,value=beta,enable={{is_default_branch}}
|
||||
|
||||
permissions:
|
||||
packages: write
|
||||
@ -65,14 +62,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build go binary (beta)
|
||||
if: env.IMAGE_IS_PROD != 'true'
|
||||
run: bash build.sh beta docker-multiplatform
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build go binary (release)
|
||||
if: env.IMAGE_IS_PROD == 'true'
|
||||
run: bash build.sh release docker-multiplatform
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -88,7 +78,7 @@ jobs:
|
||||
!build/musl-libs/**
|
||||
|
||||
build_binary_lite:
|
||||
name: Build Binaries for Docker Release
|
||||
name: Build Binaries for Docker Release (Lite)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -111,14 +101,7 @@ jobs:
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build go binary (beta)
|
||||
if: env.IMAGE_IS_PROD != 'true'
|
||||
run: bash build.sh beta lite docker-multiplatform
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build go binary (release)
|
||||
if: env.IMAGE_IS_PROD == 'true'
|
||||
run: bash build.sh release lite docker-multiplatform
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
@ -181,7 +164,7 @@ jobs:
|
||||
if: env.IMAGE_PUSH == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_ORG_NAME }}
|
||||
username: ${{ vars.DOCKERHUB_ORG_NAME_BACKUP || env.DOCKERHUB_ORG_NAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
@ -192,13 +175,11 @@ jobs:
|
||||
${{ env.REGISTRY }}/${{ env.GHCR_ORG_NAME }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_ORG_NAME }}/${{ env.IMAGE_NAME_DOCKERHUB }}
|
||||
tags: >
|
||||
${{ env.IMAGE_IS_PROD == 'true' && (
|
||||
github.event_name == 'workflow_dispatch'
|
||||
${{ github.event_name == 'workflow_dispatch'
|
||||
&& format('type=raw,value={0}', github.event.inputs.manual_tag)
|
||||
|| format('type=raw,value={0}', github.ref_name)
|
||||
) || env.IMAGE_TAGS_BETA }}
|
||||
|| format('type=raw,value={0}', github.ref_name) }}
|
||||
flavor: |
|
||||
latest=${{ env.IMAGE_IS_PROD }}
|
||||
latest=${{ github.event_name == 'push' || github.event.inputs.as_latest == 'true' }}
|
||||
${{ matrix.tag_favor }}
|
||||
|
||||
- name: Build and push
|
||||
@ -215,7 +196,7 @@ jobs:
|
||||
|
||||
release_docker_lite:
|
||||
needs: build_binary_lite
|
||||
name: Release Docker image
|
||||
name: Release Docker image (Lite)
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
@ -261,7 +242,7 @@ jobs:
|
||||
if: env.IMAGE_PUSH == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_ORG_NAME }}
|
||||
username: ${{ vars.DOCKERHUB_ORG_NAME_BACKUP || env.DOCKERHUB_ORG_NAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
@ -272,13 +253,11 @@ jobs:
|
||||
${{ env.REGISTRY }}/${{ env.GHCR_ORG_NAME }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.DOCKERHUB_ORG_NAME }}/${{ env.IMAGE_NAME_DOCKERHUB }}
|
||||
tags: >
|
||||
${{ env.IMAGE_IS_PROD == 'true' && (
|
||||
github.event_name == 'workflow_dispatch'
|
||||
${{ github.event_name == 'workflow_dispatch'
|
||||
&& format('type=raw,value={0}', github.event.inputs.manual_tag)
|
||||
|| format('type=raw,value={0}', github.ref_name)
|
||||
) || env.IMAGE_TAGS_BETA }}
|
||||
|| format('type=raw,value={0}', github.ref_name) }}
|
||||
flavor: |
|
||||
latest=${{ env.IMAGE_IS_PROD }}
|
||||
latest=${{ github.event_name == 'push' || github.event.inputs.as_latest == 'true' }}
|
||||
${{ matrix.tag_favor }}
|
||||
|
||||
- name: Build and push
|
||||
@ -291,4 +270,4 @@ jobs:
|
||||
build-args: ${{ matrix.build_arg }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
platforms: ${{ env.RELEASE_PLATFORMS }}
|
||||
platforms: ${{ env.RELEASE_PLATFORMS }}
|
69
.github/workflows/release_freebsd.yml
vendored
69
.github/workflows/release_freebsd.yml
vendored
@ -1,69 +0,0 @@
|
||||
name: Release builds (Freebsd)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release_freebsd:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release freebsd
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
||||
|
||||
release_freebsd_lite:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release lite freebsd
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
69
.github/workflows/release_linux_musl.yml
vendored
69
.github/workflows/release_linux_musl.yml
vendored
@ -1,69 +0,0 @@
|
||||
name: Release builds (linux_musl)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release_linux_musl:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release linux_musl
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
||||
|
||||
release_linux_musl_lite:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release lite linux_musl
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
70
.github/workflows/release_linux_musl_arm.yml
vendored
70
.github/workflows/release_linux_musl_arm.yml
vendored
@ -1,70 +0,0 @@
|
||||
name: Release builds (linux_musl_arm)
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
release_linux_musl_arm:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release linux_musl_arm
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
||||
|
||||
release_linux_musl_arm_lite:
|
||||
strategy:
|
||||
matrix:
|
||||
platform: [ ubuntu-latest ]
|
||||
go-version: [ '1.21' ]
|
||||
name: Release
|
||||
runs-on: ${{ matrix.platform }}
|
||||
steps:
|
||||
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: ${{ matrix.go-version }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Build
|
||||
run: |
|
||||
bash build.sh release lite linux_musl_arm
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload assets
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: build/compress/*
|
||||
|
9
.github/workflows/test_docker.yml
vendored
9
.github/workflows/test_docker.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: Docker Beta Release
|
||||
name: Beta Release (Docker)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
@ -20,7 +20,6 @@ env:
|
||||
IMAGE_NAME_DOCKERHUB: openlist
|
||||
REGISTRY: ghcr.io
|
||||
ARTIFACT_NAME: 'binaries_docker_release'
|
||||
ARTIFACT_NAME_LITE: 'binaries_docker_release_lite'
|
||||
RELEASE_PLATFORMS: 'linux/amd64,linux/arm64,linux/arm/v7,linux/386,linux/arm/v6,linux/s390x,linux/ppc64le,linux/riscv64'
|
||||
IMAGE_PUSH: ${{ github.event_name == 'push' }}
|
||||
IMAGE_TAGS_BETA: |
|
||||
@ -29,7 +28,7 @@ env:
|
||||
|
||||
jobs:
|
||||
build_binary:
|
||||
name: Build Binaries for Docker Release
|
||||
name: Build Binaries for Docker Release (Beta)
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
@ -69,7 +68,7 @@ jobs:
|
||||
|
||||
release_docker:
|
||||
needs: build_binary
|
||||
name: Release Docker image
|
||||
name: Release Docker image (Beta)
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
packages: write
|
||||
@ -117,7 +116,7 @@ jobs:
|
||||
if: env.IMAGE_PUSH == 'true'
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
username: ${{ env.DOCKERHUB_ORG_NAME }}
|
||||
username: ${{ vars.DOCKERHUB_ORG_NAME_BACKUP || env.DOCKERHUB_ORG_NAME }}
|
||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
|
@ -19,7 +19,7 @@ jobs:
|
||||
uses: peter-evans/repository-dispatch@v3
|
||||
with:
|
||||
token: ${{ secrets.EXTERNAL_REPO_TOKEN_LUCI_APP_OPENLIST }}
|
||||
repository: ${{ vars.HOOK_REPO || 'OpenListTeam/luci-app-openlist' }}
|
||||
repository: ${{ vars.HOOK_REPO || 'OpenListTeam/OpenList-OpenWRT' }}
|
||||
event-type: update-hashes
|
||||
client-payload: |
|
||||
{
|
||||
|
28
README.md
28
README.md
@ -20,6 +20,34 @@
|
||||
- [CODE OF CONDUCT](./CODE_OF_CONDUCT.md)
|
||||
- [LICENSE](./LICENSE)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
OpenList is an open-source project independently maintained by the OpenList Team, following the AGPL-3.0 license and committed to maintaining complete code openness and modification transparency.
|
||||
|
||||
We have noticed the emergence of some third-party projects in the community with names similar to this project, such as OpenListApp/OpenListApp, as well as some paid proprietary software using the same or similar naming. To avoid user confusion, we hereby declare:
|
||||
|
||||
- OpenList has no official association with any third-party derivative projects.
|
||||
|
||||
- All software, code, and services of this project are maintained by the OpenList Team and are freely available on GitHub.
|
||||
|
||||
- Project documentation and API services primarily rely on charitable resources provided by Cloudflare. There are currently no paid plans or commercial deployments, and the use of existing features does not involve any costs.
|
||||
|
||||
We respect the community's rights to free use and derivative development, but we also strongly urge downstream projects:
|
||||
|
||||
- Should not use the "OpenList" name for impersonation promotion or commercial gain;
|
||||
|
||||
- Must not distribute OpenList-based code in a closed-source manner or violate AGPL license terms.
|
||||
|
||||
To better maintain healthy ecosystem development, we recommend:
|
||||
|
||||
- Clearly indicate the project source and choose appropriate open-source licenses in accordance with the open-source spirit;
|
||||
|
||||
- If involving commercial use, please avoid using "OpenList" or any confusing naming as the project name;
|
||||
|
||||
- If you need to use materials located under OpenListTeam/Logo, you may modify and use them under compliance with the agreement.
|
||||
|
||||
Thank you for your support and understanding of the OpenList project.
|
||||
|
||||
## Features
|
||||
|
||||
- [x] Multiple storages
|
||||
|
28
README_cn.md
28
README_cn.md
@ -20,6 +20,34 @@
|
||||
- [行为准则](./CODE_OF_CONDUCT.md)
|
||||
- [许可证](./LICENSE)
|
||||
|
||||
## 免责声明
|
||||
|
||||
OpenList 是一个由 OpenList 团队独立维护的开源项目,遵循 AGPL-3.0 许可证,致力于保持完整的代码开放性和修改透明性。
|
||||
|
||||
我们注意到社区中出现了一些与本项目名称相似的第三方项目,如 OpenListApp/OpenListApp,以及部分采用相同或近似命名的收费专有软件。为避免用户误解,现声明如下:
|
||||
|
||||
- OpenList 与任何第三方衍生项目无官方关联。
|
||||
|
||||
- 本项目的全部软件、代码与服务由 OpenList 团队维护,可在 GitHub 免费获取。
|
||||
|
||||
- 项目文档与 API 服务均主要依托于 Cloudflare 提供的公益资源,目前无任何收费计划或商业部署,现有功能使用不涉及任何支出。
|
||||
|
||||
我们尊重社区的自由使用与衍生开发权利,但也强烈呼吁下游项目:
|
||||
|
||||
- 不应以“OpenList”名义进行冒名宣传或获取商业利益;
|
||||
|
||||
- 不得将基于 OpenList 的代码进行闭源分发或违反 AGPL 许可证条款。
|
||||
|
||||
为了更好地维护生态健康发展,我们建议:
|
||||
|
||||
- 明确注明项目来源,并以符合开源精神的方式选择适当的开源许可证;
|
||||
|
||||
- 如涉及商业用途,请避免使用“OpenList”或任何会产生混淆的方式作为项目名称;
|
||||
|
||||
- 若需使用本项目位于 OpenListTeam/Logo 下的素材,可在遵守协议的前提下进行修改后使用。
|
||||
|
||||
感谢您对 OpenList 项目的支持与理解。
|
||||
|
||||
## 功能
|
||||
|
||||
- [x] 多种存储
|
||||
|
28
README_ja.md
28
README_ja.md
@ -20,6 +20,34 @@
|
||||
- [行動規範](./CODE_OF_CONDUCT.md)
|
||||
- [ライセンス](./LICENSE)
|
||||
|
||||
## 免責事項
|
||||
|
||||
OpenListは、OpenListチームが独立して維持するオープンソースプロジェクトであり、AGPL-3.0ライセンスに従い、完全なコードの開放性と変更の透明性を維持することに専念しています。
|
||||
|
||||
コミュニティ内で、OpenListApp/OpenListAppなど、本プロジェクトと類似した名称を持つサードパーティプロジェクトや、同一または類似した命名を採用する有料専有ソフトウェアが出現していることを確認しています。ユーザーの誤解を避けるため、以下のように宣言いたします:
|
||||
|
||||
- OpenListは、いかなるサードパーティ派生プロジェクトとも公式な関連性はありません。
|
||||
|
||||
- 本プロジェクトのすべてのソフトウェア、コード、サービスはOpenListチームによって維持され、GitHubで無料で取得できます。
|
||||
|
||||
- プロジェクトドキュメントとAPIサービスは主にCloudflareが提供する公益リソースに依存しており、現在有料プランや商業展開はなく、既存機能の使用に費用は発生しません。
|
||||
|
||||
私たちはコミュニティの自由な使用と派生開発の権利を尊重しますが、下流プロジェクトに強く呼びかけます:
|
||||
|
||||
- 「OpenList」の名前で偽装宣伝や商業利益を得るべきではありません;
|
||||
|
||||
- OpenListベースのコードをクローズドソースで配布したり、AGPLライセンス条項に違反してはいけません。
|
||||
|
||||
エコシステムの健全な発展をより良く維持するため、以下を推奨します:
|
||||
|
||||
- プロジェクトの出典を明確に示し、オープンソース精神に合致する適切なオープンソースライセンスを選択する;
|
||||
|
||||
- 商業用途が関わる場合は、「OpenList」や混乱を招く可能性のある名前をプロジェクト名として使用することを避ける;
|
||||
|
||||
- OpenListTeam/Logo下の素材を使用する必要がある場合は、協定を遵守した上で修正して使用できます。
|
||||
|
||||
OpenListプロジェクトへのご支援とご理解をありがとうございます。
|
||||
|
||||
## 特徴
|
||||
|
||||
- [x] 複数ストレージ
|
||||
|
28
README_nl.md
28
README_nl.md
@ -20,6 +20,34 @@
|
||||
- [Gedragscode](./CODE_OF_CONDUCT.md)
|
||||
- [Licentie](./LICENSE)
|
||||
|
||||
## Disclaimer
|
||||
|
||||
OpenList is een open-source project dat onafhankelijk wordt onderhouden door het OpenList Team, volgend op de AGPL-3.0 licentie en toegewijd aan het behouden van volledige code openheid en transparantie van wijzigingen.
|
||||
|
||||
We hebben gemerkt dat er in de gemeenschap enkele derde partij projecten zijn verschenen met namen vergelijkbaar met dit project, zoals OpenListApp/OpenListApp, evenals enkele betaalde eigendomssoftware die dezelfde of soortgelijke naamgeving gebruikt. Om verwarring bij gebruikers te voorkomen, verklaren we hierbij:
|
||||
|
||||
- OpenList heeft geen officiële associatie met enige derde partij afgeleide projecten.
|
||||
|
||||
- Alle software, code en diensten van dit project worden onderhouden door het OpenList Team en zijn gratis beschikbaar op GitHub.
|
||||
|
||||
- Projectdocumentatie en API diensten zijn voornamelijk afhankelijk van liefdadigheidsbronnen verstrekt door Cloudflare. Er zijn momenteel geen betaalplannen of commerciële implementaties, en het gebruik van bestaande functies brengt geen kosten met zich mee.
|
||||
|
||||
We respecteren de rechten van de gemeenschap voor vrij gebruik en afgeleide ontwikkeling, maar we roepen downstream projecten ook ten zeerste op:
|
||||
|
||||
- Mogen niet de "OpenList" naam gebruiken voor namaakpromotie of commercieel gewin;
|
||||
|
||||
- Mogen OpenList-gebaseerde code niet distribueren op een closed-source manier of AGPL licentievoorwaarden schenden.
|
||||
|
||||
Om een gezonde ecosysteemontwikkeling beter te onderhouden, bevelen we aan:
|
||||
|
||||
- Duidelijk de projectbron aangeven en passende open-source licenties kiezen in overeenstemming met de open-source geest;
|
||||
|
||||
- Bij commercieel gebruik, vermijd het gebruik van "OpenList" of enige verwarrende naamgeving als projectnaam;
|
||||
|
||||
- Als u materialen onder OpenListTeam/Logo moet gebruiken, kunt u deze wijzigen en gebruiken onder naleving van de overeenkomst.
|
||||
|
||||
Dank u voor uw ondersteuning en begrip
|
||||
|
||||
## Functies
|
||||
|
||||
- [x] Meerdere opslagmogelijkheden
|
||||
|
10
build.sh
10
build.sh
@ -121,8 +121,8 @@ BuildDev() {
|
||||
xgo -targets=windows/amd64,darwin/amd64,darwin/arm64 -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
mv "$appName"-* dist
|
||||
cd dist
|
||||
cp ./"$appName"-windows-amd64.exe ./"$appName"-windows-amd64-upx.exe
|
||||
upx -9 ./"$appName"-windows-amd64-upx.exe
|
||||
# cp ./"$appName"-windows-amd64.exe ./"$appName"-windows-amd64-upx.exe
|
||||
# upx -9 ./"$appName"-windows-amd64-upx.exe
|
||||
find . -type f -print0 | xargs -0 md5sum >md5.txt
|
||||
cat md5.txt
|
||||
}
|
||||
@ -188,9 +188,9 @@ BuildRelease() {
|
||||
BuildWinArm64 ./build/"$appName"-windows-arm64.exe
|
||||
xgo -out "$appName" -ldflags="$ldflags" -tags=jsoniter .
|
||||
# why? Because some target platforms seem to have issues with upx compression
|
||||
upx -9 ./"$appName"-linux-amd64
|
||||
cp ./"$appName"-windows-amd64.exe ./"$appName"-windows-amd64-upx.exe
|
||||
upx -9 ./"$appName"-windows-amd64-upx.exe
|
||||
# upx -9 ./"$appName"-linux-amd64
|
||||
# cp ./"$appName"-windows-amd64.exe ./"$appName"-windows-amd64-upx.exe
|
||||
# upx -9 ./"$appName"-windows-amd64-upx.exe
|
||||
mv "$appName"-* build
|
||||
}
|
||||
|
||||
|
@ -18,7 +18,6 @@ var config = driver.Config{
|
||||
Name: "115 Cloud",
|
||||
DefaultRoot: "0",
|
||||
// OnlyProxy: true,
|
||||
// OnlyLocal: true,
|
||||
// NoOverwriteUpload: true,
|
||||
}
|
||||
|
||||
|
@ -306,6 +306,22 @@ func (d *Open115) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Open115) OfflineDownload(ctx context.Context, uris []string, dstDir model.Obj) ([]string, error) {
|
||||
return d.client.AddOfflineTaskURIs(ctx, uris, dstDir.GetID())
|
||||
}
|
||||
|
||||
func (d *Open115) DeleteOfflineTask(ctx context.Context, infoHash string, deleteFiles bool) error {
|
||||
return d.client.DeleteOfflineTask(ctx, infoHash, deleteFiles)
|
||||
}
|
||||
|
||||
func (d *Open115) OfflineList(ctx context.Context) (*sdk.OfflineTaskListResp, error) {
|
||||
resp, err := d.client.OfflineTaskList(ctx, 1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// func (d *Open115) GetArchiveMeta(ctx context.Context, obj model.Obj, args model.ArchiveArgs) (model.ArchiveMeta, error) {
|
||||
// // TODO get archive file meta-info, return errs.NotImplement to use an internal archive tool, optional
|
||||
// return nil, errs.NotImplement
|
||||
|
@ -11,23 +11,14 @@ type Addition struct {
|
||||
// define other
|
||||
OrderBy string `json:"order_by" type:"select" options:"file_name,file_size,user_utime,file_type"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc"`
|
||||
LimitRate float64 `json:"limit_rate" type:"float" default:"1" help:"limit all api request rate ([limit]r/1s)"`
|
||||
LimitRate float64 `json:"limit_rate" type:"float" default:"1" help:"limit all api request rate ([limit]r/1s)"`
|
||||
AccessToken string `json:"access_token" required:"true"`
|
||||
RefreshToken string `json:"refresh_token" required:"true"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "115 Open",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "115 Open",
|
||||
DefaultRoot: "0",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -19,12 +19,7 @@ type Addition struct {
|
||||
var config = driver.Config{
|
||||
Name: "115 Share",
|
||||
DefaultRoot: "0",
|
||||
// OnlyProxy: true,
|
||||
// OnlyLocal: true,
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: true,
|
||||
NoUpload: true,
|
||||
NoUpload: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -15,17 +15,10 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "123PanShare",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "123PanShare",
|
||||
LocalSort: true,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "0",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -3,6 +3,7 @@ package alias
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
@ -11,8 +12,10 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/fs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/sign"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||
)
|
||||
|
||||
type Alias struct {
|
||||
@ -111,21 +114,43 @@ func (d *Alias) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
for _, dst := range dsts {
|
||||
link, err := d.link(ctx, dst, sub, args)
|
||||
if err == nil {
|
||||
link.Expiration = nil // 去除非必要缓存,d.link里op.Lin有缓存
|
||||
if !args.Redirect && len(link.URL) > 0 {
|
||||
// 正常情况下 多并发 仅支持返回URL的驱动
|
||||
// alias套娃alias 可以让crypt、mega等驱动(不返回URL的) 支持并发
|
||||
if d.DownloadConcurrency > 0 {
|
||||
link.Concurrency = d.DownloadConcurrency
|
||||
}
|
||||
if d.DownloadPartSize > 0 {
|
||||
link.PartSize = d.DownloadPartSize * utils.KB
|
||||
reqPath := stdpath.Join(dst, sub)
|
||||
link, file, err := d.link(ctx, reqPath, args)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
var resultLink *model.Link
|
||||
if link != nil {
|
||||
resultLink = &model.Link{
|
||||
URL: link.URL,
|
||||
Header: link.Header,
|
||||
RangeReader: link.RangeReader,
|
||||
SyncClosers: utils.NewSyncClosers(link),
|
||||
}
|
||||
if link.MFile != nil {
|
||||
resultLink.RangeReader = &model.FileRangeReader{
|
||||
RangeReaderIF: stream.GetRangeReaderFromMFile(file.GetSize(), link.MFile),
|
||||
}
|
||||
}
|
||||
return link, nil
|
||||
|
||||
} else {
|
||||
resultLink = &model.Link{
|
||||
URL: fmt.Sprintf("%s/p%s?sign=%s",
|
||||
common.GetApiUrl(ctx),
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath)),
|
||||
}
|
||||
|
||||
}
|
||||
if !args.Redirect {
|
||||
if d.DownloadConcurrency > 0 {
|
||||
resultLink.Concurrency = d.DownloadConcurrency
|
||||
}
|
||||
if d.DownloadPartSize > 0 {
|
||||
resultLink.PartSize = d.DownloadPartSize * utils.KB
|
||||
}
|
||||
}
|
||||
return resultLink, nil
|
||||
}
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
@ -251,9 +276,13 @@ func (d *Alias) Put(ctx context.Context, dstDir model.Obj, s model.FileStreamer,
|
||||
reqPath, err := d.getReqPath(ctx, dstDir, true)
|
||||
if err == nil {
|
||||
if len(reqPath) == 1 {
|
||||
return fs.PutDirectly(ctx, *reqPath[0], s)
|
||||
return fs.PutDirectly(ctx, *reqPath[0], &stream.FileStream{
|
||||
Obj: s,
|
||||
Mimetype: s.GetMimetype(),
|
||||
WebPutAsTask: s.NeedStore(),
|
||||
Reader: s,
|
||||
})
|
||||
} else {
|
||||
defer s.Close()
|
||||
file, err := s.CacheFullInTempFile()
|
||||
if err != nil {
|
||||
return err
|
||||
@ -338,14 +367,6 @@ func (d *Alias) Extract(ctx context.Context, obj model.Obj, args model.ArchiveIn
|
||||
for _, dst := range dsts {
|
||||
link, err := d.extract(ctx, dst, sub, args)
|
||||
if err == nil {
|
||||
if !args.Redirect && len(link.URL) > 0 {
|
||||
if d.DownloadConcurrency > 0 {
|
||||
link.Concurrency = d.DownloadConcurrency
|
||||
}
|
||||
if d.DownloadPartSize > 0 {
|
||||
link.PartSize = d.DownloadPartSize * utils.KB
|
||||
}
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
}
|
||||
|
@ -96,37 +96,23 @@ func (d *Alias) list(ctx context.Context, dst, sub string, args *fs.ListArgs) ([
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs) (*model.Link, error) {
|
||||
reqPath := stdpath.Join(dst, sub)
|
||||
// 参考 crypt 驱动
|
||||
func (d *Alias) link(ctx context.Context, reqPath string, args model.LinkArgs) (*model.Link, model.Obj, error) {
|
||||
storage, reqActualPath, err := op.GetStorageAndActualPath(reqPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
useRawLink := len(common.GetApiUrl(ctx)) == 0 // ftp、s3
|
||||
if !useRawLink {
|
||||
_, ok := storage.(*Alias)
|
||||
useRawLink = !ok && !args.Redirect
|
||||
// proxy || ftp,s3
|
||||
if !args.Redirect || len(common.GetApiUrl(ctx)) == 0 {
|
||||
return op.Link(ctx, storage, reqActualPath, args)
|
||||
}
|
||||
if useRawLink {
|
||||
link, _, err := op.Link(ctx, storage, reqActualPath, args)
|
||||
return link, err
|
||||
}
|
||||
_, err = fs.Get(ctx, reqPath, &fs.GetArgs{NoLog: true})
|
||||
obj, err := fs.Get(ctx, reqPath, &fs.GetArgs{NoLog: true})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if common.ShouldProxy(storage, stdpath.Base(sub)) {
|
||||
link := &model.Link{
|
||||
URL: fmt.Sprintf("%s/p%s?sign=%s",
|
||||
common.GetApiUrl(ctx),
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath)),
|
||||
}
|
||||
return link, nil
|
||||
if common.ShouldProxy(storage, stdpath.Base(reqPath)) {
|
||||
return nil, obj, nil
|
||||
}
|
||||
link, _, err := op.Link(ctx, storage, reqActualPath, args)
|
||||
return link, err
|
||||
return op.Link(ctx, storage, reqActualPath, args)
|
||||
}
|
||||
|
||||
func (d *Alias) getReqPath(ctx context.Context, obj model.Obj, isParent bool) ([]*string, error) {
|
||||
|
@ -165,7 +165,7 @@ func (d *AliDrive) Remove(ctx context.Context, obj model.Obj) error {
|
||||
}
|
||||
|
||||
func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, streamer model.FileStreamer, up driver.UpdateProgress) error {
|
||||
file := stream.FileStream{
|
||||
file := &stream.FileStream{
|
||||
Obj: streamer,
|
||||
Reader: streamer,
|
||||
Mimetype: streamer.GetMimetype(),
|
||||
@ -209,7 +209,7 @@ func (d *AliDrive) Put(ctx context.Context, dstDir model.Obj, streamer model.Fil
|
||||
io.Closer
|
||||
}{
|
||||
Reader: io.MultiReader(buf, file),
|
||||
Closer: &file,
|
||||
Closer: file,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
@ -12,6 +12,7 @@ type Addition struct {
|
||||
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
|
||||
UseOnlineAPI bool `json:"use_online_api" default:"true"`
|
||||
AlipanType string `json:"alipan_type" required:"true" type:"select" default:"default" options:"default,alipanTV"`
|
||||
APIAddress string `json:"api_url_address" default:"https://api.oplist.org/alicloud/renewapi"`
|
||||
ClientID string `json:"client_id" help:"Keep it empty if you don't have one"`
|
||||
ClientSecret string `json:"client_secret" help:"Keep it empty if you don't have one"`
|
||||
@ -24,12 +25,6 @@ type Addition struct {
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "AliyundriveOpen",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "root",
|
||||
NoOverwriteUpload: true,
|
||||
}
|
||||
|
@ -27,13 +27,20 @@ func (d *AliyundriveOpen) _refreshToken() (string, string, error) {
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrorMessage string `json:"text"`
|
||||
}
|
||||
|
||||
// 根据AlipanType选项设置driver_txt
|
||||
driverTxt := "alicloud_qr"
|
||||
if d.AlipanType == "alipanTV" {
|
||||
driverTxt = "alicloud_tv"
|
||||
}
|
||||
|
||||
_, err := base.RestyClient.R().
|
||||
SetHeader("User-Agent", "Mozilla/5.0 (Macintosh; Apple macOS 15_5) AppleWebKit/537.36 (KHTML, like Gecko) Safari/537.36 Chrome/138.0.0.0 Openlist/425.6.30").
|
||||
SetResult(&resp).
|
||||
SetQueryParams(map[string]string{
|
||||
"refresh_ui": d.RefreshToken,
|
||||
"server_use": "true",
|
||||
"driver_txt": "alicloud_qr",
|
||||
"driver_txt": driverTxt,
|
||||
}).
|
||||
Get(u)
|
||||
if err != nil {
|
||||
|
@ -32,7 +32,6 @@ func init() {
|
||||
config: driver.Config{
|
||||
Name: "ChaoXingGroupDrive",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "-1",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
|
@ -26,15 +26,8 @@ type Addition struct {
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Cloudreve V4",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "cloudreve://my",
|
||||
CheckStatus: true,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: true,
|
||||
}
|
||||
|
||||
|
@ -1,12 +1,14 @@
|
||||
package crypt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
stdpath "path"
|
||||
"regexp"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
@ -241,6 +243,9 @@ func (d *Crypt) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
//return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
// https://github.com/rclone/rclone/blob/v1.67.0/backend/crypt/cipher.go#L37
|
||||
const fileHeaderSize = 32
|
||||
|
||||
func (d *Crypt) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
dstDirActualPath, err := d.getActualPathForRemote(file.GetPath(), false)
|
||||
if err != nil {
|
||||
@ -251,58 +256,64 @@ func (d *Crypt) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if remoteLink.RangeReadCloser == nil && remoteLink.MFile == nil && len(remoteLink.URL) == 0 {
|
||||
rrf, err := stream.GetRangeReaderFromLink(remoteFile.GetSize(), remoteLink)
|
||||
if err != nil {
|
||||
_ = remoteLink.Close()
|
||||
return nil, fmt.Errorf("the remote storage driver need to be enhanced to support encrytion")
|
||||
}
|
||||
resultRangeReadCloser := &model.RangeReadCloser{}
|
||||
resultRangeReadCloser.TryAdd(remoteLink.MFile)
|
||||
if remoteLink.RangeReadCloser != nil {
|
||||
resultRangeReadCloser.AddClosers(remoteLink.RangeReadCloser.GetClosers())
|
||||
}
|
||||
remoteFileSize := remoteFile.GetSize()
|
||||
rangeReaderFunc := func(ctx context.Context, underlyingOffset, underlyingLength int64) (io.ReadCloser, error) {
|
||||
length := underlyingLength
|
||||
if underlyingLength >= 0 && underlyingOffset+underlyingLength >= remoteFileSize {
|
||||
length = -1
|
||||
}
|
||||
if remoteLink.MFile != nil {
|
||||
_, err := remoteLink.MFile.Seek(underlyingOffset, io.SeekStart)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
//keep reuse same MFile and close at last.
|
||||
return io.NopCloser(remoteLink.MFile), nil
|
||||
}
|
||||
rrc := remoteLink.RangeReadCloser
|
||||
if rrc == nil && len(remoteLink.URL) > 0 {
|
||||
var err error
|
||||
rrc, err = stream.GetRangeReadCloserFromLink(remoteFileSize, remoteLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resultRangeReadCloser.AddClosers(rrc.GetClosers())
|
||||
remoteLink.RangeReadCloser = rrc
|
||||
}
|
||||
if rrc != nil {
|
||||
remoteReader, err := rrc.RangeRead(ctx, http_range.Range{Start: underlyingOffset, Length: length})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return remoteReader, nil
|
||||
}
|
||||
return nil, errs.NotSupport
|
||||
|
||||
}
|
||||
resultRangeReadCloser.RangeReader = func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||
readSeeker, err := d.cipher.DecryptDataSeek(ctx, rangeReaderFunc, httpRange.Start, httpRange.Length)
|
||||
mu := &sync.Mutex{}
|
||||
var fileHeader []byte
|
||||
rangeReaderFunc := func(ctx context.Context, offset, limit int64) (io.ReadCloser, error) {
|
||||
length := limit
|
||||
if offset == 0 && limit > 0 {
|
||||
mu.Lock()
|
||||
if limit <= fileHeaderSize {
|
||||
defer mu.Unlock()
|
||||
if fileHeader != nil {
|
||||
return io.NopCloser(bytes.NewReader(fileHeader[:limit])), nil
|
||||
}
|
||||
length = fileHeaderSize
|
||||
} else if fileHeader == nil {
|
||||
defer mu.Unlock()
|
||||
} else {
|
||||
mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
remoteReader, err := rrf.RangeRead(ctx, http_range.Range{Start: offset, Length: length})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return readSeeker, nil
|
||||
}
|
||||
|
||||
if offset == 0 && limit > 0 {
|
||||
fileHeader = make([]byte, fileHeaderSize)
|
||||
n, _ := io.ReadFull(remoteReader, fileHeader)
|
||||
if n != fileHeaderSize {
|
||||
fileHeader = nil
|
||||
return nil, fmt.Errorf("can't read data, expected=%d, got=%d", fileHeaderSize, n)
|
||||
}
|
||||
if limit <= fileHeaderSize {
|
||||
remoteReader.Close()
|
||||
return io.NopCloser(bytes.NewReader(fileHeader[:limit])), nil
|
||||
} else {
|
||||
remoteReader = utils.ReadCloser{
|
||||
Reader: io.MultiReader(bytes.NewReader(fileHeader), remoteReader),
|
||||
Closer: remoteReader,
|
||||
}
|
||||
}
|
||||
}
|
||||
return remoteReader, nil
|
||||
}
|
||||
return &model.Link{
|
||||
RangeReadCloser: resultRangeReadCloser,
|
||||
RangeReader: stream.RangeReaderFunc(func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||
readSeeker, err := d.cipher.DecryptDataSeek(ctx, rangeReaderFunc, httpRange.Start, httpRange.Length)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return readSeeker, nil
|
||||
}),
|
||||
SyncClosers: utils.NewSyncClosers(remoteLink),
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -26,17 +26,12 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Crypt",
|
||||
LocalSort: true,
|
||||
OnlyLocal: true,
|
||||
OnlyProxy: true,
|
||||
NoCache: true,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "Crypt",
|
||||
LocalSort: true,
|
||||
OnlyProxy: true,
|
||||
NoCache: true,
|
||||
DefaultRoot: "/",
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -16,17 +16,9 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Doubao",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "Doubao",
|
||||
LocalSort: true,
|
||||
DefaultRoot: "0",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -12,17 +12,10 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "DoubaoShare",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "DoubaoShare",
|
||||
LocalSort: true,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "/",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -18,13 +18,6 @@ type Addition struct {
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Dropbox",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "",
|
||||
NoOverwriteUpload: true,
|
||||
}
|
||||
|
||||
|
@ -16,17 +16,9 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "FebBox",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "FebBox",
|
||||
NoUpload: true,
|
||||
DefaultRoot: "0",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/jlaffaye/ftp"
|
||||
)
|
||||
|
||||
@ -26,7 +27,7 @@ func (d *FTP) GetAddition() driver.Additional {
|
||||
}
|
||||
|
||||
func (d *FTP) Init(ctx context.Context) error {
|
||||
return d.login()
|
||||
return d._login()
|
||||
}
|
||||
|
||||
func (d *FTP) Drop(ctx context.Context) error {
|
||||
@ -65,15 +66,22 @@ func (d *FTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*m
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r := NewFileReader(d.conn, encode(file.GetPath(), d.Encoding), file.GetSize())
|
||||
link := &model.Link{
|
||||
remoteFile := NewFileReader(d.conn, encode(file.GetPath(), d.Encoding), file.GetSize())
|
||||
if remoteFile != nil && !d.Config().OnlyLinkMFile {
|
||||
return &model.Link{
|
||||
RangeReader: &model.FileRangeReader{
|
||||
RangeReaderIF: stream.RateLimitRangeReaderFunc(stream.GetRangeReaderFromMFile(file.GetSize(), remoteFile)),
|
||||
},
|
||||
SyncClosers: utils.NewSyncClosers(remoteFile),
|
||||
}, nil
|
||||
}
|
||||
return &model.Link{
|
||||
MFile: &stream.RateLimitFile{
|
||||
File: r,
|
||||
File: remoteFile,
|
||||
Limiter: stream.ServerDownloadLimit,
|
||||
Ctx: ctx,
|
||||
},
|
||||
}
|
||||
return link, nil
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *FTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
|
@ -31,10 +31,11 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "FTP",
|
||||
LocalSort: true,
|
||||
OnlyLocal: true,
|
||||
DefaultRoot: "/",
|
||||
Name: "FTP",
|
||||
LocalSort: true,
|
||||
OnlyLinkMFile: true,
|
||||
DefaultRoot: "/",
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -1,18 +1,28 @@
|
||||
package ftp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/singleflight"
|
||||
"github.com/jlaffaye/ftp"
|
||||
)
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
func (d *FTP) login() error {
|
||||
err, _, _ := singleflight.ErrorGroup.Do(fmt.Sprintf("FTP.login:%p", d), func() (error, error) {
|
||||
return d._login(), nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *FTP) _login() error {
|
||||
|
||||
if d.conn != nil {
|
||||
_, err := d.conn.CurrentDir()
|
||||
if err == nil {
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"text/template"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/ProtonMail/go-crypto/openpgp"
|
||||
@ -96,7 +97,7 @@ func getPathCommonAncestor(a, b string) (ancestor, aChildName, bChildName, aRest
|
||||
}
|
||||
|
||||
func getUsername(ctx context.Context) string {
|
||||
user, ok := ctx.Value("user").(*model.User)
|
||||
user, ok := ctx.Value(conf.UserKey).(*model.User)
|
||||
if !ok {
|
||||
return "<system>"
|
||||
}
|
||||
|
@ -15,17 +15,8 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "GitHub Releases",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "GitHub Releases",
|
||||
NoUpload: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
@ -253,8 +254,8 @@ func (d *HalalCloud) getLink(ctx context.Context, file model.Obj, args model.Lin
|
||||
chunks := getChunkSizes(result.Sizes)
|
||||
resultRangeReader := func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||
length := httpRange.Length
|
||||
if httpRange.Length >= 0 && httpRange.Start+httpRange.Length >= size {
|
||||
length = -1
|
||||
if httpRange.Length < 0 || httpRange.Start+httpRange.Length >= size {
|
||||
length = size - httpRange.Start
|
||||
}
|
||||
oo := &openObject{
|
||||
ctx: ctx,
|
||||
@ -276,10 +277,9 @@ func (d *HalalCloud) getLink(ctx context.Context, file model.Obj, args model.Lin
|
||||
duration = time.Until(time.Now().Add(time.Hour))
|
||||
}
|
||||
|
||||
resultRangeReadCloser := &model.RangeReadCloser{RangeReader: resultRangeReader}
|
||||
return &model.Link{
|
||||
RangeReadCloser: resultRangeReadCloser,
|
||||
Expiration: &duration,
|
||||
RangeReader: stream.RateLimitRangeReaderFunc(resultRangeReader),
|
||||
Expiration: &duration,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
@ -18,17 +18,10 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "HalalCloud",
|
||||
LocalSort: false,
|
||||
OnlyLocal: true,
|
||||
OnlyProxy: true,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "HalalCloud",
|
||||
OnlyProxy: true,
|
||||
DefaultRoot: "/",
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -29,17 +29,8 @@ func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &ILanZou{
|
||||
config: driver.Config{
|
||||
Name: "ILanZou",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "ILanZou",
|
||||
DefaultRoot: "0",
|
||||
},
|
||||
conf: Conf{
|
||||
base: "https://api.ilanzou.com",
|
||||
@ -55,17 +46,8 @@ func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &ILanZou{
|
||||
config: driver.Config{
|
||||
Name: "FeijiPan",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "FeijiPan",
|
||||
DefaultRoot: "0",
|
||||
},
|
||||
conf: Conf{
|
||||
base: "https://api.feijipan.com",
|
||||
|
@ -17,7 +17,6 @@ var config = driver.Config{
|
||||
Name: "IPFS API",
|
||||
DefaultRoot: "/",
|
||||
LocalSort: true,
|
||||
OnlyProxy: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -14,8 +14,7 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "KodBox",
|
||||
DefaultRoot: "",
|
||||
Name: "KodBox",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -13,17 +13,9 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "LenovoNasShare",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: true,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "LenovoNasShare",
|
||||
LocalSort: true,
|
||||
NoUpload: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/sign"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||
"github.com/OpenListTeam/times"
|
||||
@ -172,19 +173,6 @@ func (d *Local) FileInfoToObj(ctx context.Context, f fs.FileInfo, reqPath string
|
||||
}
|
||||
return &file
|
||||
}
|
||||
func (d *Local) GetMeta(ctx context.Context, path string) (model.Obj, error) {
|
||||
f, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
file := d.FileInfoToObj(ctx, f, path, path)
|
||||
//h := "123123"
|
||||
//if s, ok := f.(model.SetHash); ok && file.GetHash() == ("","") {
|
||||
// s.SetHash(h,"SHA1")
|
||||
//}
|
||||
return file, nil
|
||||
|
||||
}
|
||||
|
||||
func (d *Local) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
path = filepath.Join(d.GetRootPath(), path)
|
||||
@ -220,7 +208,7 @@ func (d *Local) Get(ctx context.Context, path string) (model.Obj, error) {
|
||||
|
||||
func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
fullPath := file.GetPath()
|
||||
var link model.Link
|
||||
link := &model.Link{}
|
||||
if args.Type == "thumb" && utils.Ext(file.GetName()) != "svg" {
|
||||
var buf *bytes.Buffer
|
||||
var thumbPath *string
|
||||
@ -252,7 +240,14 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
||||
}
|
||||
link.MFile = open
|
||||
}
|
||||
return &link, nil
|
||||
if link.MFile != nil && !d.Config().OnlyLinkMFile {
|
||||
link.AddIfCloser(link.MFile)
|
||||
link.RangeReader = &model.FileRangeReader{
|
||||
RangeReaderIF: stream.GetRangeReaderFromMFile(file.GetSize(), link.MFile),
|
||||
}
|
||||
link.MFile = nil
|
||||
}
|
||||
return link, nil
|
||||
}
|
||||
|
||||
func (d *Local) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
|
@ -17,11 +17,12 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Local",
|
||||
OnlyLocal: true,
|
||||
LocalSort: true,
|
||||
NoCache: true,
|
||||
DefaultRoot: "/",
|
||||
Name: "Local",
|
||||
OnlyLinkMFile: false,
|
||||
LocalSort: true,
|
||||
NoCache: true,
|
||||
DefaultRoot: "/",
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -14,6 +14,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"github.com/t3rm1n4l/go-mega"
|
||||
@ -95,8 +96,8 @@ func (d *Mega) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
|
||||
size := file.GetSize()
|
||||
resultRangeReader := func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||
length := httpRange.Length
|
||||
if httpRange.Length >= 0 && httpRange.Start+httpRange.Length >= size {
|
||||
length = -1
|
||||
if httpRange.Length < 0 || httpRange.Start+httpRange.Length >= size {
|
||||
length = size - httpRange.Start
|
||||
}
|
||||
var down *mega.Download
|
||||
err := utils.Retry(3, time.Second, func() (err error) {
|
||||
@ -114,11 +115,9 @@ func (d *Mega) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
|
||||
|
||||
return readers.NewLimitedReadCloser(oo, length), nil
|
||||
}
|
||||
resultRangeReadCloser := &model.RangeReadCloser{RangeReader: resultRangeReader}
|
||||
resultLink := &model.Link{
|
||||
RangeReadCloser: resultRangeReadCloser,
|
||||
}
|
||||
return resultLink, nil
|
||||
return &model.Link{
|
||||
RangeReader: stream.RateLimitRangeReaderFunc(resultRangeReader),
|
||||
}, nil
|
||||
}
|
||||
return nil, fmt.Errorf("unable to convert dir to mega n")
|
||||
}
|
||||
|
@ -18,7 +18,8 @@ type Addition struct {
|
||||
var config = driver.Config{
|
||||
Name: "Mega_nz",
|
||||
LocalSort: true,
|
||||
OnlyLocal: true,
|
||||
OnlyProxy: true,
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -15,17 +15,8 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Misskey",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "Misskey",
|
||||
DefaultRoot: "/",
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -27,8 +27,7 @@ func (a *Addition) GetRootId() string {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "MoPan",
|
||||
// DefaultRoot: "root, / or other",
|
||||
Name: "MoPan",
|
||||
CheckStatus: true,
|
||||
Alert: "warning|This network disk may store your password in clear text. Please set your password carefully",
|
||||
}
|
||||
|
@ -73,7 +73,7 @@ func (d *NeteaseMusic) List(ctx context.Context, dir model.Obj, args model.ListA
|
||||
|
||||
func (d *NeteaseMusic) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
if lrc, ok := file.(*LyricObj); ok {
|
||||
if args.Type == "parsed" {
|
||||
if args.Type == "parsed" && !args.Redirect {
|
||||
return lrc.getLyricLink(), nil
|
||||
} else {
|
||||
return lrc.getProxyLink(ctx), nil
|
||||
|
@ -10,6 +10,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/sign"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils/random"
|
||||
"github.com/OpenListTeam/OpenList/v4/server/common"
|
||||
@ -54,7 +55,9 @@ func (lrc *LyricObj) getProxyLink(ctx context.Context) *model.Link {
|
||||
|
||||
func (lrc *LyricObj) getLyricLink() *model.Link {
|
||||
return &model.Link{
|
||||
MFile: strings.NewReader(lrc.lyric),
|
||||
RangeReader: &model.FileRangeReader{
|
||||
RangeReaderIF: stream.GetRangeReaderFromMFile(int64(len(lrc.lyric)), strings.NewReader(lrc.lyric)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,6 @@ var config = driver.Config{
|
||||
OnlyProxy: true,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -17,9 +17,8 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "PikPak",
|
||||
LocalSort: true,
|
||||
DefaultRoot: "",
|
||||
Name: "PikPak",
|
||||
LocalSort: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -15,10 +15,9 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "PikPakShare",
|
||||
LocalSort: true,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "",
|
||||
Name: "PikPakShare",
|
||||
LocalSort: true,
|
||||
NoUpload: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -28,7 +28,7 @@ func init() {
|
||||
return &QuarkOpen{
|
||||
config: driver.Config{
|
||||
Name: "QuarkOpen",
|
||||
OnlyLocal: true,
|
||||
OnlyProxy: true,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
|
@ -27,7 +27,6 @@ func init() {
|
||||
return &QuarkOrUC{
|
||||
config: driver.Config{
|
||||
Name: "Quark",
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
@ -43,7 +42,7 @@ func init() {
|
||||
return &QuarkOrUC{
|
||||
config: driver.Config{
|
||||
Name: "UC",
|
||||
OnlyLocal: true,
|
||||
OnlyProxy: true,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
|
@ -126,25 +126,13 @@ func (d *QuarkUCTV) List(ctx context.Context, dir model.Obj, args model.ListArgs
|
||||
}
|
||||
|
||||
func (d *QuarkUCTV) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
var fileLink FileLink
|
||||
_, err := d.request(ctx, "/file", "GET", func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"method": "download",
|
||||
"group_by": "source",
|
||||
"fid": file.GetID(),
|
||||
"resolution": "low,normal,high,super,2k,4k",
|
||||
"support": "dolby_vision",
|
||||
})
|
||||
}, &fileLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
f := file.(*Files)
|
||||
|
||||
if d.Addition.VideoLinkMethod == "streaming" && f.Category == 1 && f.Size > 0 {
|
||||
return d.getTranscodingLink(ctx, file)
|
||||
}
|
||||
|
||||
return &model.Link{
|
||||
URL: fileLink.Data.DownloadURL,
|
||||
Concurrency: 3,
|
||||
PartSize: 10 * utils.MB,
|
||||
}, nil
|
||||
return d.getDownloadLink(ctx, file)
|
||||
}
|
||||
|
||||
func (d *QuarkUCTV) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
||||
|
@ -14,6 +14,8 @@ type Addition struct {
|
||||
DeviceID string `json:"device_id" required:"false" default:""`
|
||||
// 登陆所用的数据 无需手动填写
|
||||
QueryToken string `json:"query_token" required:"false" default:"" help:"don't edit'"`
|
||||
// 视频文件链接获取方式 download(可获取源视频) or streaming(获取转码后的视频)
|
||||
VideoLinkMethod string `json:"link_method" required:"true" type:"select" options:"download,streaming" default:"download"`
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
@ -30,7 +32,6 @@ func init() {
|
||||
return &QuarkUCTV{
|
||||
config: driver.Config{
|
||||
Name: "QuarkTV",
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
NoUpload: true,
|
||||
@ -39,8 +40,8 @@ func init() {
|
||||
api: "https://open-api-drive.quark.cn",
|
||||
clientID: "d3194e61504e493eb6222857bccfed94",
|
||||
signKey: "kw2dvtd7p4t3pjl2d9ed9yc8yej8kw2d",
|
||||
appVer: "1.5.6",
|
||||
channel: "CP",
|
||||
appVer: "1.8.2.2",
|
||||
channel: "GENERAL",
|
||||
codeApi: "http://api.extscreen.com/quarkdrive",
|
||||
},
|
||||
}
|
||||
@ -49,7 +50,6 @@ func init() {
|
||||
return &QuarkUCTV{
|
||||
config: driver.Config{
|
||||
Name: "UCTV",
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
NoUpload: true,
|
||||
@ -58,7 +58,7 @@ func init() {
|
||||
api: "https://open-api-drive.uc.cn",
|
||||
clientID: "5acf882d27b74502b7040b0c65519aa7",
|
||||
signKey: "l3srvtd7p42l0d0x1u8d7yc8ye9kki4d",
|
||||
appVer: "1.6.5",
|
||||
appVer: "1.7.2.2",
|
||||
channel: "UCTVOFFICIALWEB",
|
||||
codeApi: "http://api.extscreen.com/ucdrive",
|
||||
},
|
||||
|
@ -92,7 +92,32 @@ type FilesData struct {
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type FileLink struct {
|
||||
type StreamingFileLink struct {
|
||||
CommonRsp
|
||||
Data struct {
|
||||
DefaultResolution string `json:"default_resolution"`
|
||||
LastPlayTime int `json:"last_play_time"`
|
||||
VideoInfo []struct {
|
||||
Resolution string `json:"resolution"`
|
||||
Accessable int `json:"accessable"`
|
||||
TransStatus string `json:"trans_status"`
|
||||
Duration int `json:"duration,omitempty"`
|
||||
Size int64 `json:"size,omitempty"`
|
||||
Format string `json:"format,omitempty"`
|
||||
Width int `json:"width,omitempty"`
|
||||
Height int `json:"height,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
Bitrate float64 `json:"bitrate,omitempty"`
|
||||
DolbyVision struct {
|
||||
Profile int `json:"profile"`
|
||||
Level int `json:"level"`
|
||||
} `json:"dolby_vision,omitempty"`
|
||||
} `json:"video_info"`
|
||||
AudioInfo []interface{} `json:"audio_info"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type DownloadFileLink struct {
|
||||
CommonRsp
|
||||
Data struct {
|
||||
Fid string `json:"fid"`
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
@ -210,3 +211,47 @@ func (d *QuarkUCTV) generateReqSign(method string, pathname string, key string)
|
||||
|
||||
return timestamp, xPanTokenHex, reqIDHex
|
||||
}
|
||||
|
||||
func (d *QuarkUCTV) getTranscodingLink(ctx context.Context, file model.Obj) (*model.Link, error) {
|
||||
var fileLink StreamingFileLink
|
||||
_, err := d.request(ctx, "/file", "GET", func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"method": "streaming",
|
||||
"group_by": "source",
|
||||
"fid": file.GetID(),
|
||||
"resolution": "low,normal,high,super,2k,4k",
|
||||
"support": "dolby_vision",
|
||||
})
|
||||
}, &fileLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Link{
|
||||
URL: fileLink.Data.VideoInfo[0].URL,
|
||||
Concurrency: 3,
|
||||
PartSize: 10 * utils.MB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *QuarkUCTV) getDownloadLink(ctx context.Context, file model.Obj) (*model.Link, error) {
|
||||
var fileLink DownloadFileLink
|
||||
_, err := d.request(ctx, "/file", "GET", func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"method": "download",
|
||||
"group_by": "source",
|
||||
"fid": file.GetID(),
|
||||
"resolution": "low,normal,high,super,2k,4k",
|
||||
"support": "dolby_vision",
|
||||
})
|
||||
}, &fileLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Link{
|
||||
URL: fileLink.Data.DownloadURL,
|
||||
Concurrency: 3,
|
||||
PartSize: 10 * utils.MB,
|
||||
}, nil
|
||||
}
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/url"
|
||||
stdpath "path"
|
||||
"strings"
|
||||
@ -158,7 +157,7 @@ func (d *S3) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) e
|
||||
Name: getPlaceholderName(d.Placeholder),
|
||||
Modified: time.Now(),
|
||||
},
|
||||
Reader: io.NopCloser(bytes.NewReader([]byte{})),
|
||||
Reader: bytes.NewReader([]byte{}),
|
||||
Mimetype: "application/octet-stream",
|
||||
}, func(float64) {})
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ func (d *SFTP) GetAddition() driver.Additional {
|
||||
}
|
||||
|
||||
func (d *SFTP) Init(ctx context.Context) error {
|
||||
return d.initClient()
|
||||
return d._initClient()
|
||||
}
|
||||
|
||||
func (d *SFTP) Drop(ctx context.Context) error {
|
||||
@ -63,6 +63,14 @@ func (d *SFTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if remoteFile != nil && !d.Config().OnlyLinkMFile {
|
||||
return &model.Link{
|
||||
RangeReader: &model.FileRangeReader{
|
||||
RangeReaderIF: stream.RateLimitRangeReaderFunc(stream.GetRangeReaderFromMFile(file.GetSize(), remoteFile)),
|
||||
},
|
||||
SyncClosers: utils.NewSyncClosers(remoteFile),
|
||||
}, nil
|
||||
}
|
||||
return &model.Link{
|
||||
MFile: &stream.RateLimitFile{
|
||||
File: remoteFile,
|
||||
|
@ -16,11 +16,12 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "SFTP",
|
||||
LocalSort: true,
|
||||
OnlyLocal: true,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: true,
|
||||
Name: "SFTP",
|
||||
LocalSort: true,
|
||||
OnlyLinkMFile: false,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: true,
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -1,8 +1,10 @@
|
||||
package sftp
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/singleflight"
|
||||
"github.com/pkg/sftp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"golang.org/x/crypto/ssh"
|
||||
@ -11,6 +13,12 @@ import (
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
func (d *SFTP) initClient() error {
|
||||
err, _, _ := singleflight.ErrorGroup.Do(fmt.Sprintf("SFTP.initClient:%p", d), func() (error, error) {
|
||||
return d._initClient(), nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
func (d *SFTP) _initClient() error {
|
||||
var auth ssh.AuthMethod
|
||||
if len(d.PrivateKey) > 0 {
|
||||
var err error
|
||||
@ -52,7 +60,9 @@ func (d *SFTP) clientReconnectOnConnectionError() error {
|
||||
return nil
|
||||
}
|
||||
log.Debugf("[sftp] discarding closed sftp connection: %v", err)
|
||||
_ = d.client.Close()
|
||||
if d.client != nil {
|
||||
_ = d.client.Close()
|
||||
}
|
||||
err = d.initClient()
|
||||
return err
|
||||
}
|
||||
|
@ -30,10 +30,10 @@ func (d *SMB) GetAddition() driver.Additional {
|
||||
}
|
||||
|
||||
func (d *SMB) Init(ctx context.Context) error {
|
||||
if strings.Index(d.Addition.Address, ":") < 0 {
|
||||
if !strings.Contains(d.Addition.Address, ":") {
|
||||
d.Addition.Address = d.Addition.Address + ":445"
|
||||
}
|
||||
return d.initFS()
|
||||
return d._initFS()
|
||||
}
|
||||
|
||||
func (d *SMB) Drop(ctx context.Context) error {
|
||||
@ -81,6 +81,13 @@ func (d *SMB) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*m
|
||||
return nil, err
|
||||
}
|
||||
d.updateLastConnTime()
|
||||
if remoteFile != nil && !d.Config().OnlyLinkMFile {
|
||||
return &model.Link{
|
||||
RangeReader: &model.FileRangeReader{
|
||||
RangeReaderIF: stream.RateLimitRangeReaderFunc(stream.GetRangeReaderFromMFile(file.GetSize(), remoteFile)),
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return &model.Link{
|
||||
MFile: &stream.RateLimitFile{
|
||||
File: remoteFile,
|
||||
|
@ -14,11 +14,12 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "SMB",
|
||||
LocalSort: true,
|
||||
OnlyLocal: true,
|
||||
DefaultRoot: ".",
|
||||
NoCache: true,
|
||||
Name: "SMB",
|
||||
LocalSort: true,
|
||||
OnlyLinkMFile: false,
|
||||
DefaultRoot: ".",
|
||||
NoCache: true,
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -1,6 +1,7 @@
|
||||
package smb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"net"
|
||||
"os"
|
||||
@ -8,6 +9,7 @@ import (
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/singleflight"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
|
||||
"github.com/hirochachacha/go-smb2"
|
||||
@ -26,6 +28,12 @@ func (d *SMB) getLastConnTime() time.Time {
|
||||
}
|
||||
|
||||
func (d *SMB) initFS() error {
|
||||
err, _, _ := singleflight.ErrorGroup.Do(fmt.Sprintf("SMB.initFS:%p", d), func() (error, error) {
|
||||
return d._initFS(), nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
func (d *SMB) _initFS() error {
|
||||
conn, err := net.Dial("tcp", d.Address)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -13,13 +13,14 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Strm",
|
||||
LocalSort: true,
|
||||
NoCache: true,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "/",
|
||||
OnlyLocal: true,
|
||||
OnlyProxy: true,
|
||||
Name: "Strm",
|
||||
LocalSort: true,
|
||||
NoCache: true,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "/",
|
||||
OnlyLinkMFile: true,
|
||||
OnlyProxy: true,
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -16,7 +16,7 @@ type Addition struct {
|
||||
var config = driver.Config{
|
||||
Name: "Template",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyLinkMFile: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
@ -25,6 +25,7 @@ var config = driver.Config{
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
NoLinkURL: false,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -85,7 +85,6 @@ func (i *Addition) GetIdentity() string {
|
||||
var config = driver.Config{
|
||||
Name: "ThunderX",
|
||||
LocalSort: true,
|
||||
OnlyProxy: false,
|
||||
}
|
||||
|
||||
var configExpert = driver.Config{
|
||||
|
@ -16,17 +16,10 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "UrlTree",
|
||||
LocalSort: true,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: true,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "",
|
||||
CheckStatus: true,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "UrlTree",
|
||||
LocalSort: true,
|
||||
NoCache: true,
|
||||
CheckStatus: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -14,11 +14,11 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Virtual",
|
||||
OnlyLocal: true,
|
||||
LocalSort: true,
|
||||
NeedMs: true,
|
||||
//NoCache: true,
|
||||
Name: "Virtual",
|
||||
OnlyLinkMFile: true,
|
||||
LocalSort: true,
|
||||
NeedMs: true,
|
||||
NoLinkURL: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -14,12 +14,9 @@ type Addition struct {
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "WeiYun",
|
||||
LocalSort: false,
|
||||
OnlyProxy: true,
|
||||
CheckStatus: true,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: false,
|
||||
Name: "WeiYun",
|
||||
OnlyProxy: true,
|
||||
CheckStatus: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -18,15 +18,7 @@ type Addition struct {
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "WoPan",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "0",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: true,
|
||||
}
|
||||
|
||||
|
4
go.mod
4
go.mod
@ -33,7 +33,7 @@ require (
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3
|
||||
github.com/gin-contrib/cors v1.7.6
|
||||
github.com/gin-gonic/gin v1.10.1
|
||||
github.com/go-resty/resty/v2 v2.14.0
|
||||
github.com/go-resty/resty/v2 v2.16.5
|
||||
github.com/go-webauthn/webauthn v0.11.1
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2
|
||||
github.com/google/uuid v1.6.0
|
||||
@ -89,7 +89,7 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/OpenListTeam/115-sdk-go v0.2.0
|
||||
github.com/OpenListTeam/115-sdk-go v0.2.1
|
||||
github.com/STARRY-S/zip v0.2.1 // indirect
|
||||
github.com/aymerick/douceur v0.2.0 // indirect
|
||||
github.com/blevesearch/go-faiss v1.0.25 // indirect
|
||||
|
4
go.sum
4
go.sum
@ -36,6 +36,8 @@ github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
|
||||
github.com/OpenListTeam/115-sdk-go v0.2.0 h1:qNEYpGQg++INLFXYzVW94uGFzCKAIoJJx19DBrsDvlU=
|
||||
github.com/OpenListTeam/115-sdk-go v0.2.0/go.mod h1:cfvitk2lwe6036iNi2h+iNxwxWDifKZsSvNtrur5BqU=
|
||||
github.com/OpenListTeam/115-sdk-go v0.2.1 h1:tzRUqdktS3h4o69+CXRDVwL0jYN7ccuX8TZWmLxkBGo=
|
||||
github.com/OpenListTeam/115-sdk-go v0.2.1/go.mod h1:cfvitk2lwe6036iNi2h+iNxwxWDifKZsSvNtrur5BqU=
|
||||
github.com/OpenListTeam/go-cache v0.1.0 h1:eV2+FCP+rt+E4OCJqLUW7wGccWZNJMV0NNkh+uChbAI=
|
||||
github.com/OpenListTeam/go-cache v0.1.0/go.mod h1:AHWjKhNK3LE4rorVdKyEALDHoeMnP8SjiNyfVlB+Pz4=
|
||||
github.com/OpenListTeam/gsync v0.1.0 h1:ywzGybOvA3lW8K1BUjKZ2IUlT2FSlzPO4DOazfYXjcs=
|
||||
@ -310,6 +312,8 @@ github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc
|
||||
github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo=
|
||||
github.com/go-resty/resty/v2 v2.14.0 h1:/rhkzsAqGQkozwfKS5aFAbb6TyKd3zyFRWcdRXLPCAU=
|
||||
github.com/go-resty/resty/v2 v2.14.0/go.mod h1:IW6mekUOsElt9C7oWr0XRt9BNSD6D5rr9mhk6NjmNHg=
|
||||
github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM=
|
||||
github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-webauthn/webauthn v0.11.1 h1:5G/+dg91/VcaJHTtJUfwIlNJkLwbJCcnUc4W8VtkpzA=
|
||||
|
@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
func NewAuthnInstance(c *gin.Context) (*webauthn.WebAuthn, error) {
|
||||
siteUrl, err := url.Parse(common.GetApiUrl(c))
|
||||
siteUrl, err := url.Parse(common.GetApiUrl(c.Request.Context()))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strconv"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/cmd/flags"
|
||||
@ -15,10 +16,16 @@ import (
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
var initialSettingItems []model.SettingItem
|
||||
|
||||
func initSettings() {
|
||||
InitialSettings()
|
||||
initialSettingItems := InitialSettings()
|
||||
isActive := func(key string) bool {
|
||||
for _, item := range initialSettingItems {
|
||||
if item.Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// check deprecated
|
||||
settings, err := op.GetSettingItems()
|
||||
if err != nil {
|
||||
@ -35,13 +42,16 @@ func initSettings() {
|
||||
}
|
||||
settingMap[v.Key] = &v
|
||||
}
|
||||
op.MigrationSettingItems = map[string]op.MigrationValueItem{}
|
||||
// create or save setting
|
||||
save := false
|
||||
var saveItems []model.SettingItem
|
||||
for i := range initialSettingItems {
|
||||
item := &initialSettingItems[i]
|
||||
item.Index = uint(i)
|
||||
if len(item.MigrationValue) == 0 {
|
||||
item.MigrationValue = item.Value
|
||||
migrationValue := item.MigrationValue
|
||||
if len(migrationValue) > 0 {
|
||||
op.MigrationSettingItems[item.Key] = op.MigrationValueItem{MigrationValue: item.MigrationValue, Value: item.Value}
|
||||
item.MigrationValue = ""
|
||||
}
|
||||
// err
|
||||
stored, ok := settingMap[item.Key]
|
||||
@ -52,7 +62,8 @@ func initSettings() {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if stored != nil && item.Key != conf.VERSION && stored.Value != item.MigrationValue {
|
||||
if item.Key != conf.VERSION && stored != nil &&
|
||||
(len(migrationValue) == 0 || stored.Value != migrationValue) {
|
||||
item.Value = stored.Value
|
||||
}
|
||||
_, err = op.HandleSettingItemHook(item)
|
||||
@ -60,13 +71,12 @@ func initSettings() {
|
||||
utils.Log.Errorf("failed to execute hook on %s: %+v", item.Key, err)
|
||||
continue
|
||||
}
|
||||
// save
|
||||
if stored == nil || *item != *stored {
|
||||
save = true
|
||||
saveItems = append(saveItems, *item)
|
||||
}
|
||||
}
|
||||
if save {
|
||||
err = db.SaveSettingItems(initialSettingItems)
|
||||
if len(saveItems) > 0 {
|
||||
err = db.SaveSettingItems(saveItems)
|
||||
if err != nil {
|
||||
utils.Log.Fatalf("failed save setting: %+v", err)
|
||||
} else {
|
||||
@ -75,15 +85,6 @@ func initSettings() {
|
||||
}
|
||||
}
|
||||
|
||||
func isActive(key string) bool {
|
||||
for _, item := range initialSettingItems {
|
||||
if item.Key == key {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func InitialSettings() []model.SettingItem {
|
||||
var token string
|
||||
if flags.Dev {
|
||||
@ -91,7 +92,7 @@ func InitialSettings() []model.SettingItem {
|
||||
} else {
|
||||
token = random.Token()
|
||||
}
|
||||
initialSettingItems = []model.SettingItem{
|
||||
initialSettingItems := []model.SettingItem{
|
||||
// site settings
|
||||
{Key: conf.VERSION, Value: conf.Version, Type: conf.TypeString, Group: model.SITE, Flag: model.READONLY},
|
||||
//{Key: conf.ApiUrl, Value: "", Type: conf.TypeString, Group: model.SITE},
|
||||
@ -223,7 +224,12 @@ func InitialSettings() []model.SettingItem {
|
||||
{Key: conf.StreamMaxServerDownloadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||||
{Key: conf.StreamMaxServerUploadSpeed, Value: "-1", Type: conf.TypeNumber, Group: model.TRAFFIC, Flag: model.PRIVATE},
|
||||
}
|
||||
initialSettingItems = append(initialSettingItems, tool.Tools.Items()...)
|
||||
additionalSettingItems := tool.Tools.Items()
|
||||
// 固定顺序
|
||||
sort.Slice(additionalSettingItems, func(i, j int) bool {
|
||||
return additionalSettingItems[i].Key < additionalSettingItems[j].Key
|
||||
})
|
||||
initialSettingItems = append(initialSettingItems, additionalSettingItems...)
|
||||
if flags.Dev {
|
||||
initialSettingItems = append(initialSettingItems, []model.SettingItem{
|
||||
{Key: "test_deprecated", Value: "test_value", Type: conf.TypeString, Flag: model.DEPRECATED},
|
||||
|
@ -63,6 +63,9 @@ const (
|
||||
// 115
|
||||
Pan115TempDir = "115_temp_dir"
|
||||
|
||||
// 115_open
|
||||
Pan115OpenTempDir = "115_open_temp_dir"
|
||||
|
||||
// pikpak
|
||||
PikPakTempDir = "pikpak_temp_dir"
|
||||
|
||||
@ -146,7 +149,19 @@ const (
|
||||
)
|
||||
|
||||
// ContextKey is the type of context keys.
|
||||
type ContextKey int
|
||||
|
||||
const (
|
||||
NoTaskKey = "no_task"
|
||||
ApiUrlKey = "api_url"
|
||||
_ ContextKey = iota
|
||||
|
||||
NoTaskKey
|
||||
ApiUrlKey
|
||||
UserKey
|
||||
MetaKey
|
||||
MetaPassKey
|
||||
ClientIPKey
|
||||
ProxyHeaderKey
|
||||
RequestHeaderKey
|
||||
UserAgentKey
|
||||
PathKey
|
||||
)
|
||||
|
@ -1,20 +1,26 @@
|
||||
package driver
|
||||
|
||||
type Config struct {
|
||||
Name string `json:"name"`
|
||||
LocalSort bool `json:"local_sort"`
|
||||
OnlyLocal bool `json:"only_local"`
|
||||
OnlyProxy bool `json:"only_proxy"`
|
||||
NoCache bool `json:"no_cache"`
|
||||
NoUpload bool `json:"no_upload"`
|
||||
NeedMs bool `json:"need_ms"` // if need get message from user, such as validate code
|
||||
DefaultRoot string `json:"default_root"`
|
||||
CheckStatus bool `json:"-"`
|
||||
Alert string `json:"alert"` //info,success,warning,danger
|
||||
NoOverwriteUpload bool `json:"-"` // whether to support overwrite upload
|
||||
ProxyRangeOption bool `json:"-"`
|
||||
Name string `json:"name"`
|
||||
LocalSort bool `json:"local_sort"`
|
||||
// if the driver returns Link with MFile, this should be set to true
|
||||
OnlyLinkMFile bool `json:"only_local"`
|
||||
OnlyProxy bool `json:"only_proxy"`
|
||||
NoCache bool `json:"no_cache"`
|
||||
NoUpload bool `json:"no_upload"`
|
||||
// if need get message from user, such as validate code
|
||||
NeedMs bool `json:"need_ms"`
|
||||
DefaultRoot string `json:"default_root"`
|
||||
CheckStatus bool `json:"-"`
|
||||
//info,success,warning,danger
|
||||
Alert string `json:"alert"`
|
||||
// whether to support overwrite upload
|
||||
NoOverwriteUpload bool `json:"-"`
|
||||
ProxyRangeOption bool `json:"-"`
|
||||
// if the driver returns Link without URL, this should be set to true
|
||||
NoLinkURL bool `json:"-"`
|
||||
}
|
||||
|
||||
func (c Config) MustProxy() bool {
|
||||
return c.OnlyProxy || c.OnlyLocal
|
||||
return c.OnlyProxy || c.OnlyLinkMFile || c.NoLinkURL
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
"io"
|
||||
"math/rand"
|
||||
"mime"
|
||||
"net/http"
|
||||
"os"
|
||||
stdpath "path"
|
||||
"path/filepath"
|
||||
@ -68,9 +67,7 @@ func (t *ArchiveDownloadTask) RunWithoutPushUploadTask() (*ArchiveContentUploadT
|
||||
if t.srcStorage == nil {
|
||||
t.srcStorage, err = op.GetStorageByMountPath(t.SrcStorageMp)
|
||||
}
|
||||
srcObj, tool, ss, err := op.GetArchiveToolAndStream(t.Ctx(), t.srcStorage, t.SrcObjPath, model.LinkArgs{
|
||||
Header: http.Header{},
|
||||
})
|
||||
srcObj, tool, ss, err := op.GetArchiveToolAndStream(t.Ctx(), t.srcStorage, t.SrcObjPath, model.LinkArgs{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -355,7 +352,7 @@ func archiveDecompress(ctx context.Context, srcObjPath, dstDirPath string, args
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
taskCreator, _ := ctx.Value("user").(*model.User)
|
||||
taskCreator, _ := ctx.Value(conf.UserKey).(*model.User)
|
||||
tsk := &ArchiveDownloadTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
Creator: taskCreator,
|
||||
|
@ -3,7 +3,6 @@ package fs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
stdpath "path"
|
||||
"time"
|
||||
|
||||
@ -86,26 +85,24 @@ func _copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool
|
||||
}
|
||||
if !srcObj.IsDir() {
|
||||
// copy file directly
|
||||
link, _, err := op.Link(ctx, srcStorage, srcObjActualPath, model.LinkArgs{
|
||||
Header: http.Header{},
|
||||
})
|
||||
link, _, err := op.Link(ctx, srcStorage, srcObjActualPath, model.LinkArgs{})
|
||||
if err != nil {
|
||||
return nil, errors.WithMessagef(err, "failed get [%s] link", srcObjPath)
|
||||
}
|
||||
fs := stream.FileStream{
|
||||
// any link provided is seekable
|
||||
ss, err := stream.NewSeekableStream(&stream.FileStream{
|
||||
Obj: srcObj,
|
||||
Ctx: ctx,
|
||||
}
|
||||
// any link provided is seekable
|
||||
ss, err := stream.NewSeekableStream(fs, link)
|
||||
}, link)
|
||||
if err != nil {
|
||||
_ = link.Close()
|
||||
return nil, errors.WithMessagef(err, "failed get [%s] stream", srcObjPath)
|
||||
}
|
||||
return nil, op.Put(ctx, dstStorage, dstDirActualPath, ss, nil, false)
|
||||
}
|
||||
}
|
||||
// not in the same storage
|
||||
taskCreator, _ := ctx.Value("user").(*model.User)
|
||||
taskCreator, _ := ctx.Value(conf.UserKey).(*model.User)
|
||||
t := &CopyTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
Creator: taskCreator,
|
||||
@ -165,19 +162,17 @@ func copyFileBetween2Storages(tsk *CopyTask, srcStorage, dstStorage driver.Drive
|
||||
return errors.WithMessagef(err, "failed get src [%s] file", srcFilePath)
|
||||
}
|
||||
tsk.SetTotalBytes(srcFile.GetSize())
|
||||
link, _, err := op.Link(tsk.Ctx(), srcStorage, srcFilePath, model.LinkArgs{
|
||||
Header: http.Header{},
|
||||
})
|
||||
link, _, err := op.Link(tsk.Ctx(), srcStorage, srcFilePath, model.LinkArgs{})
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get [%s] link", srcFilePath)
|
||||
}
|
||||
fs := stream.FileStream{
|
||||
// any link provided is seekable
|
||||
ss, err := stream.NewSeekableStream(&stream.FileStream{
|
||||
Obj: srcFile,
|
||||
Ctx: tsk.Ctx(),
|
||||
}
|
||||
// any link provided is seekable
|
||||
ss, err := stream.NewSeekableStream(fs, link)
|
||||
}, link)
|
||||
if err != nil {
|
||||
_ = link.Close()
|
||||
return errors.WithMessagef(err, "failed get [%s] stream", srcFilePath)
|
||||
}
|
||||
return op.Put(tsk.Ctx(), dstStorage, dstDirPath, ss, tsk.SetProgress, true)
|
||||
|
@ -3,6 +3,7 @@ package fs
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
@ -12,8 +13,8 @@ import (
|
||||
|
||||
// List files
|
||||
func list(ctx context.Context, path string, args *ListArgs) ([]model.Obj, error) {
|
||||
meta, _ := ctx.Value("meta").(*model.Meta)
|
||||
user, _ := ctx.Value("user").(*model.User)
|
||||
meta, _ := ctx.Value(conf.MetaKey).(*model.Meta)
|
||||
user, _ := ctx.Value(conf.UserKey).(*model.User)
|
||||
virtualFiles := op.GetStorageVirtualFilesByPath(path)
|
||||
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
||||
if err != nil && len(virtualFiles) == 0 {
|
||||
|
@ -3,11 +3,11 @@ package fs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
stdpath "path"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
@ -346,23 +346,18 @@ func (t *MoveTask) copyFile(srcStorage, dstStorage driver.Driver, srcFilePath, d
|
||||
return errors.WithMessagef(err, "failed get src [%s] file", srcFilePath)
|
||||
}
|
||||
|
||||
link, _, err := op.Link(t.Ctx(), srcStorage, srcFilePath, model.LinkArgs{
|
||||
Header: http.Header{},
|
||||
})
|
||||
link, _, err := op.Link(t.Ctx(), srcStorage, srcFilePath, model.LinkArgs{})
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get [%s] link", srcFilePath)
|
||||
}
|
||||
|
||||
fs := stream.FileStream{
|
||||
ss, err := stream.NewSeekableStream(&stream.FileStream{
|
||||
Obj: srcFile,
|
||||
Ctx: t.Ctx(),
|
||||
}
|
||||
|
||||
ss, err := stream.NewSeekableStream(fs, link)
|
||||
}, link)
|
||||
if err != nil {
|
||||
_ = link.Close()
|
||||
return errors.WithMessagef(err, "failed get [%s] stream", srcFilePath)
|
||||
}
|
||||
|
||||
return op.Put(t.Ctx(), dstStorage, dstDirPath, ss, nil, true)
|
||||
}
|
||||
|
||||
@ -592,7 +587,7 @@ func _moveWithValidation(ctx context.Context, srcObjPath, dstDirPath string, val
|
||||
}
|
||||
}
|
||||
|
||||
taskCreator, _ := ctx.Value("user").(*model.User)
|
||||
taskCreator, _ := ctx.Value(conf.UserKey).(*model.User)
|
||||
|
||||
// Create task immediately without any synchronous checks to avoid blocking frontend
|
||||
// All validation and type checking will be done asynchronously in the Run method
|
||||
|
@ -5,13 +5,14 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/task"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/OpenListTeam/tache"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type UploadTask struct {
|
||||
@ -55,7 +56,7 @@ func putAsTask(ctx context.Context, dstDirPath string, file model.FileStreamer)
|
||||
//file.SetReader(tempFile)
|
||||
//file.SetTmpFile(tempFile)
|
||||
}
|
||||
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
||||
taskCreator, _ := ctx.Value(conf.UserKey).(*model.User) // taskCreator is nil when convert failed
|
||||
t := &UploadTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
Creator: taskCreator,
|
||||
@ -73,9 +74,11 @@ func putAsTask(ctx context.Context, dstDirPath string, file model.FileStreamer)
|
||||
func putDirectly(ctx context.Context, dstDirPath string, file model.FileStreamer, lazyCache ...bool) error {
|
||||
storage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
||||
if err != nil {
|
||||
_ = file.Close()
|
||||
return errors.WithMessage(err, "failed get storage")
|
||||
}
|
||||
if storage.Config().NoUpload {
|
||||
_ = file.Close()
|
||||
return errors.WithStack(errs.UploadNotSupported)
|
||||
}
|
||||
return op.Put(ctx, storage, dstDirActualPath, file, nil, lazyCache...)
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
)
|
||||
@ -28,7 +29,7 @@ func WalkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
|
||||
}
|
||||
meta, _ := op.GetNearestMeta(name)
|
||||
// Read directory names.
|
||||
objs, err := List(context.WithValue(ctx, "meta", meta), name, &ListArgs{})
|
||||
objs, err := List(context.WithValue(ctx, conf.MetaKey, meta), name, &ListArgs{})
|
||||
if err != nil {
|
||||
return walkFnErr
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package model
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
@ -24,16 +25,25 @@ type LinkArgs struct {
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
URL string `json:"url"` // most common way
|
||||
Header http.Header `json:"header"` // needed header (for url)
|
||||
RangeReadCloser RangeReadCloserIF `json:"-"` // recommended way if can't use URL
|
||||
MFile io.ReadSeeker `json:"-"` // best for local,smb... file system, which exposes MFile
|
||||
URL string `json:"url"` // most common way
|
||||
Header http.Header `json:"header"` // needed header (for url)
|
||||
RangeReader RangeReaderIF `json:"-"` // recommended way if can't use URL
|
||||
MFile File `json:"-"` // best for local,smb... file system, which exposes MFile
|
||||
|
||||
Expiration *time.Duration // local cache expire Duration
|
||||
|
||||
//for accelerating request, use multi-thread downloading
|
||||
Concurrency int `json:"concurrency"`
|
||||
PartSize int `json:"part_size"`
|
||||
|
||||
utils.SyncClosers `json:"-"`
|
||||
}
|
||||
|
||||
func (l *Link) Close() error {
|
||||
if clr, ok := l.MFile.(io.Closer); ok {
|
||||
return errors.Join(clr.Close(), l.SyncClosers.Close())
|
||||
}
|
||||
return l.SyncClosers.Close()
|
||||
}
|
||||
|
||||
type OtherArgs struct {
|
||||
@ -74,23 +84,24 @@ type ArchiveDecompressArgs struct {
|
||||
PutIntoNewDir bool
|
||||
}
|
||||
|
||||
type RangeReadCloserIF interface {
|
||||
type RangeReaderIF interface {
|
||||
RangeRead(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
type RangeReadCloserIF interface {
|
||||
RangeReaderIF
|
||||
utils.ClosersIF
|
||||
}
|
||||
|
||||
var _ RangeReadCloserIF = (*RangeReadCloser)(nil)
|
||||
|
||||
type RangeReadCloser struct {
|
||||
RangeReader RangeReaderFunc
|
||||
RangeReader RangeReaderIF
|
||||
utils.Closers
|
||||
}
|
||||
|
||||
func (r *RangeReadCloser) RangeRead(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||
rc, err := r.RangeReader(ctx, httpRange)
|
||||
r.Closers.Add(rc)
|
||||
rc, err := r.RangeReader.RangeRead(ctx, httpRange)
|
||||
r.Add(rc)
|
||||
return rc, err
|
||||
}
|
||||
|
||||
// type WriterFunc func(w io.Writer) error
|
||||
type RangeReaderFunc func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error)
|
||||
|
@ -1,6 +1,9 @@
|
||||
package model
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"errors"
|
||||
"io"
|
||||
)
|
||||
|
||||
// File is basic file level accessing interface
|
||||
type File interface {
|
||||
@ -8,3 +11,22 @@ type File interface {
|
||||
io.ReaderAt
|
||||
io.Seeker
|
||||
}
|
||||
type FileCloser struct {
|
||||
File
|
||||
io.Closer
|
||||
}
|
||||
|
||||
func (f *FileCloser) Close() error {
|
||||
var errs []error
|
||||
if clr, ok := f.File.(io.Closer); ok {
|
||||
errs = append(errs, clr.Close())
|
||||
}
|
||||
if f.Closer != nil {
|
||||
errs = append(errs, f.Closer.Close())
|
||||
}
|
||||
return errors.Join(errs...)
|
||||
}
|
||||
|
||||
type FileRangeReader struct {
|
||||
RangeReaderIF
|
||||
}
|
||||
|
@ -37,7 +37,7 @@ type Obj interface {
|
||||
// FileStreamer ->check FileStream for more comments
|
||||
type FileStreamer interface {
|
||||
io.Reader
|
||||
io.Closer
|
||||
utils.ClosersIF
|
||||
Obj
|
||||
GetMimetype() string
|
||||
//SetReader(io.Reader)
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils/random"
|
||||
"github.com/OpenListTeam/go-cache"
|
||||
"github.com/go-webauthn/webauthn/webauthn"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
@ -21,6 +22,13 @@ const (
|
||||
|
||||
const StaticHashSalt = "https://github.com/alist-org/alist"
|
||||
|
||||
var LoginCache = cache.NewMemCache[int]()
|
||||
|
||||
var (
|
||||
DefaultLockDuration = time.Minute * 5
|
||||
DefaultMaxAuthRetries = 5
|
||||
)
|
||||
|
||||
type User struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"` // unique key
|
||||
Username string `json:"username" gorm:"unique" binding:"required"` // username
|
||||
|
@ -12,6 +12,7 @@ import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
||||
@ -70,7 +71,7 @@ func (d Downloader) Download(ctx context.Context, p *HttpRequestParams) (readClo
|
||||
|
||||
var finalP HttpRequestParams
|
||||
awsutil.Copy(&finalP, p)
|
||||
if finalP.Range.Length == -1 {
|
||||
if finalP.Range.Length < 0 || finalP.Range.Start+finalP.Range.Length > finalP.Size {
|
||||
finalP.Range.Length = finalP.Size - finalP.Range.Start
|
||||
}
|
||||
impl := downloader{params: &finalP, cfg: d, ctx: ctx}
|
||||
@ -120,7 +121,7 @@ type ConcurrencyLimit struct {
|
||||
Limit int // 需要大于0
|
||||
}
|
||||
|
||||
var ErrExceedMaxConcurrency = errors.New("ExceedMaxConcurrency")
|
||||
var ErrExceedMaxConcurrency = ErrorHttpStatusCode(http.StatusTooManyRequests)
|
||||
|
||||
func (l *ConcurrencyLimit) sub() error {
|
||||
l._m.Lock()
|
||||
@ -181,6 +182,7 @@ func (d *downloader) download() (io.ReadCloser, error) {
|
||||
resp.Body = utils.NewReadCloser(resp.Body, func() error {
|
||||
d.m.Lock()
|
||||
defer d.m.Unlock()
|
||||
var err error
|
||||
if closeFunc != nil {
|
||||
d.concurrencyFinish()
|
||||
err = closeFunc()
|
||||
@ -199,7 +201,7 @@ func (d *downloader) download() (io.ReadCloser, error) {
|
||||
d.pos = d.params.Range.Start
|
||||
d.maxPos = d.params.Range.Start + d.params.Range.Length
|
||||
d.concurrency = d.cfg.Concurrency
|
||||
d.sendChunkTask(true)
|
||||
_ = d.sendChunkTask(true)
|
||||
|
||||
var rc io.ReadCloser = NewMultiReadCloser(d.bufs[0], d.interrupt, d.finishBuf)
|
||||
|
||||
@ -303,7 +305,7 @@ func (d *downloader) finishBuf(id int) (isLast bool, nextBuf *Buf) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
d.sendChunkTask(false)
|
||||
_ = d.sendChunkTask(false)
|
||||
|
||||
d.readingID = id
|
||||
return false, d.getBuf(id)
|
||||
@ -398,14 +400,15 @@ var errInfiniteRetry = errors.New("infinite retry")
|
||||
func (d *downloader) tryDownloadChunk(params *HttpRequestParams, ch *chunk) (int64, error) {
|
||||
resp, err := d.cfg.HttpClient(d.ctx, params)
|
||||
if err != nil {
|
||||
if resp == nil {
|
||||
statusCode, ok := errors.Unwrap(err).(ErrorHttpStatusCode)
|
||||
if !ok {
|
||||
return 0, err
|
||||
}
|
||||
if resp.StatusCode == http.StatusRequestedRangeNotSatisfiable {
|
||||
if statusCode == http.StatusRequestedRangeNotSatisfiable {
|
||||
return 0, err
|
||||
}
|
||||
if ch.id == 0 { //第1个任务 有限的重试,超过重试就会结束请求
|
||||
switch resp.StatusCode {
|
||||
switch statusCode {
|
||||
default:
|
||||
return 0, err
|
||||
case http.StatusTooManyRequests:
|
||||
@ -414,7 +417,7 @@ func (d *downloader) tryDownloadChunk(params *HttpRequestParams, ch *chunk) (int
|
||||
case http.StatusGatewayTimeout:
|
||||
}
|
||||
<-time.After(time.Millisecond * 200)
|
||||
return 0, &errNeedRetry{err: fmt.Errorf("http request failure,status: %d", resp.StatusCode)}
|
||||
return 0, &errNeedRetry{err: err}
|
||||
}
|
||||
|
||||
// 来到这 说明第1个分片下载 连接成功了
|
||||
@ -450,7 +453,7 @@ func (d *downloader) tryDownloadChunk(params *HttpRequestParams, ch *chunk) (int
|
||||
return 0, err
|
||||
}
|
||||
}
|
||||
d.sendChunkTask(true)
|
||||
_ = d.sendChunkTask(true)
|
||||
n, err := utils.CopyWithBuffer(ch.buf, resp.Body)
|
||||
|
||||
if err != nil {
|
||||
@ -552,12 +555,26 @@ type chunk struct {
|
||||
|
||||
func DefaultHttpRequestFunc(ctx context.Context, params *HttpRequestParams) (*http.Response, error) {
|
||||
header := http_range.ApplyRangeToHttpHeader(params.Range, params.HeaderRef)
|
||||
return RequestHttp(ctx, "GET", header, params.URL)
|
||||
}
|
||||
|
||||
res, err := RequestHttp(ctx, "GET", header, params.URL)
|
||||
if err != nil {
|
||||
return res, err
|
||||
func GetRangeReaderHttpRequestFunc(rangeReader model.RangeReaderIF) HttpRequestFunc {
|
||||
return func(ctx context.Context, params *HttpRequestParams) (*http.Response, error) {
|
||||
rc, err := rangeReader.RangeRead(ctx, params.Range)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &http.Response{
|
||||
StatusCode: http.StatusPartialContent,
|
||||
Status: http.StatusText(http.StatusPartialContent),
|
||||
Body: rc,
|
||||
Header: http.Header{
|
||||
"Content-Range": {params.Range.ContentRange(params.Size)},
|
||||
},
|
||||
ContentLength: params.Range.Length,
|
||||
}, nil
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type HttpRequestParams struct {
|
||||
|
@ -114,14 +114,14 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time
|
||||
|
||||
// 使用请求的Context
|
||||
// 不然从sendContent读不到数据,即使请求断开CopyBuffer也会一直堵塞
|
||||
ctx := context.WithValue(r.Context(), "request_header", r.Header)
|
||||
ctx := r.Context()
|
||||
switch {
|
||||
case len(ranges) == 0:
|
||||
reader, err := RangeReadCloser.RangeRead(ctx, http_range.Range{Length: -1})
|
||||
if err != nil {
|
||||
code = http.StatusRequestedRangeNotSatisfiable
|
||||
if errors.Is(err, ErrExceedMaxConcurrency) {
|
||||
code = http.StatusTooManyRequests
|
||||
if statusCode, ok := errors.Unwrap(err).(ErrorHttpStatusCode); ok {
|
||||
code = int(statusCode)
|
||||
}
|
||||
http.Error(w, err.Error(), code)
|
||||
return nil
|
||||
@ -143,8 +143,8 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time
|
||||
sendContent, err = RangeReadCloser.RangeRead(ctx, ra)
|
||||
if err != nil {
|
||||
code = http.StatusRequestedRangeNotSatisfiable
|
||||
if errors.Is(err, ErrExceedMaxConcurrency) {
|
||||
code = http.StatusTooManyRequests
|
||||
if statusCode, ok := errors.Unwrap(err).(ErrorHttpStatusCode); ok {
|
||||
code = int(statusCode)
|
||||
}
|
||||
http.Error(w, err.Error(), code)
|
||||
return nil
|
||||
@ -205,8 +205,8 @@ func ServeHTTP(w http.ResponseWriter, r *http.Request, name string, modTime time
|
||||
log.Warnf("Maybe size incorrect or reader not giving correct/full data, or connection closed before finish. written bytes: %d ,sendSize:%d, ", written, sendSize)
|
||||
}
|
||||
code = http.StatusInternalServerError
|
||||
if errors.Is(err, ErrExceedMaxConcurrency) {
|
||||
code = http.StatusTooManyRequests
|
||||
if statusCode, ok := errors.Unwrap(err).(ErrorHttpStatusCode); ok {
|
||||
code = int(statusCode)
|
||||
}
|
||||
w.WriteHeader(code)
|
||||
return err
|
||||
@ -259,11 +259,17 @@ func RequestHttp(ctx context.Context, httpMethod string, headerOverride http.Hea
|
||||
_ = res.Body.Close()
|
||||
msg := string(all)
|
||||
log.Debugln(msg)
|
||||
return res, fmt.Errorf("http request [%s] failure,status: %d response:%s", URL, res.StatusCode, msg)
|
||||
return nil, fmt.Errorf("http request [%s] failure,status: %w response:%s", URL, ErrorHttpStatusCode(res.StatusCode), msg)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
type ErrorHttpStatusCode int
|
||||
|
||||
func (e ErrorHttpStatusCode) Error() string {
|
||||
return fmt.Sprintf("%d|%s", e, http.StatusText(int(e)))
|
||||
}
|
||||
|
||||
var once sync.Once
|
||||
var httpClient *http.Client
|
||||
|
||||
|
139
internal/offline_download/115_open/client.go
Normal file
139
internal/offline_download/115_open/client.go
Normal file
@ -0,0 +1,139 @@
|
||||
package _115_open
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
_115_open "github.com/OpenListTeam/OpenList/v4/drivers/115_open"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/setting"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/offline_download/tool"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
)
|
||||
|
||||
type Open115 struct {
|
||||
}
|
||||
|
||||
func (o *Open115) Name() string {
|
||||
return "115 Open"
|
||||
}
|
||||
|
||||
func (o *Open115) Items() []model.SettingItem {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Open115) Run(task *tool.DownloadTask) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (o *Open115) Init() (string, error) {
|
||||
return "ok", nil
|
||||
}
|
||||
|
||||
func (o *Open115) IsReady() bool {
|
||||
tempDir := setting.GetStr(conf.Pan115OpenTempDir)
|
||||
if tempDir == "" {
|
||||
return false
|
||||
}
|
||||
storage, _, err := op.GetStorageAndActualPath(tempDir)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if _, ok := storage.(*_115_open.Open115); !ok {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (o *Open115) AddURL(args *tool.AddUrlArgs) (string, error) {
|
||||
storage, actualPath, err := op.GetStorageAndActualPath(args.TempDir)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
driver115Open, ok := storage.(*_115_open.Open115)
|
||||
if !ok {
|
||||
return "", fmt.Errorf("unsupported storage driver for offline download, only 115 Cloud is supported")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
if err := op.MakeDir(ctx, storage, actualPath); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
parentDir, err := op.GetUnwrap(ctx, storage, actualPath)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
hashs, err := driver115Open.OfflineDownload(ctx, []string{args.Url}, parentDir)
|
||||
if err != nil || len(hashs) < 1 {
|
||||
return "", fmt.Errorf("failed to add offline download task: %w", err)
|
||||
}
|
||||
|
||||
return hashs[0], nil
|
||||
}
|
||||
|
||||
func (o *Open115) Remove(task *tool.DownloadTask) error {
|
||||
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
driver115Open, ok := storage.(*_115_open.Open115)
|
||||
if !ok {
|
||||
return fmt.Errorf("unsupported storage driver for offline download, only 115 Open is supported")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
if err := driver115Open.DeleteOfflineTask(ctx, task.GID, false); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *Open115) Status(task *tool.DownloadTask) (*tool.Status, error) {
|
||||
storage, _, err := op.GetStorageAndActualPath(task.TempDir)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
driver115Open, ok := storage.(*_115_open.Open115)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported storage driver for offline download, only 115 Open is supported")
|
||||
}
|
||||
|
||||
tasks, err := driver115Open.OfflineList(context.Background())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
s := &tool.Status{
|
||||
Progress: 0,
|
||||
NewGID: "",
|
||||
Completed: false,
|
||||
Status: "the task has been deleted",
|
||||
Err: nil,
|
||||
}
|
||||
|
||||
for _, t := range tasks.Tasks {
|
||||
if t.InfoHash == task.GID {
|
||||
s.Progress = float64(t.PercentDone)
|
||||
s.Status = t.GetStatus()
|
||||
s.Completed = t.IsDone()
|
||||
s.TotalBytes = t.Size
|
||||
if t.IsFailed() {
|
||||
s.Err = fmt.Errorf(t.GetStatus())
|
||||
}
|
||||
return s, nil
|
||||
}
|
||||
}
|
||||
s.Err = fmt.Errorf("the task has been deleted")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var _ tool.Tool = (*Open115)(nil)
|
||||
|
||||
func init() {
|
||||
tool.Tools.Add(&Open115{})
|
||||
}
|
@ -2,6 +2,7 @@ package offline_download
|
||||
|
||||
import (
|
||||
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/115"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/115_open"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/aria2"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/http"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download/pikpak"
|
||||
|
@ -2,6 +2,7 @@ package tool
|
||||
|
||||
import (
|
||||
"context"
|
||||
_115_open "github.com/OpenListTeam/OpenList/v4/drivers/115_open"
|
||||
|
||||
"net/url"
|
||||
stdpath "path"
|
||||
@ -94,6 +95,12 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
|
||||
} else {
|
||||
tempDir = filepath.Join(setting.GetStr(conf.Pan115TempDir), uid)
|
||||
}
|
||||
case "115 Open":
|
||||
if _, ok := storage.(*_115_open.Open115); ok {
|
||||
tempDir = args.DstDirPath
|
||||
} else {
|
||||
tempDir = filepath.Join(setting.GetStr(conf.Pan115OpenTempDir), uid)
|
||||
}
|
||||
case "PikPak":
|
||||
if _, ok := storage.(*pikpak.PikPak); ok {
|
||||
tempDir = args.DstDirPath
|
||||
@ -115,7 +122,7 @@ func AddURL(ctx context.Context, args *AddURLArgs) (task.TaskExtensionInfo, erro
|
||||
}
|
||||
}
|
||||
|
||||
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
||||
taskCreator, _ := ctx.Value(conf.UserKey).(*model.User) // taskCreator is nil when convert failed
|
||||
t := &DownloadTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
Creator: taskCreator,
|
||||
|
@ -103,6 +103,9 @@ outer:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if t.tool.Name() == "115 Open" {
|
||||
return nil
|
||||
}
|
||||
t.Status = "offline download completed, maybe transferring"
|
||||
// hack for qBittorrent
|
||||
if t.tool.Name() == "qBittorrent" {
|
||||
@ -166,7 +169,7 @@ func (t *DownloadTask) Update() (bool, error) {
|
||||
|
||||
func (t *DownloadTask) Transfer() error {
|
||||
toolName := t.tool.Name()
|
||||
if toolName == "115 Cloud" || toolName == "PikPak" || toolName == "Thunder" || toolName == "ThunderBrowser" {
|
||||
if toolName == "115 Cloud" || toolName == "115 Open" || toolName == "PikPak" || toolName == "Thunder" || toolName == "ThunderBrowser" {
|
||||
// 如果不是直接下载到目标路径,则进行转存
|
||||
if t.TempDir != t.DstDirPath {
|
||||
return transferObj(t.Ctx(), t.TempDir, t.DstDirPath, t.DeletePolicy)
|
||||
@ -178,7 +181,7 @@ func (t *DownloadTask) Transfer() error {
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed get dst storage")
|
||||
}
|
||||
taskCreator, _ := t.Ctx().Value("user").(*model.User)
|
||||
taskCreator, _ := t.Ctx().Value(conf.UserKey).(*model.User)
|
||||
task := &TransferTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
Creator: taskCreator,
|
||||
|
@ -3,12 +3,12 @@ package tool
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
stdpath "path"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
@ -43,11 +43,11 @@ func (t *TransferTask) Run() error {
|
||||
defer func() { t.SetEndTime(time.Now()) }()
|
||||
if t.SrcStorage == nil {
|
||||
if t.DeletePolicy == UploadDownloadStream {
|
||||
rrc, err := stream.GetRangeReadCloserFromLink(t.GetTotalBytes(), &model.Link{URL: t.Url})
|
||||
rr, err := stream.GetRangeReaderFromLink(t.GetTotalBytes(), &model.Link{URL: t.Url})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r, err := rrc.RangeRead(t.Ctx(), http_range.Range{Length: t.GetTotalBytes()})
|
||||
r, err := rr.RangeRead(t.Ctx(), http_range.Range{Length: t.GetTotalBytes()})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -63,9 +63,8 @@ func (t *TransferTask) Run() error {
|
||||
},
|
||||
Reader: r,
|
||||
Mimetype: mimetype,
|
||||
Closers: utils.NewClosers(rrc),
|
||||
Closers: utils.NewClosers(r),
|
||||
}
|
||||
defer s.Close()
|
||||
return op.Put(t.Ctx(), t.DstStorage, t.DstDirPath, s, t.SetProgress)
|
||||
}
|
||||
return transferStdPath(t)
|
||||
@ -118,7 +117,7 @@ func transferStd(ctx context.Context, tempDir, dstDirPath string, deletePolicy D
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
taskCreator, _ := ctx.Value("user").(*model.User)
|
||||
taskCreator, _ := ctx.Value(conf.UserKey).(*model.User)
|
||||
for _, entry := range entries {
|
||||
t := &TransferTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
@ -218,7 +217,7 @@ func transferObj(ctx context.Context, tempDir, dstDirPath string, deletePolicy D
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed list src [%s] objs", tempDir)
|
||||
}
|
||||
taskCreator, _ := ctx.Value("user").(*model.User) // taskCreator is nil when convert failed
|
||||
taskCreator, _ := ctx.Value(conf.UserKey).(*model.User) // taskCreator is nil when convert failed
|
||||
for _, obj := range objs {
|
||||
t := &TransferTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
@ -279,19 +278,17 @@ func transferObjFile(t *TransferTask) error {
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get src [%s] file", t.SrcObjPath)
|
||||
}
|
||||
link, _, err := op.Link(t.Ctx(), t.SrcStorage, t.SrcObjPath, model.LinkArgs{
|
||||
Header: http.Header{},
|
||||
})
|
||||
link, _, err := op.Link(t.Ctx(), t.SrcStorage, t.SrcObjPath, model.LinkArgs{})
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get [%s] link", t.SrcObjPath)
|
||||
}
|
||||
fs := stream.FileStream{
|
||||
// any link provided is seekable
|
||||
ss, err := stream.NewSeekableStream(&stream.FileStream{
|
||||
Obj: srcFile,
|
||||
Ctx: t.Ctx(),
|
||||
}
|
||||
// any link provided is seekable
|
||||
ss, err := stream.NewSeekableStream(fs, link)
|
||||
}, link)
|
||||
if err != nil {
|
||||
_ = link.Close()
|
||||
return errors.WithMessagef(err, "failed get [%s] stream", t.SrcObjPath)
|
||||
}
|
||||
t.SetTotalBytes(srcFile.GetSize())
|
||||
|
@ -31,12 +31,6 @@ func GetArchiveMeta(ctx context.Context, storage driver.Driver, path string, arg
|
||||
}
|
||||
path = utils.FixAndCleanPath(path)
|
||||
key := Key(storage, path)
|
||||
if !args.Refresh {
|
||||
if meta, ok := archiveMetaCache.Get(key); ok {
|
||||
log.Debugf("use cache when get %s archive meta", path)
|
||||
return meta, nil
|
||||
}
|
||||
}
|
||||
fn := func() (*model.ArchiveMetaProvider, error) {
|
||||
_, m, err := getArchiveMeta(ctx, storage, path, args)
|
||||
if err != nil {
|
||||
@ -47,10 +41,16 @@ func GetArchiveMeta(ctx context.Context, storage driver.Driver, path string, arg
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
if storage.Config().OnlyLocal {
|
||||
if storage.Config().OnlyLinkMFile {
|
||||
meta, err := fn()
|
||||
return meta, err
|
||||
}
|
||||
if !args.Refresh {
|
||||
if meta, ok := archiveMetaCache.Get(key); ok {
|
||||
log.Debugf("use cache when get %s archive meta", path)
|
||||
return meta, nil
|
||||
}
|
||||
}
|
||||
meta, err, _ := archiveMetaG.Do(key, fn)
|
||||
return meta, err
|
||||
}
|
||||
@ -62,12 +62,7 @@ func GetArchiveToolAndStream(ctx context.Context, storage driver.Driver, path st
|
||||
}
|
||||
baseName, ext, found := strings.Cut(obj.GetName(), ".")
|
||||
if !found {
|
||||
if clr, ok := l.MFile.(io.Closer); ok {
|
||||
_ = clr.Close()
|
||||
}
|
||||
if l.RangeReadCloser != nil {
|
||||
_ = l.RangeReadCloser.Close()
|
||||
}
|
||||
_ = l.Close()
|
||||
return nil, nil, nil, errors.Errorf("failed get archive tool: the obj does not have an extension.")
|
||||
}
|
||||
partExt, t, err := tool.GetArchiveTool("." + ext)
|
||||
@ -75,23 +70,13 @@ func GetArchiveToolAndStream(ctx context.Context, storage driver.Driver, path st
|
||||
var e error
|
||||
partExt, t, e = tool.GetArchiveTool(stdpath.Ext(obj.GetName()))
|
||||
if e != nil {
|
||||
if clr, ok := l.MFile.(io.Closer); ok {
|
||||
_ = clr.Close()
|
||||
}
|
||||
if l.RangeReadCloser != nil {
|
||||
_ = l.RangeReadCloser.Close()
|
||||
}
|
||||
_ = l.Close()
|
||||
return nil, nil, nil, errors.WithMessagef(stderrors.Join(err, e), "failed get archive tool: %s", ext)
|
||||
}
|
||||
}
|
||||
ss, err := stream.NewSeekableStream(stream.FileStream{Ctx: ctx, Obj: obj}, l)
|
||||
ss, err := stream.NewSeekableStream(&stream.FileStream{Ctx: ctx, Obj: obj}, l)
|
||||
if err != nil {
|
||||
if clr, ok := l.MFile.(io.Closer); ok {
|
||||
_ = clr.Close()
|
||||
}
|
||||
if l.RangeReadCloser != nil {
|
||||
_ = l.RangeReadCloser.Close()
|
||||
}
|
||||
_ = l.Close()
|
||||
return nil, nil, nil, errors.WithMessagef(err, "failed get [%s] stream", path)
|
||||
}
|
||||
ret := []*stream.SeekableStream{ss}
|
||||
@ -107,14 +92,9 @@ func GetArchiveToolAndStream(ctx context.Context, storage driver.Driver, path st
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
ss, err = stream.NewSeekableStream(stream.FileStream{Ctx: ctx, Obj: o}, l)
|
||||
ss, err = stream.NewSeekableStream(&stream.FileStream{Ctx: ctx, Obj: o}, l)
|
||||
if err != nil {
|
||||
if clr, ok := l.MFile.(io.Closer); ok {
|
||||
_ = clr.Close()
|
||||
}
|
||||
if l.RangeReadCloser != nil {
|
||||
_ = l.RangeReadCloser.Close()
|
||||
}
|
||||
_ = l.Close()
|
||||
for _, s := range ret {
|
||||
_ = s.Close()
|
||||
}
|
||||
@ -375,12 +355,12 @@ func ArchiveGet(ctx context.Context, storage driver.Driver, path string, args mo
|
||||
}
|
||||
|
||||
type extractLink struct {
|
||||
Link *model.Link
|
||||
Obj model.Obj
|
||||
*model.Link
|
||||
Obj model.Obj
|
||||
}
|
||||
|
||||
var extractCache = cache.NewMemCache(cache.WithShards[*extractLink](16))
|
||||
var extractG singleflight.Group[*extractLink]
|
||||
var extractG = singleflight.Group[*extractLink]{Remember: true}
|
||||
|
||||
func DriverExtract(ctx context.Context, storage driver.Driver, path string, args model.ArchiveInnerArgs) (*model.Link, model.Obj, error) {
|
||||
if storage.Config().CheckStatus && storage.GetStorage().Status != WORK {
|
||||
@ -389,9 +369,9 @@ func DriverExtract(ctx context.Context, storage driver.Driver, path string, args
|
||||
key := stdpath.Join(Key(storage, path), args.InnerPath)
|
||||
if link, ok := extractCache.Get(key); ok {
|
||||
return link.Link, link.Obj, nil
|
||||
} else if link, ok := extractCache.Get(key + ":" + args.IP); ok {
|
||||
return link.Link, link.Obj, nil
|
||||
}
|
||||
|
||||
var forget utils.CloseFunc
|
||||
fn := func() (*extractLink, error) {
|
||||
link, err := driverExtract(ctx, storage, path, args)
|
||||
if err != nil {
|
||||
@ -400,16 +380,33 @@ func DriverExtract(ctx context.Context, storage driver.Driver, path string, args
|
||||
if link.Link.Expiration != nil {
|
||||
extractCache.Set(key, link, cache.WithEx[*extractLink](*link.Link.Expiration))
|
||||
}
|
||||
link.Add(forget)
|
||||
return link, nil
|
||||
}
|
||||
if storage.Config().OnlyLocal {
|
||||
|
||||
if storage.Config().OnlyLinkMFile {
|
||||
link, err := fn()
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
return link.Link, link.Obj, nil
|
||||
}
|
||||
|
||||
forget = func() error {
|
||||
if forget != nil {
|
||||
forget = nil
|
||||
linkG.Forget(key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
link, err, _ := extractG.Do(key, fn)
|
||||
if err == nil && !link.AcquireReference() {
|
||||
link, err, _ = extractG.Do(key, fn)
|
||||
if err == nil {
|
||||
link.AcquireReference()
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user