mirror of
https://github.com/AlistGo/alist.git
synced 2025-11-25 11:29:45 +08:00
Compare commits
1 Commits
fix-aliyun
...
permission
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0e1604300e |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -10,4 +10,4 @@ liberapay: # Replace with a single Liberapay username
|
||||
issuehunt: # Replace with a single IssueHunt username
|
||||
otechie: # Replace with a single Otechie username
|
||||
lfx_crowdfunding: # Replace with a single LFX Crowdfunding project-name e.g., cloud-foundry
|
||||
custom: ['https://alistgo.com/guide/sponsor.html']
|
||||
custom: ['https://alist.nn.ci/guide/sponsor.html']
|
||||
|
||||
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
8
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -16,14 +16,14 @@ body:
|
||||
您必须勾选以下所有内容,否则您的issue可能会被直接关闭。或者您可以去[讨论区](https://github.com/alist-org/alist/discussions)
|
||||
options:
|
||||
- label: |
|
||||
I have read the [documentation](https://alistgo.com).
|
||||
我已经阅读了[文档](https://alistgo.com)。
|
||||
I have read the [documentation](https://alist.nn.ci).
|
||||
我已经阅读了[文档](https://alist.nn.ci)。
|
||||
- label: |
|
||||
I'm sure there are no duplicate issues or discussions.
|
||||
我确定没有重复的issue或讨论。
|
||||
- label: |
|
||||
I'm sure it's due to `AList` and not something else(such as [Network](https://alistgo.com/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 `Operational`).
|
||||
我确定是`AList`的问题,而不是其他原因(例如[网络](https://alistgo.com/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),`依赖`或`操作`)。
|
||||
I'm sure it's due to `AList` and not something else(such as [Network](https://alist.nn.ci/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 `Operational`).
|
||||
我确定是`AList`的问题,而不是其他原因(例如[网络](https://alist.nn.ci/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),`依赖`或`操作`)。
|
||||
- label: |
|
||||
I'm sure this issue is not fixed in the latest version.
|
||||
我确定这个问题在最新版本中没有被修复。
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -7,7 +7,7 @@ body:
|
||||
label: Please make sure of the following things
|
||||
description: You may select more than one, even select all.
|
||||
options:
|
||||
- label: I have read the [documentation](https://alistgo.com).
|
||||
- label: I have read the [documentation](https://alist.nn.ci).
|
||||
- label: I'm sure there are no duplicate issues or discussions.
|
||||
- label: I'm sure this feature is not implemented.
|
||||
- label: I'm sure it's a reasonable and popular requirement.
|
||||
|
||||
4
.github/workflows/beta_release.yml
vendored
4
.github/workflows/beta_release.yml
vendored
@@ -119,7 +119,7 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: AlistGo/desktop-release
|
||||
repository: alist-org/desktop-release
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -135,4 +135,4 @@ jobs:
|
||||
with:
|
||||
github_token: ${{ secrets.MY_TOKEN }}
|
||||
branch: main
|
||||
repository: AlistGo/desktop-release
|
||||
repository: alist-org/desktop-release
|
||||
4
.github/workflows/release.yml
vendored
4
.github/workflows/release.yml
vendored
@@ -72,7 +72,7 @@ jobs:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
repository: AlistGo/desktop-release
|
||||
repository: alist-org/desktop-release
|
||||
ref: main
|
||||
persist-credentials: false
|
||||
fetch-depth: 0
|
||||
@@ -89,4 +89,4 @@ jobs:
|
||||
with:
|
||||
github_token: ${{ secrets.MY_TOKEN }}
|
||||
branch: main
|
||||
repository: AlistGo/desktop-release
|
||||
repository: alist-org/desktop-release
|
||||
13
.github/workflows/release_docker.yml
vendored
13
.github/workflows/release_docker.yml
vendored
@@ -18,7 +18,6 @@ env:
|
||||
REGISTRY: 'xhofe/alist'
|
||||
REGISTRY_USERNAME: 'xhofe'
|
||||
REGISTRY_PASSWORD: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||
GITHUB_CR_REPO: ghcr.io/${{ github.repository }}
|
||||
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' }}
|
||||
@@ -115,21 +114,11 @@ jobs:
|
||||
username: ${{ env.REGISTRY_USERNAME }}
|
||||
password: ${{ env.REGISTRY_PASSWORD }}
|
||||
|
||||
- name: Login to GHCR
|
||||
uses: docker/login-action@v3
|
||||
with:
|
||||
logout: true
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@v5
|
||||
with:
|
||||
images: |
|
||||
${{ env.REGISTRY }}
|
||||
${{ env.GITHUB_CR_REPO }}
|
||||
images: ${{ env.REGISTRY }}
|
||||
tags: ${{ env.IMAGE_IS_PROD == 'true' && '' || env.IMAGE_TAGS_BETA }}
|
||||
flavor: |
|
||||
${{ env.IMAGE_IS_PROD == 'true' && 'latest=true' || '' }}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<a href="https://alistgo.com"><img width="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<a href="https://alist.nn.ci"><img width="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<p><em>🗂️A file list program that supports multiple storages, powered by Gin and Solidjs.</em></p>
|
||||
<div>
|
||||
<a href="https://goreportcard.com/report/github.com/alist-org/alist/v3">
|
||||
@@ -31,7 +31,7 @@
|
||||
<a href="https://hub.docker.com/r/xhofe/alist">
|
||||
<img src="https://img.shields.io/docker/pulls/xhofe/alist?color=%2348BB78&logo=docker&label=pulls" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://alistgo.com/guide/sponsor.html">
|
||||
<a href="https://alist.nn.ci/guide/sponsor.html">
|
||||
<img src="https://img.shields.io/badge/%24-sponsor-F87171.svg" alt="sponsor" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -88,7 +88,7 @@ English | [中文](./README_cn.md) | [日本語](./README_ja.md) | [Contributing
|
||||
- [x] Dark mode
|
||||
- [x] I18n
|
||||
- [x] Protected routes (password protection and authentication)
|
||||
- [x] WebDav (see https://alistgo.com/guide/webdav.html for details)
|
||||
- [x] WebDav (see https://alist.nn.ci/guide/webdav.html for details)
|
||||
- [x] [Docker Deploy](https://hub.docker.com/r/xhofe/alist)
|
||||
- [x] Cloudflare Workers proxy
|
||||
- [x] File/Folder package download
|
||||
@@ -112,7 +112,7 @@ Please go to our [discussion forum](https://github.com/alist-org/alist/discussio
|
||||
## Sponsor
|
||||
|
||||
AList is an open-source software, if you happen to like this project and want me to keep going, please consider sponsoring me or providing a single donation! Thanks for all the love and support:
|
||||
https://alistgo.com/guide/sponsor.html
|
||||
https://alist.nn.ci/guide/sponsor.html
|
||||
|
||||
### Special sponsors
|
||||
|
||||
|
||||
10
README_cn.md
10
README_cn.md
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<a href="https://alistgo.com"><img width="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<a href="https://alist.nn.ci"><img width="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<p><em>🗂一个支持多存储的文件列表程序,使用 Gin 和 Solidjs。</em></p>
|
||||
<div>
|
||||
<a href="https://goreportcard.com/report/github.com/alist-org/alist/v3">
|
||||
@@ -31,7 +31,7 @@
|
||||
<a href="https://hub.docker.com/r/xhofe/alist">
|
||||
<img src="https://img.shields.io/docker/pulls/xhofe/alist?color=%2348BB78&logo=docker&label=pulls" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://alistgo.com/zh/guide/sponsor.html">
|
||||
<a href="https://alist.nn.ci/zh/guide/sponsor.html">
|
||||
<img src="https://img.shields.io/badge/%24-sponsor-F87171.svg" alt="sponsor" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -86,7 +86,7 @@
|
||||
- [x] 黑暗模式
|
||||
- [x] 国际化
|
||||
- [x] 受保护的路由(密码保护和身份验证)
|
||||
- [x] WebDav (具体见 https://alistgo.com/zh/guide/webdav.html)
|
||||
- [x] WebDav (具体见 https://alist.nn.ci/zh/guide/webdav.html)
|
||||
- [x] [Docker 部署](https://hub.docker.com/r/xhofe/alist)
|
||||
- [x] Cloudflare workers 中转
|
||||
- [x] 文件/文件夹打包下载
|
||||
@@ -97,7 +97,7 @@
|
||||
|
||||
## 文档
|
||||
|
||||
<https://alistgo.com/zh/>
|
||||
<https://alist.nn.ci/zh/>
|
||||
|
||||
## Demo
|
||||
|
||||
@@ -109,7 +109,7 @@
|
||||
|
||||
## 赞助
|
||||
|
||||
AList 是一个开源软件,如果你碰巧喜欢这个项目,并希望我继续下去,请考虑赞助我或提供一个单一的捐款!感谢所有的爱和支持:https://alistgo.com/zh/guide/sponsor.html
|
||||
AList 是一个开源软件,如果你碰巧喜欢这个项目,并希望我继续下去,请考虑赞助我或提供一个单一的捐款!感谢所有的爱和支持:https://alist.nn.ci/zh/guide/sponsor.html
|
||||
|
||||
### 特别赞助
|
||||
|
||||
|
||||
10
README_ja.md
10
README_ja.md
@@ -1,5 +1,5 @@
|
||||
<div align="center">
|
||||
<a href="https://alistgo.com"><img width="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<a href="https://alist.nn.ci"><img width="100px" alt="logo" src="https://cdn.jsdelivr.net/gh/alist-org/logo@main/logo.svg"/></a>
|
||||
<p><em>🗂️Gin と Solidjs による、複数のストレージをサポートするファイルリストプログラム。</em></p>
|
||||
<div>
|
||||
<a href="https://goreportcard.com/report/github.com/alist-org/alist/v3">
|
||||
@@ -31,7 +31,7 @@
|
||||
<a href="https://hub.docker.com/r/xhofe/alist">
|
||||
<img src="https://img.shields.io/docker/pulls/xhofe/alist?color=%2348BB78&logo=docker&label=pulls" alt="Downloads" />
|
||||
</a>
|
||||
<a href="https://alistgo.com/guide/sponsor.html">
|
||||
<a href="https://alist.nn.ci/guide/sponsor.html">
|
||||
<img src="https://img.shields.io/badge/%24-sponsor-F87171.svg" alt="sponsor" />
|
||||
</a>
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@
|
||||
- [x] ダークモード
|
||||
- [x] 国際化
|
||||
- [x] 保護されたルート (パスワード保護と認証)
|
||||
- [x] WebDav (詳細は https://alistgo.com/guide/webdav.html を参照)
|
||||
- [x] WebDav (詳細は https://alist.nn.ci/guide/webdav.html を参照)
|
||||
- [x] [Docker デプロイ](https://hub.docker.com/r/xhofe/alist)
|
||||
- [x] Cloudflare ワーカープロキシ
|
||||
- [x] ファイル/フォルダパッケージのダウンロード
|
||||
@@ -98,7 +98,7 @@
|
||||
|
||||
## ドキュメント
|
||||
|
||||
<https://alistgo.com/>
|
||||
<https://alist.nn.ci/>
|
||||
|
||||
## デモ
|
||||
|
||||
@@ -111,7 +111,7 @@
|
||||
## スポンサー
|
||||
|
||||
AList はオープンソースのソフトウェアです。もしあなたがこのプロジェクトを気に入ってくださり、続けて欲しいと思ってくださるなら、ぜひスポンサーになってくださるか、1口でも寄付をしてくださるようご検討ください!すべての愛とサポートに感謝します:
|
||||
https://alistgo.com/guide/sponsor.html
|
||||
https://alist.nn.ci/guide/sponsor.html
|
||||
|
||||
### スペシャルスポンサー
|
||||
|
||||
|
||||
4
build.sh
4
build.sh
@@ -93,7 +93,7 @@ BuildDocker() {
|
||||
|
||||
PrepareBuildDockerMusl() {
|
||||
mkdir -p build/musl-libs
|
||||
BASE="https://github.com/go-cross/musl-toolchain-archive/releases/latest/download/"
|
||||
BASE="https://musl.cc/"
|
||||
FILES=(x86_64-linux-musl-cross aarch64-linux-musl-cross i486-linux-musl-cross s390x-linux-musl-cross armv6-linux-musleabihf-cross armv7l-linux-musleabihf-cross riscv64-linux-musl-cross powerpc64le-linux-musl-cross)
|
||||
for i in "${FILES[@]}"; do
|
||||
url="${BASE}${i}.tgz"
|
||||
@@ -245,7 +245,7 @@ BuildReleaseFreeBSD() {
|
||||
cgo_cc="clang --target=${CGO_ARGS[$i]} --sysroot=/opt/freebsd/${os_arch}"
|
||||
echo building for freebsd-${os_arch}
|
||||
sudo mkdir -p "/opt/freebsd/${os_arch}"
|
||||
wget -q https://download.freebsd.org/releases/${os_arch}/14.3-RELEASE/base.txz
|
||||
wget -q https://download.freebsd.org/releases/${os_arch}/14.1-RELEASE/base.txz
|
||||
sudo tar -xf ./base.txz -C /opt/freebsd/${os_arch}
|
||||
rm base.txz
|
||||
export GOOS=freebsd
|
||||
|
||||
@@ -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 Xhofe and friends in Go/Solid.js.
|
||||
Complete documentation is available at https://alistgo.com/`,
|
||||
Complete documentation is available at https://alist.nn.ci/`,
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
@@ -27,7 +27,7 @@ func Execute() {
|
||||
}
|
||||
|
||||
func init() {
|
||||
RootCmd.PersistentFlags().StringVar(&flags.DataDir, "data", "data", "data folder")
|
||||
RootCmd.PersistentFlags().StringVar(&flags.DataDir, "data", "/data", "data folder")
|
||||
RootCmd.PersistentFlags().BoolVar(&flags.Debug, "debug", false, "start with debug mode")
|
||||
RootCmd.PersistentFlags().BoolVar(&flags.NoPrefix, "no-prefix", false, "disable env prefix")
|
||||
RootCmd.PersistentFlags().BoolVar(&flags.Dev, "dev", false, "start with dev mode")
|
||||
|
||||
@@ -163,10 +163,10 @@ func (d *Pan123) login() error {
|
||||
SetHeaders(map[string]string{
|
||||
"origin": "https://www.123pan.com",
|
||||
"referer": "https://www.123pan.com/",
|
||||
//"user-agent": "Dart/2.19(dart:io)-alist",
|
||||
"user-agent": "Dart/2.19(dart:io)-alist",
|
||||
"platform": "web",
|
||||
"app-version": "3",
|
||||
"user-agent": base.UserAgent,
|
||||
//"user-agent": base.UserAgent,
|
||||
}).
|
||||
SetBody(body).Post(SignIn)
|
||||
if err != nil {
|
||||
@@ -202,7 +202,7 @@ do:
|
||||
"origin": "https://www.123pan.com",
|
||||
"referer": "https://www.123pan.com/",
|
||||
"authorization": "Bearer " + d.AccessToken,
|
||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
|
||||
"user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) alist-client",
|
||||
"platform": "web",
|
||||
"app-version": "3",
|
||||
//"user-agent": base.UserAgent,
|
||||
|
||||
@@ -324,7 +324,7 @@ func (y *Cloud189PC) login() (err error) {
|
||||
_, err = y.client.R().
|
||||
SetResult(&tokenInfo).SetError(&erron).
|
||||
SetQueryParams(clientSuffix()).
|
||||
SetQueryParam("redirectURL", loginresp.ToUrl).
|
||||
SetQueryParam("redirectURL", url.QueryEscape(loginresp.ToUrl)).
|
||||
Post(API_URL + "/getSessionForPC.action")
|
||||
if err != nil {
|
||||
return
|
||||
|
||||
@@ -55,7 +55,7 @@ func (d *AliDrive) Init(ctx context.Context) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.DriveId = d.Addition.DeviceID
|
||||
d.DriveId = utils.Json.Get(res, "default_drive_id").ToString()
|
||||
d.UserID = utils.Json.Get(res, "user_id").ToString()
|
||||
d.cron = cron.NewCron(time.Hour * 2)
|
||||
d.cron.Do(func() {
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
type Addition struct {
|
||||
driver.RootID
|
||||
RefreshToken string `json:"refresh_token" required:"true"`
|
||||
DeviceID string `json:"device_id" required:"true"`
|
||||
//DeviceID string `json:"device_id" required:"true"`
|
||||
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"ASC,DESC"`
|
||||
RapidUpload bool `json:"rapid_upload"`
|
||||
|
||||
@@ -22,7 +22,6 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/baidu_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/chaoxing"
|
||||
_ "github.com/alist-org/alist/v3/drivers/cloudreve"
|
||||
_ "github.com/alist-org/alist/v3/drivers/cloudreve_v4"
|
||||
_ "github.com/alist-org/alist/v3/drivers/crypt"
|
||||
_ "github.com/alist-org/alist/v3/drivers/doubao"
|
||||
_ "github.com/alist-org/alist/v3/drivers/doubao_share"
|
||||
|
||||
@@ -18,7 +18,6 @@ import (
|
||||
type Cloudreve struct {
|
||||
model.Storage
|
||||
Addition
|
||||
ref *Cloudreve
|
||||
}
|
||||
|
||||
func (d *Cloudreve) Config() driver.Config {
|
||||
@@ -38,18 +37,8 @@ func (d *Cloudreve) Init(ctx context.Context) error {
|
||||
return d.login()
|
||||
}
|
||||
|
||||
func (d *Cloudreve) InitReference(storage driver.Driver) error {
|
||||
refStorage, ok := storage.(*Cloudreve)
|
||||
if ok {
|
||||
d.ref = refStorage
|
||||
return nil
|
||||
}
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Cloudreve) Drop(ctx context.Context) error {
|
||||
d.Cookie = ""
|
||||
d.ref = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,12 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
@@ -21,6 +19,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/pkg/cookie"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
json "github.com/json-iterator/go"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
@@ -36,9 +35,6 @@ func (d *Cloudreve) getUA() string {
|
||||
}
|
||||
|
||||
func (d *Cloudreve) request(method string, path string, callback base.ReqCallback, out interface{}) error {
|
||||
if d.ref != nil {
|
||||
return d.ref.request(method, path, callback, out)
|
||||
}
|
||||
u := d.Address + "/api/v3" + path
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
@@ -83,11 +79,11 @@ func (d *Cloudreve) request(method string, path string, callback base.ReqCallbac
|
||||
}
|
||||
if out != nil && r.Data != nil {
|
||||
var marshal []byte
|
||||
marshal, err = jsoniter.Marshal(r.Data)
|
||||
marshal, err = json.Marshal(r.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = jsoniter.Unmarshal(marshal, out)
|
||||
err = json.Unmarshal(marshal, out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -191,9 +187,12 @@ func (d *Cloudreve) upLocal(ctx context.Context, stream model.FileStreamer, u Up
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
utils.Log.Debugf("[Cloudreve-Local] upload: %d", finish)
|
||||
var byteSize = DEFAULT
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Cloudreve-Local] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
if left < DEFAULT {
|
||||
byteSize = left
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
@@ -206,26 +205,9 @@ func (d *Cloudreve) upLocal(ctx context.Context, stream model.FileStreamer, u Up
|
||||
req.SetHeader("Content-Length", strconv.FormatInt(byteSize, 10))
|
||||
req.SetHeader("User-Agent", d.getUA())
|
||||
req.SetBody(driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
req.AddRetryCondition(func(r *resty.Response, err error) bool {
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if r.IsError() {
|
||||
return true
|
||||
}
|
||||
var retryResp Resp
|
||||
jErr := base.RestyClient.JSONUnmarshal(r.Body(), &retryResp)
|
||||
if jErr != nil {
|
||||
return true
|
||||
}
|
||||
if retryResp.Code != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
break
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
@@ -240,15 +222,16 @@ 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()
|
||||
}
|
||||
utils.Log.Debugf("[Cloudreve-Remote] upload: %d", finish)
|
||||
var byteSize = DEFAULT
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Cloudreve-Remote] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
if left < DEFAULT {
|
||||
byteSize = left
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
@@ -265,43 +248,14 @@ func (d *Cloudreve) upRemote(ctx context.Context, stream model.FileStreamer, u U
|
||||
// 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 {
|
||||
finish += byteSize
|
||||
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
|
||||
_ = res.Body.Close()
|
||||
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)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -310,15 +264,16 @@ 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()
|
||||
}
|
||||
utils.Log.Debugf("[Cloudreve-OneDrive] upload: %d", finish)
|
||||
var byteSize = DEFAULT
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Cloudreve-OneDrive] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
if left < DEFAULT {
|
||||
byteSize = left
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
@@ -340,31 +295,22 @@ func (d *Cloudreve) upOneDrive(ctx context.Context, stream model.FileStreamer, u
|
||||
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:
|
||||
if res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200 {
|
||||
data, _ := io.ReadAll(res.Body)
|
||||
res.Body.Close()
|
||||
_ = res.Body.Close()
|
||||
return errors.New(string(data))
|
||||
default:
|
||||
res.Body.Close()
|
||||
retryCount = 0
|
||||
finish += byteSize
|
||||
}
|
||||
_ = res.Body.Close()
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
}
|
||||
// 上传成功发送回调请求
|
||||
return d.request(http.MethodPost, "/callback/onedrive/finish/"+u.SessionID, func(req *resty.Request) {
|
||||
err := d.request(http.MethodPost, "/callback/onedrive/finish/"+u.SessionID, func(req *resty.Request) {
|
||||
req.SetBody("{}")
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Cloudreve) upS3(ctx context.Context, stream model.FileStreamer, u UploadInfo, up driver.UpdateProgress) error {
|
||||
@@ -372,15 +318,16 @@ 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()
|
||||
}
|
||||
utils.Log.Debugf("[Cloudreve-S3] upload: %d", finish)
|
||||
var byteSize = DEFAULT
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Cloudreve-S3] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
if left < DEFAULT {
|
||||
byteSize = left
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
@@ -399,27 +346,11 @@ func (d *Cloudreve) upS3(ctx context.Context, stream model.FileStreamer, u Uploa
|
||||
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("faild to get ETag from header")
|
||||
default:
|
||||
retryCount = 0
|
||||
etags = append(etags, etag)
|
||||
finish += byteSize
|
||||
_ = res.Body.Close()
|
||||
etags = append(etags, res.Header.Get("ETag"))
|
||||
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
|
||||
|
||||
@@ -1,305 +0,0 @@
|
||||
package cloudreve_v4
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
)
|
||||
|
||||
type CloudreveV4 struct {
|
||||
model.Storage
|
||||
Addition
|
||||
ref *CloudreveV4
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Config() driver.Config {
|
||||
if d.ref != nil {
|
||||
return d.ref.Config()
|
||||
}
|
||||
if d.EnableVersionUpload {
|
||||
config.NoOverwriteUpload = false
|
||||
}
|
||||
return config
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Init(ctx context.Context) error {
|
||||
// removing trailing slash
|
||||
d.Address = strings.TrimSuffix(d.Address, "/")
|
||||
op.MustSaveDriverStorage(d)
|
||||
if d.ref != nil {
|
||||
return nil
|
||||
}
|
||||
if d.AccessToken == "" && d.RefreshToken != "" {
|
||||
return d.refreshToken()
|
||||
}
|
||||
if d.Username != "" {
|
||||
return d.login()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) InitReference(storage driver.Driver) error {
|
||||
refStorage, ok := storage.(*CloudreveV4)
|
||||
if ok {
|
||||
d.ref = refStorage
|
||||
return nil
|
||||
}
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Drop(ctx context.Context) error {
|
||||
d.ref = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
const pageSize int = 100
|
||||
var f []File
|
||||
var r FileResp
|
||||
params := map[string]string{
|
||||
"page_size": strconv.Itoa(pageSize),
|
||||
"uri": dir.GetPath(),
|
||||
"order_by": d.OrderBy,
|
||||
"order_direction": d.OrderDirection,
|
||||
"page": "0",
|
||||
}
|
||||
|
||||
for {
|
||||
err := d.request(http.MethodGet, "/file", func(req *resty.Request) {
|
||||
req.SetQueryParams(params)
|
||||
}, &r)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f = append(f, r.Files...)
|
||||
if r.Pagination.NextToken == "" || len(r.Files) < pageSize {
|
||||
break
|
||||
}
|
||||
params["next_page_token"] = r.Pagination.NextToken
|
||||
}
|
||||
|
||||
return utils.SliceConvert(f, func(src File) (model.Obj, error) {
|
||||
if d.EnableFolderSize && src.Type == 1 {
|
||||
var ds FolderSummaryResp
|
||||
err := d.request(http.MethodGet, "/file/info", func(req *resty.Request) {
|
||||
req.SetQueryParam("uri", src.Path)
|
||||
req.SetQueryParam("folder_summary", "true")
|
||||
}, &ds)
|
||||
if err == nil && ds.FolderSummary.Size > 0 {
|
||||
src.Size = ds.FolderSummary.Size
|
||||
}
|
||||
}
|
||||
var thumb model.Thumbnail
|
||||
if d.EnableThumb && src.Type == 0 {
|
||||
var t FileThumbResp
|
||||
err := d.request(http.MethodGet, "/file/thumb", func(req *resty.Request) {
|
||||
req.SetQueryParam("uri", src.Path)
|
||||
}, &t)
|
||||
if err == nil && t.URL != "" {
|
||||
thumb = model.Thumbnail{
|
||||
Thumbnail: t.URL,
|
||||
}
|
||||
}
|
||||
}
|
||||
return &model.ObjThumb{
|
||||
Object: model.Object{
|
||||
ID: src.ID,
|
||||
Path: src.Path,
|
||||
Name: src.Name,
|
||||
Size: src.Size,
|
||||
Modified: src.UpdatedAt,
|
||||
Ctime: src.CreatedAt,
|
||||
IsFolder: src.Type == 1,
|
||||
},
|
||||
Thumbnail: thumb,
|
||||
}, nil
|
||||
})
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
var url FileUrlResp
|
||||
err := d.request(http.MethodPost, "/file/url", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"uris": []string{file.GetPath()},
|
||||
"download": true,
|
||||
})
|
||||
}, &url)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if len(url.Urls) == 0 {
|
||||
return nil, errors.New("server returns no url")
|
||||
}
|
||||
exp := time.Until(url.Expires)
|
||||
return &model.Link{
|
||||
URL: url.Urls[0].URL,
|
||||
Expiration: &exp,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"type": "folder",
|
||||
"uri": parentDir.GetPath() + "/" + dirName,
|
||||
"error_on_conflict": true,
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return d.request(http.MethodPost, "/file/move", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"uris": []string{srcObj.GetPath()},
|
||||
"dst": dstDir.GetPath(),
|
||||
"copy": false,
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"new_name": newName,
|
||||
"uri": srcObj.GetPath(),
|
||||
})
|
||||
}, nil)
|
||||
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return d.request(http.MethodPost, "/file/move", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"uris": []string{srcObj.GetPath()},
|
||||
"dst": dstDir.GetPath(),
|
||||
"copy": true,
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Remove(ctx context.Context, obj model.Obj) error {
|
||||
return d.request(http.MethodDelete, "/file", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"uris": []string{obj.GetPath()},
|
||||
"unlink": false,
|
||||
"skip_soft_delete": true,
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) error {
|
||||
if file.GetSize() == 0 {
|
||||
// 空文件使用新建文件方法,避免上传卡锁
|
||||
return d.request(http.MethodPost, "/file/create", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"type": "file",
|
||||
"uri": dstDir.GetPath() + "/" + file.GetName(),
|
||||
"error_on_conflict": true,
|
||||
})
|
||||
}, nil)
|
||||
}
|
||||
var p StoragePolicy
|
||||
var r FileResp
|
||||
var u FileUploadResp
|
||||
var err error
|
||||
params := map[string]string{
|
||||
"page_size": "10",
|
||||
"uri": dstDir.GetPath(),
|
||||
"order_by": "created_at",
|
||||
"order_direction": "asc",
|
||||
"page": "0",
|
||||
}
|
||||
err = d.request(http.MethodGet, "/file", func(req *resty.Request) {
|
||||
req.SetQueryParams(params)
|
||||
}, &r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
p = r.StoragePolicy
|
||||
body := base.Json{
|
||||
"uri": dstDir.GetPath() + "/" + file.GetName(),
|
||||
"size": file.GetSize(),
|
||||
"policy_id": p.ID,
|
||||
"last_modified": file.ModTime().UnixMilli(),
|
||||
"mime_type": "",
|
||||
}
|
||||
if d.EnableVersionUpload {
|
||||
body["entity_type"] = "version"
|
||||
}
|
||||
err = d.request(http.MethodPut, "/file/upload", func(req *resty.Request) {
|
||||
req.SetBody(body)
|
||||
}, &u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if u.StoragePolicy.Relay {
|
||||
err = d.upLocal(ctx, file, u, up)
|
||||
} else {
|
||||
switch u.StoragePolicy.Type {
|
||||
case "local":
|
||||
err = d.upLocal(ctx, file, u, up)
|
||||
case "remote":
|
||||
err = d.upRemote(ctx, file, u, up)
|
||||
case "onedrive":
|
||||
err = d.upOneDrive(ctx, file, u, up)
|
||||
case "s3":
|
||||
err = d.upS3(ctx, file, u, up)
|
||||
default:
|
||||
return errs.NotImplement
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// 删除失败的会话
|
||||
_ = d.request(http.MethodDelete, "/file/upload", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"id": u.SessionID,
|
||||
"uri": u.URI,
|
||||
})
|
||||
}, nil)
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) 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
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) ListArchive(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) ([]model.Obj, error) {
|
||||
// TODO list args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) Extract(ctx context.Context, obj model.Obj, args model.ArchiveInnerArgs) (*model.Link, error) {
|
||||
// TODO return link of file args.InnerPath in the archive obj, return errs.NotImplement to use an internal archive tool, optional
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) ArchiveDecompress(ctx context.Context, srcObj, dstDir model.Obj, args model.ArchiveDecompressArgs) ([]model.Obj, error) {
|
||||
// TODO extract args.InnerPath path in the archive srcObj to the dstDir location, optional
|
||||
// a folder with the same name as the archive file needs to be created to store the extracted results if args.PutIntoNewDir
|
||||
// return errs.NotImplement to use an internal archive tool
|
||||
return nil, errs.NotImplement
|
||||
}
|
||||
|
||||
//func (d *CloudreveV4) Other(ctx context.Context, args model.OtherArgs) (interface{}, error) {
|
||||
// return nil, errs.NotSupport
|
||||
//}
|
||||
|
||||
var _ driver.Driver = (*CloudreveV4)(nil)
|
||||
@@ -1,44 +0,0 @@
|
||||
package cloudreve_v4
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
// Usually one of two
|
||||
driver.RootPath
|
||||
// driver.RootID
|
||||
// define other
|
||||
Address string `json:"address" required:"true"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
CustomUA string `json:"custom_ua"`
|
||||
EnableFolderSize bool `json:"enable_folder_size"`
|
||||
EnableThumb bool `json:"enable_thumb"`
|
||||
EnableVersionUpload bool `json:"enable_version_upload"`
|
||||
OrderBy string `json:"order_by" type:"select" options:"name,size,updated_at,created_at" default:"name" required:"true"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc" required:"true"`
|
||||
}
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &CloudreveV4{}
|
||||
})
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
package cloudreve_v4
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
type Object struct {
|
||||
model.Object
|
||||
StoragePolicy StoragePolicy
|
||||
}
|
||||
|
||||
type Resp struct {
|
||||
Code int `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
type BasicConfigResp struct {
|
||||
InstanceID string `json:"instance_id"`
|
||||
// Title string `json:"title"`
|
||||
// Themes string `json:"themes"`
|
||||
// DefaultTheme string `json:"default_theme"`
|
||||
User struct {
|
||||
ID string `json:"id"`
|
||||
// Nickname string `json:"nickname"`
|
||||
// CreatedAt time.Time `json:"created_at"`
|
||||
// Anonymous bool `json:"anonymous"`
|
||||
Group struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Permission string `json:"permission"`
|
||||
} `json:"group"`
|
||||
} `json:"user"`
|
||||
// Logo string `json:"logo"`
|
||||
// LogoLight string `json:"logo_light"`
|
||||
// CaptchaReCaptchaKey string `json:"captcha_ReCaptchaKey"`
|
||||
CaptchaType string `json:"captcha_type"` // support 'normal' only
|
||||
// AppPromotion bool `json:"app_promotion"`
|
||||
}
|
||||
|
||||
type SiteLoginConfigResp struct {
|
||||
LoginCaptcha bool `json:"login_captcha"`
|
||||
Authn bool `json:"authn"`
|
||||
}
|
||||
|
||||
type PrepareLoginResp struct {
|
||||
WebauthnEnabled bool `json:"webauthn_enabled"`
|
||||
PasswordEnabled bool `json:"password_enabled"`
|
||||
}
|
||||
|
||||
type CaptchaResp struct {
|
||||
Image string `json:"image"`
|
||||
Ticket string `json:"ticket"`
|
||||
}
|
||||
|
||||
type Token struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
AccessExpires time.Time `json:"access_expires"`
|
||||
RefreshExpires time.Time `json:"refresh_expires"`
|
||||
}
|
||||
|
||||
type TokenResponse struct {
|
||||
User struct {
|
||||
ID string `json:"id"`
|
||||
// Email string `json:"email"`
|
||||
// Nickname string `json:"nickname"`
|
||||
Status string `json:"status"`
|
||||
// CreatedAt time.Time `json:"created_at"`
|
||||
Group struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Permission string `json:"permission"`
|
||||
// DirectLinkBatchSize int `json:"direct_link_batch_size"`
|
||||
// TrashRetention int `json:"trash_retention"`
|
||||
} `json:"group"`
|
||||
// Language string `json:"language"`
|
||||
} `json:"user"`
|
||||
Token Token `json:"token"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
Type int `json:"type"` // 0: file, 1: folder
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
Size int64 `json:"size"`
|
||||
Metadata interface{} `json:"metadata"`
|
||||
Path string `json:"path"`
|
||||
Capability string `json:"capability"`
|
||||
Owned bool `json:"owned"`
|
||||
PrimaryEntity string `json:"primary_entity"`
|
||||
}
|
||||
|
||||
type StoragePolicy struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
MaxSize int64 `json:"max_size"`
|
||||
Relay bool `json:"relay,omitempty"`
|
||||
}
|
||||
|
||||
type Pagination struct {
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
IsCursor bool `json:"is_cursor"`
|
||||
NextToken string `json:"next_token,omitempty"`
|
||||
}
|
||||
|
||||
type Props struct {
|
||||
Capability string `json:"capability"`
|
||||
MaxPageSize int `json:"max_page_size"`
|
||||
OrderByOptions []string `json:"order_by_options"`
|
||||
OrderDirectionOptions []string `json:"order_direction_options"`
|
||||
}
|
||||
|
||||
type FileResp struct {
|
||||
Files []File `json:"files"`
|
||||
Parent File `json:"parent"`
|
||||
Pagination Pagination `json:"pagination"`
|
||||
Props Props `json:"props"`
|
||||
ContextHint string `json:"context_hint"`
|
||||
MixedType bool `json:"mixed_type"`
|
||||
StoragePolicy StoragePolicy `json:"storage_policy"`
|
||||
}
|
||||
|
||||
type FileUrlResp struct {
|
||||
Urls []struct {
|
||||
URL string `json:"url"`
|
||||
} `json:"urls"`
|
||||
Expires time.Time `json:"expires"`
|
||||
}
|
||||
|
||||
type FileUploadResp struct {
|
||||
// UploadID string `json:"upload_id"`
|
||||
SessionID string `json:"session_id"`
|
||||
ChunkSize int64 `json:"chunk_size"`
|
||||
Expires int64 `json:"expires"`
|
||||
StoragePolicy StoragePolicy `json:"storage_policy"`
|
||||
URI string `json:"uri"`
|
||||
CompleteURL string `json:"completeURL,omitempty"` // for S3-like
|
||||
CallbackSecret string `json:"callback_secret,omitempty"` // for S3-like, OneDrive
|
||||
UploadUrls []string `json:"upload_urls,omitempty"` // for not-local
|
||||
Credential string `json:"credential,omitempty"` // for local
|
||||
}
|
||||
|
||||
type FileThumbResp struct {
|
||||
URL string `json:"url"`
|
||||
Expires time.Time `json:"expires"`
|
||||
}
|
||||
|
||||
type FolderSummaryResp struct {
|
||||
File
|
||||
FolderSummary struct {
|
||||
Size int64 `json:"size"`
|
||||
Files int64 `json:"files"`
|
||||
Folders int64 `json:"folders"`
|
||||
Completed bool `json:"completed"`
|
||||
CalculatedAt time.Time `json:"calculated_at"`
|
||||
} `json:"folder_summary"`
|
||||
}
|
||||
@@ -1,476 +0,0 @@
|
||||
package cloudreve_v4
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/internal/setting"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
func (d *CloudreveV4) getUA() string {
|
||||
if d.CustomUA != "" {
|
||||
return d.CustomUA
|
||||
}
|
||||
return base.UserAgent
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) request(method string, path string, callback base.ReqCallback, out any) error {
|
||||
if d.ref != nil {
|
||||
return d.ref.request(method, path, callback, out)
|
||||
}
|
||||
u := d.Address + "/api/v4" + path
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"User-Agent": d.getUA(),
|
||||
})
|
||||
if d.AccessToken != "" {
|
||||
req.SetHeader("Authorization", "Bearer "+d.AccessToken)
|
||||
}
|
||||
|
||||
var r Resp
|
||||
req.SetResult(&r)
|
||||
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
|
||||
resp, err := req.Execute(method, u)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !resp.IsSuccess() {
|
||||
return errors.New(resp.String())
|
||||
}
|
||||
|
||||
if r.Code != 0 {
|
||||
if r.Code == 401 && d.RefreshToken != "" && path != "/session/token/refresh" {
|
||||
// try to refresh token
|
||||
err = d.refreshToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return d.request(method, path, callback, out)
|
||||
}
|
||||
return errors.New(r.Msg)
|
||||
}
|
||||
|
||||
if out != nil && r.Data != nil {
|
||||
var marshal []byte
|
||||
marshal, err = json.Marshal(r.Data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(marshal, out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) login() error {
|
||||
var siteConfig SiteLoginConfigResp
|
||||
err := d.request(http.MethodGet, "/site/config/login", nil, &siteConfig)
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
if !prepareLogin.PasswordEnabled {
|
||||
return errors.New("password not enabled")
|
||||
}
|
||||
if prepareLogin.WebauthnEnabled {
|
||||
return errors.New("webauthn not support")
|
||||
}
|
||||
for range 5 {
|
||||
err = d.doLogin(siteConfig.LoginCaptcha)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
if err.Error() != "CAPTCHA not match." {
|
||||
break
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) doLogin(needCaptcha bool) error {
|
||||
var err error
|
||||
loginBody := base.Json{
|
||||
"email": d.Username,
|
||||
"password": d.Password,
|
||||
}
|
||||
if needCaptcha {
|
||||
var config BasicConfigResp
|
||||
err = d.request(http.MethodGet, "/site/config/basic", nil, &config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if config.CaptchaType != "normal" {
|
||||
return fmt.Errorf("captcha type %s not support", config.CaptchaType)
|
||||
}
|
||||
var captcha CaptchaResp
|
||||
err = d.request(http.MethodGet, "/site/captcha", nil, &captcha)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !strings.HasPrefix(captcha.Image, "data:image/png;base64,") {
|
||||
return errors.New("can not get captcha")
|
||||
}
|
||||
loginBody["ticket"] = captcha.Ticket
|
||||
i := strings.Index(captcha.Image, ",")
|
||||
dec := base64.NewDecoder(base64.StdEncoding, strings.NewReader(captcha.Image[i+1:]))
|
||||
vRes, err := base.RestyClient.R().SetMultipartField(
|
||||
"image", "validateCode.png", "image/png", dec).
|
||||
Post(setting.GetStr(conf.OcrApi))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if jsoniter.Get(vRes.Body(), "status").ToInt() != 200 {
|
||||
return errors.New("ocr error:" + jsoniter.Get(vRes.Body(), "msg").ToString())
|
||||
}
|
||||
captchaCode := jsoniter.Get(vRes.Body(), "result").ToString()
|
||||
if captchaCode == "" {
|
||||
return errors.New("ocr error: empty result")
|
||||
}
|
||||
loginBody["captcha"] = captchaCode
|
||||
}
|
||||
var token TokenResponse
|
||||
err = d.request(http.MethodPost, "/session/token", func(req *resty.Request) {
|
||||
req.SetBody(loginBody)
|
||||
}, &token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.AccessToken, d.RefreshToken = token.Token.AccessToken, token.Token.RefreshToken
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) refreshToken() error {
|
||||
var token Token
|
||||
if token.RefreshToken == "" {
|
||||
if d.Username != "" {
|
||||
err := d.login()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot login to get refresh token, error: %s", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
err := d.request(http.MethodPost, "/session/token/refresh", func(req *resty.Request) {
|
||||
req.SetBody(base.Json{
|
||||
"refresh_token": d.RefreshToken,
|
||||
})
|
||||
}, &token)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.AccessToken, d.RefreshToken = token.AccessToken, token.RefreshToken
|
||||
op.MustSaveDriverStorage(d)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) upLocal(ctx context.Context, file model.FileStreamer, u FileUploadResp, up driver.UpdateProgress) error {
|
||||
var finish int64 = 0
|
||||
var chunk int = 0
|
||||
DEFAULT := int64(u.ChunkSize)
|
||||
if DEFAULT == 0 {
|
||||
// support relay
|
||||
DEFAULT = file.GetSize()
|
||||
}
|
||||
for finish < file.GetSize() {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
left := file.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[CloudreveV4-Local] 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
|
||||
}
|
||||
err = d.request(http.MethodPost, "/file/upload/"+u.SessionID+"/"+strconv.Itoa(chunk), func(req *resty.Request) {
|
||||
req.SetHeader("Content-Type", "application/octet-stream")
|
||||
req.SetContentLength(true)
|
||||
req.SetHeader("Content-Length", strconv.FormatInt(byteSize, 10))
|
||||
req.SetBody(driver.NewLimitedUploadStream(ctx, bytes.NewReader(byteData)))
|
||||
req.AddRetryCondition(func(r *resty.Response, err error) bool {
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
if r.IsError() {
|
||||
return true
|
||||
}
|
||||
var retryResp Resp
|
||||
jErr := base.RestyClient.JSONUnmarshal(r.Body(), &retryResp)
|
||||
if jErr != nil {
|
||||
return true
|
||||
}
|
||||
if retryResp.Code != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(file.GetSize()))
|
||||
chunk++
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) upRemote(ctx context.Context, file model.FileStreamer, u FileUploadResp, up driver.UpdateProgress) error {
|
||||
uploadUrl := u.UploadUrls[0]
|
||||
credential := u.Credential
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) upOneDrive(ctx context.Context, file model.FileStreamer, u FileUploadResp, up driver.UpdateProgress) error {
|
||||
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)
|
||||
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())
|
||||
finish += byteSize
|
||||
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()))
|
||||
}
|
||||
}
|
||||
// 上传成功发送回调请求
|
||||
return d.request(http.MethodPost, "/callback/onedrive/"+u.SessionID+"/"+u.CallbackSecret, func(req *resty.Request) {
|
||||
req.SetBody("{}")
|
||||
}, nil)
|
||||
}
|
||||
|
||||
func (d *CloudreveV4) upS3(ctx context.Context, file model.FileStreamer, u FileUploadResp, up driver.UpdateProgress) error {
|
||||
var finish int64 = 0
|
||||
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)
|
||||
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("faild to get ETag from header")
|
||||
default:
|
||||
retryCount = 0
|
||||
etags = append(etags, etag)
|
||||
finish += byteSize
|
||||
up(float64(finish) * 100 / float64(file.GetSize()))
|
||||
chunk++
|
||||
}
|
||||
}
|
||||
|
||||
// s3LikeFinishUpload
|
||||
bodyBuilder := &strings.Builder{}
|
||||
bodyBuilder.WriteString("<CompleteMultipartUpload>")
|
||||
for i, etag := range etags {
|
||||
bodyBuilder.WriteString(fmt.Sprintf(
|
||||
`<Part><PartNumber>%d</PartNumber><ETag>%s</ETag></Part>`,
|
||||
i+1, // PartNumber 从 1 开始
|
||||
etag,
|
||||
))
|
||||
}
|
||||
bodyBuilder.WriteString("</CompleteMultipartUpload>")
|
||||
req, err := http.NewRequest(
|
||||
"POST",
|
||||
u.CompleteURL,
|
||||
strings.NewReader(bodyBuilder.String()),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/xml")
|
||||
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 != http.StatusOK {
|
||||
body, _ := io.ReadAll(res.Body)
|
||||
return fmt.Errorf("up status: %d, error: %s", res.StatusCode, string(body))
|
||||
}
|
||||
|
||||
// 上传成功发送回调请求
|
||||
return d.request(http.MethodPost, "/callback/s3/"+u.SessionID+"/"+u.CallbackSecret, func(req *resty.Request) {
|
||||
req.SetBody("{}")
|
||||
}, nil)
|
||||
}
|
||||
@@ -11,7 +11,7 @@ type Addition struct {
|
||||
IsSharepoint bool `json:"is_sharepoint"`
|
||||
ClientID string `json:"client_id" required:"true"`
|
||||
ClientSecret string `json:"client_secret" required:"true"`
|
||||
RedirectUri string `json:"redirect_uri" required:"true" default:"https://alistgo.com/tool/onedrive/callback"`
|
||||
RedirectUri string `json:"redirect_uri" required:"true" default:"https://alist.nn.ci/tool/onedrive/callback"`
|
||||
RefreshToken string `json:"refresh_token" required:"true"`
|
||||
SiteId string `json:"site_id"`
|
||||
ChunkSize int64 `json:"chunk_size" type:"number" default:"5"`
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
stdpath "path"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var onedriveHostMap = map[string]Host{
|
||||
@@ -204,18 +204,19 @@ 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()
|
||||
}
|
||||
log.Debugf("upload: %d", finish)
|
||||
var byteSize int64 = DEFAULT
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[Onedrive] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
if left < DEFAULT {
|
||||
byteSize = left
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -227,31 +228,19 @@ func (d *Onedrive) upBig(ctx context.Context, dstDir model.Obj, stream model.Fil
|
||||
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()))
|
||||
finish += byteSize
|
||||
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:
|
||||
if 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()))
|
||||
}
|
||||
res.Body.Close()
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
stdpath "path"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@@ -18,6 +17,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/go-resty/resty/v2"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
var onedriveHostMap = map[string]Host{
|
||||
@@ -154,18 +154,19 @@ 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()
|
||||
}
|
||||
log.Debugf("upload: %d", finish)
|
||||
var byteSize int64 = DEFAULT
|
||||
left := stream.GetSize() - finish
|
||||
byteSize := min(left, DEFAULT)
|
||||
utils.Log.Debugf("[OnedriveAPP] upload range: %d-%d/%d", finish, finish+byteSize-1, stream.GetSize())
|
||||
if left < DEFAULT {
|
||||
byteSize = left
|
||||
}
|
||||
byteData := make([]byte, byteSize)
|
||||
n, err := io.ReadFull(stream, byteData)
|
||||
utils.Log.Debug(err, n)
|
||||
log.Debug(err, n)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -177,31 +178,19 @@ func (d *OnedriveAPP) upBig(ctx context.Context, dstDir model.Obj, stream model.
|
||||
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()))
|
||||
finish += byteSize
|
||||
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:
|
||||
if 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()))
|
||||
}
|
||||
res.Body.Close()
|
||||
up(float64(finish) * 100 / float64(stream.GetSize()))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
11
go.mod
11
go.mod
@@ -3,6 +3,8 @@ module github.com/alist-org/alist/v3
|
||||
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/KirCute/ftpserverlib-pasvportmap v1.25.0
|
||||
github.com/KirCute/sftpd-alist v0.0.12
|
||||
github.com/ProtonMail/go-crypto v1.0.0
|
||||
@@ -73,6 +75,7 @@ require (
|
||||
golang.org/x/time v0.8.0
|
||||
google.golang.org/appengine v1.6.8
|
||||
gopkg.in/ldap.v3 v3.1.0
|
||||
gorm.io/datatypes v1.2.5
|
||||
gorm.io/driver/mysql v1.5.7
|
||||
gorm.io/driver/postgres v1.5.9
|
||||
gorm.io/driver/sqlite v1.5.6
|
||||
@@ -80,9 +83,8 @@ require (
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.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
|
||||
)
|
||||
|
||||
require (
|
||||
@@ -109,7 +111,6 @@ require (
|
||||
github.com/ipfs/boxo v0.12.0 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.1 // indirect
|
||||
github.com/klauspost/pgzip v1.2.6 // indirect
|
||||
github.com/kr/text v0.2.0 // indirect
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.27
|
||||
github.com/nwaples/rardecode/v2 v2.0.0-beta.4.0.20241112120701-034e449c6e78
|
||||
@@ -167,7 +168,7 @@ require (
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.20.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.7.0 // indirect
|
||||
github.com/go-sql-driver/mysql v1.8.1 // indirect
|
||||
github.com/go-webauthn/x v0.1.12 // indirect
|
||||
github.com/goccy/go-json v0.10.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 // indirect
|
||||
@@ -181,7 +182,7 @@ require (
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/ipfs/go-cid v0.4.1
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect
|
||||
github.com/jackc/pgx/v5 v5.5.5 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/jinzhu/now v1.1.5 // indirect
|
||||
|
||||
32
go.sum
32
go.sum
@@ -19,12 +19,20 @@ cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+
|
||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
|
||||
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0 h1:g0EZJwz7xkXQiZAI5xi9f3WWFYBlX1CPTrR+NDToRkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.17.0/go.mod h1:XCW7KnZet0Opnr7HccfUw1PLc4CjHqpcaxW8DHklNkQ=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0 h1:B/dfvscEQtew9dVuoxqxrUKKv8Ih2f55PydknDamU+g=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.8.0/go.mod h1:fiPSssYvltE08HJchL04dOy+RD4hgrjph0cwGGMntdI=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0 h1:ywEEhmNahHBihViHepv3xPBn1663uRv2t2q/ESv9seY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.10.0/go.mod h1:iZDifYGJTIgIIkYRNWPENUnqx6bJ2xnSDFI2tjwZNuY=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0 h1:PiSrjRPpkQNjrM8H0WwKMnZUdu1RGMtd/LdGKUrOo+c=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.6.0/go.mod h1:oDrbWx4ewMylP7xHivfgixbfGBT6APAwsSoHRKotnIc=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0 h1:UXT0o77lXQrikd1kgwIPQOUect7EoR/+sbP4wQKdzxM=
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.6.0/go.mod h1:cTvi54pg19DoT07ekoeMgE/taAwNtCShVeZqA+Iv2xI=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2 h1:kYRSnvJju5gYVyhkij+RTJ/VR6QIUaCfWeaFm2ycsjQ=
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.3.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
|
||||
github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||
@@ -172,7 +180,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3 h1:HVTnpeuvF6Owjd5mniCL8DEXo7uYXdQEmOP4FJbV5tg=
|
||||
github.com/crackcomm/go-gitignore v0.0.0-20170627025303-887ab5e44cc3/go.mod h1:p1d6YEZWvFzEh4KLyvBcVSnrfNDDvK2zfK/4x2v/4pE=
|
||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
@@ -245,8 +252,9 @@ github.com/go-playground/validator/v10 v10.20.0 h1:K9ISHbSaI0lyB2eWMPJo+kOS/FBEx
|
||||
github.com/go-playground/validator/v10 v10.20.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/go-resty/resty/v2 v2.14.0 h1:/rhkzsAqGQkozwfKS5aFAbb6TyKd3zyFRWcdRXLPCAU=
|
||||
github.com/go-resty/resty/v2 v2.14.0/go.mod h1:IW6mekUOsElt9C7oWr0XRt9BNSD6D5rr9mhk6NjmNHg=
|
||||
github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc=
|
||||
github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
|
||||
github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y=
|
||||
github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg=
|
||||
github.com/go-webauthn/webauthn v0.11.1 h1:5G/+dg91/VcaJHTtJUfwIlNJkLwbJCcnUc4W8VtkpzA=
|
||||
github.com/go-webauthn/webauthn v0.11.1/go.mod h1:YXRm1WG0OtUyDFaVAgB5KG7kVqW+6dYCJ7FTQH4SxEE=
|
||||
github.com/go-webauthn/x v0.1.12 h1:RjQ5cvApzyU/xLCiP+rub0PE4HBZsLggbxGR5ZpUf/A=
|
||||
@@ -259,6 +267,10 @@ github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOW
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk=
|
||||
github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
|
||||
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=
|
||||
github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551 h1:gtexQ/VGyN+VVFRXSFiguSNcXmS6rkKT+X7FdIrTtfo=
|
||||
github.com/golang/geo v0.0.0-20210211234256-740aa86cb551/go.mod h1:QZ0nwyI2jOfgRAoBvP+ab5aRr7c9x7lhGEJrKvBwjWI=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
@@ -347,8 +359,8 @@ github.com/ipfs/go-ipfs-api v0.7.0 h1:CMBNCUl0b45coC+lQCXEVpMhwoqjiaCwUIrM+coYW2
|
||||
github.com/ipfs/go-ipfs-api v0.7.0/go.mod h1:AIxsTNB0+ZhkqIfTZpdZ0VR/cpX5zrXjATa3prSay3g=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk=
|
||||
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 h1:L0QtFUgDarD7Fpv9jeVMgy/+Ec0mtnmYuImjTz6dtDA=
|
||||
github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw=
|
||||
github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A=
|
||||
github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk=
|
||||
@@ -398,6 +410,8 @@ 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/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||
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=
|
||||
@@ -436,6 +450,8 @@ github.com/mholt/archives v0.1.0 h1:FacgJyrjiuyomTuNA92X5GyRBRZjE43Y/lrzKIlF35Q=
|
||||
github.com/mholt/archives v0.1.0/go.mod h1:j/Ire/jm42GN7h90F5kzj6hf6ZFzEH66de+hmjEKu+I=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk=
|
||||
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
|
||||
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
|
||||
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
|
||||
github.com/minio/sha256-simd v1.0.1 h1:6kaan5IFmwTNynnKKpDHe6FWHohJOHhCPchzK49dzMM=
|
||||
github.com/minio/sha256-simd v1.0.1/go.mod h1:Pz6AKMiUdngCLpeTL/RJY1M9rUuPMYujV5xJjtbRSN8=
|
||||
github.com/minio/sio v0.4.0 h1:u4SWVEm5lXSqU42ZWawV0D9I5AZ5YMmo2RXpEQ/kRhc=
|
||||
@@ -492,6 +508,8 @@ github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6
|
||||
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
|
||||
github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ=
|
||||
github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
@@ -739,8 +757,6 @@ golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
|
||||
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
|
||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
|
||||
golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||
@@ -946,12 +962,16 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gorm.io/datatypes v1.2.5 h1:9UogU3jkydFVW1bIVVeoYsTpLRgwDVW3rHfJG6/Ek9I=
|
||||
gorm.io/datatypes v1.2.5/go.mod h1:I5FUdlKpLb5PMqeMQhm30CQ6jXP8Rj89xkTeCSAaAD4=
|
||||
gorm.io/driver/mysql v1.5.7 h1:MndhOPYOfEp2rHKgkZIhJ16eVUIRf2HmzgoPmh7FCWo=
|
||||
gorm.io/driver/mysql v1.5.7/go.mod h1:sEtPWMiqiN1N1cMXoXmBbd8C6/l+TESwriotuRRpkDM=
|
||||
gorm.io/driver/postgres v1.5.9 h1:DkegyItji119OlcaLjqN11kHoUgZ/j13E0jkJZgD6A8=
|
||||
gorm.io/driver/postgres v1.5.9/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI=
|
||||
gorm.io/driver/sqlite v1.5.6 h1:fO/X46qn5NUEEOZtnjJRWRzZMe8nqJiQ9E+0hi+hKQE=
|
||||
gorm.io/driver/sqlite v1.5.6/go.mod h1:U+J8craQU6Fzkcvu8oLeAQmi50TkwPEhHDEjQZXDah4=
|
||||
gorm.io/driver/sqlserver v1.5.4 h1:xA+Y1KDNspv79q43bPyjDMUgHoYHLhXYmdFcYPobg8g=
|
||||
gorm.io/driver/sqlserver v1.5.4/go.mod h1:+frZ/qYmuna11zHPlh5oc2O6ZA/lS88Keb0XSH1Zh/g=
|
||||
gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8=
|
||||
gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg=
|
||||
gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ=
|
||||
|
||||
@@ -6,6 +6,8 @@ func InitData() {
|
||||
initUser()
|
||||
initSettings()
|
||||
initTasks()
|
||||
initPermissions()
|
||||
initRoles()
|
||||
if flags.Dev {
|
||||
initDevData()
|
||||
initDevDo()
|
||||
|
||||
@@ -26,7 +26,8 @@ func initDevData() {
|
||||
Username: "Noah",
|
||||
Password: "hsu",
|
||||
BasePath: "/data",
|
||||
Role: 0,
|
||||
//Role: []int{0},
|
||||
RoleInfo: []uint{0},
|
||||
Permission: 512,
|
||||
})
|
||||
if err != nil {
|
||||
|
||||
50
internal/bootstrap/data/permission.go
Normal file
50
internal/bootstrap/data/permission.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
func initPermissions() {
|
||||
_, err := op.GetPermissionByName("guest")
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
pg := &model.Permission{
|
||||
Name: "guest",
|
||||
Permission: 0x4000, // 14 bit(can access dir)
|
||||
PathPattern: "*",
|
||||
AllowOpInfo: []string{"upload", "download", "delete"},
|
||||
CreateTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
}
|
||||
if err := op.CreatePermission(pg); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
utils.Log.Infof("Successfully created the guest permission ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = op.GetPermissionByName("admin")
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
pa := &model.Permission{
|
||||
Name: "admin",
|
||||
Permission: 0x70FF, // 0、1、2、3、4、5、6、7、12、13、14 bit
|
||||
PathPattern: "",
|
||||
AllowOpInfo: []string{"upload", "download", "delete"},
|
||||
CreateTime: time.Now(),
|
||||
UpdateTime: time.Now(),
|
||||
}
|
||||
if err := op.CreatePermission(pa); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
utils.Log.Infof("Successfully created the admin permission ")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
47
internal/bootstrap/data/role.go
Normal file
47
internal/bootstrap/data/role.go
Normal file
@@ -0,0 +1,47 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/pkg/errors"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
func initRoles() {
|
||||
_, err := op.GetRoleByName("guest")
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
roleGuest := &model.Role{
|
||||
Name: "guest",
|
||||
PermissionInfo: []uint{1},
|
||||
CreateTime: time.Time{},
|
||||
UpdateTime: time.Time{},
|
||||
}
|
||||
if err := op.CreateRole(roleGuest); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
utils.Log.Infof("Successfully created the guest role ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_, err = op.GetRoleByName("admin")
|
||||
if err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
roleAdmin := &model.Role{
|
||||
Name: "admin",
|
||||
PermissionInfo: []uint{2},
|
||||
CreateTime: time.Time{},
|
||||
UpdateTime: time.Time{},
|
||||
}
|
||||
if err := op.CreateRole(roleAdmin); err != nil {
|
||||
panic(err)
|
||||
} else {
|
||||
utils.Log.Infof("Successfully created the admin role ")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -155,7 +155,7 @@ func InitialSettings() []model.SettingItem {
|
||||
([[:xdigit:]]{1,4}(?::[[:xdigit:]]{1,4}){7}|::|:(?::[[:xdigit:]]{1,4}){1,6}|[[:xdigit:]]{1,4}:(?::[[:xdigit:]]{1,4}){1,5}|(?:[[:xdigit:]]{1,4}:){2}(?::[[:xdigit:]]{1,4}){1,4}|(?:[[:xdigit:]]{1,4}:){3}(?::[[:xdigit:]]{1,4}){1,3}|(?:[[:xdigit:]]{1,4}:){4}(?::[[:xdigit:]]{1,4}){1,2}|(?:[[:xdigit:]]{1,4}:){5}:[[:xdigit:]]{1,4}|(?:[[:xdigit:]]{1,4}:){1,6}:)
|
||||
(?U)access_token=(.*)&`,
|
||||
Type: conf.TypeText, Group: model.GLOBAL, Flag: model.PRIVATE},
|
||||
{Key: conf.OcrApi, Value: "https://api.alistgo.com/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
|
||||
{Key: conf.OcrApi, Value: "https://api.nn.ci/ocr/file/json", Type: conf.TypeString, Group: model.GLOBAL},
|
||||
{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},
|
||||
|
||||
@@ -29,7 +29,8 @@ func initUser() {
|
||||
Username: "admin",
|
||||
Salt: salt,
|
||||
PwdHash: model.TwoHashPwd(adminPassword, salt),
|
||||
Role: model.ADMIN,
|
||||
//Role: []int{model.ADMIN},
|
||||
RoleInfo: []uint{model.ADMIN},
|
||||
BasePath: "/",
|
||||
Authn: "[]",
|
||||
// 0(can see hidden) - 7(can remove) & 12(can read archives) - 13(can decompress archives)
|
||||
@@ -52,7 +53,8 @@ func initUser() {
|
||||
Username: "guest",
|
||||
PwdHash: model.TwoHashPwd("guest", salt),
|
||||
Salt: salt,
|
||||
Role: model.GUEST,
|
||||
//Role: []int{model.GUEST},
|
||||
RoleInfo: []uint{model.GUEST},
|
||||
BasePath: "/",
|
||||
Permission: 0,
|
||||
Disabled: true,
|
||||
|
||||
@@ -12,7 +12,8 @@ var db *gorm.DB
|
||||
|
||||
func Init(d *gorm.DB) {
|
||||
db = d
|
||||
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey))
|
||||
err := AutoMigrate(new(model.Storage), new(model.User), new(model.Meta), new(model.SettingItem), new(model.SearchNode), new(model.TaskItem), new(model.SSHPublicKey),
|
||||
new(model.Permission), new(model.Role))
|
||||
if err != nil {
|
||||
log.Fatalf("failed migrate database: %s", err.Error())
|
||||
}
|
||||
|
||||
53
internal/db/permission.go
Normal file
53
internal/db/permission.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func CreatePermission(p *model.Permission) error {
|
||||
return errors.WithStack(db.Create(p).Error)
|
||||
}
|
||||
|
||||
func UpdatePermission(p *model.Permission) error {
|
||||
return errors.WithStack(db.Save(p).Error)
|
||||
}
|
||||
|
||||
func GetPermissions(pageIndex, pageSize int) (permissions []model.Permission, count int64, err error) {
|
||||
permissionDB := db.Model(&model.Permission{})
|
||||
if err := permissionDB.Count(&count).Error; err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed get permissions count")
|
||||
}
|
||||
if err := permissionDB.Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&permissions).Error; err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed get find permissions")
|
||||
}
|
||||
return permissions, count, nil
|
||||
}
|
||||
|
||||
func DeletePermissionById(id uint) error {
|
||||
return errors.WithStack(db.Delete(&model.Permission{}, id).Error)
|
||||
}
|
||||
|
||||
func GetPermissionById(id uint) (*model.Permission, error) {
|
||||
var p model.Permission
|
||||
if err := db.First(&p, id).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "failed get permission by id %d", id)
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func GetPermissionByIds(ids []uint) ([]model.Permission, error) {
|
||||
var p []model.Permission
|
||||
if err := db.Where("id in (?)", ids).First(&p).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "failed get permission by ids %v", ids)
|
||||
}
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func GetPermissionByName(name string) (*model.Permission, error) {
|
||||
var p model.Permission
|
||||
if err := db.Where("name = ?", name).First(&p).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "failed get permission by name %v", name)
|
||||
}
|
||||
return &p, nil
|
||||
}
|
||||
53
internal/db/role.go
Normal file
53
internal/db/role.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func CreateRole(r *model.Role) error {
|
||||
return errors.WithStack(db.Create(r).Error)
|
||||
}
|
||||
|
||||
func UpdateRole(r *model.Role) error {
|
||||
return errors.WithStack(db.Save(r).Error)
|
||||
}
|
||||
|
||||
func GetRoles(pageIndex, pageSize int) (roles []model.Role, count int64, err error) {
|
||||
roleDB := db.Model(&model.Role{})
|
||||
if err := roleDB.Count(&count).Error; err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed get roles count")
|
||||
}
|
||||
if err := roleDB.Order(columnName("id")).Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&roles).Error; err != nil {
|
||||
return nil, 0, errors.Wrapf(err, "failed get find roles")
|
||||
}
|
||||
return roles, count, nil
|
||||
}
|
||||
|
||||
func DeleteRoleById(id uint) error {
|
||||
return errors.WithStack(db.Delete(&model.Role{}, id).Error)
|
||||
}
|
||||
|
||||
func GetRoleById(id uint) (*model.Role, error) {
|
||||
var r model.Role
|
||||
if err := db.First(&r, id).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "failed get role")
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
func GetRoleByIds(ids []uint) ([]model.Role, error) {
|
||||
var r []model.Role
|
||||
if err := db.Where("id in (?)", ids).Find(&r).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "failed get roles by ids: %v", ids)
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func GetRoleByName(name string) (*model.Role, error) {
|
||||
var r model.Role
|
||||
if err := db.Where("name = ?", name).First(&r).Error; err != nil {
|
||||
return nil, errors.Wrapf(err, "failed get role by name %v", name)
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
@@ -2,6 +2,7 @@ package db
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"gorm.io/datatypes"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
@@ -9,10 +10,16 @@ import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func GetUserByRole(role int) (*model.User, error) {
|
||||
user := model.User{Role: role}
|
||||
func GetUserByRole(role []int) (*model.User, error) {
|
||||
var user model.User
|
||||
/*user := model.User{Role: role}
|
||||
if err := db.Where(user).Take(&user).Error; err != nil {
|
||||
return nil, err
|
||||
}*/
|
||||
cond := datatypes.JSONArrayQuery("role").Contains(role)
|
||||
err := db.Where(cond).Take(&user).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
128
internal/model/permission.go
Normal file
128
internal/model/permission.go
Normal file
@@ -0,0 +1,128 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Permission struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"` //权限唯一主键
|
||||
Name string `json:"name"` //权限名称
|
||||
// Determine permissions by bit
|
||||
// 0: can see hidden files
|
||||
// 1: can access without password
|
||||
// 2: can add offline download tasks
|
||||
// 3: can mkdir and upload
|
||||
// 4: can rename
|
||||
// 5: can move
|
||||
// 6: can copy
|
||||
// 7: can remove
|
||||
// 8: webdav read
|
||||
// 9: webdav write
|
||||
// 10: ftp/sftp login and read
|
||||
// 11: ftp/sftp write
|
||||
// 12: can read archives
|
||||
// 13: can decompress archives
|
||||
// 14: dir access control
|
||||
Permission int32 `json:"permission"`
|
||||
PathPattern string `json:"path_pattern"` // 目录路径模式
|
||||
AllowOp datatypes.JSON `gorm:"type:json;column:allow_op" json:"allow_op"` //允许的操作upload/download/delete等
|
||||
AllowOpInfo AllowOpSlice `gorm:"-" json:"allow_op_info"`
|
||||
CreateTime time.Time `json:"create_time"` //创建时间
|
||||
UpdateTime time.Time `json:"update_time"` //修改时间
|
||||
}
|
||||
|
||||
type AllowOpSlice []string
|
||||
|
||||
func (p *Permission) BeforeCreate(db *gorm.DB) (err error) {
|
||||
if p.AllowOpInfo != nil {
|
||||
p.AllowOp, err = json.Marshal(p.AllowOpInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Permission) BeforeUpdate(db *gorm.DB) (err error) {
|
||||
if p.AllowOpInfo != nil {
|
||||
p.AllowOp, err = json.Marshal(p.AllowOpInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Permission) AfterFind(db *gorm.DB) (err error) {
|
||||
p.AllowOpInfo = AllowOpSlice{}
|
||||
if len(p.AllowOp) > 0 {
|
||||
err = json.Unmarshal(p.AllowOp, &p.AllowOpInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *Permission) CanSeeHides() bool {
|
||||
return p.Permission&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanAccessWithoutPassword() bool {
|
||||
return (p.Permission>>1)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanAddOfflineDownloadTasks() bool {
|
||||
return (p.Permission>>2)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanWrite() bool {
|
||||
return (p.Permission>>3)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanRename() bool {
|
||||
return (p.Permission>>4)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanMove() bool {
|
||||
return (p.Permission>>5)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanCopy() bool {
|
||||
return (p.Permission>>6)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanRemove() bool {
|
||||
return (p.Permission>>7)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanWebdavRead() bool {
|
||||
return (p.Permission>>8)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanWebdavManage() bool {
|
||||
return (p.Permission>>9)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanFTPAccess() bool {
|
||||
return (p.Permission>>10)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanFTPManage() bool {
|
||||
return (p.Permission>>11)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanReadArchives() bool {
|
||||
return (p.Permission>>12)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanDecompress() bool {
|
||||
return (p.Permission>>13)&1 == 1
|
||||
}
|
||||
|
||||
func (p *Permission) CanAccessDir() bool {
|
||||
return (p.Permission>>14)&1 == 1
|
||||
}
|
||||
49
internal/model/role.go
Normal file
49
internal/model/role.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package model
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
ID uint `json:"id" gorm:"primaryKey"` //角色唯一主键
|
||||
Name string `json:"name"` //角色名称
|
||||
Permissions datatypes.JSON `gorm:"type:json;column:permissions" json:"permissions"` //权限id
|
||||
PermissionInfo PermissionIdSlice `gorm:"-" json:"permission_info"`
|
||||
CreateTime time.Time `json:"create_time"` //创建时间
|
||||
UpdateTime time.Time `json:"update_time"` //修改时间
|
||||
}
|
||||
type PermissionIdSlice []uint
|
||||
|
||||
func (r *Role) BeforeCreate(db *gorm.DB) (err error) {
|
||||
if r.PermissionInfo != nil {
|
||||
r.Permissions, err = json.Marshal(r.PermissionInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Role) BeforeUpdate(db *gorm.DB) (err error) {
|
||||
if r.PermissionInfo != nil {
|
||||
r.Permissions, err = json.Marshal(r.PermissionInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *Role) AfterFind(db *gorm.DB) (err error) {
|
||||
r.PermissionInfo = PermissionIdSlice{}
|
||||
if len(r.Permissions) > 0 {
|
||||
err = json.Unmarshal(r.Permissions, &r.PermissionInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -4,6 +4,8 @@ import (
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/internal/errs"
|
||||
@@ -29,7 +31,8 @@ type User struct {
|
||||
Salt string `json:"-"` // unique salt
|
||||
Password string `json:"password"` // password
|
||||
BasePath string `json:"base_path"` // base path
|
||||
Role int `json:"role"` // user's role
|
||||
Role datatypes.JSON `gorm:"type:json;column:role" json:"role"` // user's role
|
||||
RoleInfo RoleIdSlice `gorm:"-" json:"role_info"`
|
||||
Disabled bool `json:"disabled"`
|
||||
// Determine permissions by bit
|
||||
// 0: can see hidden files
|
||||
@@ -52,12 +55,60 @@ type User struct {
|
||||
Authn string `gorm:"type:text" json:"-"`
|
||||
}
|
||||
|
||||
type RoleIdSlice []uint
|
||||
|
||||
func (u *User) BeforeCreate(db *gorm.DB) (err error) {
|
||||
if u.RoleInfo != nil {
|
||||
u.Role, err = json.Marshal(u.RoleInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) BeforeUpdate(db *gorm.DB) (err error) {
|
||||
if u.RoleInfo != nil {
|
||||
u.Role, err = json.Marshal(u.RoleInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) AfterFind(db *gorm.DB) (err error) {
|
||||
u.RoleInfo = RoleIdSlice{}
|
||||
if len(u.Role) > 0 {
|
||||
err = json.Unmarshal(u.Role, &u.RoleInfo)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *User) IsGuest() bool {
|
||||
return u.Role == GUEST
|
||||
isGuest := true
|
||||
for _, role := range u.Role {
|
||||
if role != GUEST {
|
||||
isGuest = false
|
||||
break
|
||||
}
|
||||
}
|
||||
return isGuest
|
||||
//return u.Role == GUEST
|
||||
}
|
||||
|
||||
func (u *User) IsAdmin() bool {
|
||||
return u.Role == ADMIN
|
||||
isAdmin := true
|
||||
for _, role := range u.Role {
|
||||
if role != ADMIN {
|
||||
isAdmin = false
|
||||
}
|
||||
}
|
||||
return isAdmin
|
||||
//return u.Role == ADMIN
|
||||
}
|
||||
|
||||
func (u *User) ValidateRawPassword(password string) error {
|
||||
@@ -177,5 +228,5 @@ func (u *User) WebAuthnCredentials() []webauthn.Credential {
|
||||
}
|
||||
|
||||
func (u *User) WebAuthnIcon() string {
|
||||
return "https://alistgo.com/logo.svg"
|
||||
return "https://alist.nn.ci/logo.svg"
|
||||
}
|
||||
|
||||
30
internal/op/permission.go
Normal file
30
internal/op/permission.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package op
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
func CreatePermission(p *model.Permission) error {
|
||||
return db.CreatePermission(p)
|
||||
}
|
||||
|
||||
func GetPermissions(pageIndex, pageSize int) (permissions []model.Permission, count int64, err error) {
|
||||
return db.GetPermissions(pageIndex, pageSize)
|
||||
}
|
||||
|
||||
func GetPermissionById(id uint) (*model.Permission, error) {
|
||||
return db.GetPermissionById(id)
|
||||
}
|
||||
|
||||
func UpdatePermission(p *model.Permission) error {
|
||||
return db.UpdatePermission(p)
|
||||
}
|
||||
|
||||
func DeletePermissionById(id uint) error {
|
||||
return db.DeletePermissionById(id)
|
||||
}
|
||||
|
||||
func GetPermissionByName(name string) (*model.Permission, error) {
|
||||
return db.GetPermissionByName(name)
|
||||
}
|
||||
50
internal/op/role.go
Normal file
50
internal/op/role.go
Normal file
@@ -0,0 +1,50 @@
|
||||
package op
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/db"
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
)
|
||||
|
||||
func CreateRole(r *model.Role) error {
|
||||
return db.CreateRole(r)
|
||||
}
|
||||
|
||||
func GetRoles(pageIndex, pageSize int) (roles []model.Role, count int64, err error) {
|
||||
return db.GetRoles(pageIndex, pageSize)
|
||||
}
|
||||
|
||||
func GetRoleById(id uint) (*model.Role, error) {
|
||||
return db.GetRoleById(id)
|
||||
}
|
||||
|
||||
func GetRoleByIds(ids []uint) ([]model.Role, error) {
|
||||
return db.GetRoleByIds(ids)
|
||||
}
|
||||
|
||||
func UpdateRole(r *model.Role) error {
|
||||
return db.UpdateRole(r)
|
||||
}
|
||||
|
||||
func DeleteRoleById(id uint) error {
|
||||
return db.DeleteRoleById(id)
|
||||
}
|
||||
|
||||
func GetPermissionByRoleIds(ids []uint) ([]model.Permission, error) {
|
||||
roles, err := db.GetRoleByIds(ids)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
perIds := make([]uint, 0)
|
||||
for _, v := range roles {
|
||||
perIds = append(perIds, v.PermissionInfo...)
|
||||
}
|
||||
permissions, err := db.GetPermissionByIds(perIds)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return permissions, nil
|
||||
}
|
||||
|
||||
func GetRoleByName(name string) (*model.Role, error) {
|
||||
return db.GetRoleByName(name)
|
||||
}
|
||||
@@ -18,7 +18,7 @@ var adminUser *model.User
|
||||
|
||||
func GetAdmin() (*model.User, error) {
|
||||
if adminUser == nil {
|
||||
user, err := db.GetUserByRole(model.ADMIN)
|
||||
user, err := db.GetUserByRole([]int{model.ADMIN})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -29,7 +29,7 @@ func GetAdmin() (*model.User, error) {
|
||||
|
||||
func GetGuest() (*model.User, error) {
|
||||
if guestUser == nil {
|
||||
user, err := db.GetUserByRole(model.GUEST)
|
||||
user, err := db.GetUserByRole([]int{model.GUEST})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func GetGuest() (*model.User, error) {
|
||||
return guestUser, nil
|
||||
}
|
||||
|
||||
func GetUserByRole(role int) (*model.User, error) {
|
||||
func GetUserByRole(role []int) (*model.User, error) {
|
||||
return db.GetUserByRole(role)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,9 @@ func loginHash(c *gin.Context, req *LoginReq) {
|
||||
type UserResp struct {
|
||||
model.User
|
||||
Otp bool `json:"otp"`
|
||||
Permission int32 `json:"permission"`
|
||||
PathPattern []string `json:"path_pattern"` // 目录路径模式,当Permission第14bit位为1时用到
|
||||
AllowOpInfo model.AllowOpSlice `json:"allow_op_info"`
|
||||
}
|
||||
|
||||
// CurrentUser get current user by token
|
||||
@@ -103,9 +106,41 @@ func CurrentUser(c *gin.Context) {
|
||||
if userResp.OtpSecret != "" {
|
||||
userResp.Otp = true
|
||||
}
|
||||
permissions, err := op.GetPermissionByRoleIds(user.RoleInfo)
|
||||
if err != nil || len(permissions) == 0 {
|
||||
common.ErrorResp(c, err, 400)
|
||||
}
|
||||
if len(permissions) == 1 {
|
||||
userResp.Permission = permissions[0].Permission
|
||||
userResp.PathPattern = append(userResp.PathPattern, permissions[0].PathPattern)
|
||||
userResp.AllowOpInfo = permissions[0].AllowOpInfo
|
||||
} else {
|
||||
var per int32
|
||||
for _, perm := range permissions {
|
||||
per |= perm.Permission
|
||||
userResp.PathPattern = append(userResp.PathPattern, perm.PathPattern)
|
||||
userResp.AllowOpInfo = append(userResp.AllowOpInfo, perm.AllowOpInfo...)
|
||||
}
|
||||
userResp.PathPattern = uniqStr(userResp.PathPattern)
|
||||
userResp.AllowOpInfo = uniqStr(userResp.AllowOpInfo)
|
||||
userResp.Permission = per
|
||||
}
|
||||
common.SuccessResp(c, userResp)
|
||||
}
|
||||
|
||||
func uniqStr(str []string) []string {
|
||||
seen := make(map[string]int)
|
||||
j := 0
|
||||
for i, v := range str {
|
||||
if _, ok := seen[v]; !ok {
|
||||
str[j] = str[i]
|
||||
seen[v] = j
|
||||
j++
|
||||
}
|
||||
}
|
||||
return str[:j]
|
||||
}
|
||||
|
||||
func UpdateCurrent(c *gin.Context) {
|
||||
var req model.User
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
|
||||
@@ -131,7 +131,8 @@ func ladpRegister(username string) (*model.User, error) {
|
||||
Password: random.String(16),
|
||||
Permission: int32(setting.GetInt(conf.LdapDefaultPermission, 0)),
|
||||
BasePath: setting.GetStr(conf.LdapDefaultDir),
|
||||
Role: 0,
|
||||
//Role: []int{0},
|
||||
RoleInfo: []uint{0},
|
||||
Disabled: false,
|
||||
}
|
||||
if err := db.CreateUser(user); err != nil {
|
||||
|
||||
69
server/handles/permission.go
Normal file
69
server/handles/permission.go
Normal file
@@ -0,0 +1,69 @@
|
||||
package handles
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func CreatePermission(c *gin.Context) {
|
||||
var req model.Permission
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := op.CreatePermission(&req); err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func ListPermissions(c *gin.Context) {
|
||||
var req model.PageReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
req.Validate()
|
||||
log.Debugf("%+v", req)
|
||||
permissions, total, err := op.GetPermissions(req.Page, req.PerPage)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, common.PageResp{
|
||||
Content: permissions,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
func UpdatePermission(c *gin.Context) {
|
||||
var req model.Permission
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := op.UpdatePermission(&req); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func DeletePermission(c *gin.Context) {
|
||||
idStr := c.Query("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := op.DeletePermissionById(uint(id)); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
87
server/handles/role.go
Normal file
87
server/handles/role.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package handles
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/model"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/gin-gonic/gin"
|
||||
log "github.com/sirupsen/logrus"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
func CreateRole(c *gin.Context) {
|
||||
var req model.Role
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := op.CreateRole(&req); err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func ListRoles(c *gin.Context) {
|
||||
var req model.PageReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
req.Validate()
|
||||
log.Debugf("%+v", req)
|
||||
permissions, total, err := op.GetRoles(req.Page, req.PerPage)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500, true)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, common.PageResp{
|
||||
Content: permissions,
|
||||
Total: total,
|
||||
})
|
||||
}
|
||||
|
||||
func UpdateRole(c *gin.Context) {
|
||||
var req model.Role
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := op.UpdateRole(&req); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
} else {
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
}
|
||||
|
||||
func DeleteRole(c *gin.Context) {
|
||||
idStr := c.Query("id")
|
||||
id, err := strconv.Atoi(idStr)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
if err := op.DeleteRoleById(uint(id)); err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c)
|
||||
}
|
||||
|
||||
type GetPermissionByRoleIdsReq struct {
|
||||
Ids []uint `json:"ids"`
|
||||
}
|
||||
|
||||
func GetPermissionByRoleIds(c *gin.Context) {
|
||||
var req GetPermissionByRoleIdsReq
|
||||
if err := c.ShouldBind(&req); err != nil {
|
||||
common.ErrorResp(c, err, 400)
|
||||
return
|
||||
}
|
||||
permissions, err := op.GetPermissionByRoleIds(req.Ids)
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
common.SuccessResp(c, permissions)
|
||||
}
|
||||
@@ -154,7 +154,8 @@ func autoRegister(username, userID string, err error) (*model.User, error) {
|
||||
Password: random.String(16),
|
||||
Permission: int32(setting.GetInt(conf.SSODefaultPermission, 0)),
|
||||
BasePath: setting.GetStr(conf.SSODefaultDir),
|
||||
Role: 0,
|
||||
//Role: []int{0},
|
||||
RoleInfo: []uint{0},
|
||||
Disabled: false,
|
||||
SsoID: userID,
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ func getTaskInfo[T task.TaskExtensionInfo](task T) TaskInfo {
|
||||
creatorRole := -1
|
||||
if task.GetCreator() != nil {
|
||||
creatorName = task.GetCreator().Username
|
||||
creatorRole = task.GetCreator().Role
|
||||
creatorRole = int(task.GetCreator().RoleInfo[0])
|
||||
}
|
||||
return TaskInfo{
|
||||
ID: task.GetID(),
|
||||
|
||||
@@ -60,10 +60,10 @@ func UpdateUser(c *gin.Context) {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
}
|
||||
if user.Role != req.Role {
|
||||
/*if !reflect.DeepEqual(user.Role, req.Role) {
|
||||
common.ErrorStrResp(c, "role can not be changed", 400)
|
||||
return
|
||||
}
|
||||
}*/
|
||||
if req.Password == "" {
|
||||
req.PwdHash = user.PwdHash
|
||||
req.Salt = user.Salt
|
||||
|
||||
@@ -161,6 +161,20 @@ func admin(g *gin.RouterGroup) {
|
||||
index.POST("/stop", middlewares.SearchIndex, handles.StopIndex)
|
||||
index.POST("/clear", middlewares.SearchIndex, handles.ClearIndex)
|
||||
index.GET("/progress", middlewares.SearchIndex, handles.GetProgress)
|
||||
|
||||
permission := g.Group("/permission")
|
||||
permission.POST("/create", handles.CreatePermission)
|
||||
permission.GET("/list", handles.ListPermissions)
|
||||
permission.POST("/update", handles.UpdatePermission)
|
||||
permission.POST("/delete", handles.DeletePermission)
|
||||
|
||||
role := g.Group("/role")
|
||||
role.POST("/create", handles.CreateRole)
|
||||
role.GET("/list", handles.ListRoles)
|
||||
role.POST("/update", handles.UpdateRole)
|
||||
role.POST("/delete", handles.DeleteRole)
|
||||
role.GET("/get_permission", handles.GetPermissionByRoleIds)
|
||||
|
||||
}
|
||||
|
||||
func _fs(g *gin.RouterGroup) {
|
||||
|
||||
Reference in New Issue
Block a user