mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-09-20 20:56:20 +08:00
Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
e97f0a289e | |||
89f35170b3 | |||
8188fb2d7d | |||
87cf95f50b | |||
8ab26cb823 | |||
5880c8e1af | |||
14bf4ecb4c | |||
04a5e58781 | |||
bbd4389345 | |||
f350ccdf95 | |||
4f2de9395e | |||
b0dbbebfb0 | |||
0c27b4bd47 | |||
736cd9e5f2 | |||
c7a603c926 | |||
a28d6d5693 | |||
e59d2233e2 | |||
01914a06ef | |||
6499374d1c | |||
b054919d5c | |||
048ee9c2e5 | |||
23394548ca | |||
b04677b806 | |||
e4c902dd93 | |||
5d8bd258c0 | |||
08c5283c8c | |||
10a14f10cd | |||
f86ebc52a0 | |||
016ed90efa | |||
d76407b201 | |||
5de6b660f2 | |||
71ada3b656 | |||
dc42f0e226 | |||
74bf9f6467 |
56
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
56
.github/PULL_REQUEST_TEMPLATE.md
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<!--
|
||||||
|
Provide a general summary of your changes in the Title above.
|
||||||
|
The PR title must start with `feat(): `, `docs(): `, `fix(): `, `style(): `, or `refactor(): `, `chore(): `. For example: `feat(component): add new feature`.
|
||||||
|
If it spans multiple components, use the main component as the prefix and enumerate in the title, describe in the body.
|
||||||
|
-->
|
||||||
|
<!--
|
||||||
|
在上方标题中提供您更改的总体摘要。
|
||||||
|
PR 标题需以 `feat(): `, `docs(): `, `fix(): `, `style(): `, `refactor(): `, `chore(): ` 其中之一开头,例如:`feat(component): 新增功能`。
|
||||||
|
如果跨多个组件,请使用主要组件作为前缀,并在标题中枚举、描述中说明。
|
||||||
|
-->
|
||||||
|
|
||||||
|
## Description / 描述
|
||||||
|
|
||||||
|
<!-- Describe your changes in detail -->
|
||||||
|
<!-- 详细描述您的更改 -->
|
||||||
|
|
||||||
|
## Motivation and Context / 背景
|
||||||
|
|
||||||
|
<!-- Why is this change required? What problem does it solve? -->
|
||||||
|
<!-- 为什么需要此更改?它解决了什么问题? -->
|
||||||
|
|
||||||
|
<!-- If it fixes an open issue, please link to the issue here. -->
|
||||||
|
<!-- 如果修复了一个打开的issue,请在此处链接到该issue -->
|
||||||
|
|
||||||
|
Closes #XXXX
|
||||||
|
|
||||||
|
<!-- or -->
|
||||||
|
<!-- 或者 -->
|
||||||
|
|
||||||
|
Relates to #XXXX
|
||||||
|
|
||||||
|
## How Has This Been Tested? / 测试
|
||||||
|
|
||||||
|
<!-- Please describe in detail how you tested your changes. -->
|
||||||
|
<!-- 请详细描述您如何测试更改 -->
|
||||||
|
|
||||||
|
## Checklist / 检查清单
|
||||||
|
|
||||||
|
<!-- Go over all the following points, and put an `x` in all the boxes that apply. -->
|
||||||
|
<!-- 检查以下所有要点,并在所有适用的框中打`x` -->
|
||||||
|
|
||||||
|
<!-- If you're unsure about any of these, don't hesitate to ask. We're here to help! -->
|
||||||
|
<!-- 如果您对其中任何一项不确定,请不要犹豫提问。我们会帮助您! -->
|
||||||
|
|
||||||
|
- [ ] I have read the [CONTRIBUTING](https://github.com/OpenListTeam/OpenList/blob/main/CONTRIBUTING.md) document.
|
||||||
|
我已阅读 [CONTRIBUTING](https://github.com/OpenListTeam/OpenList/blob/main/CONTRIBUTING.md) 文档。
|
||||||
|
- [ ] I have formatted my code with `go fmt` or [prettier](https://prettier.io/).
|
||||||
|
我已使用 `go fmt` 或 [prettier](https://prettier.io/) 格式化提交的代码。
|
||||||
|
- [ ] I have added appropriate labels to this PR (or mentioned needed labels in the description if lacking permissions).
|
||||||
|
我已为此 PR 添加了适当的标签(如无权限或需要的标签不存在,请在描述中说明,管理员将后续处理)。
|
||||||
|
- [ ] I have requested review from relevant code authors using the "Request review" feature when applicable.
|
||||||
|
我已在适当情况下使用"Request review"功能请求相关代码作者进行审查。
|
||||||
|
- [ ] I have updated the repository accordingly (If it’s needed).
|
||||||
|
我已相应更新了相关仓库(若适用)。
|
||||||
|
- [ ] [OpenList-Frontend](https://github.com/OpenListTeam/OpenList-Frontend) #XXXX
|
||||||
|
- [ ] [OpenList-Docs](https://github.com/OpenListTeam/OpenList-Docs) #XXXX
|
38
.github/workflows/sync_repo.yml
vendored
Normal file
38
.github/workflows/sync_repo.yml
vendored
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
name: Sync to Gitee
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
sync:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
name: Sync GitHub to Gitee
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Setup SSH
|
||||||
|
run: |
|
||||||
|
mkdir -p ~/.ssh
|
||||||
|
echo "${{ secrets.GITEE_SSH_PRIVATE_KEY }}" > ~/.ssh/id_rsa
|
||||||
|
chmod 600 ~/.ssh/id_rsa
|
||||||
|
ssh-keyscan gitee.com >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
|
- name: Create single commit and push
|
||||||
|
run: |
|
||||||
|
git config user.name "GitHub Actions"
|
||||||
|
git config user.email "actions@github.com"
|
||||||
|
|
||||||
|
# Create a new branch
|
||||||
|
git checkout --orphan new-main
|
||||||
|
git add .
|
||||||
|
git commit -m "Sync from GitHub: $(date)"
|
||||||
|
|
||||||
|
# Add Gitee remote and force push
|
||||||
|
git remote add gitee ${{ vars.GITEE_REPO_URL }}
|
||||||
|
git push --force gitee new-main:main
|
77
CONTRIBUTING.md
Normal file
77
CONTRIBUTING.md
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
# Contributing
|
||||||
|
|
||||||
|
## Setup your machine
|
||||||
|
|
||||||
|
`OpenList` is written in [Go](https://golang.org/) and [SolidJS](https://www.solidjs.com/).
|
||||||
|
|
||||||
|
Prerequisites:
|
||||||
|
|
||||||
|
- [git](https://git-scm.com)
|
||||||
|
- [Go 1.24+](https://golang.org/doc/install)
|
||||||
|
- [gcc](https://gcc.gnu.org/)
|
||||||
|
- [nodejs](https://nodejs.org/)
|
||||||
|
|
||||||
|
## Cloning a fork
|
||||||
|
|
||||||
|
Fork and clone `OpenList` and `OpenList-Frontend` anywhere:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git clone https://github.com/<your-username>/OpenList.git
|
||||||
|
$ git clone --recurse-submodules https://github.com/<your-username>/OpenList-Frontend.git
|
||||||
|
```
|
||||||
|
|
||||||
|
## Creating a branch
|
||||||
|
|
||||||
|
Create a new branch from the `main` branch, with an appropriate name.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ git checkout -b <branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Preview your change
|
||||||
|
|
||||||
|
### backend
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ go run main.go
|
||||||
|
```
|
||||||
|
|
||||||
|
### frontend
|
||||||
|
|
||||||
|
```shell
|
||||||
|
$ pnpm dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add a new driver
|
||||||
|
|
||||||
|
Copy `drivers/template` folder and rename it, and follow the comments in it.
|
||||||
|
|
||||||
|
## Create a commit
|
||||||
|
|
||||||
|
Commit messages should be well formatted, and to make that "standardized".
|
||||||
|
|
||||||
|
Submit your pull request. For PR titles, follow [Conventional Commits](https://www.conventionalcommits.org).
|
||||||
|
|
||||||
|
https://github.com/OpenListTeam/OpenList/issues/376
|
||||||
|
|
||||||
|
It's suggested to sign your commits. See: [How to sign commits](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits)
|
||||||
|
|
||||||
|
## Submit a pull request
|
||||||
|
|
||||||
|
Please make sure your code has been formatted with `go fmt` or [prettier](https://prettier.io/) before submitting.
|
||||||
|
|
||||||
|
Push your branch to your `openlist` fork and open a pull request against the `main` branch.
|
||||||
|
|
||||||
|
## Merge your pull request
|
||||||
|
|
||||||
|
Your pull request will be merged after review. Please wait for the maintainer to merge your pull request after review.
|
||||||
|
|
||||||
|
At least 1 approving review is required by reviewers with write access. You can also request a review from maintainers.
|
||||||
|
|
||||||
|
## Delete your branch
|
||||||
|
|
||||||
|
(Optional) After your pull request is merged, you can delete your branch.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Thank you for your contribution! Let's make OpenList better together!
|
@ -20,11 +20,12 @@ ARG GID=1001
|
|||||||
|
|
||||||
WORKDIR /opt/openlist/
|
WORKDIR /opt/openlist/
|
||||||
|
|
||||||
COPY --chmod=755 --from=builder /app/bin/openlist ./
|
RUN addgroup -g ${GID} ${USER} && \
|
||||||
COPY --chmod=755 entrypoint.sh /entrypoint.sh
|
adduser -D -u ${UID} -G ${USER} ${USER} && \
|
||||||
RUN adduser -u ${UID} -g ${GID} -h /opt/openlist/data -D -s /bin/sh ${USER} \
|
mkdir -p /opt/openlist/data
|
||||||
&& chown -R ${UID}:${GID} /opt \
|
|
||||||
&& chown -R ${UID}:${GID} /entrypoint.sh
|
COPY --from=builder --chmod=755 --chown=${UID}:${GID} /app/bin/openlist ./
|
||||||
|
COPY --chmod=755 --chown=${UID}:${GID} entrypoint.sh /entrypoint.sh
|
||||||
|
|
||||||
USER ${USER}
|
USER ${USER}
|
||||||
RUN /entrypoint.sh version
|
RUN /entrypoint.sh version
|
@ -10,12 +10,12 @@ ARG GID=1001
|
|||||||
|
|
||||||
WORKDIR /opt/openlist/
|
WORKDIR /opt/openlist/
|
||||||
|
|
||||||
COPY --chmod=755 /build/${TARGETPLATFORM}/openlist ./
|
RUN addgroup -g ${GID} ${USER} && \
|
||||||
COPY --chmod=755 entrypoint.sh /entrypoint.sh
|
adduser -D -u ${UID} -G ${USER} ${USER} && \
|
||||||
|
mkdir -p /opt/openlist/data
|
||||||
|
|
||||||
RUN adduser -u ${UID} -g ${GID} -h /opt/openlist/data -D -s /bin/sh ${USER} \
|
COPY --chmod=755 --chown=${UID}:${GID} /build/${TARGETPLATFORM}/openlist ./
|
||||||
&& chown -R ${UID}:${GID} /opt \
|
COPY --chmod=755 --chown=${UID}:${GID} entrypoint.sh /entrypoint.sh
|
||||||
&& chown -R ${UID}:${GID} /entrypoint.sh
|
|
||||||
|
|
||||||
USER ${USER}
|
USER ${USER}
|
||||||
RUN /entrypoint.sh version
|
RUN /entrypoint.sh version
|
11
buf.gen.yaml
11
buf.gen.yaml
@ -1,11 +0,0 @@
|
|||||||
version: v1
|
|
||||||
plugins:
|
|
||||||
- plugin: buf.build/protocolbuffers/go:v1.36.7
|
|
||||||
out: .
|
|
||||||
opt:
|
|
||||||
- paths=source_relative
|
|
||||||
- plugin: buf.build/grpc/go:v1.5.1
|
|
||||||
out: .
|
|
||||||
opt:
|
|
||||||
- paths=source_relative
|
|
||||||
- require_unimplemented_servers=false
|
|
@ -1,42 +1,51 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v5/cmd/flags"
|
"github.com/OpenListTeam/OpenList/v4/internal/bootstrap"
|
||||||
"github.com/OpenListTeam/OpenList/v5/internal/bootstrap"
|
"github.com/OpenListTeam/OpenList/v4/internal/bootstrap/data"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/OpenListTeam/OpenList/v4/internal/db"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Init(ctx context.Context) {
|
func Init() {
|
||||||
if flags.Dev {
|
|
||||||
flags.Debug = true
|
|
||||||
}
|
|
||||||
initLogrus()
|
|
||||||
bootstrap.InitConfig()
|
bootstrap.InitConfig()
|
||||||
bootstrap.InitDriverPlugins()
|
bootstrap.Log()
|
||||||
|
bootstrap.InitDB()
|
||||||
|
data.InitData()
|
||||||
|
bootstrap.InitStreamLimit()
|
||||||
|
bootstrap.InitIndex()
|
||||||
|
bootstrap.InitUpgradePatch()
|
||||||
}
|
}
|
||||||
|
|
||||||
func Release() {
|
func Release() {
|
||||||
|
db.Close()
|
||||||
}
|
}
|
||||||
|
|
||||||
func initLog(l *logrus.Logger) {
|
var pid = -1
|
||||||
if flags.Debug {
|
var pidFile string
|
||||||
l.SetLevel(logrus.DebugLevel)
|
|
||||||
l.SetReportCaller(true)
|
func initDaemon() {
|
||||||
} else {
|
ex, err := os.Executable()
|
||||||
l.SetLevel(logrus.InfoLevel)
|
if err != nil {
|
||||||
l.SetReportCaller(false)
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
exPath := filepath.Dir(ex)
|
||||||
|
_ = os.MkdirAll(filepath.Join(exPath, "daemon"), 0700)
|
||||||
|
pidFile = filepath.Join(exPath, "daemon/pid")
|
||||||
|
if utils.Exists(pidFile) {
|
||||||
|
bytes, err := os.ReadFile(pidFile)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("failed to read pid file", err)
|
||||||
|
}
|
||||||
|
id, err := strconv.Atoi(string(bytes))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal("failed to parse pid data", err)
|
||||||
|
}
|
||||||
|
pid = id
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
func initLogrus() {
|
|
||||||
formatter := logrus.TextFormatter{
|
|
||||||
ForceColors: true,
|
|
||||||
EnvironmentOverrideColors: true,
|
|
||||||
TimestampFormat: "2006-01-02 15:04:05",
|
|
||||||
FullTimestamp: true,
|
|
||||||
}
|
|
||||||
logrus.SetFormatter(&formatter)
|
|
||||||
initLog(logrus.StandardLogger())
|
|
||||||
}
|
|
||||||
|
@ -1,40 +1,10 @@
|
|||||||
package flags
|
package flags
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
|
|
||||||
"github.com/sirupsen/logrus"
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ConfigFile string
|
DataDir string
|
||||||
Debug bool
|
Debug bool
|
||||||
NoPrefix bool
|
NoPrefix bool
|
||||||
Dev bool
|
Dev bool
|
||||||
ForceBinDir bool
|
ForceBinDir bool
|
||||||
LogStd bool
|
LogStd bool
|
||||||
|
|
||||||
pwd string
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Program working directory
|
|
||||||
func PWD() string {
|
|
||||||
if pwd != "" {
|
|
||||||
return pwd
|
|
||||||
}
|
|
||||||
if ForceBinDir {
|
|
||||||
ex, err := os.Executable()
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
pwd = filepath.Dir(ex)
|
|
||||||
return pwd
|
|
||||||
}
|
|
||||||
d, err := os.Getwd()
|
|
||||||
if err != nil {
|
|
||||||
logrus.Fatal(err)
|
|
||||||
}
|
|
||||||
pwd = d
|
|
||||||
return d
|
|
||||||
}
|
|
||||||
|
11
cmd/root.go
11
cmd/root.go
@ -4,7 +4,10 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v5/cmd/flags"
|
"github.com/OpenListTeam/OpenList/v4/cmd/flags"
|
||||||
|
_ "github.com/OpenListTeam/OpenList/v4/drivers"
|
||||||
|
_ "github.com/OpenListTeam/OpenList/v4/internal/archive"
|
||||||
|
_ "github.com/OpenListTeam/OpenList/v4/internal/offline_download"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -24,10 +27,10 @@ func Execute() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.PersistentFlags().StringVarP(&flags.ConfigFile, "config", "c", "data/config.json", "config file")
|
RootCmd.PersistentFlags().StringVar(&flags.DataDir, "data", "data", "data folder")
|
||||||
RootCmd.PersistentFlags().BoolVar(&flags.Debug, "debug", false, "start with debug mode")
|
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.NoPrefix, "no-prefix", false, "disable env prefix")
|
||||||
RootCmd.PersistentFlags().BoolVar(&flags.Dev, "dev", false, "start with dev mode")
|
RootCmd.PersistentFlags().BoolVar(&flags.Dev, "dev", false, "start with dev mode")
|
||||||
RootCmd.PersistentFlags().BoolVarP(&flags.ForceBinDir, "force-bin-dir", "f", false, "force to use the directory where the binary file is located as data directory")
|
RootCmd.PersistentFlags().BoolVar(&flags.ForceBinDir, "force-bin-dir", false, "Force to use the directory where the binary file is located as data directory")
|
||||||
RootCmd.PersistentFlags().BoolVar(&flags.LogStd, "log-std", false, "force to log to std")
|
RootCmd.PersistentFlags().BoolVar(&flags.LogStd, "log-std", false, "Force to log to std")
|
||||||
}
|
}
|
||||||
|
199
cmd/server.go
199
cmd/server.go
@ -13,9 +13,15 @@ import (
|
|||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v5/cmd/flags"
|
"github.com/OpenListTeam/OpenList/v4/cmd/flags"
|
||||||
"github.com/OpenListTeam/OpenList/v5/internal/conf"
|
"github.com/OpenListTeam/OpenList/v4/internal/bootstrap"
|
||||||
"github.com/OpenListTeam/OpenList/v5/server"
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/fs"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/server"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/server/middlewares"
|
||||||
|
"github.com/OpenListTeam/sftpd-openlist"
|
||||||
|
ftpserver "github.com/fclairamb/ftpserverlib"
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -29,127 +35,220 @@ var ServerCmd = &cobra.Command{
|
|||||||
Short: "Start the server at the specified address",
|
Short: "Start the server at the specified address",
|
||||||
Long: `Start the server at the specified address
|
Long: `Start the server at the specified address
|
||||||
the address is defined in config file`,
|
the address is defined in config file`,
|
||||||
Run: func(_ *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
serverCtx, serverCancel := context.WithCancel(context.Background())
|
Init()
|
||||||
defer serverCancel()
|
if conf.Conf.DelayedStart != 0 {
|
||||||
Init(serverCtx)
|
utils.Log.Infof("delayed start for %d seconds", conf.Conf.DelayedStart)
|
||||||
|
time.Sleep(time.Duration(conf.Conf.DelayedStart) * time.Second)
|
||||||
if !flags.Debug {
|
}
|
||||||
|
bootstrap.InitOfflineDownloadTools()
|
||||||
|
bootstrap.LoadStorages()
|
||||||
|
bootstrap.InitTaskManager()
|
||||||
|
if !flags.Debug && !flags.Dev {
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
}
|
}
|
||||||
r := gin.New()
|
r := gin.New()
|
||||||
r.Use(gin.LoggerWithWriter(log.StandardLogger().Out))
|
|
||||||
r.Use(gin.RecoveryWithWriter(log.StandardLogger().Out))
|
|
||||||
server.Init(r)
|
|
||||||
|
|
||||||
|
// gin log
|
||||||
|
if conf.Conf.Log.Filter.Enable {
|
||||||
|
r.Use(middlewares.FilteredLogger())
|
||||||
|
} else {
|
||||||
|
r.Use(gin.LoggerWithWriter(log.StandardLogger().Out))
|
||||||
|
}
|
||||||
|
r.Use(gin.RecoveryWithWriter(log.StandardLogger().Out))
|
||||||
|
|
||||||
|
server.Init(r)
|
||||||
var httpHandler http.Handler = r
|
var httpHandler http.Handler = r
|
||||||
if conf.Conf.Scheme.EnableH2c {
|
if conf.Conf.Scheme.EnableH2c {
|
||||||
httpHandler = h2c.NewHandler(r, &http2.Server{})
|
httpHandler = h2c.NewHandler(r, &http2.Server{})
|
||||||
}
|
}
|
||||||
var httpSrv, httpsSrv, unixSrv *http.Server
|
var httpSrv, httpsSrv, unixSrv *http.Server
|
||||||
if conf.Conf.Scheme.HttpPort > 0 {
|
if conf.Conf.Scheme.HttpPort != -1 {
|
||||||
httpBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpPort)
|
httpBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpPort)
|
||||||
log.Infoln("start HTTP server", "@", httpBase)
|
fmt.Printf("start HTTP server @ %s\n", httpBase)
|
||||||
|
utils.Log.Infof("start HTTP server @ %s", httpBase)
|
||||||
httpSrv = &http.Server{Addr: httpBase, Handler: httpHandler}
|
httpSrv = &http.Server{Addr: httpBase, Handler: httpHandler}
|
||||||
go func() {
|
go func() {
|
||||||
err := httpSrv.ListenAndServe()
|
err := httpSrv.ListenAndServe()
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Errorln("start HTTP server", ":", err)
|
utils.Log.Fatalf("failed to start http: %s", err.Error())
|
||||||
serverCancel()
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if conf.Conf.Scheme.HttpsPort > 0 {
|
if conf.Conf.Scheme.HttpsPort != -1 {
|
||||||
httpsBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpsPort)
|
httpsBase := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.Scheme.HttpsPort)
|
||||||
log.Infoln("start HTTPS server", "@", httpsBase)
|
fmt.Printf("start HTTPS server @ %s\n", httpsBase)
|
||||||
|
utils.Log.Infof("start HTTPS server @ %s", httpsBase)
|
||||||
httpsSrv = &http.Server{Addr: httpsBase, Handler: r}
|
httpsSrv = &http.Server{Addr: httpsBase, Handler: r}
|
||||||
go func() {
|
go func() {
|
||||||
err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
err := httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Errorln("start HTTPS server", ":", err)
|
utils.Log.Fatalf("failed to start https: %s", err.Error())
|
||||||
serverCancel()
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if conf.Conf.Scheme.UnixFile != "" {
|
if conf.Conf.Scheme.UnixFile != "" {
|
||||||
log.Infoln("start Unix server", "@", conf.Conf.Scheme.UnixFile)
|
fmt.Printf("start unix server @ %s\n", conf.Conf.Scheme.UnixFile)
|
||||||
|
utils.Log.Infof("start unix server @ %s", conf.Conf.Scheme.UnixFile)
|
||||||
unixSrv = &http.Server{Handler: httpHandler}
|
unixSrv = &http.Server{Handler: httpHandler}
|
||||||
go func() {
|
go func() {
|
||||||
listener, err := net.Listen("unix", conf.Conf.Scheme.UnixFile)
|
listener, err := net.Listen("unix", conf.Conf.Scheme.UnixFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("start Unix server", ":", err)
|
utils.Log.Fatalf("failed to listen unix: %+v", err)
|
||||||
serverCancel()
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
// set socket file permission
|
||||||
mode, err := strconv.ParseUint(conf.Conf.Scheme.UnixFilePerm, 8, 32)
|
mode, err := strconv.ParseUint(conf.Conf.Scheme.UnixFilePerm, 8, 32)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("parse unix_file_perm", ":", err)
|
utils.Log.Errorf("failed to parse socket file permission: %+v", err)
|
||||||
} else {
|
} else {
|
||||||
err = os.Chmod(conf.Conf.Scheme.UnixFile, os.FileMode(mode))
|
err = os.Chmod(conf.Conf.Scheme.UnixFile, os.FileMode(mode))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorln("chmod socket file", ":", err)
|
utils.Log.Errorf("failed to chmod socket file: %+v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = unixSrv.Serve(listener)
|
err = unixSrv.Serve(listener)
|
||||||
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
log.Errorln("start Unix server", ":", err)
|
utils.Log.Fatalf("failed to start unix: %s", err.Error())
|
||||||
serverCancel()
|
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
if conf.Conf.S3.Port != -1 && conf.Conf.S3.Enable {
|
||||||
|
s3r := gin.New()
|
||||||
|
s3r.Use(gin.LoggerWithWriter(log.StandardLogger().Out), gin.RecoveryWithWriter(log.StandardLogger().Out))
|
||||||
|
server.InitS3(s3r)
|
||||||
|
s3Base := fmt.Sprintf("%s:%d", conf.Conf.Scheme.Address, conf.Conf.S3.Port)
|
||||||
|
fmt.Printf("start S3 server @ %s\n", s3Base)
|
||||||
|
utils.Log.Infof("start S3 server @ %s", s3Base)
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
if conf.Conf.S3.SSL {
|
||||||
|
httpsSrv = &http.Server{Addr: s3Base, Handler: s3r}
|
||||||
|
err = httpsSrv.ListenAndServeTLS(conf.Conf.Scheme.CertFile, conf.Conf.Scheme.KeyFile)
|
||||||
|
}
|
||||||
|
if !conf.Conf.S3.SSL {
|
||||||
|
httpSrv = &http.Server{Addr: s3Base, Handler: s3r}
|
||||||
|
err = httpSrv.ListenAndServe()
|
||||||
|
}
|
||||||
|
if err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
utils.Log.Fatalf("failed to start s3 server: %s", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
var ftpDriver *server.FtpMainDriver
|
||||||
|
var ftpServer *ftpserver.FtpServer
|
||||||
|
if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable {
|
||||||
|
var err error
|
||||||
|
ftpDriver, err = server.NewMainDriver()
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.Fatalf("failed to start ftp driver: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("start ftp server on %s\n", conf.Conf.FTP.Listen)
|
||||||
|
utils.Log.Infof("start ftp server on %s", conf.Conf.FTP.Listen)
|
||||||
|
go func() {
|
||||||
|
ftpServer = ftpserver.NewFtpServer(ftpDriver)
|
||||||
|
err = ftpServer.ListenAndServe()
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.Fatalf("problem ftp server listening: %s", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var sftpDriver *server.SftpDriver
|
||||||
|
var sftpServer *sftpd.SftpServer
|
||||||
|
if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable {
|
||||||
|
var err error
|
||||||
|
sftpDriver, err = server.NewSftpDriver()
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.Fatalf("failed to start sftp driver: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Printf("start sftp server on %s", conf.Conf.SFTP.Listen)
|
||||||
|
utils.Log.Infof("start sftp server on %s", conf.Conf.SFTP.Listen)
|
||||||
|
go func() {
|
||||||
|
sftpServer = sftpd.NewSftpServer(sftpDriver)
|
||||||
|
err = sftpServer.RunServer()
|
||||||
|
if err != nil {
|
||||||
|
utils.Log.Fatalf("problem sftp server listening: %s", err.Error())
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Wait for interrupt signal to gracefully shutdown the server with
|
||||||
|
// a timeout of 1 second.
|
||||||
quit := make(chan os.Signal, 1)
|
quit := make(chan os.Signal, 1)
|
||||||
// kill (no param) default send syscanll.SIGTERM
|
// kill (no param) default send syscanll.SIGTERM
|
||||||
// kill -2 is syscall.SIGINT
|
// kill -2 is syscall.SIGINT
|
||||||
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
|
// kill -9 is syscall. SIGKILL but can"t be catch, so don't need add it
|
||||||
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
|
||||||
select {
|
<-quit
|
||||||
case <-quit:
|
utils.Log.Println("Shutdown server...")
|
||||||
case <-serverCtx.Done():
|
fs.ArchiveContentUploadTaskManager.RemoveAll()
|
||||||
}
|
|
||||||
|
|
||||||
log.Println("shutdown server...")
|
|
||||||
Release()
|
Release()
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 1*time.Second)
|
||||||
quitCtx, quitCancel := context.WithTimeout(context.Background(), time.Second)
|
defer cancel()
|
||||||
defer quitCancel()
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
if httpSrv != nil {
|
if conf.Conf.Scheme.HttpPort != -1 {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := httpSrv.Shutdown(quitCtx); err != nil {
|
if err := httpSrv.Shutdown(ctx); err != nil {
|
||||||
log.Errorln("shutdown HTTP server", ":", err)
|
utils.Log.Fatal("HTTP server shutdown err: ", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if httpsSrv != nil {
|
if conf.Conf.Scheme.HttpsPort != -1 {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := httpsSrv.Shutdown(quitCtx); err != nil {
|
if err := httpsSrv.Shutdown(ctx); err != nil {
|
||||||
log.Errorln("shutdown HTTPS server", ":", err)
|
utils.Log.Fatal("HTTPS server shutdown err: ", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
if unixSrv != nil {
|
if conf.Conf.Scheme.UnixFile != "" {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
if err := unixSrv.Shutdown(quitCtx); err != nil {
|
if err := unixSrv.Shutdown(ctx); err != nil {
|
||||||
log.Errorln("shutdown Unix server", ":", err)
|
utils.Log.Fatal("Unix server shutdown err: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if conf.Conf.FTP.Listen != "" && conf.Conf.FTP.Enable && ftpServer != nil && ftpDriver != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
ftpDriver.Stop()
|
||||||
|
if err := ftpServer.Stop(); err != nil {
|
||||||
|
utils.Log.Fatal("FTP server shutdown err: ", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
if conf.Conf.SFTP.Listen != "" && conf.Conf.SFTP.Enable && sftpServer != nil && sftpDriver != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
defer wg.Done()
|
||||||
|
if err := sftpServer.Close(); err != nil {
|
||||||
|
utils.Log.Fatal("SFTP server shutdown err: ", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
log.Println("server exit")
|
utils.Log.Println("Server exit")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
RootCmd.AddCommand(ServerCmd)
|
RootCmd.AddCommand(ServerCmd)
|
||||||
|
|
||||||
|
// Here you will define your flags and configuration settings.
|
||||||
|
|
||||||
|
// Cobra supports Persistent Flags which will work for this command
|
||||||
|
// and all subcommands, e.g.:
|
||||||
|
// serverCmd.PersistentFlags().String("foo", "", "A help for foo")
|
||||||
|
|
||||||
|
// Cobra supports local flags which will only run when this command
|
||||||
|
// is called directly, e.g.:
|
||||||
|
// serverCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
|
||||||
}
|
}
|
||||||
|
|
||||||
// OutOpenListInit 暴露用于外部启动server的函数
|
// OutOpenListInit 暴露用于外部启动server的函数
|
||||||
|
@ -6,10 +6,9 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- '5244:5244'
|
- '5244:5244'
|
||||||
- '5245:5245'
|
- '5245:5245'
|
||||||
|
user: '0:0'
|
||||||
environment:
|
environment:
|
||||||
- PUID=0
|
|
||||||
- PGID=0
|
|
||||||
- UMASK=022
|
- UMASK=022
|
||||||
- TZ=UTC
|
- TZ=Asia/Shanghai
|
||||||
container_name: openlist
|
container_name: openlist
|
||||||
image: 'openlistteam/openlist:latest'
|
image: 'openlistteam/openlist:latest'
|
60
drivers/115/appver.go
Normal file
60
drivers/115/appver.go
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
package _115
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
|
driver115 "github.com/SheltonZhu/115driver/pkg/driver"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
md5Salt = "Qclm8MGWUv59TnrR0XPg"
|
||||||
|
appVer = "35.6.0.3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (d *Pan115) getAppVersion() (string, error) {
|
||||||
|
result := VersionResp{}
|
||||||
|
res, err := base.RestyClient.R().Get(driver115.ApiGetVersion)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
err = utils.Json.Unmarshal(res.Body(), &result)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if len(result.Error) > 0 {
|
||||||
|
return "", errors.New(result.Error)
|
||||||
|
}
|
||||||
|
return result.Data.Win.Version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Pan115) getAppVer() string {
|
||||||
|
ver, err := d.getAppVersion()
|
||||||
|
if err != nil {
|
||||||
|
log.Warnf("[115] get app version failed: %v", err)
|
||||||
|
return appVer
|
||||||
|
}
|
||||||
|
if len(ver) > 0 {
|
||||||
|
return ver
|
||||||
|
}
|
||||||
|
return appVer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Pan115) initAppVer() {
|
||||||
|
appVer = d.getAppVer()
|
||||||
|
log.Debugf("use app version: %v", appVer)
|
||||||
|
}
|
||||||
|
|
||||||
|
type VersionResp struct {
|
||||||
|
Error string `json:"error,omitempty"`
|
||||||
|
Data Versions `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Versions struct {
|
||||||
|
Win Version `json:"win"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Version struct {
|
||||||
|
Version string `json:"version_code"`
|
||||||
|
}
|
@ -17,6 +17,7 @@ import (
|
|||||||
type Open123 struct {
|
type Open123 struct {
|
||||||
model.Storage
|
model.Storage
|
||||||
Addition
|
Addition
|
||||||
|
UID uint64
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Open123) Config() driver.Config {
|
func (d *Open123) Config() driver.Config {
|
||||||
@ -69,13 +70,45 @@ func (d *Open123) List(ctx context.Context, dir model.Obj, args model.ListArgs)
|
|||||||
func (d *Open123) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
func (d *Open123) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||||
fileId, _ := strconv.ParseInt(file.GetID(), 10, 64)
|
fileId, _ := strconv.ParseInt(file.GetID(), 10, 64)
|
||||||
|
|
||||||
|
if d.DirectLink {
|
||||||
|
res, err := d.getDirectLink(fileId)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if d.DirectLinkPrivateKey == "" {
|
||||||
|
duration := 365 * 24 * time.Hour // 缓存1年
|
||||||
|
return &model.Link{
|
||||||
|
URL: res.Data.URL,
|
||||||
|
Expiration: &duration,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
uid, err := d.getUID()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
duration := time.Duration(d.DirectLinkValidDuration) * time.Minute
|
||||||
|
|
||||||
|
newURL, err := d.SignURL(res.Data.URL, d.DirectLinkPrivateKey,
|
||||||
|
uid, duration)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &model.Link{
|
||||||
|
URL: newURL,
|
||||||
|
Expiration: &duration,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
res, err := d.getDownloadInfo(fileId)
|
res, err := d.getDownloadInfo(fileId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
link := model.Link{URL: res.Data.DownloadUrl}
|
return &model.Link{URL: res.Data.DownloadUrl}, nil
|
||||||
return &link, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Open123) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
func (d *Open123) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
@ -23,6 +23,11 @@ type Addition struct {
|
|||||||
// 上传线程数
|
// 上传线程数
|
||||||
UploadThread int `json:"UploadThread" type:"number" default:"3" help:"the threads of upload"`
|
UploadThread int `json:"UploadThread" type:"number" default:"3" help:"the threads of upload"`
|
||||||
|
|
||||||
|
// 使用直链
|
||||||
|
DirectLink bool `json:"DirectLink" type:"bool" default:"false" required:"false" help:"use direct link when download file"`
|
||||||
|
DirectLinkPrivateKey string `json:"DirectLinkPrivateKey" required:"false" help:"private key for direct link, if URL authentication is enabled"`
|
||||||
|
DirectLinkValidDuration int64 `json:"DirectLinkValidDuration" type:"number" default:"30" required:"false" help:"minutes, if URL authentication is enabled"`
|
||||||
|
|
||||||
driver.RootID
|
driver.RootID
|
||||||
}
|
}
|
||||||
|
|
@ -127,19 +127,19 @@ type RefreshTokenResp struct {
|
|||||||
type UserInfoResp struct {
|
type UserInfoResp struct {
|
||||||
BaseResp
|
BaseResp
|
||||||
Data struct {
|
Data struct {
|
||||||
UID int64 `json:"uid"`
|
UID uint64 `json:"uid"`
|
||||||
Username string `json:"username"`
|
// Username string `json:"username"`
|
||||||
DisplayName string `json:"displayName"`
|
// DisplayName string `json:"displayName"`
|
||||||
HeadImage string `json:"headImage"`
|
// HeadImage string `json:"headImage"`
|
||||||
Passport string `json:"passport"`
|
// Passport string `json:"passport"`
|
||||||
Mail string `json:"mail"`
|
// Mail string `json:"mail"`
|
||||||
SpaceUsed int64 `json:"spaceUsed"`
|
// SpaceUsed int64 `json:"spaceUsed"`
|
||||||
SpacePermanent int64 `json:"spacePermanent"`
|
// SpacePermanent int64 `json:"spacePermanent"`
|
||||||
SpaceTemp int64 `json:"spaceTemp"`
|
// SpaceTemp int64 `json:"spaceTemp"`
|
||||||
SpaceTempExpr string `json:"spaceTempExpr"`
|
// SpaceTempExpr int64 `json:"spaceTempExpr"`
|
||||||
Vip bool `json:"vip"`
|
// Vip bool `json:"vip"`
|
||||||
DirectTraffic int64 `json:"directTraffic"`
|
// DirectTraffic int64 `json:"directTraffic"`
|
||||||
IsHideUID bool `json:"isHideUID"`
|
// IsHideUID bool `json:"isHideUID"`
|
||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,6 +158,13 @@ type DownloadInfoResp struct {
|
|||||||
} `json:"data"`
|
} `json:"data"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DirectLinkResp struct {
|
||||||
|
BaseResp
|
||||||
|
Data struct {
|
||||||
|
URL string `json:"url"`
|
||||||
|
} `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
// 创建文件V2返回
|
// 创建文件V2返回
|
||||||
type UploadCreateResp struct {
|
type UploadCreateResp struct {
|
||||||
BaseResp
|
BaseResp
|
@ -70,6 +70,8 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
|||||||
var reader *stream.SectionReader
|
var reader *stream.SectionReader
|
||||||
var rateLimitedRd io.Reader
|
var rateLimitedRd io.Reader
|
||||||
sliceMD5 := ""
|
sliceMD5 := ""
|
||||||
|
// 表单
|
||||||
|
b := bytes.NewBuffer(make([]byte, 0, 2048))
|
||||||
threadG.GoWithLifecycle(errgroup.Lifecycle{
|
threadG.GoWithLifecycle(errgroup.Lifecycle{
|
||||||
Before: func(ctx context.Context) error {
|
Before: func(ctx context.Context) error {
|
||||||
if reader == nil {
|
if reader == nil {
|
||||||
@ -84,7 +86,6 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
rateLimitedRd = driver.NewLimitedUploadStream(ctx, reader)
|
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
@ -92,9 +93,8 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
|||||||
// 重置分片reader位置,因为HashReader、上一次失败已经读取到分片EOF
|
// 重置分片reader位置,因为HashReader、上一次失败已经读取到分片EOF
|
||||||
reader.Seek(0, io.SeekStart)
|
reader.Seek(0, io.SeekStart)
|
||||||
|
|
||||||
// 创建表单数据
|
b.Reset()
|
||||||
var b bytes.Buffer
|
w := multipart.NewWriter(b)
|
||||||
w := multipart.NewWriter(&b)
|
|
||||||
// 添加表单字段
|
// 添加表单字段
|
||||||
err = w.WriteField("preuploadID", createResp.Data.PreuploadID)
|
err = w.WriteField("preuploadID", createResp.Data.PreuploadID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -109,21 +109,20 @@ func (d *Open123) Upload(ctx context.Context, file model.FileStreamer, createRes
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
// 写入文件内容
|
// 写入文件内容
|
||||||
fw, err := w.CreateFormFile("slice", fmt.Sprintf("%s.part%d", file.GetName(), partNumber))
|
_, err = w.CreateFormFile("slice", fmt.Sprintf("%s.part%d", file.GetName(), partNumber))
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = utils.CopyWithBuffer(fw, rateLimitedRd)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
headSize := b.Len()
|
||||||
err = w.Close()
|
err = w.Close()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
head := bytes.NewReader(b.Bytes()[:headSize])
|
||||||
|
tail := bytes.NewReader(b.Bytes()[headSize:])
|
||||||
|
rateLimitedRd = driver.NewLimitedUploadStream(ctx, io.MultiReader(head, reader, tail))
|
||||||
// 创建请求并设置header
|
// 创建请求并设置header
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadDomain+"/upload/v2/file/slice", &b)
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, uploadDomain+"/upload/v2/file/slice", rateLimitedRd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
@ -1,15 +1,20 @@
|
|||||||
package _123_open
|
package _123_open
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/md5"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
|
"github.com/google/uuid"
|
||||||
log "github.com/sirupsen/logrus"
|
log "github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -20,7 +25,8 @@ var ( //不同情况下获取的AccessTokenQPS限制不同 如下模块化易于
|
|||||||
RefreshToken = InitApiInfo(Api+"/api/v1/oauth2/access_token", 1)
|
RefreshToken = InitApiInfo(Api+"/api/v1/oauth2/access_token", 1)
|
||||||
UserInfo = InitApiInfo(Api+"/api/v1/user/info", 1)
|
UserInfo = InitApiInfo(Api+"/api/v1/user/info", 1)
|
||||||
FileList = InitApiInfo(Api+"/api/v2/file/list", 3)
|
FileList = InitApiInfo(Api+"/api/v2/file/list", 3)
|
||||||
DownloadInfo = InitApiInfo(Api+"/api/v1/file/download_info", 0)
|
DownloadInfo = InitApiInfo(Api+"/api/v1/file/download_info", 5)
|
||||||
|
DirectLink = InitApiInfo(Api+"/api/v1/direct-link/url", 5)
|
||||||
Mkdir = InitApiInfo(Api+"/upload/v1/file/mkdir", 2)
|
Mkdir = InitApiInfo(Api+"/upload/v1/file/mkdir", 2)
|
||||||
Move = InitApiInfo(Api+"/api/v1/file/move", 1)
|
Move = InitApiInfo(Api+"/api/v1/file/move", 1)
|
||||||
Rename = InitApiInfo(Api+"/api/v1/file/name", 1)
|
Rename = InitApiInfo(Api+"/api/v1/file/name", 1)
|
||||||
@ -80,8 +86,24 @@ func (d *Open123) Request(apiInfo *ApiInfo, method string, callback base.ReqCall
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (d *Open123) flushAccessToken() error {
|
func (d *Open123) flushAccessToken() error {
|
||||||
if d.Addition.ClientID != "" {
|
if d.ClientID != "" {
|
||||||
if d.Addition.ClientSecret != "" {
|
if d.RefreshToken != "" {
|
||||||
|
var resp RefreshTokenResp
|
||||||
|
_, err := d.Request(RefreshToken, http.MethodPost, func(req *resty.Request) {
|
||||||
|
req.SetQueryParam("client_id", d.ClientID)
|
||||||
|
if d.ClientSecret != "" {
|
||||||
|
req.SetQueryParam("client_secret", d.ClientSecret)
|
||||||
|
}
|
||||||
|
req.SetQueryParam("grant_type", "refresh_token")
|
||||||
|
req.SetQueryParam("refresh_token", d.RefreshToken)
|
||||||
|
}, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
d.AccessToken = resp.AccessToken
|
||||||
|
d.RefreshToken = resp.RefreshToken
|
||||||
|
op.MustSaveDriverStorage(d)
|
||||||
|
} else if d.ClientSecret != "" {
|
||||||
var resp AccessTokenResp
|
var resp AccessTokenResp
|
||||||
_, err := d.Request(AccessToken, http.MethodPost, func(req *resty.Request) {
|
_, err := d.Request(AccessToken, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
||||||
@ -94,24 +116,38 @@ func (d *Open123) flushAccessToken() error {
|
|||||||
}
|
}
|
||||||
d.AccessToken = resp.Data.AccessToken
|
d.AccessToken = resp.Data.AccessToken
|
||||||
op.MustSaveDriverStorage(d)
|
op.MustSaveDriverStorage(d)
|
||||||
} else if d.Addition.RefreshToken != "" {
|
|
||||||
var resp RefreshTokenResp
|
|
||||||
_, err := d.Request(RefreshToken, http.MethodPost, func(req *resty.Request) {
|
|
||||||
req.SetQueryParam("client_id", d.ClientID)
|
|
||||||
req.SetQueryParam("grant_type", "refresh_token")
|
|
||||||
req.SetQueryParam("refresh_token", d.Addition.RefreshToken)
|
|
||||||
}, &resp)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
d.AccessToken = resp.AccessToken
|
|
||||||
d.RefreshToken = resp.RefreshToken
|
|
||||||
op.MustSaveDriverStorage(d)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Open123) SignURL(originURL, privateKey string, uid uint64, validDuration time.Duration) (newURL string, err error) {
|
||||||
|
// 生成Unix时间戳
|
||||||
|
ts := time.Now().Add(validDuration).Unix()
|
||||||
|
|
||||||
|
// 生成随机数(建议使用UUID,不能包含中划线(-))
|
||||||
|
rand := strings.ReplaceAll(uuid.New().String(), "-", "")
|
||||||
|
|
||||||
|
// 解析URL
|
||||||
|
objURL, err := url.Parse(originURL)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 待签名字符串,格式:path-timestamp-rand-uid-privateKey
|
||||||
|
unsignedStr := fmt.Sprintf("%s-%d-%s-%d-%s", objURL.Path, ts, rand, uid, privateKey)
|
||||||
|
md5Hash := md5.Sum([]byte(unsignedStr))
|
||||||
|
// 生成鉴权参数,格式:timestamp-rand-uid-md5hash
|
||||||
|
authKey := fmt.Sprintf("%d-%s-%d-%x", ts, rand, uid, md5Hash)
|
||||||
|
|
||||||
|
// 添加鉴权参数到URL查询参数
|
||||||
|
v := objURL.Query()
|
||||||
|
v.Add("auth_key", authKey)
|
||||||
|
objURL.RawQuery = v.Encode()
|
||||||
|
|
||||||
|
return objURL.String(), nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Open123) getUserInfo() (*UserInfoResp, error) {
|
func (d *Open123) getUserInfo() (*UserInfoResp, error) {
|
||||||
var resp UserInfoResp
|
var resp UserInfoResp
|
||||||
|
|
||||||
@ -122,6 +158,18 @@ func (d *Open123) getUserInfo() (*UserInfoResp, error) {
|
|||||||
return &resp, nil
|
return &resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Open123) getUID() (uint64, error) {
|
||||||
|
if d.UID != 0 {
|
||||||
|
return d.UID, nil
|
||||||
|
}
|
||||||
|
resp, err := d.getUserInfo()
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
d.UID = resp.Data.UID
|
||||||
|
return resp.Data.UID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Open123) getFiles(parentFileId int64, limit int, lastFileId int64) (*FileListResp, error) {
|
func (d *Open123) getFiles(parentFileId int64, limit int, lastFileId int64) (*FileListResp, error) {
|
||||||
var resp FileListResp
|
var resp FileListResp
|
||||||
|
|
||||||
@ -159,6 +207,21 @@ func (d *Open123) getDownloadInfo(fileId int64) (*DownloadInfoResp, error) {
|
|||||||
return &resp, nil
|
return &resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Open123) getDirectLink(fileId int64) (*DirectLinkResp, error) {
|
||||||
|
var resp DirectLinkResp
|
||||||
|
|
||||||
|
_, err := d.Request(DirectLink, http.MethodGet, func(req *resty.Request) {
|
||||||
|
req.SetQueryParams(map[string]string{
|
||||||
|
"fileID": strconv.FormatInt(fileId, 10),
|
||||||
|
})
|
||||||
|
}, &resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (d *Open123) mkdir(parentID int64, name string) error {
|
func (d *Open123) mkdir(parentID int64, name string) error {
|
||||||
_, err := d.Request(Mkdir, http.MethodPost, func(req *resty.Request) {
|
_, err := d.Request(Mkdir, http.MethodPost, func(req *resty.Request) {
|
||||||
req.SetBody(base.Json{
|
req.SetBody(base.Json{
|
@ -534,16 +534,15 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
if size > partSize {
|
if size > partSize {
|
||||||
part = (size + partSize - 1) / partSize
|
part = (size + partSize - 1) / partSize
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 生成所有 partInfos
|
||||||
partInfos := make([]PartInfo, 0, part)
|
partInfos := make([]PartInfo, 0, part)
|
||||||
for i := int64(0); i < part; i++ {
|
for i := int64(0); i < part; i++ {
|
||||||
if utils.IsCanceled(ctx) {
|
if utils.IsCanceled(ctx) {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
start := i * partSize
|
start := i * partSize
|
||||||
byteSize := size - start
|
byteSize := min(size-start, partSize)
|
||||||
if byteSize > partSize {
|
|
||||||
byteSize = partSize
|
|
||||||
}
|
|
||||||
partNumber := i + 1
|
partNumber := i + 1
|
||||||
partInfo := PartInfo{
|
partInfo := PartInfo{
|
||||||
PartNumber: partNumber,
|
PartNumber: partNumber,
|
||||||
@ -591,17 +590,20 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
// resp.Data.RapidUpload: true 支持快传,但此处直接检测是否返回分片的上传地址
|
// resp.Data.RapidUpload: true 支持快传,但此处直接检测是否返回分片的上传地址
|
||||||
// 快传的情况下同样需要手动处理冲突
|
// 快传的情况下同样需要手动处理冲突
|
||||||
if resp.Data.PartInfos != nil {
|
if resp.Data.PartInfos != nil {
|
||||||
// 读取前100个分片的上传地址
|
// Progress
|
||||||
uploadPartInfos := resp.Data.PartInfos
|
p := driver.NewProgress(size, up)
|
||||||
|
rateLimited := driver.NewLimitedUploadStream(ctx, stream)
|
||||||
|
|
||||||
// 获取后续分片的上传地址
|
// 先上传前100个分片
|
||||||
for i := 101; i < len(partInfos); i += 100 {
|
err = d.uploadPersonalParts(ctx, partInfos, resp.Data.PartInfos, rateLimited, p)
|
||||||
end := i + 100
|
if err != nil {
|
||||||
if end > len(partInfos) {
|
return err
|
||||||
end = len(partInfos)
|
|
||||||
}
|
}
|
||||||
batchPartInfos := partInfos[i:end]
|
|
||||||
|
|
||||||
|
// 如果还有剩余分片,分批获取上传地址并上传
|
||||||
|
for i := 100; i < len(partInfos); i += 100 {
|
||||||
|
end := min(i+100, len(partInfos))
|
||||||
|
batchPartInfos := partInfos[i:end]
|
||||||
moredata := base.Json{
|
moredata := base.Json{
|
||||||
"fileId": resp.Data.FileId,
|
"fileId": resp.Data.FileId,
|
||||||
"uploadId": resp.Data.UploadId,
|
"uploadId": resp.Data.UploadId,
|
||||||
@ -617,44 +619,13 @@ func (d *Yun139) Put(ctx context.Context, dstDir model.Obj, stream model.FileStr
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
uploadPartInfos = append(uploadPartInfos, moreresp.Data.PartInfos...)
|
err = d.uploadPersonalParts(ctx, partInfos, moreresp.Data.PartInfos, rateLimited, p)
|
||||||
}
|
|
||||||
|
|
||||||
// Progress
|
|
||||||
p := driver.NewProgress(size, up)
|
|
||||||
|
|
||||||
rateLimited := driver.NewLimitedUploadStream(ctx, stream)
|
|
||||||
// 上传所有分片
|
|
||||||
for _, uploadPartInfo := range uploadPartInfos {
|
|
||||||
index := uploadPartInfo.PartNumber - 1
|
|
||||||
partSize := partInfos[index].PartSize
|
|
||||||
log.Debugf("[139] uploading part %+v/%+v", index, len(uploadPartInfos))
|
|
||||||
limitReader := io.LimitReader(rateLimited, partSize)
|
|
||||||
|
|
||||||
// Update Progress
|
|
||||||
r := io.TeeReader(limitReader, p)
|
|
||||||
|
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadPartInfo.UploadUrl, r)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
req.Header.Set("Content-Type", "application/octet-stream")
|
|
||||||
req.Header.Set("Content-Length", fmt.Sprint(partSize))
|
|
||||||
req.Header.Set("Origin", "https://yun.139.com")
|
|
||||||
req.Header.Set("Referer", "https://yun.139.com/")
|
|
||||||
req.ContentLength = partSize
|
|
||||||
|
|
||||||
res, err := base.HttpClient.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_ = res.Body.Close()
|
|
||||||
log.Debugf("[139] uploaded: %+v", res)
|
|
||||||
if res.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("unexpected status code: %d", res.StatusCode)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 全部分片上传完毕后,complete
|
||||||
data = base.Json{
|
data = base.Json{
|
||||||
"contentHash": fullHash,
|
"contentHash": fullHash,
|
||||||
"contentHashAlgorithm": "SHA256",
|
"contentHashAlgorithm": "SHA256",
|
@ -1,9 +1,11 @@
|
|||||||
package _139
|
package _139
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
@ -13,6 +15,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
"github.com/OpenListTeam/OpenList/v4/drivers/base"
|
||||||
|
"github.com/OpenListTeam/OpenList/v4/internal/driver"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
"github.com/OpenListTeam/OpenList/v4/internal/model"
|
||||||
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
"github.com/OpenListTeam/OpenList/v4/internal/op"
|
||||||
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
||||||
@ -623,3 +626,47 @@ func (d *Yun139) getPersonalCloudHost() string {
|
|||||||
}
|
}
|
||||||
return d.PersonalCloudHost
|
return d.PersonalCloudHost
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *Yun139) uploadPersonalParts(ctx context.Context, partInfos []PartInfo, uploadPartInfos []PersonalPartInfo, rateLimited *driver.RateLimitReader, p *driver.Progress) error {
|
||||||
|
// 确保数组以 PartNumber 从小到大排序
|
||||||
|
sort.Slice(uploadPartInfos, func(i, j int) bool {
|
||||||
|
return uploadPartInfos[i].PartNumber < uploadPartInfos[j].PartNumber
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, uploadPartInfo := range uploadPartInfos {
|
||||||
|
index := uploadPartInfo.PartNumber - 1
|
||||||
|
if index < 0 || index >= len(partInfos) {
|
||||||
|
return fmt.Errorf("invalid PartNumber %d: index out of bounds (partInfos length: %d)", uploadPartInfo.PartNumber, len(partInfos))
|
||||||
|
}
|
||||||
|
partSize := partInfos[index].PartSize
|
||||||
|
log.Debugf("[139] uploading part %+v/%+v", index, len(partInfos))
|
||||||
|
limitReader := io.LimitReader(rateLimited, partSize)
|
||||||
|
r := io.TeeReader(limitReader, p)
|
||||||
|
req, err := http.NewRequestWithContext(ctx, http.MethodPut, uploadPartInfo.UploadUrl, r)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
req.Header.Set("Content-Type", "application/octet-stream")
|
||||||
|
req.Header.Set("Content-Length", fmt.Sprint(partSize))
|
||||||
|
req.Header.Set("Origin", "https://yun.139.com")
|
||||||
|
req.Header.Set("Referer", "https://yun.139.com/")
|
||||||
|
req.ContentLength = partSize
|
||||||
|
err = func() error {
|
||||||
|
res, err := base.HttpClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer res.Body.Close()
|
||||||
|
log.Debugf("[139] uploaded: %+v", res)
|
||||||
|
if res.StatusCode != http.StatusOK {
|
||||||
|
body, _ := io.ReadAll(res.Body)
|
||||||
|
return fmt.Errorf("unexpected status code: %d, body: %s", res.StatusCode, string(body))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -131,6 +131,7 @@ func (y *Cloud189TV) put(ctx context.Context, url string, headers map[string]str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 请求完成后http.Client会Close Request.Body
|
||||||
resp, err := base.HttpClient.Do(req)
|
resp, err := base.HttpClient.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@ -333,6 +334,10 @@ func (y *Cloud189TV) OldUpload(ctx context.Context, dstDir model.Obj, file model
|
|||||||
|
|
||||||
// 网盘中不存在该文件,开始上传
|
// 网盘中不存在该文件,开始上传
|
||||||
status := GetUploadFileStatusResp{CreateUploadFileResp: *uploadInfo}
|
status := GetUploadFileStatusResp{CreateUploadFileResp: *uploadInfo}
|
||||||
|
// driver.RateLimitReader会尝试Close底层的reader
|
||||||
|
// 但这里的tempFile是一个*os.File,Close后就没法继续读了
|
||||||
|
// 所以这里用io.NopCloser包一层
|
||||||
|
rateLimitedRd := driver.NewLimitedUploadStream(ctx, io.NopCloser(tempFile))
|
||||||
for status.GetSize() < file.GetSize() && status.FileDataExists != 1 {
|
for status.GetSize() < file.GetSize() && status.FileDataExists != 1 {
|
||||||
if utils.IsCanceled(ctx) {
|
if utils.IsCanceled(ctx) {
|
||||||
return nil, ctx.Err()
|
return nil, ctx.Err()
|
||||||
@ -350,7 +355,7 @@ func (y *Cloud189TV) OldUpload(ctx context.Context, dstDir model.Obj, file model
|
|||||||
header["Edrive-UploadFileId"] = fmt.Sprint(status.UploadFileId)
|
header["Edrive-UploadFileId"] = fmt.Sprint(status.UploadFileId)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := y.put(ctx, status.FileUploadUrl, header, true, tempFile, isFamily)
|
_, err := y.put(ctx, status.FileUploadUrl, header, true, rateLimitedRd, isFamily)
|
||||||
if err, ok := err.(*RespErr); ok && err.Code != "InputStreamReadError" {
|
if err, ok := err.(*RespErr); ok && err.Code != "InputStreamReadError" {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
@ -472,14 +472,16 @@ func (y *Cloud189PC) refreshSession() (err error) {
|
|||||||
// 普通上传
|
// 普通上传
|
||||||
// 无法上传大小为0的文件
|
// 无法上传大小为0的文件
|
||||||
func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) {
|
func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress, isFamily bool, overwrite bool) (model.Obj, error) {
|
||||||
size := file.GetSize()
|
// 文件大小
|
||||||
sliceSize := min(size, partSize(size))
|
fileSize := file.GetSize()
|
||||||
|
// 分片大小,不得为文件大小
|
||||||
|
sliceSize := partSize(fileSize)
|
||||||
|
|
||||||
params := Params{
|
params := Params{
|
||||||
"parentFolderId": dstDir.GetID(),
|
"parentFolderId": dstDir.GetID(),
|
||||||
"fileName": url.QueryEscape(file.GetName()),
|
"fileName": url.QueryEscape(file.GetName()),
|
||||||
"fileSize": fmt.Sprint(file.GetSize()),
|
"fileSize": fmt.Sprint(fileSize),
|
||||||
"sliceSize": fmt.Sprint(sliceSize),
|
"sliceSize": fmt.Sprint(sliceSize), // 必须为特定分片大小
|
||||||
"lazyCheck": "1",
|
"lazyCheck": "1",
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,10 +514,10 @@ func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file mo
|
|||||||
retry.DelayType(retry.BackOffDelay))
|
retry.DelayType(retry.BackOffDelay))
|
||||||
|
|
||||||
count := 1
|
count := 1
|
||||||
if size > sliceSize {
|
if fileSize > sliceSize {
|
||||||
count = int((size + sliceSize - 1) / sliceSize)
|
count = int((fileSize + sliceSize - 1) / sliceSize)
|
||||||
}
|
}
|
||||||
lastPartSize := size % sliceSize
|
lastPartSize := fileSize % sliceSize
|
||||||
if lastPartSize == 0 {
|
if lastPartSize == 0 {
|
||||||
lastPartSize = sliceSize
|
lastPartSize = sliceSize
|
||||||
}
|
}
|
||||||
@ -535,9 +537,9 @@ func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file mo
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
offset := int64((i)-1) * sliceSize
|
offset := int64((i)-1) * sliceSize
|
||||||
size := sliceSize
|
partSize := sliceSize
|
||||||
if i == count {
|
if i == count {
|
||||||
size = lastPartSize
|
partSize = lastPartSize
|
||||||
}
|
}
|
||||||
partInfo := ""
|
partInfo := ""
|
||||||
var reader *stream.SectionReader
|
var reader *stream.SectionReader
|
||||||
@ -546,14 +548,14 @@ func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file mo
|
|||||||
Before: func(ctx context.Context) error {
|
Before: func(ctx context.Context) error {
|
||||||
if reader == nil {
|
if reader == nil {
|
||||||
var err error
|
var err error
|
||||||
reader, err = ss.GetSectionReader(offset, size)
|
reader, err = ss.GetSectionReader(offset, partSize)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
silceMd5.Reset()
|
silceMd5.Reset()
|
||||||
w, err := utils.CopyWithBuffer(writers, reader)
|
w, err := utils.CopyWithBuffer(writers, reader)
|
||||||
if w != size {
|
if w != partSize {
|
||||||
return fmt.Errorf("failed to read all data: (expect =%d, actual =%d) %w", size, w, err)
|
return fmt.Errorf("failed to read all data: (expect =%d, actual =%d) %w", partSize, w, err)
|
||||||
}
|
}
|
||||||
// 计算块md5并进行hex和base64编码
|
// 计算块md5并进行hex和base64编码
|
||||||
md5Bytes := silceMd5.Sum(nil)
|
md5Bytes := silceMd5.Sum(nil)
|
||||||
@ -595,7 +597,7 @@ func (y *Cloud189PC) StreamUpload(ctx context.Context, dstDir model.Obj, file mo
|
|||||||
fileMd5Hex = strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
fileMd5Hex = strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
||||||
}
|
}
|
||||||
sliceMd5Hex := fileMd5Hex
|
sliceMd5Hex := fileMd5Hex
|
||||||
if file.GetSize() > sliceSize {
|
if fileSize > sliceSize {
|
||||||
sliceMd5Hex = strings.ToUpper(utils.GetMD5EncodeStr(strings.Join(silceMd5Hexs, "\n")))
|
sliceMd5Hex = strings.ToUpper(utils.GetMD5EncodeStr(strings.Join(silceMd5Hexs, "\n")))
|
||||||
}
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user