mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-09-19 20:26:26 +08:00
Compare commits
91 Commits
v4.0.7
...
SenkjM-pat
Author | SHA1 | Date | |
---|---|---|---|
37e00774b3 | |||
4e9c30f49d | |||
0ee31a3f36 | |||
23bddf991e | |||
da8d6607cf | |||
6134574dac | |||
b273232f87 | |||
358e4d851e | |||
e8a1ed638a | |||
4106e2a996 | |||
c2271df64e | |||
d4b8570eb8 | |||
bd297e8ccc | |||
923d282c8a | |||
4d8c4d7089 | |||
e93ab76036 | |||
a9f02ecdac | |||
93849a3b5b | |||
c2e0d0c9ce | |||
4a713363ee | |||
3da8ccb7a7 | |||
676b8cff0b | |||
57cf28fc90 | |||
8cf90e074d | |||
74c2ed8306 | |||
5f03edd683 | |||
8b65c918d4 | |||
b5f0e3e5ee | |||
179894ff37 | |||
e2fc89c637 | |||
cacf67b181 | |||
afb043e1d6 | |||
d9debb81ad | |||
4c069fddd6 | |||
b450a2104d | |||
7d0de17daf | |||
bba4fb2203 | |||
a20c2020f8 | |||
a92b5eb929 | |||
6817494a41 | |||
5a0d8ee1b8 | |||
012e51c551 | |||
59ec1dbc9b | |||
6bb28d13f9 | |||
811a862288 | |||
74d32fd4d7 | |||
cedb3d488d | |||
86324d2d6b | |||
648079ae24 | |||
e8d45398d6 | |||
0c461991f9 | |||
2a4c546a8b | |||
750d4eb3f6 | |||
cc01b410a4 | |||
e5fbe72581 | |||
283f3723d1 | |||
ad8c7b37a1 | |||
a84ffb96e9 | |||
19c6b6f930 | |||
eed3c0533c | |||
c72ba9828a | |||
4965a1b909 | |||
1bba550469 | |||
d678322b18 | |||
efd8897bdf | |||
7c7cec0993 | |||
3838ef0663 | |||
9e610af114 | |||
0177177238 | |||
a77e515c9b | |||
4af16ab009 | |||
da35423198 | |||
9612d61e60 | |||
92f396df10 | |||
9557834342 | |||
288ba2fcda | |||
f3920b02f7 | |||
2ec9dad3db | |||
e11227fe2d | |||
859931b78c | |||
b591524ac3 | |||
dc26b4fce5 | |||
f8cf02a2da | |||
a214e794f4 | |||
54d761b371 | |||
bea7a9b0e4 | |||
a46f4cff18 | |||
8eb2d600c7 | |||
ffb6c2a180 | |||
8e19a0fb07 | |||
79f4f96217 |
6
.github/ISSUE_TEMPLATE/00-bug_report_zh.yml
vendored
6
.github/ISSUE_TEMPLATE/00-bug_report_zh.yml
vendored
@ -25,11 +25,11 @@ body:
|
||||
- label: |
|
||||
我确认我的描述清晰,语法礼貌,能帮助开发者快速定位问题,并符合社区规则。
|
||||
- label: |
|
||||
我已确认阅读了[OpenList文档](https://docs.oplist.org)。
|
||||
我已确认阅读了[OpenList文档](https://doc.oplist.org)。
|
||||
- label: |
|
||||
我已确认没有重复的问题或讨论。
|
||||
- label: |
|
||||
我已确认是`OpenList`的问题,而不是其他原因(例如 [网络](https://docs.oplist.org/zh/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host) ,`依赖`或`操作`)。
|
||||
我已确认是`OpenList`的问题,而不是其他原因(例如 [网络](https://doc.oplist.org/faq/howto#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host-1) ,`依赖`或`操作`)。
|
||||
- label: |
|
||||
我认为此问题必须由`OpenList`处理,而非第三方。
|
||||
- label: |
|
||||
@ -72,7 +72,7 @@ body:
|
||||
attributes:
|
||||
label: 日志(可选)
|
||||
description: |
|
||||
请复制粘贴错误日志,或者截图。(可隐藏隐私字段)
|
||||
请复制粘贴错误日志,或者截图。(可隐藏隐私字段) [查看方法](https://doc.oplist.org/faq/howto#%E5%A6%82%E4%BD%95%E5%BF%AB%E9%80%9F%E5%AE%9A%E4%BD%8Dbug)
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
|
6
.github/ISSUE_TEMPLATE/01-bug_report_en.yml
vendored
6
.github/ISSUE_TEMPLATE/01-bug_report_en.yml
vendored
@ -25,11 +25,11 @@ body:
|
||||
- label: |
|
||||
I confirm my description is clear, polite, helps developers quickly locate the issue, and complies with community rules.
|
||||
- label: |
|
||||
I have read the [OpenList documentation](https://docs.oplist.org).
|
||||
I have read the [OpenList documentation](https://doc.oplist.org).
|
||||
- label: |
|
||||
I confirm there are no duplicate issues or discussions.
|
||||
- label: |
|
||||
I confirm this is an `OpenList` issue, not caused by other reasons (such as [network](https://docs.oplist.org/faq/howto.html#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host), dependencies, or operation).
|
||||
I confirm this is an `OpenList` issue, not caused by other reasons (such as [network](https://doc.oplist.org/faq/howto#tls-handshake-timeout-read-connection-reset-by-peer-dns-lookup-failed-connect-connection-refused-client-timeout-exceeded-while-awaiting-headers-no-such-host-1), dependencies, or operation).
|
||||
- label: |
|
||||
I believe this issue must be handled by `OpenList` and not by a third party.
|
||||
- label: |
|
||||
@ -72,7 +72,7 @@ body:
|
||||
attributes:
|
||||
label: Logs (optional)
|
||||
description: |
|
||||
Please copy and paste any relevant log output or screenshots. (You may mask sensitive fields)
|
||||
Please copy and paste any relevant log output or screenshots. (You may mask sensitive fields) [Guide](https://doc.oplist.org/faq/howto#how-to-quickly-locate-bugs)
|
||||
- type: textarea
|
||||
id: reproduction
|
||||
attributes:
|
||||
|
@ -19,7 +19,7 @@ body:
|
||||
- label: |
|
||||
我确认我的描述清晰,语法礼貌,能帮助开发者快速定位问题,并符合社区规则。
|
||||
- label: |
|
||||
我已确认阅读了[OpenList文档](https://docs.oplist.org)。
|
||||
我已确认阅读了[OpenList文档](https://doc.oplist.org)。
|
||||
- label: |
|
||||
我已确认没有重复的问题或讨论。
|
||||
- label: |
|
||||
|
@ -19,7 +19,7 @@ body:
|
||||
- label: |
|
||||
I confirm my description is clear, polite, helps developers quickly locate the issue, and complies with community rules.
|
||||
- label: |
|
||||
I have read the [OpenList documentation](https://docs.oplist.org).
|
||||
I have read the [OpenList documentation](https://doc.oplist.org).
|
||||
- label: |
|
||||
I confirm there are no duplicate issues or discussions.
|
||||
- label: |
|
||||
|
21
.github/config.yml
vendored
21
.github/config.yml
vendored
@ -1,21 +0,0 @@
|
||||
# Configuration for welcome - https://github.com/behaviorbot/welcome
|
||||
|
||||
# Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
|
||||
|
||||
# Comment to be posted to on first time issues
|
||||
newIssueWelcomeComment: >
|
||||
Thanks for opening your first issue here! Be sure to follow the issue template!
|
||||
|
||||
# Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
|
||||
|
||||
# Comment to be posted to on PRs from first time contributors in your repository
|
||||
newPRWelcomeComment: >
|
||||
Thanks for opening this pull request! Please check out our contributing guidelines.
|
||||
|
||||
# Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
|
||||
|
||||
# Comment to be posted to on pull requests merged by a first time user
|
||||
firstPRMergeComment: >
|
||||
Congrats on merging your first pull request! We here at behavior bot are proud of you!
|
||||
|
||||
# It is recommend to include as many gifs and emojis as possible
|
21
.github/stale.yml
vendored
21
.github/stale.yml
vendored
@ -1,21 +0,0 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 44
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 20
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- accepted
|
||||
- security
|
||||
- working
|
||||
- pr-welcome
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: stale
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: >
|
||||
This issue was closed due to inactive more than 52 days. You can reopen or
|
||||
recreate it if you think it should continue. Thank you for your contributions again.
|
13
.github/workflows/beta_release.yml
vendored
13
.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
|
||||
@ -65,7 +61,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- target: "!(*musl*|*windows-arm64*|*android*|*freebsd*)" # xgo
|
||||
- target: "!(*musl*|*windows-arm64*|*android*|*freebsd*)" # xgo and loongarch
|
||||
hash: "md5"
|
||||
- target: "linux-!(arm*)-musl*" #musl-not-arm
|
||||
hash: "md5-linux-musl"
|
||||
@ -89,7 +85,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22"
|
||||
go-version: "1.24.5"
|
||||
|
||||
- name: Setup web
|
||||
run: bash build.sh dev web
|
||||
@ -97,10 +93,11 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build
|
||||
uses: OpenListTeam/cgo-actions@v1.1.2
|
||||
uses: OpenListTeam/cgo-actions@v1.2.2
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
musl-target-format: $os-$musl-$arch
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
out-dir: build
|
||||
output: openlist-$target$ext
|
||||
musl-base-url: "https://github.com/OpenListTeam/musl-compilers/releases/latest/download/"
|
||||
|
15
.github/workflows/build.yml
vendored
15
.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
|
||||
@ -36,7 +33,7 @@ jobs:
|
||||
- name: Setup Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: "1.22"
|
||||
go-version: "1.24.5"
|
||||
|
||||
- name: Setup web
|
||||
run: bash build.sh dev web
|
||||
@ -44,10 +41,11 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build
|
||||
uses: OpenListTeam/cgo-actions@v1.1.2
|
||||
uses: OpenListTeam/cgo-actions@v1.2.2
|
||||
with:
|
||||
targets: ${{ matrix.target }}
|
||||
musl-target-format: $os-$musl-$arch
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
out-dir: build
|
||||
x-flags: |
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.BuiltAt=$built_at
|
||||
@ -55,9 +53,10 @@ jobs:
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.GitCommit=$git_commit
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.Version=$tag
|
||||
github.com/OpenListTeam/OpenList/v4/internal/conf.WebVersion=dev
|
||||
output: openlist$ext
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: openlist_${{ env.SHA }}_${{ matrix.target }}
|
||||
name: openlist_${{ steps.short-sha.outputs.sha }}_${{ matrix.target }}
|
||||
path: build/*
|
||||
|
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:
|
||||
|
61
.github/workflows/issue_pr_comment.yml
vendored
Normal file
61
.github/workflows/issue_pr_comment.yml
vendored
Normal file
@ -0,0 +1,61 @@
|
||||
name: Issue or PR Auto Reply
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened]
|
||||
pull_request:
|
||||
types: [opened]
|
||||
|
||||
permissions:
|
||||
issues: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
auto-reply:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'issues'
|
||||
steps:
|
||||
- name: Check issue for unchecked tasks and reply
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const issueBody = context.payload.issue.body || "";
|
||||
const unchecked = /- \[ \] /.test(issueBody);
|
||||
let comment = "感谢您联系OpenList。我们会尽快回复您。\n";
|
||||
comment += "Thanks for contacting OpenList. We will reply to you as soon as possible.\n\n";
|
||||
if (unchecked) {
|
||||
comment += "由于您提出的 Issue 中包含部分未确认的项目,为了更好地管理项目,在人工审核后可能会直接关闭此问题。\n";
|
||||
comment += "如果您能确认并补充相关未确认项目的信息,欢迎随时重新提交。我们会及时关注并处理。感谢您的理解与支持!\n";
|
||||
comment += "Since your issue contains some unchecked tasks, it may be closed after manual review.\n";
|
||||
comment += "If you can confirm and provide information for the unchecked tasks, feel free to resubmit.\n";
|
||||
comment += "We will pay attention and handle it in a timely manner.\n\n";
|
||||
comment += "感谢您的理解与支持!\n";
|
||||
comment += "Thank you for your understanding and support!\n";
|
||||
}
|
||||
await github.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
|
||||
pr-title-check:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'pull_request'
|
||||
steps:
|
||||
- name: Check PR title for required prefix and comment
|
||||
uses: actions/github-script@v7
|
||||
with:
|
||||
script: |
|
||||
const title = context.payload.pull_request.title || "";
|
||||
const ok = /^(feat|docs|fix|style|refactor|chore)\(.+?\): /i.test(title);
|
||||
if (!ok) {
|
||||
let comment = "⚠️ PR 标题需以 `feat(): `, `docs(): `, `fix(): `, `style(): `, `refactor(): `, `chore(): ` 其中之一开头,例如:`feat(component): 新增功能`。\n";
|
||||
comment += "⚠️ The PR title must start with `feat(): `, `docs(): `, `fix(): `, `style(): `, or `refactor(): `, `chore(): `. For example: `feat(component): add new feature`.\n\n";
|
||||
comment += "如果跨多个组件,请使用主要组件作为前缀,并在标题中枚举、描述中说明。\n";
|
||||
comment += "If it spans multiple components, use the main component as the prefix and enumerate in the title, describe in the body.\n\n";
|
||||
await github.rest.issues.createComment({
|
||||
...context.repo,
|
||||
issue_number: context.issue.number,
|
||||
body: comment
|
||||
});
|
||||
}
|
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: |
|
||||
{
|
||||
|
17
Dockerfile
17
Dockerfile
@ -17,7 +17,7 @@ WORKDIR /opt/openlist/
|
||||
|
||||
RUN apk update && \
|
||||
apk upgrade --no-cache && \
|
||||
apk add --no-cache bash ca-certificates su-exec tzdata; \
|
||||
apk add --no-cache bash ca-certificates su-exec tzdata runit; \
|
||||
[ "$INSTALL_FFMPEG" = "true" ] && apk add --no-cache ffmpeg; \
|
||||
[ "$INSTALL_ARIA2" = "true" ] && apk add --no-cache curl aria2 && \
|
||||
mkdir -p /opt/aria2/.aria2 && \
|
||||
@ -28,9 +28,24 @@ RUN apk update && \
|
||||
sed -i 's|/root/.aria2|/opt/aria2/.aria2|g' /opt/aria2/.aria2/script.conf && \
|
||||
sed -i 's|/root|/opt/aria2|g' /opt/aria2/.aria2/aria2.conf && \
|
||||
sed -i 's|/root|/opt/aria2|g' /opt/aria2/.aria2/script.conf && \
|
||||
mkdir -p /opt/service/stop/aria2/log && \
|
||||
echo '#!/bin/sh' > /opt/service/stop/aria2/run && \
|
||||
echo 'exec 2>&1' >> /opt/service/stop/aria2/run && \
|
||||
echo 'exec aria2c --enable-rpc --rpc-allow-origin-all --conf-path=/opt/aria2/.aria2/aria2.conf' >> /opt/service/stop/aria2/run && \
|
||||
echo '#!/bin/sh' > /opt/service/stop/aria2/log/run && \
|
||||
echo 'mkdir -p /opt/openlist/data/log/aria2 2>/dev/null' >> /opt/service/stop/aria2/log/run && \
|
||||
echo 'exec svlogd /opt/openlist/data/log/aria2' >> /opt/service/stop/aria2/log/run && \
|
||||
chmod +x /opt/service/stop/aria2/run /opt/service/stop/aria2/log/run && \
|
||||
touch /opt/aria2/.aria2/aria2.session && \
|
||||
/opt/aria2/.aria2/tracker.sh ; \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
RUN mkdir -p /opt/service/start/openlist && \
|
||||
echo '#!/bin/sh' > /opt/service/start/openlist/run && \
|
||||
echo 'exec 2>&1' >> /opt/service/start/openlist/run && \
|
||||
echo 'cd /opt/openlist' >> /opt/service/start/openlist/run && \
|
||||
echo 'exec ./openlist server --no-prefix' >> /opt/service/start/openlist/run && \
|
||||
chmod +x /opt/service/start/openlist/run
|
||||
|
||||
COPY --chmod=755 --from=builder /app/bin/openlist ./
|
||||
COPY --chmod=755 entrypoint.sh /entrypoint.sh
|
||||
|
@ -9,7 +9,7 @@ WORKDIR /opt/openlist/
|
||||
|
||||
RUN apk update && \
|
||||
apk upgrade --no-cache && \
|
||||
apk add --no-cache bash ca-certificates su-exec tzdata; \
|
||||
apk add --no-cache bash ca-certificates su-exec tzdata runit; \
|
||||
[ "$INSTALL_FFMPEG" = "true" ] && apk add --no-cache ffmpeg; \
|
||||
[ "$INSTALL_ARIA2" = "true" ] && apk add --no-cache curl aria2 && \
|
||||
mkdir -p /opt/aria2/.aria2 && \
|
||||
@ -20,9 +20,24 @@ RUN apk update && \
|
||||
sed -i 's|/root/.aria2|/opt/aria2/.aria2|g' /opt/aria2/.aria2/script.conf && \
|
||||
sed -i 's|/root|/opt/aria2|g' /opt/aria2/.aria2/aria2.conf && \
|
||||
sed -i 's|/root|/opt/aria2|g' /opt/aria2/.aria2/script.conf && \
|
||||
mkdir -p /opt/service/stop/aria2/log && \
|
||||
echo '#!/bin/sh' > /opt/service/stop/aria2/run && \
|
||||
echo 'exec 2>&1' >> /opt/service/stop/aria2/run && \
|
||||
echo 'exec aria2c --enable-rpc --rpc-allow-origin-all --conf-path=/opt/aria2/.aria2/aria2.conf' >> /opt/service/stop/aria2/run && \
|
||||
echo '#!/bin/sh' > /opt/service/stop/aria2/log/run && \
|
||||
echo 'mkdir -p /opt/openlist/data/log/aria2 2>/dev/null' >> /opt/service/stop/aria2/log/run && \
|
||||
echo 'exec svlogd /opt/openlist/data/log/aria2' >> /opt/service/stop/aria2/log/run && \
|
||||
chmod +x /opt/service/stop/aria2/run /opt/service/stop/aria2/log/run && \
|
||||
touch /opt/aria2/.aria2/aria2.session && \
|
||||
/opt/aria2/.aria2/tracker.sh ; \
|
||||
rm -rf /var/cache/apk/*
|
||||
|
||||
RUN mkdir -p /opt/service/start/openlist && \
|
||||
echo '#!/bin/sh' > /opt/service/start/openlist/run && \
|
||||
echo 'exec 2>&1' >> /opt/service/start/openlist/run && \
|
||||
echo 'cd /opt/openlist' >> /opt/service/start/openlist/run && \
|
||||
echo 'exec ./openlist server --no-prefix' >> /opt/service/start/openlist/run && \
|
||||
chmod +x /opt/service/start/openlist/run
|
||||
|
||||
COPY --chmod=755 /build/${TARGETPLATFORM}/openlist ./
|
||||
COPY --chmod=755 entrypoint.sh /entrypoint.sh
|
||||
|
33
README.md
33
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
|
||||
@ -78,8 +106,9 @@
|
||||
|
||||
## Document
|
||||
|
||||
- 📘 [Docs & Install Guide](https://docs.oplist.org)
|
||||
- 📚 [Backup Docs Site](https://docs.openlist.team)
|
||||
- 📘 [Global Site](https://doc.oplist.org)
|
||||
- 📚 [Backup Site](https://doc.openlist.team)
|
||||
- 🌏 [CN Site](https://doc.oplist.org.cn)
|
||||
|
||||
## Demo
|
||||
|
||||
|
33
README_cn.md
33
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] 多种存储
|
||||
@ -78,8 +106,9 @@
|
||||
|
||||
## 文档
|
||||
|
||||
- 📘 [文档与安装指南](https://docs.oplist.org)
|
||||
- 📚 [备用文档站点](https://docs.openlist.team)
|
||||
- 🌏 [国内站点](https://doc.oplist.org.cn)
|
||||
- 📘 [海外站点](https://doc.oplist.org)
|
||||
- 📚 [备用站点](https://doc.openlist.team)
|
||||
|
||||
## 演示
|
||||
|
||||
|
33
README_ja.md
33
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] 複数ストレージ
|
||||
@ -78,8 +106,9 @@
|
||||
|
||||
## ドキュメント
|
||||
|
||||
- 📘 [ドキュメント・インストールガイド](https://docs.oplist.org)
|
||||
- 📚 [バックアップドキュメントサイト](https://docs.openlist.team)
|
||||
- 📘 [グローバルサイト](https://doc.oplist.org)
|
||||
- 📚 [バックアップサイト](https://doc.openlist.team)
|
||||
- 🌏 [CNサイト](https://doc.oplist.org.cn)
|
||||
|
||||
## デモ
|
||||
|
||||
|
33
README_nl.md
33
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
|
||||
@ -78,8 +106,9 @@
|
||||
|
||||
## Documentatie
|
||||
|
||||
- 📘 [Documentatie & Installatiegids](https://docs.oplist.org)
|
||||
- 📚 [Back-up documentatiesite](https://docs.openlist.team)
|
||||
- 📘 [Global Site](https://doc.oplist.org)
|
||||
- 📚 [Backup Site](https://doc.openlist.team)
|
||||
- 🌏 [CN Site](https://doc.oplist.org.cn)
|
||||
|
||||
## Demo
|
||||
|
||||
|
11
build.sh
11
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
|
||||
}
|
||||
|
||||
@ -278,6 +278,7 @@ BuildReleaseFreeBSD() {
|
||||
freebsd_version=$(eval "curl -fsSL --max-time 2 $githubAuthArgs \"https://api.github.com/repos/freebsd/freebsd-src/tags\"" | \
|
||||
jq -r '.[].name' | \
|
||||
grep '^release/14\.' | \
|
||||
grep -v -- '-p[0-9]*$' | \
|
||||
sort -V | \
|
||||
tail -1 | \
|
||||
sed 's/release\///' | \
|
||||
|
@ -16,7 +16,7 @@ var RootCmd = &cobra.Command{
|
||||
Short: "A file list program that supports multiple storage.",
|
||||
Long: `A file list program that supports multiple storage,
|
||||
built with love by OpenListTeam.
|
||||
Complete documentation is available at https://docs.openlist.team/`,
|
||||
Complete documentation is available at https://doc.oplist.org/`,
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
|
@ -19,6 +19,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/fs"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/OpenListTeam/OpenList/v4/server"
|
||||
"github.com/OpenListTeam/OpenList/v4/server/middlewares"
|
||||
"github.com/OpenListTeam/sftpd-openlist"
|
||||
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||
"github.com/gin-gonic/gin"
|
||||
@ -47,7 +48,15 @@ the address is defined in config file`,
|
||||
gin.SetMode(gin.ReleaseMode)
|
||||
}
|
||||
r := gin.New()
|
||||
r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out))
|
||||
|
||||
// gin log
|
||||
if conf.Conf.Log.Filter.Enable {
|
||||
r.Use(middlewares.FilteredLogger())
|
||||
} else {
|
||||
r.Use(gin.LoggerWithWriter(log.StandardLogger().Out))
|
||||
}
|
||||
r.Use(gin.RecoveryWithWriter(log.StandardLogger().Out))
|
||||
|
||||
server.Init(r)
|
||||
var httpHandler http.Handler = r
|
||||
if conf.Conf.Scheme.EnableH2c {
|
||||
|
@ -186,7 +186,9 @@ func (d *Pan115) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
||||
preHash = strings.ToUpper(preHash)
|
||||
fullHash := stream.GetHash().GetHash(utils.SHA1)
|
||||
if len(fullHash) != utils.SHA1.Width {
|
||||
_, fullHash, err = streamPkg.CacheFullInTempFileAndHash(stream, utils.SHA1)
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, fullHash, err = streamPkg.CacheFullInTempFileAndHash(stream, cacheFileProgress, utils.SHA1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -18,7 +18,6 @@ var config = driver.Config{
|
||||
Name: "115 Cloud",
|
||||
DefaultRoot: "0",
|
||||
// OnlyProxy: true,
|
||||
// OnlyLocal: true,
|
||||
// NoOverwriteUpload: true,
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
sdk "github.com/OpenListTeam/115-sdk-go"
|
||||
"github.com/OpenListTeam/OpenList/v4/cmd/flags"
|
||||
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
@ -16,7 +17,6 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
sdk "github.com/OpenListTeam/115-sdk-go"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
@ -222,7 +222,9 @@ func (d *Open115) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
|
||||
}
|
||||
sha1 := file.GetHash().GetHash(utils.SHA1)
|
||||
if len(sha1) != utils.SHA1.Width {
|
||||
_, sha1, err = stream.CacheFullInTempFileAndHash(file, utils.SHA1)
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, sha1, err = stream.CacheFullInTempFileAndHash(file, cacheFileProgress, utils.SHA1)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -252,6 +254,7 @@ func (d *Open115) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
|
||||
return err
|
||||
}
|
||||
if resp.Status == 2 {
|
||||
up(100)
|
||||
return nil
|
||||
}
|
||||
// 2. two way verify
|
||||
@ -286,6 +289,7 @@ func (d *Open115) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
|
||||
return err
|
||||
}
|
||||
if resp.Status == 2 {
|
||||
up(100)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@ -302,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,16 @@ 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,string" 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)"`
|
||||
UseOnlineAPI bool `json:"use_online_api" default:"true"`
|
||||
APIAddress string `json:"api_url_address" default:"https://api.oplist.org/115cloud/renewapi"`
|
||||
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() {
|
||||
|
@ -6,12 +6,12 @@ import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
sdk "github.com/OpenListTeam/115-sdk-go"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/aliyun/aliyun-oss-go-sdk/oss"
|
||||
"github.com/avast/retry-go"
|
||||
sdk "github.com/OpenListTeam/115-sdk-go"
|
||||
)
|
||||
|
||||
func calPartSize(fileSize int64) int64 {
|
||||
@ -121,7 +121,7 @@ func (d *Open115) multpartUpload(ctx context.Context, stream model.FileStreamer,
|
||||
} else {
|
||||
offset += partSize
|
||||
}
|
||||
up(float64(offset) / float64(fileSize))
|
||||
up(float64(offset) * 100 / float64(fileSize))
|
||||
}
|
||||
|
||||
// callbackRespBytes := make([]byte, 1024)
|
||||
|
@ -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() {
|
||||
|
@ -188,7 +188,9 @@ func (d *Pan123) Put(ctx context.Context, dstDir model.Obj, file model.FileStrea
|
||||
etag := file.GetHash().GetHash(utils.MD5)
|
||||
var err error
|
||||
if len(etag) < utils.MD5.Width {
|
||||
_, etag, err = stream.CacheFullInTempFileAndHash(file, utils.MD5)
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, etag, err = stream.CacheFullInTempFileAndHash(file, cacheFileProgress, utils.MD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -109,7 +109,9 @@ func (d *Open123) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
|
||||
etag := file.GetHash().GetHash(utils.MD5)
|
||||
|
||||
if len(etag) < utils.MD5.Width {
|
||||
_, etag, err = stream.CacheFullInTempFileAndHash(file, utils.MD5)
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, etag, err = stream.CacheFullInTempFileAndHash(file, cacheFileProgress, utils.MD5)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -121,7 +123,6 @@ func (d *Open123) Put(ctx context.Context, dstDir model.Obj, file model.FileStre
|
||||
if createResp.Data.Reuse {
|
||||
return nil
|
||||
}
|
||||
up(10)
|
||||
|
||||
return d.Upload(ctx, file, createResp, up)
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -522,7 +522,9 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
||||
var err error
|
||||
fullHash := stream.GetHash().GetHash(utils.SHA256)
|
||||
if len(fullHash) != utils.SHA256.Width {
|
||||
_, fullHash, err = streamPkg.CacheFullInTempFileAndHash(stream, utils.SHA256)
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, fullHash, err = streamPkg.CacheFullInTempFileAndHash(stream, cacheFileProgress, utils.SHA256)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -820,7 +820,9 @@ func (y *Cloud189PC) GetMultiUploadUrls(ctx context.Context, isFamily bool, uplo
|
||||
|
||||
// 旧版本上传,家庭云不支持覆盖
|
||||
func (y *Cloud189PC) OldUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) {
|
||||
tempFile, fileMd5, err := stream.CacheFullInTempFileAndHash(file, utils.MD5)
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
tempFile, fileMd5, err := stream.CacheFullInTempFileAndHash(file, cacheFileProgress, utils.MD5)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -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,20 +114,44 @@ 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 {
|
||||
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),
|
||||
ContentLength: link.ContentLength,
|
||||
}
|
||||
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
|
||||
}
|
||||
@ -166,7 +193,8 @@ func (d *Alias) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
}
|
||||
if len(srcPath) == len(dstPath) {
|
||||
for i := range srcPath {
|
||||
err = errors.Join(err, fs.Move(ctx, *srcPath[i], *dstPath[i]))
|
||||
_, e := fs.Move(ctx, *srcPath[i], *dstPath[i])
|
||||
err = errors.Join(err, e)
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
@ -250,9 +278,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
|
||||
@ -337,14 +369,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,
|
||||
}
|
||||
|
@ -194,7 +194,9 @@ func (d *AliyundriveOpen) upload(ctx context.Context, dstDir model.Obj, stream m
|
||||
|
||||
hash := stream.GetHash().GetHash(utils.SHA1)
|
||||
if len(hash) != utils.SHA1.Width {
|
||||
_, hash, err = streamPkg.CacheFullInTempFileAndHash(stream, utils.SHA1)
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, hash, err = streamPkg.CacheFullInTempFileAndHash(stream, cacheFileProgress, utils.SHA1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -63,7 +63,6 @@ import (
|
||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/thunder"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/thunder_browser"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/thunderx"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/trainbit"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/url_tree"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/uss"
|
||||
_ "github.com/OpenListTeam/OpenList/v4/drivers/virtual"
|
||||
|
@ -32,7 +32,6 @@ func init() {
|
||||
config: driver.Config{
|
||||
Name: "ChaoXingGroupDrive",
|
||||
OnlyProxy: true,
|
||||
OnlyLocal: false,
|
||||
DefaultRoot: "-1",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
|
@ -20,6 +20,7 @@ type Addition struct {
|
||||
var config = driver.Config{
|
||||
Name: "Cloudreve",
|
||||
DefaultRoot: "/",
|
||||
LocalSort: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/setting"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/cookie"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
@ -240,68 +241,61 @@ func (d *Cloudreve) upRemote(ctx context.Context, stream model.FileStreamer, u U
|
||||
var finish int64 = 0
|
||||
var chunk int = 0
|
||||
DEFAULT := int64(u.ChunkSize)
|
||||
retryCount := 0
|
||||
maxRetries := 3
|
||||
for finish < stream.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Cloudreve-Remote] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
err := retry.Do(
|
||||
func() error {
|
||||
utils.Log.Debugf("[Cloudreve-Remote] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadUrl+"?chunk="+strconv.Itoa(chunk),
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("Authorization", fmt.Sprint(credential))
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("server error: %d", res.StatusCode)
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var up Resp
|
||||
err = json.Unmarshal(body, &up)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if up.Code != 0 {
|
||||
return errors.New(up.Msg)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("POST", uploadUrl+"?chunk="+strconv.Itoa(chunk),
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.ContentLength = byteSize
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||
req.Header.Set("Authorization", fmt.Sprint(credential))
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
err = func() error {
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
return errors.New(res.Status)
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var up Resp
|
||||
err = json.Unmarshal(body, &up)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if up.Code != 0 {
|
||||
return errors.New(up.Msg)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err == nil {
|
||||
retryCount = 0
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
chunk++
|
||||
} else {
|
||||
retryCount++
|
||||
if retryCount > maxRetries {
|
||||
return fmt.Errorf("upload failed after %d retries due to server errors, error: %s", maxRetries, err)
|
||||
}
|
||||
backoff := time.Duration(1<<retryCount) * time.Second
|
||||
utils.Log.Warnf("[Cloudreve-Remote] server errors while uploading, retrying after %v...", backoff)
|
||||
time.Sleep(backoff)
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
chunk++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -310,55 +304,53 @@ func (d *Cloudreve) upOneDrive(ctx context.Context, stream model.FileStreamer, u
|
||||
uploadUrl := u.UploadURLs[0]
|
||||
var finish int64 = 0
|
||||
DEFAULT := int64(u.ChunkSize)
|
||||
retryCount := 0
|
||||
maxRetries := 3
|
||||
for finish < stream.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Cloudreve-OneDrive] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
err := retry.Do(
|
||||
func() error {
|
||||
utils.Log.Debugf("[Cloudreve-OneDrive] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadUrl,
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/api/driveitem_createuploadsession
|
||||
switch {
|
||||
case res.StatusCode >= 500 && res.StatusCode <= 504:
|
||||
return fmt.Errorf("server error: %d", res.StatusCode)
|
||||
case res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200:
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}, retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", uploadUrl, driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.ContentLength = byteSize
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/api/driveitem_createuploadsession
|
||||
switch {
|
||||
case res.StatusCode >= 500 && res.StatusCode <= 504:
|
||||
retryCount++
|
||||
if retryCount > maxRetries {
|
||||
res.Body.Close()
|
||||
return fmt.Errorf("upload failed after %d retries due to server errors, error %d", maxRetries, res.StatusCode)
|
||||
}
|
||||
backoff := time.Duration(1<<retryCount) * time.Second
|
||||
utils.Log.Warnf("[Cloudreve-OneDrive] server errors %d while uploading, retrying after %v...", res.StatusCode, backoff)
|
||||
time.Sleep(backoff)
|
||||
case res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200:
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
res.Body.Close()
|
||||
retryCount = 0
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
// 上传成功发送回调请求
|
||||
return d.request(http.MethodPost, "/callback/onedrive/finish/"+u.SessionID, func(req *resty.Request) {
|
||||
@ -371,54 +363,54 @@ func (d *Cloudreve) upS3(ctx context.Context, stream model.FileStreamer, u Uploa
|
||||
var chunk int = 0
|
||||
var etags []string
|
||||
DEFAULT := int64(u.ChunkSize)
|
||||
retryCount := 0
|
||||
maxRetries := 3
|
||||
for finish < stream.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Cloudreve-S3] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
err := retry.Do(
|
||||
func() error {
|
||||
utils.Log.Debugf("[Cloudreve-S3] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, u.UploadURLs[chunk],
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewBuffer(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
etag := res.Header.Get("ETag")
|
||||
res.Body.Close()
|
||||
switch {
|
||||
case res.StatusCode != 200:
|
||||
return fmt.Errorf("server error: %d", res.StatusCode)
|
||||
case etag == "":
|
||||
return errors.New("failed to get ETag from header")
|
||||
default:
|
||||
etags = append(etags, etag)
|
||||
return nil
|
||||
}
|
||||
}, retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", u.UploadURLs[chunk],
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewBuffer(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.ContentLength = byteSize
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
etag := res.Header.Get("ETag")
|
||||
res.Body.Close()
|
||||
switch {
|
||||
case res.StatusCode != 200:
|
||||
retryCount++
|
||||
if retryCount > maxRetries {
|
||||
return fmt.Errorf("upload failed after %d retries due to server errors, error %d", maxRetries, res.StatusCode)
|
||||
}
|
||||
backoff := time.Duration(1<<retryCount) * time.Second
|
||||
utils.Log.Warnf("[Cloudreve-S3] server errors %d while uploading, retrying after %v...", res.StatusCode, backoff)
|
||||
time.Sleep(backoff)
|
||||
case etag == "":
|
||||
return errors.New("failed to get ETag from header")
|
||||
default:
|
||||
retryCount = 0
|
||||
etags = append(etags, etag)
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
chunk++
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
chunk++
|
||||
}
|
||||
|
||||
// s3LikeFinishUpload
|
||||
// https://github.com/cloudreve/frontend/blob/b485bf297974cbe4834d2e8e744ae7b7e5b2ad39/src/component/Uploader/core/api/index.ts#L204-L252
|
||||
bodyBuilder := &strings.Builder{}
|
||||
@ -431,8 +423,8 @@ func (d *Cloudreve) upS3(ctx context.Context, stream model.FileStreamer, u Uploa
|
||||
))
|
||||
}
|
||||
bodyBuilder.WriteString("</CompleteMultipartUpload>")
|
||||
req, err := http.NewRequest(
|
||||
"POST",
|
||||
req, err := http.NewRequestWithContext(ctx,
|
||||
http.MethodPost,
|
||||
u.CompleteURL,
|
||||
strings.NewReader(bodyBuilder.String()),
|
||||
)
|
||||
|
@ -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,
|
||||
}
|
||||
|
||||
|
@ -47,7 +47,13 @@ type BasicConfigResp struct {
|
||||
|
||||
type SiteLoginConfigResp struct {
|
||||
LoginCaptcha bool `json:"login_captcha"`
|
||||
Authn bool `json:"authn"`
|
||||
// RegCaptcha bool `json:"reg_captcha"`
|
||||
// ForgetCaptcha bool `json:"forget_captcha"`
|
||||
// RegisterEnabled bool `json:"register_enabled"`
|
||||
// TosURL string `json:"tos_url"`
|
||||
// PrivacyPolicyURL string `json:"privacy_policy_url"`
|
||||
// SsoDisplayName string `json:"sso_display_name"`
|
||||
// OidcDisplayName string `json:"oidc_display_name"`
|
||||
}
|
||||
|
||||
type PrepareLoginResp struct {
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/setting"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
@ -95,9 +96,6 @@ func (d *CloudreveV4) login() error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !siteConfig.Authn {
|
||||
return errors.New("authn not support")
|
||||
}
|
||||
var prepareLogin PrepareLoginResp
|
||||
err = d.request(http.MethodGet, "/session/prepare?email="+d.Addition.Username, nil, &prepareLogin)
|
||||
if err != nil {
|
||||
@ -258,68 +256,61 @@ func (d *CloudreveV4) upRemote(ctx context.Context, file model.FileStreamer, u F
|
||||
var finish int64 = 0
|
||||
var chunk int = 0
|
||||
DEFAULT := int64(u.ChunkSize)
|
||||
retryCount := 0
|
||||
maxRetries := 3
|
||||
for finish < file.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := file.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[CloudreveV4-Remote] upload range: %d-%d/%d", finish, finish+byteSize-1, file.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
err := retry.Do(
|
||||
func() error {
|
||||
utils.Log.Debugf("[CloudreveV4-Remote] upload range: %d-%d/%d", finish, finish+byteSize-1, file.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadUrl+"?chunk="+strconv.Itoa(chunk),
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("Authorization", fmt.Sprint(credential))
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
return fmt.Errorf("server error: %d", res.StatusCode)
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var up Resp
|
||||
err = json.Unmarshal(body, &up)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if up.Code != 0 {
|
||||
return errors.New(up.Msg)
|
||||
}
|
||||
return nil
|
||||
}, retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("POST", uploadUrl+"?chunk="+strconv.Itoa(chunk),
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.ContentLength = byteSize
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||
req.Header.Set("Authorization", fmt.Sprint(credential))
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
err = func() error {
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
if res.StatusCode != 200 {
|
||||
return errors.New(res.Status)
|
||||
}
|
||||
body, err := io.ReadAll(res.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var up Resp
|
||||
err = json.Unmarshal(body, &up)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if up.Code != 0 {
|
||||
return errors.New(up.Msg)
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
if err == nil {
|
||||
retryCount = 0
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(file.GetSize()))
|
||||
chunk++
|
||||
} else {
|
||||
retryCount++
|
||||
if retryCount > maxRetries {
|
||||
return fmt.Errorf("upload failed after %d retries due to server errors, error: %s", maxRetries, err)
|
||||
}
|
||||
backoff := time.Duration(1<<retryCount) * time.Second
|
||||
utils.Log.Warnf("[Cloudreve-Remote] server errors while uploading, retrying after %v...", backoff)
|
||||
time.Sleep(backoff)
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(file.GetSize()))
|
||||
chunk++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -328,55 +319,54 @@ func (d *CloudreveV4) upOneDrive(ctx context.Context, file model.FileStreamer, u
|
||||
uploadUrl := u.UploadUrls[0]
|
||||
var finish int64 = 0
|
||||
DEFAULT := int64(u.ChunkSize)
|
||||
retryCount := 0
|
||||
maxRetries := 3
|
||||
for finish < file.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := file.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[CloudreveV4-OneDrive] upload range: %d-%d/%d", finish, finish+byteSize-1, file.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
err := retry.Do(
|
||||
func() error {
|
||||
utils.Log.Debugf("[CloudreveV4-OneDrive] upload range: %d-%d/%d", finish, finish+byteSize-1, file.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadUrl,
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, file.GetSize()))
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/api/driveitem_createuploadsession
|
||||
switch {
|
||||
case res.StatusCode >= 500 && res.StatusCode <= 504:
|
||||
return fmt.Errorf("server error: %d", res.StatusCode)
|
||||
case res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200:
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}, retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPut, uploadUrl, driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.ContentLength = byteSize
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, file.GetSize()))
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/api/driveitem_createuploadsession
|
||||
switch {
|
||||
case res.StatusCode >= 500 && res.StatusCode <= 504:
|
||||
retryCount++
|
||||
if retryCount > maxRetries {
|
||||
res.Body.Close()
|
||||
return fmt.Errorf("upload failed after %d retries due to server errors, error %d", maxRetries, res.StatusCode)
|
||||
}
|
||||
backoff := time.Duration(1<<retryCount) * time.Second
|
||||
utils.Log.Warnf("[CloudreveV4-OneDrive] server errors %d while uploading, retrying after %v...", res.StatusCode, backoff)
|
||||
time.Sleep(backoff)
|
||||
case res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200:
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
res.Body.Close()
|
||||
retryCount = 0
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(file.GetSize()))
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(file.GetSize()))
|
||||
}
|
||||
// 上传成功发送回调请求
|
||||
return d.request(http.MethodPost, "/callback/onedrive/"+u.SessionID+"/"+u.CallbackSecret, func(req *resty.Request) {
|
||||
@ -389,52 +379,54 @@ func (d *CloudreveV4) upS3(ctx context.Context, file model.FileStreamer, u FileU
|
||||
var chunk int = 0
|
||||
var etags []string
|
||||
DEFAULT := int64(u.ChunkSize)
|
||||
retryCount := 0
|
||||
maxRetries := 3
|
||||
for finish < file.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := file.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[CloudreveV4-S3] upload range: %d-%d/%d", finish, finish+byteSize-1, file.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
err := retry.Do(
|
||||
func() error {
|
||||
utils.Log.Debugf("[CloudreveV4-S3] upload range: %d-%d/%d", finish, finish+byteSize-1, file.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(file, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, u.UploadUrls[chunk],
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewBuffer(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("User-Agent", d.getUA())
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
etag := res.Header.Get("ETag")
|
||||
res.Body.Close()
|
||||
switch {
|
||||
case res.StatusCode != 200:
|
||||
return fmt.Errorf("server error: %d", res.StatusCode)
|
||||
case etag == "":
|
||||
return errors.New("failed to get ETag from header")
|
||||
default:
|
||||
etags = append(etags, etag)
|
||||
return nil
|
||||
}
|
||||
},
|
||||
retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest(http.MethodPut, u.UploadUrls[chunk],
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewBuffer(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.ContentLength = byteSize
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
etag := res.Header.Get("ETag")
|
||||
res.Body.Close()
|
||||
switch {
|
||||
case res.StatusCode != 200:
|
||||
retryCount++
|
||||
if retryCount > maxRetries {
|
||||
return fmt.Errorf("upload failed after %d retries due to server errors", maxRetries)
|
||||
}
|
||||
backoff := time.Duration(1<<retryCount) * time.Second
|
||||
utils.Log.Warnf("server error %d, retrying after %v...", res.StatusCode, backoff)
|
||||
time.Sleep(backoff)
|
||||
case etag == "":
|
||||
return errors.New("failed to get ETag from header")
|
||||
default:
|
||||
retryCount = 0
|
||||
etags = append(etags, etag)
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(file.GetSize()))
|
||||
chunk++
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(file.GetSize()))
|
||||
chunk++
|
||||
}
|
||||
|
||||
// s3LikeFinishUpload
|
||||
@ -448,8 +440,8 @@ func (d *CloudreveV4) upS3(ctx context.Context, file model.FileStreamer, u FileU
|
||||
))
|
||||
}
|
||||
bodyBuilder.WriteString("</CompleteMultipartUpload>")
|
||||
req, err := http.NewRequest(
|
||||
"POST",
|
||||
req, err := http.NewRequestWithContext(ctx,
|
||||
http.MethodPost,
|
||||
u.CompleteURL,
|
||||
strings.NewReader(bodyBuilder.String()),
|
||||
)
|
||||
|
@ -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"
|
||||
@ -110,7 +112,7 @@ func (d *Crypt) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([
|
||||
//return d.list(ctx, d.RemotePath, path)
|
||||
//remoteFull
|
||||
|
||||
objs, err := fs.List(ctx, d.getPathForRemote(path, true), &fs.ListArgs{NoLog: true})
|
||||
objs, err := fs.List(ctx, d.getPathForRemote(path, true), &fs.ListArgs{NoLog: true, Refresh: args.Refresh})
|
||||
// the obj must implement the model.SetPath interface
|
||||
// return objs, err
|
||||
if err != nil {
|
||||
@ -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,61 +256,69 @@ 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 {
|
||||
remoteSize := remoteLink.ContentLength
|
||||
if remoteSize <= 0 {
|
||||
remoteSize = remoteFile.GetSize()
|
||||
}
|
||||
rrf, err := stream.GetRangeReaderFromLink(remoteSize, remoteLink)
|
||||
if err != nil {
|
||||
_ = remoteLink.Close()
|
||||
return nil, fmt.Errorf("the remote storage driver need to be enhanced to support encrytion")
|
||||
}
|
||||
remoteFileSize := remoteFile.GetSize()
|
||||
remoteClosers := utils.EmptyClosers()
|
||||
rangeReaderFunc := func(ctx context.Context, underlyingOffset, underlyingLength int64) (io.ReadCloser, error) {
|
||||
length := underlyingLength
|
||||
if underlyingLength >= 0 && underlyingOffset+underlyingLength >= remoteFileSize {
|
||||
length = -1
|
||||
}
|
||||
rrc := remoteLink.RangeReadCloser
|
||||
if len(remoteLink.URL) > 0 {
|
||||
var converted, err = stream.GetRangeReadCloserFromLink(remoteFileSize, remoteLink)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
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()
|
||||
}
|
||||
rrc = converted
|
||||
}
|
||||
if rrc != nil {
|
||||
remoteReader, err := rrc.RangeRead(ctx, http_range.Range{Start: underlyingOffset, Length: length})
|
||||
remoteClosers.AddClosers(rrc.GetClosers())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return remoteReader, nil
|
||||
}
|
||||
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.
|
||||
remoteClosers.Add(remoteLink.MFile)
|
||||
return io.NopCloser(remoteLink.MFile), nil
|
||||
}
|
||||
|
||||
return nil, errs.NotSupport
|
||||
|
||||
}
|
||||
resultRangeReader := func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||
readSeeker, err := d.cipher.DecryptDataSeek(ctx, rangeReaderFunc, httpRange.Start, httpRange.Length)
|
||||
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
|
||||
}
|
||||
|
||||
resultRangeReadCloser := &model.RangeReadCloser{RangeReader: resultRangeReader, Closers: remoteClosers}
|
||||
resultLink := &model.Link{
|
||||
RangeReadCloser: resultRangeReadCloser,
|
||||
Expiration: remoteLink.Expiration,
|
||||
}
|
||||
|
||||
return resultLink, nil
|
||||
|
||||
return &model.Link{
|
||||
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
|
||||
}
|
||||
|
||||
func (d *Crypt) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
|
@ -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() {
|
||||
|
@ -14,7 +14,7 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
stdpath "path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -353,7 +353,7 @@ func (d *Doubao) getUploadConfig(upConfig *UploadConfig, dataType string, file m
|
||||
"ServiceId": d.UploadToken.Alice[dataType].ServiceID,
|
||||
"NeedFallback": "true",
|
||||
"FileSize": strconv.FormatInt(file.GetSize(), 10),
|
||||
"FileExtension": filepath.Ext(file.GetName()),
|
||||
"FileExtension": stdpath.Ext(file.GetName()),
|
||||
"s": randomString(),
|
||||
}
|
||||
}
|
||||
@ -524,7 +524,6 @@ func (d *Doubao) UploadByMultipart(ctx context.Context, config *UploadConfig, fi
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to cache file: %w", err)
|
||||
}
|
||||
defer tempFile.Close()
|
||||
up(10.0) // 更新进度
|
||||
// 设置并行上传
|
||||
threadG, uploadCtx := errgroup.NewGroupWithContext(ctx, d.uploadThread,
|
||||
|
@ -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() {
|
||||
|
@ -7,6 +7,8 @@ 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"
|
||||
"github.com/jlaffaye/ftp"
|
||||
)
|
||||
|
||||
@ -25,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 {
|
||||
@ -64,11 +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{
|
||||
MFile: r,
|
||||
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 link, nil
|
||||
return &model.Link{
|
||||
MFile: &stream.RateLimitFile{
|
||||
File: remoteFile,
|
||||
Limiter: stream.ServerDownloadLimit,
|
||||
Ctx: ctx,
|
||||
},
|
||||
}, 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,11 +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 err != nil {
|
||||
return nil, fmt.Errorf("open download file failed: %w", err)
|
||||
if httpRange.Length < 0 || httpRange.Start+httpRange.Length >= size {
|
||||
length = size - httpRange.Start
|
||||
}
|
||||
oo := &openObject{
|
||||
ctx: ctx,
|
||||
@ -279,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() {
|
||||
|
@ -96,7 +96,3 @@ type SteamFile struct {
|
||||
func (s *SteamFile) Read(p []byte) (n int, err error) {
|
||||
return s.file.Read(p)
|
||||
}
|
||||
|
||||
func (s *SteamFile) Close() error {
|
||||
return s.file.Close()
|
||||
}
|
||||
|
@ -276,7 +276,9 @@ func (d *ILanZou) Put(ctx context.Context, dstDir model.Obj, s model.FileStreame
|
||||
etag := s.GetHash().GetHash(utils.MD5)
|
||||
var err error
|
||||
if len(etag) != utils.MD5.Width {
|
||||
_, etag, err = stream.CacheFullInTempFileAndHash(s, utils.MD5)
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, etag, err = stream.CacheFullInTempFileAndHash(s, cacheFileProgress, utils.MD5)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -29,17 +29,9 @@ 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",
|
||||
LocalSort: true,
|
||||
},
|
||||
conf: Conf{
|
||||
base: "https://api.ilanzou.com",
|
||||
@ -55,17 +47,9 @@ 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",
|
||||
LocalSort: true,
|
||||
},
|
||||
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() {
|
||||
|
@ -3,6 +3,7 @@ package LenovoNasShare
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
@ -32,6 +33,10 @@ func (d *LenovoNasShare) Init(ctx context.Context) error {
|
||||
if err := d.getStoken(); err != nil {
|
||||
return err
|
||||
}
|
||||
if !d.ShowRootFolder && d.RootFolderPath == "" {
|
||||
list, _ := d.List(ctx, File{}, model.ListArgs{})
|
||||
d.RootFolderPath = list[0].GetPath()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -43,19 +48,26 @@ func (d *LenovoNasShare) List(ctx context.Context, dir model.Obj, args model.Lis
|
||||
d.checkStoken() // 检查stoken是否过期
|
||||
files := make([]File, 0)
|
||||
|
||||
path := dir.GetPath()
|
||||
if path == "" && !d.ShowRootFolder && d.RootFolderPath != "" {
|
||||
path = d.RootFolderPath
|
||||
}
|
||||
|
||||
var resp Files
|
||||
query := map[string]string{
|
||||
"code": d.ShareId,
|
||||
"num": "5000",
|
||||
"stoken": d.stoken,
|
||||
"path": dir.GetPath(),
|
||||
"path": path,
|
||||
}
|
||||
_, err := d.request(d.Host+"/oneproxy/api/share/v1/files", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetQueryParams(query)
|
||||
}, &resp)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
files = append(files, resp.Data.List...)
|
||||
|
||||
return utils.SliceConvert(files, func(src File) (model.Obj, error) {
|
||||
@ -73,6 +85,10 @@ func (d *LenovoNasShare) getStoken() error { // 获取stoken
|
||||
if d.Host == "" {
|
||||
d.Host = "https://siot-share.lenovo.com.cn"
|
||||
}
|
||||
|
||||
parts := strings.Split(d.ShareId, "/")
|
||||
d.ShareId = parts[len(parts)-1]
|
||||
|
||||
query := map[string]string{
|
||||
"code": d.ShareId,
|
||||
"password": d.SharePwd,
|
||||
|
@ -7,23 +7,16 @@ import (
|
||||
|
||||
type Addition struct {
|
||||
driver.RootPath
|
||||
ShareId string `json:"share_id" required:"true" help:"The part after the last / in the shared link"`
|
||||
SharePwd string `json:"share_pwd" required:"true" help:"The password of the shared link"`
|
||||
Host string `json:"host" required:"true" default:"https://siot-share.lenovo.com.cn" help:"You can change it to your local area network"`
|
||||
ShareId string `json:"share_id" required:"true" help:"The part after the last / in the shared link"`
|
||||
SharePwd string `json:"share_pwd" required:"true" help:"The password of the shared link"`
|
||||
Host string `json:"host" required:"true" default:"https://siot-share.lenovo.com.cn" help:"You can change it to your local area network"`
|
||||
ShowRootFolder bool `json:"show_root_folder" default:"true"`
|
||||
}
|
||||
|
||||
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
|
||||
@ -240,10 +228,17 @@ func (d *Local) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Get thumbnail file size for Content-Length
|
||||
stat, err := open.Stat()
|
||||
if err != nil {
|
||||
open.Close()
|
||||
return nil, err
|
||||
}
|
||||
link.ContentLength = int64(stat.Size())
|
||||
link.MFile = open
|
||||
} else {
|
||||
link.MFile = model.NewNopMFile(bytes.NewReader(buf.Bytes()))
|
||||
//link.Header.Set("Content-Length", strconv.Itoa(buf.Len()))
|
||||
link.MFile = bytes.NewReader(buf.Bytes())
|
||||
link.ContentLength = int64(buf.Len())
|
||||
}
|
||||
} else {
|
||||
open, err := os.Open(fullPath)
|
||||
@ -252,7 +247,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() {
|
||||
|
@ -184,9 +184,6 @@ func (d *MediaTrack) Put(ctx context.Context, dstDir model.Obj, file model.FileS
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = tempFile.Close()
|
||||
}()
|
||||
uploader := s3manager.NewUploader(s)
|
||||
if file.GetSize() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize {
|
||||
uploader.PartSize = file.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||
|
@ -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
|
||||
|
@ -2,7 +2,6 @@ package netease_music
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
@ -11,7 +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/pkg/http_range"
|
||||
"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"
|
||||
@ -55,16 +54,9 @@ func (lrc *LyricObj) getProxyLink(ctx context.Context) *model.Link {
|
||||
}
|
||||
|
||||
func (lrc *LyricObj) getLyricLink() *model.Link {
|
||||
reader := strings.NewReader(lrc.lyric)
|
||||
return &model.Link{
|
||||
RangeReadCloser: &model.RangeReadCloser{
|
||||
RangeReader: func(ctx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
||||
if httpRange.Length < 0 {
|
||||
return io.NopCloser(reader), nil
|
||||
}
|
||||
sr := io.NewSectionReader(reader, httpRange.Start, httpRange.Length)
|
||||
return io.NopCloser(sr), nil
|
||||
},
|
||||
RangeReader: &model.FileRangeReader{
|
||||
RangeReaderIF: stream.GetRangeReaderFromMFile(int64(len(lrc.lyric)), strings.NewReader(lrc.lyric)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
@ -230,7 +231,7 @@ func toAPIMetadata(stream model.FileStreamer) Metadata {
|
||||
|
||||
func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
url := d.GetMetaUrl(false, stdpath.Join(dstDir.GetPath(), stream.GetName())) + "/createUploadSession"
|
||||
metadata := map[string]interface{}{"item": toAPIMetadata(stream)}
|
||||
metadata := map[string]any{"item": toAPIMetadata(stream)}
|
||||
res, err := d.Request(url, http.MethodPost, func(req *resty.Request) {
|
||||
req.SetBody(metadata).SetContext(ctx)
|
||||
}, nil)
|
||||
@ -240,54 +241,53 @@ func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
||||
var finish int64 = 0
|
||||
DEFAULT := d.ChunkSize * 1024 * 1024
|
||||
retryCount := 0
|
||||
maxRetries := 3
|
||||
for finish < stream.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Onedrive] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
err = retry.Do(
|
||||
func() error {
|
||||
utils.Log.Debugf("[Onedrive] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadUrl,
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/api/driveitem_createuploadsession
|
||||
switch {
|
||||
case res.StatusCode >= 500 && res.StatusCode <= 504:
|
||||
return fmt.Errorf("server error: %d", res.StatusCode)
|
||||
case res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200:
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", uploadUrl, driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.ContentLength = byteSize
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/api/driveitem_createuploadsession
|
||||
switch {
|
||||
case res.StatusCode >= 500 && res.StatusCode <= 504:
|
||||
retryCount++
|
||||
if retryCount > maxRetries {
|
||||
res.Body.Close()
|
||||
return fmt.Errorf("upload failed after %d retries due to server errors, error %d", maxRetries, res.StatusCode)
|
||||
}
|
||||
backoff := time.Duration(1<<retryCount) * time.Second
|
||||
utils.Log.Warnf("[Onedrive] server errors %d while uploading, retrying after %v...", res.StatusCode, backoff)
|
||||
time.Sleep(backoff)
|
||||
case res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200:
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
res.Body.Close()
|
||||
retryCount = 0
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/avast/retry-go"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
@ -40,7 +41,7 @@ var onedriveHostMap = map[string]Host{
|
||||
}
|
||||
|
||||
func (d *OnedriveAPP) GetMetaUrl(auth bool, path string) string {
|
||||
host, _ := onedriveHostMap[d.Region]
|
||||
host := onedriveHostMap[d.Region]
|
||||
path = utils.EncodePath(path, true)
|
||||
if auth {
|
||||
return host.Oauth
|
||||
@ -154,54 +155,53 @@ func (d *OnedriveAPP) upBig(ctx context.Context, dstDir model.Obj, stream model.
|
||||
uploadUrl := jsoniter.Get(res, "uploadUrl").ToString()
|
||||
var finish int64 = 0
|
||||
DEFAULT := d.ChunkSize * 1024 * 1024
|
||||
retryCount := 0
|
||||
maxRetries := 3
|
||||
for finish < stream.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[OnedriveAPP] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
err = retry.Do(
|
||||
func() error {
|
||||
utils.Log.Debugf("[OnedriveAPP] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadUrl,
|
||||
driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.ContentLength = byteSize
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer res.Body.Close()
|
||||
// https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/api/driveitem_createuploadsession
|
||||
switch {
|
||||
case res.StatusCode >= 500 && res.StatusCode <= 504:
|
||||
return fmt.Errorf("server error: %d", res.StatusCode)
|
||||
case res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200:
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
},
|
||||
retry.Attempts(3),
|
||||
retry.DelayType(retry.BackOffDelay),
|
||||
retry.Delay(time.Second),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequest("PUT", uploadUrl, driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req = req.WithContext(ctx)
|
||||
req.ContentLength = byteSize
|
||||
// req.Header.Set("Content-Length", strconv.Itoa(int(byteSize)))
|
||||
req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", finish, finish+byteSize-1, stream.GetSize()))
|
||||
res, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// https://learn.microsoft.com/zh-cn/onedrive/developer/rest-api/api/driveitem_createuploadsession
|
||||
switch {
|
||||
case res.StatusCode >= 500 && res.StatusCode <= 504:
|
||||
retryCount++
|
||||
if retryCount > maxRetries {
|
||||
res.Body.Close()
|
||||
return fmt.Errorf("upload failed after %d retries due to server errors, error %d", maxRetries, res.StatusCode)
|
||||
}
|
||||
backoff := time.Duration(1<<retryCount) * time.Second
|
||||
utils.Log.Warnf("[OnedriveAPP] server errors %d while uploading, retrying after %v...", res.StatusCode, backoff)
|
||||
time.Sleep(backoff)
|
||||
case res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200:
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
res.Body.Close()
|
||||
retryCount = 0
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -6,6 +6,10 @@ import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/errs"
|
||||
@ -13,9 +17,6 @@ import (
|
||||
streamPkg "github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"hash"
|
||||
"io"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type QuarkOpen struct {
|
||||
@ -157,7 +158,9 @@ func (d *QuarkOpen) Put(ctx context.Context, dstDir model.Obj, stream model.File
|
||||
}
|
||||
|
||||
if len(writers) > 0 {
|
||||
_, err := streamPkg.CacheFullInTempFileAndWriter(stream, io.MultiWriter(writers...))
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, err := streamPkg.CacheFullInTempFileAndWriter(stream, cacheFileProgress, io.MultiWriter(writers...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ func init() {
|
||||
return &QuarkOpen{
|
||||
config: driver.Config{
|
||||
Name: "QuarkOpen",
|
||||
OnlyLocal: true,
|
||||
OnlyProxy: true,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
|
@ -37,10 +37,11 @@ func (d *QuarkOrUC) GetAddition() driver.Additional {
|
||||
func (d *QuarkOrUC) Init(ctx context.Context) error {
|
||||
_, err := d.request("/config", http.MethodGet, nil, nil)
|
||||
if err == nil {
|
||||
if d.AdditionVersion != 1 {
|
||||
d.AdditionVersion = 1
|
||||
if !d.UseTransCodingAddress {
|
||||
if d.AdditionVersion != 2 {
|
||||
d.AdditionVersion = 2
|
||||
if !d.UseTransCodingAddress && len(d.DownProxyURL) == 0 {
|
||||
d.WebProxy = true
|
||||
d.WebdavPolicy = "native_proxy"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,7 +144,9 @@ func (d *QuarkOrUC) Put(ctx context.Context, dstDir model.Obj, stream model.File
|
||||
}
|
||||
|
||||
if len(writers) > 0 {
|
||||
_, err := streamPkg.CacheFullInTempFileAndWriter(stream, io.MultiWriter(writers...))
|
||||
cacheFileProgress := model.UpdateProgressWithRange(up, 0, 50)
|
||||
up = model.UpdateProgressWithRange(up, 50, 100)
|
||||
_, err := streamPkg.CacheFullInTempFileAndWriter(stream, cacheFileProgress, io.MultiWriter(writers...))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -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,
|
||||
},
|
||||
|
@ -149,7 +149,12 @@ func (d *QuarkOrUC) getTranscodingLink(file model.Obj) (*model.Link, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &model.Link{URL: resp.Data.VideoList[0].VideoInfo.URL}, nil
|
||||
return &model.Link{
|
||||
URL: resp.Data.VideoList[0].VideoInfo.URL,
|
||||
ContentLength: resp.Data.VideoList[0].VideoInfo.Size,
|
||||
Concurrency: 3,
|
||||
PartSize: 10 * utils.MB,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *QuarkOrUC) upPre(file model.FileStreamer, parentId string) (UpPreResp, error) {
|
||||
|
@ -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"`
|
||||
|
@ -10,6 +10,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
@ -210,3 +212,48 @@ 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,
|
||||
ContentLength: fileLink.Data.VideoInfo[0].Size,
|
||||
}, 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"
|
||||
@ -97,9 +96,9 @@ func (d *S3) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*mo
|
||||
input.ResponseContentDisposition = &disposition
|
||||
}
|
||||
|
||||
req, reqErr := d.linkClient.GetObjectRequest(input)
|
||||
if reqErr != nil {
|
||||
return nil, fmt.Errorf("failed to create GetObject request: %w", reqErr)
|
||||
req, _ := d.linkClient.GetObjectRequest(input)
|
||||
if req == nil {
|
||||
return nil, fmt.Errorf("failed to create GetObject request")
|
||||
}
|
||||
var link model.Link
|
||||
var err error
|
||||
@ -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) {})
|
||||
}
|
||||
|
@ -8,6 +8,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"
|
||||
"github.com/pkg/sftp"
|
||||
log "github.com/sirupsen/logrus"
|
||||
@ -29,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 {
|
||||
@ -62,10 +63,21 @@ func (d *SFTP) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
link := &model.Link{
|
||||
MFile: remoteFile,
|
||||
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 link, nil
|
||||
return &model.Link{
|
||||
MFile: &stream.RateLimitFile{
|
||||
File: remoteFile,
|
||||
Limiter: stream.ServerDownloadLimit,
|
||||
Ctx: ctx,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *SFTP) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||
|
||||
"github.com/hirochachacha/go-smb2"
|
||||
@ -29,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 {
|
||||
@ -79,11 +80,21 @@ func (d *SMB) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*m
|
||||
d.cleanLastConnTime()
|
||||
return nil, err
|
||||
}
|
||||
link := &model.Link{
|
||||
MFile: remoteFile,
|
||||
}
|
||||
d.updateLastConnTime()
|
||||
return link, nil
|
||||
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,
|
||||
Limiter: stream.ServerDownloadLimit,
|
||||
Ctx: ctx,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *SMB) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user