mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-11-25 19:37:41 +08:00
Compare commits
16 Commits
larkdrive-
...
v4.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
639b5cf7c2 | ||
|
|
b5c1386645 | ||
|
|
041868dfb8 | ||
|
|
cfbc157477 | ||
|
|
5d44806064 | ||
|
|
fc8b99c862 | ||
|
|
24560b43c0 | ||
|
|
39ca385778 | ||
|
|
ef0531ad40 | ||
|
|
12540a8abc | ||
|
|
0f5ed14fe2 | ||
|
|
ca55b89322 | ||
|
|
a3c7cb059d | ||
|
|
0f8545133b | ||
|
|
72fad1be2e | ||
|
|
b7ce7f172b |
6
.github/workflows/beta_release.yml
vendored
6
.github/workflows/beta_release.yml
vendored
@@ -46,7 +46,7 @@ jobs:
|
||||
- name: Upload assets to beta release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
body: "See CHANGELOG.md"
|
||||
body_path: CHANGELOG.md
|
||||
files: CHANGELOG.md
|
||||
prerelease: true
|
||||
tag_name: beta
|
||||
@@ -154,7 +154,7 @@ jobs:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# repository: alist-org/desktop-release
|
||||
# repository: openlistteam/desktop-release
|
||||
# ref: main
|
||||
# persist-credentials: false
|
||||
# fetch-depth: 0
|
||||
@@ -170,4 +170,4 @@ jobs:
|
||||
# with:
|
||||
# github_token: ${{ secrets.MY_TOKEN }}
|
||||
# branch: main
|
||||
# repository: alist-org/desktop-release
|
||||
# repository: openlistteam/desktop-release
|
||||
|
||||
22
.github/workflows/issue_close_question.yml
vendored
22
.github/workflows/issue_close_question.yml
vendored
@@ -1,22 +0,0 @@
|
||||
name: Close need info
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 */1 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close-need-info:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: close-issues
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'close-issues'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: 'question'
|
||||
inactive-day: 3
|
||||
close-reason: 'not_planned'
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, this issue was closed due to no activities in 3 days.
|
||||
你好 @${{ github.event.issue.user.login }},此issue因超过3天未回复被关闭。
|
||||
21
.github/workflows/issue_close_stale.yml
vendored
21
.github/workflows/issue_close_stale.yml
vendored
@@ -1,21 +0,0 @@
|
||||
name: Close inactive
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 */7 * *"
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
close-inactive:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: close-issues
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'close-issues'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
labels: 'stale'
|
||||
inactive-day: 8
|
||||
close-reason: 'not_planned'
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, 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.
|
||||
25
.github/workflows/issue_duplicate.yml
vendored
25
.github/workflows/issue_duplicate.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Issue Duplicate
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
create-comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'duplicate'
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, your issue is a duplicate and will be closed.
|
||||
你好 @${{ github.event.issue.user.login }},你的issue是重复的,将被关闭。
|
||||
- name: Close issue
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'close-issue'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
25
.github/workflows/issue_invalid.yml
vendored
25
.github/workflows/issue_invalid.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Issue Invalid
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
create-comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'invalid'
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, your issue is invalid and will be closed.
|
||||
你好 @${{ github.event.issue.user.login }},你的issue无效,将被关闭。
|
||||
- name: Close issue
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'close-issue'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
17
.github/workflows/issue_on_close.yml
vendored
17
.github/workflows/issue_on_close.yml
vendored
@@ -1,17 +0,0 @@
|
||||
name: Remove working label when issue closed
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [closed]
|
||||
|
||||
jobs:
|
||||
rm-working:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Remove working label
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'remove-labels'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
labels: 'working,pr-welcome'
|
||||
20
.github/workflows/issue_question.yml
vendored
20
.github/workflows/issue_question.yml
vendored
@@ -1,20 +0,0 @@
|
||||
name: Issue Question
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
create-comment:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'question'
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@v3.6.0
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, please input issue by template and add detail. Issues labeled by `question` will be closed if no activities in 3 days.
|
||||
你好 @${{ github.event.issue.user.login }},请按照issue模板填写, 并详细说明问题/日志记录/复现步骤/复现链接/实现思路或提供更多信息等, 3天内未回复issue自动关闭。
|
||||
19
.github/workflows/issue_similarity.yml
vendored
19
.github/workflows/issue_similarity.yml
vendored
@@ -1,19 +0,0 @@
|
||||
name: Issues Similarity Analysis
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [opened, edited]
|
||||
|
||||
jobs:
|
||||
similarity-analysis:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: analysis
|
||||
uses: actions-cool/issues-similarity-analysis@v1
|
||||
with:
|
||||
filter-threshold: 0.5
|
||||
comment-title: '### See'
|
||||
comment-body: '${index}. ${similarity} #${number}'
|
||||
show-footer: false
|
||||
show-mentioned: true
|
||||
since-days: 730
|
||||
13
.github/workflows/issue_translate.yml
vendored
13
.github/workflows/issue_translate.yml
vendored
@@ -1,13 +0,0 @@
|
||||
name: Translation Helper
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened]
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
jobs:
|
||||
translate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions-cool/translation-helper@v1.2.0
|
||||
25
.github/workflows/issue_wontfix.yml
vendored
25
.github/workflows/issue_wontfix.yml
vendored
@@ -1,25 +0,0 @@
|
||||
name: Issue Wontfix
|
||||
|
||||
on:
|
||||
issues:
|
||||
types: [labeled]
|
||||
|
||||
jobs:
|
||||
lock-issue:
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event.label.name == 'wontfix'
|
||||
steps:
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
issue-number: ${{ github.event.issue.number }}
|
||||
body: |
|
||||
Hello @${{ github.event.issue.user.login }}, this issue will not be worked on and will be closed.
|
||||
你好 @${{ github.event.issue.user.login }},这不会被处理,将被关闭。
|
||||
- name: Close issue
|
||||
uses: actions-cool/issues-helper@v3
|
||||
with:
|
||||
actions: 'close-issue'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
9
.github/workflows/release.yml
vendored
9
.github/workflows/release.yml
vendored
@@ -3,6 +3,7 @@ name: release
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
release:
|
||||
@@ -33,7 +34,7 @@ jobs:
|
||||
- name: Prerelease
|
||||
uses: irongut/EditRelease@v1.2.0
|
||||
with:
|
||||
token: ${{ secrets.MY_TOKEN }}
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
id: ${{ github.event.release.id }}
|
||||
prerelease: true
|
||||
|
||||
@@ -77,7 +78,7 @@ jobs:
|
||||
# - name: Checkout repo
|
||||
# uses: actions/checkout@v4
|
||||
# with:
|
||||
# repository: alist-org/desktop-release
|
||||
# repository: openlistteam/desktop-release
|
||||
# ref: main
|
||||
# persist-credentials: false
|
||||
# fetch-depth: 0
|
||||
@@ -86,7 +87,7 @@ jobs:
|
||||
# run: |
|
||||
# git config --local user.email "bot@nn.ci"
|
||||
# git config --local user.name "IlaBot"
|
||||
# version=$(wget -qO- -t1 -T2 "https://api.github.com/repos/alist-org/alist/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
||||
# version=$(wget -qO- -t1 -T2 "https://api.github.com/repos/openlistteam/openlist/releases/latest" | grep "tag_name" | head -n 1 | awk -F ":" '{print $2}' | sed 's/\"//g;s/,//g;s/ //g')
|
||||
# git tag -a $version -m "release $version"
|
||||
|
||||
# - name: Push tags
|
||||
@@ -94,4 +95,4 @@ jobs:
|
||||
# with:
|
||||
# github_token: ${{ secrets.MY_TOKEN }}
|
||||
# branch: main
|
||||
# repository: alist-org/desktop-release
|
||||
# repository: openlistteam/desktop-release
|
||||
|
||||
2
.github/workflows/release_android.yml
vendored
2
.github/workflows/release_android.yml
vendored
@@ -4,6 +4,8 @@ on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions: write-all
|
||||
|
||||
jobs:
|
||||
release_android:
|
||||
strategy:
|
||||
|
||||
28
.github/workflows/release_docker.yml
vendored
28
.github/workflows/release_docker.yml
vendored
@@ -2,6 +2,19 @@ name: release_docker
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
manual_tag:
|
||||
description: 'Tag name (like v0.1.0). Required if as_latest is true.'
|
||||
required: false
|
||||
type: string
|
||||
as_latest:
|
||||
description: 'Tag as latest?'
|
||||
required: true
|
||||
default: 'false'
|
||||
type: choice
|
||||
options:
|
||||
- 'true'
|
||||
- 'false'
|
||||
push:
|
||||
tags:
|
||||
- 'v*'
|
||||
@@ -17,11 +30,13 @@ env:
|
||||
REGISTRY: ghcr.io
|
||||
ARTIFACT_NAME: 'binaries_docker_release'
|
||||
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_IS_PROD: ${{ github.ref_type == 'tag' }}
|
||||
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: write-all
|
||||
|
||||
jobs:
|
||||
build_binary:
|
||||
name: Build Binaries for Docker Release
|
||||
@@ -127,9 +142,14 @@ jobs:
|
||||
images: |
|
||||
${{ env.REGISTRY }}/${{ env.ORG_NAME }}/${{ env.IMAGE_NAME }}
|
||||
${{ env.ORG_NAME }}/${{ env.IMAGE_NAME_DOCKERHUB }}
|
||||
tags: ${{ env.IMAGE_IS_PROD == 'true' && '' || env.IMAGE_TAGS_BETA }}
|
||||
tags: >
|
||||
${{ env.IMAGE_IS_PROD == 'true' && (
|
||||
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 }}
|
||||
flavor: |
|
||||
${{ env.IMAGE_IS_PROD == 'true' && 'latest=true' || '' }}
|
||||
latest=${{ env.IMAGE_IS_PROD }}
|
||||
${{ matrix.tag_favor }}
|
||||
|
||||
- name: Build and push
|
||||
|
||||
2
.github/workflows/release_freebsd.yml
vendored
2
.github/workflows/release_freebsd.yml
vendored
@@ -4,7 +4,9 @@ on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions: write-all
|
||||
jobs:
|
||||
|
||||
release_freebsd:
|
||||
strategy:
|
||||
matrix:
|
||||
|
||||
2
.github/workflows/release_linux_musl.yml
vendored
2
.github/workflows/release_linux_musl.yml
vendored
@@ -3,7 +3,7 @@ name: release_linux_musl
|
||||
on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions: write-all
|
||||
jobs:
|
||||
release_linux_musl:
|
||||
strategy:
|
||||
|
||||
1
.github/workflows/release_linux_musl_arm.yml
vendored
1
.github/workflows/release_linux_musl_arm.yml
vendored
@@ -4,6 +4,7 @@ on:
|
||||
release:
|
||||
types: [ published ]
|
||||
|
||||
permissions: write-all
|
||||
jobs:
|
||||
release_linux_musl_arm:
|
||||
strategy:
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img width="100px" alt="logo" src="https://raw.githubusercontent.com/OpenListTeam/Logo/main/OpenList.svg"/></a>
|
||||
<img width="100px" alt="logo" src="https://raw.githubusercontent.com/OpenListTeam/Logo/main/logo.svg"/></a>
|
||||
<p><em>🗂️A file list program that supports multiple storages, powered by Gin and SolidJS, fork of AList.</em></p>
|
||||
<div>
|
||||
<a href="https://goreportcard.com/report/github.com/OpenListTeam/OpenList/v3">
|
||||
@@ -95,7 +95,8 @@ English | [中文](./README_cn.md) | [日本語](./README_ja.md) | [Contributing
|
||||
|
||||
## Document
|
||||
|
||||
<https://docs.openlist.team>
|
||||
- https://docs.oplist.org
|
||||
- https://docs.openlist.team
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -125,4 +126,4 @@ The `OpenList` is open-source software licensed under the AGPL-3.0 license.
|
||||
|
||||
---
|
||||
|
||||
> [@GitHub](https://github.com/OpenListTeam) · [Telegram Group](https://t.me/OpenListTeam)
|
||||
> [@GitHub](https://github.com/OpenListTeam) · [Telegram Group](https://t.me/OpenListTeam) · [Telegram Channel](https://t.me/OpenListOfficial)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img width="100px" alt="logo" src="https://raw.githubusercontent.com/OpenListTeam/Logo/main/OpenList.svg"/></a>
|
||||
<img width="100px" alt="logo" src="https://raw.githubusercontent.com/OpenListTeam/Logo/main/logo.svg"/></a>
|
||||
<p><em>🗂一个支持多存储的文件列表程序,使用 Gin 和 SolidJS,基于 AList 项目 fork 开发</em></p>
|
||||
<div>
|
||||
<a href="https://goreportcard.com/report/github.com/OpenListTeam/OpenList/v3">
|
||||
@@ -93,6 +93,7 @@
|
||||
|
||||
## 文档
|
||||
|
||||
<https://docs.oplist.org>
|
||||
<https://docs.openlist.team>
|
||||
|
||||
## Demo
|
||||
@@ -123,4 +124,4 @@ N/A(重建中)
|
||||
|
||||
---
|
||||
|
||||
> [@GitHub](https://github.com/OpenListTeam) · [Telegram 交流群](https://t.me/OpenListTeam)
|
||||
> [@GitHub](https://github.com/OpenListTeam) · [Telegram 交流群](https://t.me/OpenListTeam) · [Telegram 频道](https://t.me/OpenListOfficial)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<img width="100px" alt="logo" src="https://raw.githubusercontent.com/OpenListTeam/Logo/main/OpenList.svg"/></a>
|
||||
<img width="100px" alt="logo" src="https://raw.githubusercontent.com/OpenListTeam/Logo/main/logo.svg"/></a>
|
||||
<p><em>🗂複数のストレージをサポートするファイルリストプログラムで、Gin と SolidJS を使用し、AList プロジェクトをフォークして開発されました。</em></p>
|
||||
<div>
|
||||
<a href="https://goreportcard.com/report/github.com/OpenListTeam/OpenList/v3">
|
||||
@@ -94,7 +94,8 @@
|
||||
|
||||
## ドキュメント
|
||||
|
||||
<https://docs.openlist.team>
|
||||
- https://docs.oplist.org
|
||||
- https://docs.openlist.team
|
||||
|
||||
## デモ
|
||||
|
||||
@@ -124,4 +125,4 @@ N/A (再構築中)
|
||||
|
||||
---
|
||||
|
||||
> [@GitHub](https://github.com/OpenListTeam) · [Telegram Group](https://t.me/OpenListTeam)
|
||||
> [@GitHub](https://github.com/OpenListTeam) · [Telegram Group](https://t.me/OpenListTeam) · [Telegram Channel](https://t.me/OpenListOfficial)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
version: '3.3'
|
||||
services:
|
||||
openlist:
|
||||
restart: always
|
||||
@@ -13,4 +12,4 @@ services:
|
||||
- UMASK=022
|
||||
- TZ=UTC
|
||||
container_name: openlist
|
||||
image: 'ghcr.io/openlistteam/openlist:latest'
|
||||
image: 'openlistteam/openlist:latest'
|
||||
|
||||
@@ -82,7 +82,6 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
||||
retry.Attempts(3),
|
||||
retry.Delay(time.Second),
|
||||
retry.DelayType(retry.BackOffDelay))
|
||||
threadG.SetLimit(3)
|
||||
|
||||
for partIndex := int64(0); partIndex < uploadNums; partIndex++ {
|
||||
if utils.IsCanceled(uploadCtx) {
|
||||
|
||||
@@ -504,7 +504,6 @@ func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file mo
|
||||
retry.Attempts(3),
|
||||
retry.Delay(time.Second),
|
||||
retry.DelayType(retry.BackOffDelay))
|
||||
threadG.SetLimit(3)
|
||||
|
||||
count := int(size / sliceSize)
|
||||
lastPartSize := size % sliceSize
|
||||
|
||||
@@ -1,118 +0,0 @@
|
||||
package alist_v2
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/drivers/base"
|
||||
"github.com/OpenListTeam/OpenList/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/server/common"
|
||||
)
|
||||
|
||||
type AListV2 struct {
|
||||
model.Storage
|
||||
Addition
|
||||
}
|
||||
|
||||
func (d *AListV2) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *AListV2) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *AListV2) Init(ctx context.Context) error {
|
||||
if len(d.Addition.Address) > 0 && string(d.Addition.Address[len(d.Addition.Address)-1]) == "/" {
|
||||
d.Addition.Address = d.Addition.Address[0 : len(d.Addition.Address)-1]
|
||||
}
|
||||
// TODO login / refresh token
|
||||
//op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *AListV2) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *AListV2) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
url := d.Address + "/api/public/path"
|
||||
var resp common.Resp[PathResp]
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).
|
||||
SetHeader("Authorization", d.AccessToken).
|
||||
SetBody(PathReq{
|
||||
PageNum: 0,
|
||||
PageSize: 0,
|
||||
Path: dir.GetPath(),
|
||||
Password: d.Password,
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var files []model.Obj
|
||||
for _, f := range resp.Data.Files {
|
||||
file := model.ObjThumb{
|
||||
Object: model.Object{
|
||||
Name: f.Name,
|
||||
Modified: *f.UpdatedAt,
|
||||
Size: f.Size,
|
||||
IsFolder: f.Type == 1,
|
||||
},
|
||||
Thumbnail: model.Thumbnail{Thumbnail: f.Thumbnail},
|
||||
}
|
||||
files = append(files, &file)
|
||||
}
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (d *AListV2) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
url := d.Address + "/api/public/path"
|
||||
var resp common.Resp[PathResp]
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).
|
||||
SetHeader("Authorization", d.AccessToken).
|
||||
SetBody(PathReq{
|
||||
PageNum: 0,
|
||||
PageSize: 0,
|
||||
Path: file.GetPath(),
|
||||
Password: d.Password,
|
||||
}).Post(url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &model.Link{
|
||||
URL: resp.Data.Files[0].Url,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *AListV2) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *AListV2) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *AListV2) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *AListV2) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *AListV2) Remove(ctx context.Context, obj model.Obj) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *AListV2) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
return errs.NotImplement
|
||||
}
|
||||
|
||||
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*AListV2)(nil)
|
||||
@@ -1,26 +0,0 @@
|
||||
package alist_v2
|
||||
|
||||
import (
|
||||
"github.com/OpenListTeam/OpenList/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
driver.RootPath
|
||||
Address string `json:"url" required:"true"`
|
||||
Password string `json:"password"`
|
||||
AccessToken string `json:"access_token"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "AList V2",
|
||||
LocalSort: true,
|
||||
NoUpload: true,
|
||||
DefaultRoot: "/",
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &AListV2{}
|
||||
})
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
package alist_v2
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type File struct {
|
||||
Id string `json:"-"`
|
||||
Name string `json:"name"`
|
||||
Size int64 `json:"size"`
|
||||
Type int `json:"type"`
|
||||
Driver string `json:"driver"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Thumbnail string `json:"thumbnail"`
|
||||
Url string `json:"url"`
|
||||
SizeStr string `json:"size_str"`
|
||||
TimeStr string `json:"time_str"`
|
||||
}
|
||||
|
||||
type PathResp struct {
|
||||
Type string `json:"type"`
|
||||
//Meta Meta `json:"meta"`
|
||||
Files []File `json:"files"`
|
||||
}
|
||||
|
||||
type PathReq struct {
|
||||
PageNum int `json:"page_num"`
|
||||
PageSize int `json:"page_size"`
|
||||
Password string `json:"password"`
|
||||
Path string `json:"path"`
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
package alist_v2
|
||||
@@ -20,12 +20,12 @@ import (
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
func (d *AliyundriveOpen) _refreshToken() (string, string, error) {
|
||||
// 使用在线API刷新Token,无需ClientID和ClientSecret
|
||||
if d.UseOnlineAPI && len(d.APIAddress) > 0 {
|
||||
if d.UseOnlineAPI && d.APIAddress != "" {
|
||||
u := d.APIAddress
|
||||
var resp struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrorMessage string `json:"text"`
|
||||
}
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).
|
||||
@@ -39,18 +39,18 @@ func (d *AliyundriveOpen) _refreshToken() (string, string, error) {
|
||||
return "", "", err
|
||||
}
|
||||
if resp.RefreshToken == "" || resp.AccessToken == "" {
|
||||
if resp.ErrorMessage != "" {
|
||||
return "", "", fmt.Errorf("failed to refresh token: %s", resp.ErrorMessage)
|
||||
}
|
||||
return "", "", fmt.Errorf("empty token returned from official API")
|
||||
}
|
||||
d.AccessToken = resp.AccessToken
|
||||
d.RefreshToken = resp.RefreshToken
|
||||
op.MustSaveDriverStorage(d)
|
||||
return "", "", nil
|
||||
return resp.RefreshToken, resp.AccessToken, nil
|
||||
}
|
||||
// 使用本地客户端的情况下检查是否为空
|
||||
|
||||
// 本地刷新逻辑,必须要求 client_id 和 client_secret
|
||||
if d.ClientID == "" || d.ClientSecret == "" {
|
||||
return "", "", fmt.Errorf("empty ClientID or ClientSecret")
|
||||
}
|
||||
// 走原有的刷新逻辑
|
||||
url := API_URL + "/oauth/access_token"
|
||||
//var resp base.TokenResp
|
||||
var e ErrResp
|
||||
|
||||
@@ -12,7 +12,6 @@ import (
|
||||
_ "github.com/OpenListTeam/OpenList/drivers/189"
|
||||
_ "github.com/OpenListTeam/OpenList/drivers/189pc"
|
||||
_ "github.com/OpenListTeam/OpenList/drivers/alias"
|
||||
_ "github.com/OpenListTeam/OpenList/drivers/alist_v2"
|
||||
_ "github.com/OpenListTeam/OpenList/drivers/aliyundrive"
|
||||
_ "github.com/OpenListTeam/OpenList/drivers/aliyundrive_open"
|
||||
_ "github.com/OpenListTeam/OpenList/drivers/aliyundrive_share"
|
||||
|
||||
@@ -295,7 +295,6 @@ func (d *BaiduNetdisk) Put(ctx context.Context, dstDir model.Obj, stream model.F
|
||||
retry.Attempts(1),
|
||||
retry.Delay(time.Second),
|
||||
retry.DelayType(retry.BackOffDelay))
|
||||
threadG.SetLimit(3)
|
||||
|
||||
for i, partseq := range precreateResp.BlockList {
|
||||
if utils.IsCanceled(upCtx) {
|
||||
|
||||
@@ -37,6 +37,7 @@ func (d *BaiduNetdisk) _refreshToken() error {
|
||||
var resp struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrorMessage string `json:"text"`
|
||||
}
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).
|
||||
@@ -50,6 +51,9 @@ func (d *BaiduNetdisk) _refreshToken() error {
|
||||
return err
|
||||
}
|
||||
if resp.RefreshToken == "" || resp.AccessToken == "" {
|
||||
if resp.ErrorMessage != "" {
|
||||
return fmt.Errorf("failed to refresh token: %s", resp.ErrorMessage)
|
||||
}
|
||||
return fmt.Errorf("empty token returned from official API")
|
||||
}
|
||||
d.AccessToken = resp.AccessToken
|
||||
|
||||
@@ -342,7 +342,6 @@ func (d *BaiduPhoto) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
retry.Attempts(3),
|
||||
retry.Delay(time.Second),
|
||||
retry.DelayType(retry.BackOffDelay))
|
||||
threadG.SetLimit(3)
|
||||
|
||||
for i, partseq := range precreateResp.BlockList {
|
||||
if utils.IsCanceled(upCtx) {
|
||||
|
||||
@@ -13,7 +13,7 @@ type Addition struct {
|
||||
RefreshToken string `json:"refresh_token" required:"true"`
|
||||
driver.RootPath
|
||||
|
||||
OauthTokenURL string `json:"oauth_token_url" default:"https://api.example.com/alist/dropbox/token"` // TODO: replace
|
||||
OauthTokenURL string `json:"oauth_token_url" default:"https://api.oplist.org/dropboxs/renewapi"` // TODO: replace
|
||||
ClientID string `json:"client_id" required:"false" help:"Keep it empty if you don't have one"`
|
||||
ClientSecret string `json:"client_secret" required:"false" help:"Keep it empty if you don't have one"`
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@ func (d *GoogleDrive) refreshToken() error {
|
||||
var resp struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrorMessage string `json:"text"`
|
||||
}
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).
|
||||
@@ -57,6 +58,9 @@ func (d *GoogleDrive) refreshToken() error {
|
||||
return err
|
||||
}
|
||||
if resp.RefreshToken == "" || resp.AccessToken == "" {
|
||||
if resp.ErrorMessage != "" {
|
||||
return fmt.Errorf("failed to refresh token: %s", resp.ErrorMessage)
|
||||
}
|
||||
return fmt.Errorf("empty token returned from official API")
|
||||
}
|
||||
d.AccessToken = resp.AccessToken
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
//go:build (linux || darwin || windows) && (amd64 || arm64)
|
||||
// +build linux darwin windows
|
||||
// +build amd64 arm64
|
||||
|
||||
package drivers
|
||||
|
||||
import (
|
||||
_ "github.com/OpenListTeam/OpenList/drivers/lark"
|
||||
)
|
||||
@@ -1,403 +0,0 @@
|
||||
package lark
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/internal/model"
|
||||
lark "github.com/larksuite/oapi-sdk-go/v3"
|
||||
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
|
||||
larkdrive "github.com/larksuite/oapi-sdk-go/v3/service/drive/v1"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
type Lark struct {
|
||||
model.Storage
|
||||
Addition
|
||||
|
||||
client *lark.Client
|
||||
rootFolderToken string
|
||||
}
|
||||
|
||||
func (c *Lark) Config() driver.Config {
|
||||
return config
|
||||
}
|
||||
|
||||
func (c *Lark) GetAddition() driver.Additional {
|
||||
return &c.Addition
|
||||
}
|
||||
|
||||
func (c *Lark) Init(ctx context.Context) error {
|
||||
c.client = lark.NewClient(c.AppId, c.AppSecret, lark.WithTokenCache(newTokenCache()))
|
||||
|
||||
paths := strings.Split(c.RootFolderPath, "/")
|
||||
token := ""
|
||||
|
||||
var ok bool
|
||||
var file *larkdrive.File
|
||||
for _, p := range paths {
|
||||
if p == "" {
|
||||
token = ""
|
||||
continue
|
||||
}
|
||||
|
||||
resp, err := c.client.Drive.File.ListByIterator(ctx, larkdrive.NewListFileReqBuilder().FolderToken(token).Build())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for {
|
||||
ok, file, err = resp.Next()
|
||||
if !ok {
|
||||
return errs.ObjectNotFound
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if *file.Type == "folder" && *file.Name == p {
|
||||
token = *file.Token
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.rootFolderToken = token
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Lark) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Lark) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
token, ok := c.getObjToken(ctx, dir.GetPath())
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
if token == emptyFolderToken {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
resp, err := c.client.Drive.File.ListByIterator(ctx, larkdrive.NewListFileReqBuilder().FolderToken(token).Build())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ok = false
|
||||
var file *larkdrive.File
|
||||
var res []model.Obj
|
||||
|
||||
for {
|
||||
ok, file, err = resp.Next()
|
||||
if !ok {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
modifiedUnix, _ := strconv.ParseInt(*file.ModifiedTime, 10, 64)
|
||||
createdUnix, _ := strconv.ParseInt(*file.CreatedTime, 10, 64)
|
||||
|
||||
f := model.Object{
|
||||
ID: *file.Token,
|
||||
Path: strings.Join([]string{c.RootFolderPath, dir.GetPath(), *file.Name}, "/"),
|
||||
Name: *file.Name,
|
||||
Size: 0,
|
||||
Modified: time.Unix(modifiedUnix, 0),
|
||||
Ctime: time.Unix(createdUnix, 0),
|
||||
IsFolder: *file.Type == "folder",
|
||||
}
|
||||
res = append(res, &f)
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *Lark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
token, ok := c.getObjToken(ctx, file.GetPath())
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
resp, err := c.client.GetTenantAccessTokenBySelfBuiltApp(ctx, &larkcore.SelfBuiltTenantAccessTokenReq{
|
||||
AppID: c.AppId,
|
||||
AppSecret: c.AppSecret,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !c.ExternalMode {
|
||||
accessToken := resp.TenantAccessToken
|
||||
|
||||
url := fmt.Sprintf("https://open.feishu.cn/open-apis/drive/v1/files/%s/download", token)
|
||||
|
||||
req, err := http.NewRequest(http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", accessToken))
|
||||
req.Header.Set("Range", "bytes=0-1")
|
||||
|
||||
ar, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if ar.StatusCode != http.StatusPartialContent {
|
||||
return nil, errors.New("failed to get download link")
|
||||
}
|
||||
|
||||
return &model.Link{
|
||||
URL: url,
|
||||
Header: http.Header{
|
||||
"Authorization": []string{fmt.Sprintf("Bearer %s", accessToken)},
|
||||
},
|
||||
}, nil
|
||||
} else {
|
||||
url := strings.Join([]string{c.TenantUrlPrefix, "file", token}, "/")
|
||||
|
||||
return &model.Link{
|
||||
URL: url,
|
||||
}, nil
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Lark) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
||||
token, ok := c.getObjToken(ctx, parentDir.GetPath())
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
body, err := larkdrive.NewCreateFolderFilePathReqBodyBuilder().FolderToken(token).Name(dirName).Build()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := c.client.Drive.File.CreateFolder(ctx,
|
||||
larkdrive.NewCreateFolderFileReqBuilder().Body(body).Build())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Success() {
|
||||
return nil, errors.New(resp.Error())
|
||||
}
|
||||
|
||||
return &model.Object{
|
||||
ID: *resp.Data.Token,
|
||||
Path: strings.Join([]string{c.RootFolderPath, parentDir.GetPath(), dirName}, "/"),
|
||||
Name: dirName,
|
||||
Size: 0,
|
||||
IsFolder: true,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Lark) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||
srcToken, ok := c.getObjToken(ctx, srcObj.GetPath())
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
dstDirToken, ok := c.getObjToken(ctx, dstDir.GetPath())
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
req := larkdrive.NewMoveFileReqBuilder().
|
||||
Body(larkdrive.NewMoveFileReqBodyBuilder().
|
||||
Type("file").
|
||||
FolderToken(dstDirToken).
|
||||
Build()).FileToken(srcToken).
|
||||
Build()
|
||||
|
||||
// 发起请求
|
||||
resp, err := c.client.Drive.File.Move(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Success() {
|
||||
return nil, errors.New(resp.Error())
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Lark) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
||||
// TODO rename obj, optional
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
|
||||
func (c *Lark) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
||||
srcToken, ok := c.getObjToken(ctx, srcObj.GetPath())
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
dstDirToken, ok := c.getObjToken(ctx, dstDir.GetPath())
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
req := larkdrive.NewCopyFileReqBuilder().
|
||||
Body(larkdrive.NewCopyFileReqBodyBuilder().
|
||||
Name(srcObj.GetName()).
|
||||
Type("file").
|
||||
FolderToken(dstDirToken).
|
||||
Build()).FileToken(srcToken).
|
||||
Build()
|
||||
|
||||
// 发起请求
|
||||
resp, err := c.client.Drive.File.Copy(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Success() {
|
||||
return nil, errors.New(resp.Error())
|
||||
}
|
||||
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *Lark) Remove(ctx context.Context, obj model.Obj) error {
|
||||
token, ok := c.getObjToken(ctx, obj.GetPath())
|
||||
if !ok {
|
||||
return errs.ObjectNotFound
|
||||
}
|
||||
|
||||
req := larkdrive.NewDeleteFileReqBuilder().
|
||||
FileToken(token).
|
||||
Type("file").
|
||||
Build()
|
||||
|
||||
// 发起请求
|
||||
resp, err := c.client.Drive.File.Delete(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if !resp.Success() {
|
||||
return errors.New(resp.Error())
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var uploadLimit = rate.NewLimiter(rate.Every(time.Second), 5)
|
||||
|
||||
func (c *Lark) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
||||
token, ok := c.getObjToken(ctx, dstDir.GetPath())
|
||||
if !ok {
|
||||
return nil, errs.ObjectNotFound
|
||||
}
|
||||
|
||||
// prepare
|
||||
req := larkdrive.NewUploadPrepareFileReqBuilder().
|
||||
FileUploadInfo(larkdrive.NewFileUploadInfoBuilder().
|
||||
FileName(stream.GetName()).
|
||||
ParentType(`explorer`).
|
||||
ParentNode(token).
|
||||
Size(int(stream.GetSize())).
|
||||
Build()).
|
||||
Build()
|
||||
|
||||
// 发起请求
|
||||
err := uploadLimit.Wait(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.client.Drive.File.UploadPrepare(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Success() {
|
||||
return nil, errors.New(resp.Error())
|
||||
}
|
||||
|
||||
uploadId := *resp.Data.UploadId
|
||||
blockSize := *resp.Data.BlockSize
|
||||
blockCount := *resp.Data.BlockNum
|
||||
|
||||
// upload
|
||||
for i := 0; i < blockCount; i++ {
|
||||
length := int64(blockSize)
|
||||
if i == blockCount-1 {
|
||||
length = stream.GetSize() - int64(i*blockSize)
|
||||
}
|
||||
|
||||
reader := driver.NewLimitedUploadStream(ctx, io.LimitReader(stream, length))
|
||||
|
||||
req := larkdrive.NewUploadPartFileReqBuilder().
|
||||
Body(larkdrive.NewUploadPartFileReqBodyBuilder().
|
||||
UploadId(uploadId).
|
||||
Seq(i).
|
||||
Size(int(length)).
|
||||
File(reader).
|
||||
Build()).
|
||||
Build()
|
||||
|
||||
// 发起请求
|
||||
err = uploadLimit.Wait(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp, err := c.client.Drive.File.UploadPart(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !resp.Success() {
|
||||
return nil, errors.New(resp.Error())
|
||||
}
|
||||
|
||||
up(float64(i) / float64(blockCount))
|
||||
}
|
||||
|
||||
//close
|
||||
closeReq := larkdrive.NewUploadFinishFileReqBuilder().
|
||||
Body(larkdrive.NewUploadFinishFileReqBodyBuilder().
|
||||
UploadId(uploadId).
|
||||
BlockNum(blockCount).
|
||||
Build()).
|
||||
Build()
|
||||
|
||||
// 发起请求
|
||||
closeResp, err := c.client.Drive.File.UploadFinish(ctx, closeReq)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !closeResp.Success() {
|
||||
return nil, errors.New(closeResp.Error())
|
||||
}
|
||||
|
||||
return &model.Object{
|
||||
ID: *closeResp.Data.FileToken,
|
||||
}, nil
|
||||
}
|
||||
|
||||
//func (d *Lark) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*Lark)(nil)
|
||||
@@ -1,36 +0,0 @@
|
||||
package lark
|
||||
|
||||
import (
|
||||
"github.com/OpenListTeam/OpenList/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
// Usually one of two
|
||||
driver.RootPath
|
||||
// define other
|
||||
AppId string `json:"app_id" type:"text" help:"app id"`
|
||||
AppSecret string `json:"app_secret" type:"text" help:"app secret"`
|
||||
ExternalMode bool `json:"external_mode" type:"bool" help:"external mode"`
|
||||
TenantUrlPrefix string `json:"tenant_url_prefix" type:"text" help:"tenant url prefix"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Lark",
|
||||
LocalSort: false,
|
||||
OnlyLocal: false,
|
||||
OnlyProxy: false,
|
||||
NoCache: false,
|
||||
NoUpload: false,
|
||||
NeedMs: false,
|
||||
DefaultRoot: "/",
|
||||
CheckStatus: false,
|
||||
Alert: "",
|
||||
NoOverwriteUpload: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Lark{}
|
||||
})
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package lark
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Xhofe/go-cache"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TokenCache struct {
|
||||
cache.ICache[string]
|
||||
}
|
||||
|
||||
func (t *TokenCache) Set(_ context.Context, key string, value string, expireTime time.Duration) error {
|
||||
t.ICache.Set(key, value, cache.WithEx[string](expireTime))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *TokenCache) Get(_ context.Context, key string) (string, error) {
|
||||
v, ok := t.ICache.Get(key)
|
||||
if ok {
|
||||
return v, nil
|
||||
}
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func newTokenCache() *TokenCache {
|
||||
c := cache.NewMemCache[string]()
|
||||
|
||||
return &TokenCache{c}
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
package lark
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/Xhofe/go-cache"
|
||||
larkdrive "github.com/larksuite/oapi-sdk-go/v3/service/drive/v1"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"path"
|
||||
"time"
|
||||
)
|
||||
|
||||
const objTokenCacheDuration = 5 * time.Minute
|
||||
const emptyFolderToken = "empty"
|
||||
|
||||
var objTokenCache = cache.NewMemCache[string]()
|
||||
var exOpts = cache.WithEx[string](objTokenCacheDuration)
|
||||
|
||||
func (c *Lark) getObjToken(ctx context.Context, folderPath string) (string, bool) {
|
||||
if token, ok := objTokenCache.Get(folderPath); ok {
|
||||
return token, true
|
||||
}
|
||||
|
||||
dir, name := path.Split(folderPath)
|
||||
// strip the last slash of dir if it exists
|
||||
if len(dir) > 0 && dir[len(dir)-1] == '/' {
|
||||
dir = dir[:len(dir)-1]
|
||||
}
|
||||
if name == "" {
|
||||
return c.rootFolderToken, true
|
||||
}
|
||||
|
||||
var parentToken string
|
||||
var found bool
|
||||
parentToken, found = c.getObjToken(ctx, dir)
|
||||
if !found {
|
||||
return emptyFolderToken, false
|
||||
}
|
||||
|
||||
req := larkdrive.NewListFileReqBuilder().FolderToken(parentToken).Build()
|
||||
resp, err := c.client.Drive.File.ListByIterator(ctx, req)
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to list files")
|
||||
return emptyFolderToken, false
|
||||
}
|
||||
|
||||
var file *larkdrive.File
|
||||
for {
|
||||
found, file, err = resp.Next()
|
||||
if !found {
|
||||
break
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
log.WithError(err).Error("failed to get next file")
|
||||
break
|
||||
}
|
||||
|
||||
if *file.Name == name {
|
||||
objTokenCache.Set(folderPath, *file.Token, exOpts)
|
||||
return *file.Token, true
|
||||
}
|
||||
}
|
||||
|
||||
return emptyFolderToken, false
|
||||
}
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/internal/sign"
|
||||
"github.com/OpenListTeam/OpenList/pkg/utils"
|
||||
"github.com/OpenListTeam/OpenList/server/common"
|
||||
"github.com/alist-org/times"
|
||||
"github.com/OpenListTeam/times"
|
||||
cp "github.com/otiai10/copy"
|
||||
log "github.com/sirupsen/logrus"
|
||||
_ "golang.org/x/image/webp"
|
||||
|
||||
@@ -298,7 +298,6 @@ func (d *MoPan) Put(ctx context.Context, dstDir model.Obj, stream model.FileStre
|
||||
retry.Attempts(3),
|
||||
retry.Delay(time.Second),
|
||||
retry.DelayType(retry.BackOffDelay))
|
||||
threadG.SetLimit(3)
|
||||
|
||||
// step.3
|
||||
parts, err := d.client.GetAllMultiUploadUrls(initUpdload.UploadFileID, initUpdload.PartInfos)
|
||||
|
||||
@@ -78,6 +78,7 @@ func (d *Onedrive) _refreshToken() error {
|
||||
var resp struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrorMessage string `json:"text"`
|
||||
}
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).
|
||||
@@ -91,6 +92,9 @@ func (d *Onedrive) _refreshToken() error {
|
||||
return err
|
||||
}
|
||||
if resp.RefreshToken == "" || resp.AccessToken == "" {
|
||||
if resp.ErrorMessage != "" {
|
||||
return fmt.Errorf("failed to refresh token: %s", resp.ErrorMessage)
|
||||
}
|
||||
return fmt.Errorf("empty token returned from official API")
|
||||
}
|
||||
d.AccessToken = resp.AccessToken
|
||||
|
||||
@@ -355,7 +355,7 @@ func (d *OpenList) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.O
|
||||
return err
|
||||
}
|
||||
|
||||
//func (d *AList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
//func (d *OpenList) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ func (d *YandexDisk) refreshToken() error {
|
||||
var resp struct {
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessToken string `json:"access_token"`
|
||||
ErrorMessage string `json:"text"`
|
||||
}
|
||||
_, err := base.RestyClient.R().
|
||||
SetResult(&resp).
|
||||
@@ -33,6 +34,9 @@ func (d *YandexDisk) refreshToken() error {
|
||||
return err
|
||||
}
|
||||
if resp.RefreshToken == "" || resp.AccessToken == "" {
|
||||
if resp.ErrorMessage != "" {
|
||||
return fmt.Errorf("failed to refresh token: %s", resp.ErrorMessage)
|
||||
}
|
||||
return fmt.Errorf("empty token returned from official API")
|
||||
}
|
||||
d.AccessToken = resp.AccessToken
|
||||
|
||||
15
go.mod
15
go.mod
@@ -3,13 +3,15 @@ module github.com/OpenListTeam/OpenList
|
||||
go 1.23.4
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0
|
||||
github.com/OpenListTeam/gofakes3 v0.1.0
|
||||
github.com/OpenListTeam/sftpd-openlist v1.0.1
|
||||
github.com/OpenListTeam/times v0.1.0
|
||||
github.com/ProtonMail/go-crypto v1.0.0
|
||||
github.com/SheltonZhu/115driver v1.0.34
|
||||
github.com/Xhofe/go-cache v0.0.0-20240804043513-b1a71927bc21
|
||||
github.com/Xhofe/rateg v0.0.0-20230728072201-251a4e1adad4
|
||||
github.com/alist-org/gofakes3 v0.0.7
|
||||
github.com/alist-org/times v0.0.0-20240721124654-efa0c7d3ad92
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/aws/aws-sdk-go v1.55.5
|
||||
@@ -25,6 +27,7 @@ require (
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/dlclark/regexp2 v1.11.4
|
||||
github.com/dustinxie/ecc v0.0.0-20210511000915-959544187564
|
||||
github.com/fclairamb/ftpserverlib v0.26.1-0.20250611192536-99cb646d0bbe
|
||||
github.com/foxxorcat/mopan-sdk-go v0.1.6
|
||||
github.com/foxxorcat/weiyun-sdk-go v0.1.3
|
||||
github.com/gin-contrib/cors v1.7.2
|
||||
@@ -40,7 +43,6 @@ require (
|
||||
github.com/jlaffaye/ftp v0.2.0
|
||||
github.com/json-iterator/go v1.1.12
|
||||
github.com/kdomanski/iso9660 v0.4.0
|
||||
github.com/larksuite/oapi-sdk-go/v3 v3.3.1
|
||||
github.com/maruel/natural v1.1.1
|
||||
github.com/meilisearch/meilisearch-go v0.27.2
|
||||
github.com/mholt/archives v0.1.0
|
||||
@@ -78,12 +80,7 @@ require (
|
||||
gorm.io/gorm v1.25.11
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 // indirect
|
||||
github.com/fclairamb/ftpserverlib v0.26.1-0.20250611192536-99cb646d0bbe // indirect
|
||||
)
|
||||
require github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 // indirect
|
||||
|
||||
require (
|
||||
github.com/STARRY-S/zip v0.2.1 // indirect
|
||||
|
||||
14
go.sum
14
go.sum
@@ -30,8 +30,16 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd h1:nzE1YQBdx1bq9IlZinHa+HVffy+NmVRoKr+wHN8fpLE=
|
||||
github.com/Max-Sum/base32768 v0.0.0-20230304063302-18e6ce5945fd/go.mod h1:C8yoIfvESpM3GD07OCHU7fqI7lhwyZ2Td1rbNbTAhnc=
|
||||
github.com/OpenListTeam/gofakes3 v0.0.7 h1:0cDGI7fLBrqumhCBto9T3ZYCL71AyGZ1l+xxJgjqe8s=
|
||||
github.com/OpenListTeam/gofakes3 v0.0.7/go.mod h1:6IyGtYGIX29fLvtXo+XZhtwX2P33KVYYj8uTgAHSu58=
|
||||
github.com/OpenListTeam/gofakes3 v0.1.0 h1:QVWIaso208bNc9L2gNZrkPiluAIg9jemZRxWPh4AVdY=
|
||||
github.com/OpenListTeam/gofakes3 v0.1.0/go.mod h1:mWMoLOLBX5qZFe1IQHsGXD4iTmIC7nFxxeTxpYvUu6Q=
|
||||
github.com/OpenListTeam/sftpd-openlist v1.0.1 h1:j4S3iPFOpnXCUKRPS7uCT4mF2VCl34GyqvH6lqwnkUU=
|
||||
github.com/OpenListTeam/sftpd-openlist v1.0.1/go.mod h1:uO/wKnbvbdq3rBLmClMTZXuCnw7XW4wlAq4dZe91a40=
|
||||
github.com/OpenListTeam/times v0.0.0-20240721124654-efa0c7d3ad92 h1:pIEI87zhv8ZzQcu65rTL7kqirrs8dR6HDiXrqWat2Fk=
|
||||
github.com/OpenListTeam/times v0.0.0-20240721124654-efa0c7d3ad92/go.mod h1:oPJwGY3sLmGgcJamGumz//0A35f4BwQRacyqLNcJTOU=
|
||||
github.com/OpenListTeam/times v0.1.0 h1:qknxw+qj5CYKgXAwydA102UEpPcpU8TYNGRmwRyPYpg=
|
||||
github.com/OpenListTeam/times v0.1.0/go.mod h1:Jx7qen5NCYzKk2w14YuvU48YYMcPa1P9a+EJePC15Pc=
|
||||
github.com/ProtonMail/go-crypto v1.0.0 h1:LRuvITjQWX+WIfr930YHG2HNfjR1uOfyf5vE0kC2U78=
|
||||
github.com/ProtonMail/go-crypto v1.0.0/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
|
||||
github.com/RoaringBitmap/roaring v1.9.3 h1:t4EbC5qQwnisr5PrP9nt0IRhRTb9gMUgQF4t4S2OByM=
|
||||
@@ -50,10 +58,6 @@ github.com/abbot/go-http-auth v0.4.0 h1:QjmvZ5gSC7jm3Zg54DqWE/T5m1t2AfDu6QlXJT0E
|
||||
github.com/abbot/go-http-auth v0.4.0/go.mod h1:Cz6ARTIzApMJDzh5bRMSUou6UMSp0IEXg9km/ci7TJM=
|
||||
github.com/aead/ecdh v0.2.0 h1:pYop54xVaq/CEREFEcukHRZfTdjiWvYIsZDXXrBapQQ=
|
||||
github.com/aead/ecdh v0.2.0/go.mod h1:a9HHtXuSo8J1Js1MwLQx2mBhkXMT6YwUmVVEY4tTB8U=
|
||||
github.com/alist-org/gofakes3 v0.0.7 h1:0cDGI7fLBrqumhCBto9T3ZYCL71AyGZ1l+xxJgjqe8s=
|
||||
github.com/alist-org/gofakes3 v0.0.7/go.mod h1:6IyGtYGIX29fLvtXo+XZhtwX2P33KVYYj8uTgAHSu58=
|
||||
github.com/alist-org/times v0.0.0-20240721124654-efa0c7d3ad92 h1:pIEI87zhv8ZzQcu65rTL7kqirrs8dR6HDiXrqWat2Fk=
|
||||
github.com/alist-org/times v0.0.0-20240721124654-efa0c7d3ad92/go.mod h1:oPJwGY3sLmGgcJamGumz//0A35f4BwQRacyqLNcJTOU=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g=
|
||||
github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andreburgaud/crypt2go v1.8.0 h1:J73vGTb1P6XL69SSuumbKs0DWn3ulbl9L92ZXBjw6pc=
|
||||
@@ -402,8 +406,6 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/larksuite/oapi-sdk-go/v3 v3.3.1 h1:DLQQEgHUAGZB6RVlceB1f6A94O206exxW2RIMH+gMUc=
|
||||
github.com/larksuite/oapi-sdk-go/v3 v3.3.1/go.mod h1:ZEplY+kwuIrj/nqw5uSCINNATcH3KdxSN7y+UxYY5fI=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6cdF0Y8=
|
||||
|
||||
@@ -104,8 +104,8 @@ func InitialSettings() []model.SettingItem {
|
||||
{Key: conf.AllowMounted, Value: "true", Type: conf.TypeBool, Group: model.SITE},
|
||||
{Key: conf.RobotsTxt, Value: "User-agent: *\nAllow: /", Type: conf.TypeText, Group: model.SITE},
|
||||
// style settings
|
||||
{Key: conf.Logo, Value: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/OpenList.svg", Type: conf.TypeText, Group: model.STYLE},
|
||||
{Key: conf.Favicon, Value: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/OpenList.svg", Type: conf.TypeString, Group: model.STYLE},
|
||||
{Key: conf.Logo, Value: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg", Type: conf.TypeText, Group: model.STYLE},
|
||||
{Key: conf.Favicon, Value: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg", Type: conf.TypeString, Group: model.STYLE},
|
||||
{Key: conf.MainColor, Value: "#1890ff", Type: conf.TypeString, Group: model.STYLE},
|
||||
{Key: "home_icon", Value: "🏠", Type: conf.TypeString, Group: model.STYLE},
|
||||
{Key: "home_container", Value: "max_980px", Type: conf.TypeSelect, Options: "max_980px,hope_container", Group: model.STYLE},
|
||||
@@ -136,9 +136,9 @@ func InitialSettings() []model.SettingItem {
|
||||
// "Google":"https://docs.google.com/gview?url=$url&embedded=true",
|
||||
//}`, Type: conf.TypeText, Group: model.PREVIEW},
|
||||
// {Key: conf.PdfViewers, Value: `{
|
||||
// "pdf.js":"https://alist-org.github.io/pdf.js/web/viewer.html?file=$url"
|
||||
// "pdf.js":"https://openlistteam.github.io/pdf.js/web/viewer.html?file=$url"
|
||||
//}`, Type: conf.TypeText, Group: model.PREVIEW},
|
||||
{Key: "audio_cover", Value: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/OpenList.svg", Type: conf.TypeString, Group: model.PREVIEW},
|
||||
{Key: "audio_cover", Value: "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg", Type: conf.TypeString, Group: model.PREVIEW},
|
||||
{Key: conf.AudioAutoplay, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW},
|
||||
{Key: conf.VideoAutoplay, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW},
|
||||
{Key: conf.PreviewArchivesByDefault, Value: "true", Type: conf.TypeBool, Group: model.PREVIEW},
|
||||
@@ -158,7 +158,7 @@ func InitialSettings() []model.SettingItem {
|
||||
{Key: conf.OcrApi, Value: "https://api.example.com/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL}, // TODO: This can be replace by a community-hosted endpoint, see https://github.com/xhofe/ocr_api_server
|
||||
{Key: conf.FilenameCharMapping, Value: `{"/": "|"}`, Type: conf.TypeText, Group: model.GLOBAL},
|
||||
{Key: conf.ForwardDirectLinkParams, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL},
|
||||
{Key: conf.IgnoreDirectLinkParams, Value: "sign,alist_ts", Type: conf.TypeString, Group: model.GLOBAL},
|
||||
{Key: conf.IgnoreDirectLinkParams, Value: "sign,openlist_ts", Type: conf.TypeString, Group: model.GLOBAL},
|
||||
{Key: conf.WebauthnLoginEnabled, Value: "false", Type: conf.TypeBool, Group: model.GLOBAL, Flag: model.PUBLIC},
|
||||
|
||||
// single settings
|
||||
|
||||
@@ -22,6 +22,7 @@ func initTasks() {
|
||||
func InitialTasks() []model.TaskItem {
|
||||
initialTaskItems = []model.TaskItem{
|
||||
{Key: "copy", PersistData: "[]"},
|
||||
{Key: "move", PersistData: "[]"},
|
||||
{Key: "download", PersistData: "[]"},
|
||||
{Key: "transfer", PersistData: "[]"},
|
||||
}
|
||||
|
||||
@@ -7,8 +7,7 @@ import (
|
||||
|
||||
// GrantAdminPermissions gives admin Permission 0(can see hidden) - 9(webdav manage) and
|
||||
// 12(can read archives) - 13(can decompress archives)
|
||||
// This patch is written to help users upgrading from older version better adapt to PR AlistGo/alist#7705 and
|
||||
// PR AlistGo/alist#7817.
|
||||
// This patch is written to help users upgrading from older version better adapt
|
||||
func GrantAdminPermissions() {
|
||||
admin, err := op.GetAdmin()
|
||||
if err == nil && (admin.Permission&0x33FF) == 0 {
|
||||
|
||||
@@ -26,6 +26,10 @@ func InitTaskManager() {
|
||||
op.RegisterSettingChangingCallback(func() {
|
||||
fs.CopyTaskManager.SetWorkersNumActive(taskFilterNegative(setting.GetInt(conf.TaskCopyThreadsNum, conf.Conf.Tasks.Copy.Workers)))
|
||||
})
|
||||
fs.MoveTaskManager = tache.NewManager[*fs.MoveTask](tache.WithWorks(setting.GetInt(conf.TaskMoveThreadsNum, conf.Conf.Tasks.Move.Workers)), tache.WithPersistFunction(db.GetTaskDataFunc("move", conf.Conf.Tasks.Move.TaskPersistant), db.UpdateTaskDataFunc("move", conf.Conf.Tasks.Move.TaskPersistant)), tache.WithMaxRetry(conf.Conf.Tasks.Move.MaxRetry))
|
||||
op.RegisterSettingChangingCallback(func() {
|
||||
fs.MoveTaskManager.SetWorkersNumActive(taskFilterNegative(setting.GetInt(conf.TaskMoveThreadsNum, conf.Conf.Tasks.Move.Workers)))
|
||||
})
|
||||
tool.DownloadTaskManager = tache.NewManager[*tool.DownloadTask](tache.WithWorks(setting.GetInt(conf.TaskOfflineDownloadThreadsNum, conf.Conf.Tasks.Download.Workers)), tache.WithPersistFunction(db.GetTaskDataFunc("download", conf.Conf.Tasks.Download.TaskPersistant), db.UpdateTaskDataFunc("download", conf.Conf.Tasks.Download.TaskPersistant)), tache.WithMaxRetry(conf.Conf.Tasks.Download.MaxRetry))
|
||||
op.RegisterSettingChangingCallback(func() {
|
||||
tool.DownloadTaskManager.SetWorkersNumActive(taskFilterNegative(setting.GetInt(conf.TaskOfflineDownloadThreadsNum, conf.Conf.Tasks.Download.Workers)))
|
||||
|
||||
@@ -58,6 +58,7 @@ type TasksConfig struct {
|
||||
Transfer TaskConfig `json:"transfer" envPrefix:"TRANSFER_"`
|
||||
Upload TaskConfig `json:"upload" envPrefix:"UPLOAD_"`
|
||||
Copy TaskConfig `json:"copy" envPrefix:"COPY_"`
|
||||
Move TaskConfig `json:"move" envPrefix:"MOVE_"`
|
||||
Decompress TaskConfig `json:"decompress" envPrefix:"DECOMPRESS_"`
|
||||
DecompressUpload TaskConfig `json:"decompress_upload" envPrefix:"DECOMPRESS_UPLOAD_"`
|
||||
AllowRetryCanceled bool `json:"allow_retry_canceled" env:"ALLOW_RETRY_CANCELED"`
|
||||
@@ -175,6 +176,11 @@ func DefaultConfig() *Config {
|
||||
MaxRetry: 2,
|
||||
// TaskPersistant: true,
|
||||
},
|
||||
Move: TaskConfig{
|
||||
Workers: 5,
|
||||
MaxRetry: 2,
|
||||
// TaskPersistant: true,
|
||||
},
|
||||
Decompress: TaskConfig{
|
||||
Workers: 5,
|
||||
MaxRetry: 2,
|
||||
|
||||
@@ -123,6 +123,7 @@ const (
|
||||
TaskOfflineDownloadTransferThreadsNum = "offline_download_transfer_task_threads_num"
|
||||
TaskUploadThreadsNum = "upload_task_threads_num"
|
||||
TaskCopyThreadsNum = "copy_task_threads_num"
|
||||
TaskMoveThreadsNum = "move_task_threads_num"
|
||||
TaskDecompressDownloadThreadsNum = "decompress_download_task_threads_num"
|
||||
TaskDecompressUploadThreadsNum = "decompress_upload_task_threads_num"
|
||||
StreamMaxClientDownloadSpeed = "max_client_download_speed"
|
||||
|
||||
@@ -74,6 +74,14 @@ func Move(ctx context.Context, srcPath, dstDirPath string, lazyCache ...bool) er
|
||||
return err
|
||||
}
|
||||
|
||||
func MoveWithTask(ctx context.Context, srcPath, dstDirPath string, lazyCache ...bool) (task.TaskExtensionInfo, error) {
|
||||
res, err := _move(ctx, srcPath, dstDirPath, lazyCache...)
|
||||
if err != nil {
|
||||
log.Errorf("failed move %s to %s: %+v", srcPath, dstDirPath, err)
|
||||
}
|
||||
return res, err
|
||||
}
|
||||
|
||||
func Copy(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (task.TaskExtensionInfo, error) {
|
||||
res, err := _copy(ctx, srcObjPath, dstDirPath, lazyCache...)
|
||||
if err != nil {
|
||||
|
||||
190
internal/fs/move.go
Normal file
190
internal/fs/move.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package fs
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
stdpath "path"
|
||||
"time"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/internal/driver"
|
||||
"github.com/OpenListTeam/OpenList/internal/errs"
|
||||
"github.com/OpenListTeam/OpenList/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/internal/task"
|
||||
"github.com/OpenListTeam/OpenList/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/xhofe/tache"
|
||||
)
|
||||
|
||||
type MoveTask struct {
|
||||
task.TaskExtension
|
||||
Status string `json:"-"`
|
||||
SrcObjPath string `json:"src_path"`
|
||||
DstDirPath string `json:"dst_path"`
|
||||
srcStorage driver.Driver `json:"-"`
|
||||
dstStorage driver.Driver `json:"-"`
|
||||
SrcStorageMp string `json:"src_storage_mp"`
|
||||
DstStorageMp string `json:"dst_storage_mp"`
|
||||
}
|
||||
|
||||
func (t *MoveTask) GetName() string {
|
||||
return fmt.Sprintf("move [%s](%s) to [%s](%s)", t.SrcStorageMp, t.SrcObjPath, t.DstStorageMp, t.DstDirPath)
|
||||
}
|
||||
|
||||
func (t *MoveTask) GetStatus() string {
|
||||
return t.Status
|
||||
}
|
||||
|
||||
func (t *MoveTask) Run() error {
|
||||
t.ReinitCtx()
|
||||
t.ClearEndTime()
|
||||
t.SetStartTime(time.Now())
|
||||
defer func() { t.SetEndTime(time.Now()) }()
|
||||
var err error
|
||||
if t.srcStorage == nil {
|
||||
t.srcStorage, err = op.GetStorageByMountPath(t.SrcStorageMp)
|
||||
}
|
||||
if t.dstStorage == nil {
|
||||
t.dstStorage, err = op.GetStorageByMountPath(t.DstStorageMp)
|
||||
}
|
||||
if err != nil {
|
||||
return errors.WithMessage(err, "failed get storage")
|
||||
}
|
||||
|
||||
return moveBetween2Storages(t, t.srcStorage, t.dstStorage, t.SrcObjPath, t.DstDirPath)
|
||||
}
|
||||
|
||||
var MoveTaskManager *tache.Manager[*MoveTask]
|
||||
|
||||
|
||||
func moveBetween2Storages(t *MoveTask, srcStorage, dstStorage driver.Driver, srcObjPath, dstDirPath string) error {
|
||||
t.Status = "getting src object"
|
||||
srcObj, err := op.Get(t.Ctx(), srcStorage, srcObjPath)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get src [%s] file", srcObjPath)
|
||||
}
|
||||
|
||||
if srcObj.IsDir() {
|
||||
t.Status = "src object is dir, listing objs"
|
||||
objs, err := op.List(t.Ctx(), srcStorage, srcObjPath, model.ListArgs{})
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed list src [%s] objs", srcObjPath)
|
||||
}
|
||||
|
||||
dstObjPath := stdpath.Join(dstDirPath, srcObj.GetName())
|
||||
t.Status = "creating destination directory"
|
||||
err = op.MakeDir(t.Ctx(), dstStorage, dstObjPath)
|
||||
if err != nil {
|
||||
// Check if this is an upload-related error and provide a clearer message
|
||||
if errors.Is(err, errs.UploadNotSupported) {
|
||||
return errors.WithMessagef(err, "destination storage [%s] does not support creating directories", dstStorage.GetStorage().MountPath)
|
||||
}
|
||||
return errors.WithMessagef(err, "failed to create destination directory [%s] in storage [%s]", dstObjPath, dstStorage.GetStorage().MountPath)
|
||||
}
|
||||
|
||||
for _, obj := range objs {
|
||||
if utils.IsCanceled(t.Ctx()) {
|
||||
return nil
|
||||
}
|
||||
srcSubObjPath := stdpath.Join(srcObjPath, obj.GetName())
|
||||
MoveTaskManager.Add(&MoveTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
Creator: t.GetCreator(),
|
||||
},
|
||||
srcStorage: srcStorage,
|
||||
dstStorage: dstStorage,
|
||||
SrcObjPath: srcSubObjPath,
|
||||
DstDirPath: dstObjPath,
|
||||
SrcStorageMp: srcStorage.GetStorage().MountPath,
|
||||
DstStorageMp: dstStorage.GetStorage().MountPath,
|
||||
})
|
||||
}
|
||||
|
||||
t.Status = "cleaning up source directory"
|
||||
err = op.Remove(t.Ctx(), srcStorage, srcObjPath)
|
||||
if err != nil {
|
||||
t.Status = "completed (source directory cleanup pending)"
|
||||
} else {
|
||||
t.Status = "completed"
|
||||
}
|
||||
return nil
|
||||
} else {
|
||||
return moveFileBetween2Storages(t, srcStorage, dstStorage, srcObjPath, dstDirPath)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
func moveFileBetween2Storages(tsk *MoveTask, srcStorage, dstStorage driver.Driver, srcFilePath, dstDirPath string) error {
|
||||
tsk.Status = "copying file to destination"
|
||||
|
||||
copyTask := &CopyTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
Creator: tsk.GetCreator(),
|
||||
},
|
||||
srcStorage: srcStorage,
|
||||
dstStorage: dstStorage,
|
||||
SrcObjPath: srcFilePath,
|
||||
DstDirPath: dstDirPath,
|
||||
SrcStorageMp: srcStorage.GetStorage().MountPath,
|
||||
DstStorageMp: dstStorage.GetStorage().MountPath,
|
||||
}
|
||||
|
||||
|
||||
copyTask.SetCtx(tsk.Ctx())
|
||||
|
||||
|
||||
err := copyBetween2Storages(copyTask, srcStorage, dstStorage, srcFilePath, dstDirPath)
|
||||
if err != nil {
|
||||
// Check if this is an upload-related error and provide a clearer message
|
||||
if errors.Is(err, errs.UploadNotSupported) {
|
||||
return errors.WithMessagef(err, "destination storage [%s] does not support file uploads", dstStorage.GetStorage().MountPath)
|
||||
}
|
||||
return errors.WithMessagef(err, "failed to copy [%s] to destination storage [%s]", srcFilePath, dstStorage.GetStorage().MountPath)
|
||||
}
|
||||
|
||||
tsk.SetProgress(50)
|
||||
|
||||
tsk.Status = "deleting source file"
|
||||
err = op.Remove(tsk.Ctx(), srcStorage, srcFilePath)
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed to delete src [%s] file from storage [%s] after successful copy", srcFilePath, srcStorage.GetStorage().MountPath)
|
||||
}
|
||||
|
||||
tsk.SetProgress(100)
|
||||
tsk.Status = "completed"
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func _move(ctx context.Context, srcObjPath, dstDirPath string, lazyCache ...bool) (task.TaskExtensionInfo, error) {
|
||||
srcStorage, srcObjActualPath, err := op.GetStorageAndActualPath(srcObjPath)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed get src storage")
|
||||
}
|
||||
dstStorage, dstDirActualPath, err := op.GetStorageAndActualPath(dstDirPath)
|
||||
if err != nil {
|
||||
return nil, errors.WithMessage(err, "failed get dst storage")
|
||||
}
|
||||
|
||||
if srcStorage.GetStorage() == dstStorage.GetStorage() {
|
||||
err = op.Move(ctx, srcStorage, srcObjActualPath, dstDirActualPath, lazyCache...)
|
||||
if !errors.Is(err, errs.NotImplement) && !errors.Is(err, errs.NotSupport) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
taskCreator, _ := ctx.Value("user").(*model.User)
|
||||
t := &MoveTask{
|
||||
TaskExtension: task.TaskExtension{
|
||||
Creator: taskCreator,
|
||||
},
|
||||
srcStorage: srcStorage,
|
||||
dstStorage: dstStorage,
|
||||
SrcObjPath: srcObjActualPath,
|
||||
DstDirPath: dstDirActualPath,
|
||||
SrcStorageMp: srcStorage.GetStorage().MountPath,
|
||||
DstStorageMp: dstStorage.GetStorage().MountPath,
|
||||
}
|
||||
MoveTaskManager.Add(t)
|
||||
return t, nil
|
||||
}
|
||||
@@ -177,5 +177,5 @@ func (u *User) WebAuthnCredentials() []webauthn.Credential {
|
||||
}
|
||||
|
||||
func (u *User) WebAuthnIcon() string {
|
||||
return "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/OpenList.svg"
|
||||
return "https://cdn.oplist.org/gh/OpenListTeam/Logo@main/logo.svg"
|
||||
}
|
||||
|
||||
@@ -165,6 +165,10 @@ func (d *downloader) download() (io.ReadCloser, error) {
|
||||
if maxPart < d.cfg.Concurrency {
|
||||
d.cfg.Concurrency = maxPart
|
||||
}
|
||||
if d.params.Range.Length == 0 {
|
||||
d.cfg.Concurrency = 1
|
||||
}
|
||||
|
||||
log.Debugf("cfgConcurrency:%d", d.cfg.Concurrency)
|
||||
|
||||
if d.cfg.Concurrency == 1 {
|
||||
@@ -353,7 +357,7 @@ func (d *downloader) downloadChunk(ch *chunk) error {
|
||||
if e, ok := err.(*errNeedRetry); ok {
|
||||
err = e.Unwrap()
|
||||
if n > 0 {
|
||||
// 测试:下载时 断开 alist向云盘发起的下载连接
|
||||
// 测试:下载时 断开openlist向云盘发起的下载连接
|
||||
// 校验:下载完后校验文件哈希值 一致
|
||||
d.incrWritten(n)
|
||||
ch.start += n
|
||||
|
||||
@@ -41,7 +41,7 @@ func WriteProgress(progress *model.IndexProgress) {
|
||||
func updateIgnorePaths(customIgnorePaths string) {
|
||||
storages := op.GetAllStorages()
|
||||
ignorePaths := make([]string, 0)
|
||||
var skipDrivers = []string{"AList V2", "OpenList", "Virtual"}
|
||||
var skipDrivers = []string{"OpenList", "Virtual"}
|
||||
v3Visited := make(map[string]bool)
|
||||
for _, storage := range storages {
|
||||
if utils.SliceContains(skipDrivers, storage.Config().Name) {
|
||||
@@ -87,7 +87,7 @@ func init() {
|
||||
return nil
|
||||
})
|
||||
op.RegisterStorageHook(func(typ string, storage driver.Driver) {
|
||||
var skipDrivers = []string{"AList V2", "OpenList", "Virtual"}
|
||||
var skipDrivers = []string{"OpenList", "Virtual"}
|
||||
if utils.SliceContains(skipDrivers, storage.Config().Name) {
|
||||
updateIgnorePaths(setting.GetStr(conf.IgnorePaths))
|
||||
}
|
||||
|
||||
@@ -96,14 +96,24 @@ func FsMove(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
}
|
||||
var addedTasks []task.TaskExtensionInfo
|
||||
for i, name := range req.Names {
|
||||
err := fs.Move(c, stdpath.Join(srcDir, name), dstDir, len(req.Names) > i+1)
|
||||
t, err := fs.MoveWithTask(c, stdpath.Join(srcDir, name), dstDir, len(req.Names) > i+1)
|
||||
if t != nil {
|
||||
addedTasks = append(addedTasks, t)
|
||||
}
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
}
|
||||
common.SuccessResp(c)
|
||||
if len(addedTasks) > 0 {
|
||||
common.SuccessResp(c, gin.H{
|
||||
"tasks": getTaskInfos(addedTasks),
|
||||
})
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func FsCopy(c *gin.Context) {
|
||||
|
||||
@@ -51,6 +51,9 @@ func FsStream(c *gin.Context) {
|
||||
}
|
||||
dir, name := stdpath.Split(path)
|
||||
sizeStr := c.GetHeader("Content-Length")
|
||||
if sizeStr == "" {
|
||||
sizeStr = "0"
|
||||
}
|
||||
size, err := strconv.ParseInt(sizeStr, 10, 64)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
|
||||
@@ -219,6 +219,7 @@ func taskRoute[T task.TaskExtensionInfo](g *gin.RouterGroup, manager task.Manage
|
||||
func SetupTaskRoute(g *gin.RouterGroup) {
|
||||
taskRoute(g.Group("/upload"), fs.UploadTaskManager)
|
||||
taskRoute(g.Group("/copy"), fs.CopyTaskManager)
|
||||
taskRoute(g.Group("/move"), fs.MoveTaskManager)
|
||||
taskRoute(g.Group("/offline_download"), tool.DownloadTaskManager)
|
||||
taskRoute(g.Group("/offline_download_transfer"), tool.TransferTaskManager)
|
||||
taskRoute(g.Group("/decompress"), fs.ArchiveDownloadTaskManager)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||
// Package s3 implements a fake s3 server for alist
|
||||
// Package s3 implements a fake s3 server for openlist
|
||||
package s3
|
||||
|
||||
import (
|
||||
@@ -21,7 +21,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/internal/stream"
|
||||
"github.com/OpenListTeam/OpenList/pkg/http_range"
|
||||
"github.com/OpenListTeam/OpenList/pkg/utils"
|
||||
"github.com/alist-org/gofakes3"
|
||||
"github.com/OpenListTeam/gofakes3"
|
||||
"github.com/ncw/swift/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||
// Package s3 implements a fake s3 server for alist
|
||||
// Package s3 implements a fake s3 server for openlist
|
||||
package s3
|
||||
|
||||
import "io"
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||
// Package s3 implements a fake s3 server for alist
|
||||
// Package s3 implements a fake s3 server for openlist
|
||||
package s3
|
||||
|
||||
import (
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/gofakes3"
|
||||
"github.com/OpenListTeam/gofakes3"
|
||||
)
|
||||
|
||||
func (b *s3Backend) entryListR(bucket, fdPath, name string, addPrefix bool, response *gofakes3.ObjectList) error {
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||
// Package s3 implements a fake s3 server for alist
|
||||
// Package s3 implements a fake s3 server for openlist
|
||||
package s3
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/OpenListTeam/OpenList/pkg/utils"
|
||||
"github.com/alist-org/gofakes3"
|
||||
"github.com/OpenListTeam/gofakes3"
|
||||
)
|
||||
|
||||
// logger output formatted message
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||
// Package s3 implements a fake s3 server for alist
|
||||
// Package s3 implements a fake s3 server for openlist
|
||||
package s3
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/alist-org/gofakes3"
|
||||
"github.com/OpenListTeam/gofakes3"
|
||||
)
|
||||
|
||||
// pager splits the object list into smulitply pages.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||
// Package s3 implements a fake s3 server for alist
|
||||
// Package s3 implements a fake s3 server for openlist
|
||||
package s3
|
||||
|
||||
import (
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"math/rand"
|
||||
"net/http"
|
||||
|
||||
"github.com/alist-org/gofakes3"
|
||||
"github.com/OpenListTeam/gofakes3"
|
||||
)
|
||||
|
||||
// Make a new S3 Server to serve the remote
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// Credits: https://pkg.go.dev/github.com/rclone/rclone@v1.65.2/cmd/serve/s3
|
||||
// Package s3 implements a fake s3 server for alist
|
||||
// Package s3 implements a fake s3 server for openlist
|
||||
package s3
|
||||
|
||||
import (
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/OpenListTeam/OpenList/internal/model"
|
||||
"github.com/OpenListTeam/OpenList/internal/op"
|
||||
"github.com/OpenListTeam/OpenList/internal/setting"
|
||||
"github.com/alist-org/gofakes3"
|
||||
"github.com/OpenListTeam/gofakes3"
|
||||
)
|
||||
|
||||
type Bucket struct {
|
||||
|
||||
Reference in New Issue
Block a user