Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
bd2418c438 | |||
8421c72c5c | |||
a80e21997c | |||
4369cbbac3 | |||
89f76d7899 | |||
ef68f84787 | |||
2c1f70fbe9 | |||
b2f5757f8d | |||
6b97b4eb20 | |||
645c10c11f | |||
571bcf07b0 | |||
63de65be45 |
@ -1,11 +1,11 @@
|
||||
FROM alpine:3.17 as builder
|
||||
FROM alpine:3.18 as builder
|
||||
LABEL stage=go-builder
|
||||
WORKDIR /app/
|
||||
COPY ./ ./
|
||||
RUN apk add --no-cache bash curl gcc git go musl-dev; \
|
||||
bash build.sh release docker
|
||||
|
||||
FROM alpine:3.17
|
||||
FROM alpine:3.18
|
||||
LABEL MAINTAINER="i@nn.ci"
|
||||
VOLUME /opt/alist/data/
|
||||
WORKDIR /opt/alist/
|
||||
|
@ -1,6 +1,6 @@
|
||||
<div align="center">
|
||||
<a href="https://alist.nn.ci"><img height="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 storage, powered by Gin and Solidjs.</em></p>
|
||||
<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">
|
||||
<img src="https://goreportcard.com/badge/github.com/alist-org/alist/v3" alt="latest version" />
|
||||
@ -62,6 +62,7 @@ English | [中文](./README_cn.md) | [Contributing](./CONTRIBUTING.md) | [CODE_O
|
||||
- [x] [YandexDisk](https://disk.yandex.com/)
|
||||
- [x] [BaiduNetdisk](http://pan.baidu.com/)
|
||||
- [x] [Terabox](https://www.terabox.com/main)
|
||||
- [x] [UC](https://drive.uc.cn)
|
||||
- [x] [Quark](https://pan.quark.cn)
|
||||
- [x] [Thunder](https://pan.xunlei.com)
|
||||
- [x] [Lanzou](https://www.lanzou.com/)
|
||||
|
@ -61,6 +61,7 @@
|
||||
- [x] [和彩云](https://yun.139.com/) (个人云, 家庭云)
|
||||
- [x] [Yandex.Disk](https://disk.yandex.com/)
|
||||
- [x] [百度网盘](http://pan.baidu.com/)
|
||||
- [x] [UC网盘](https://drive.uc.cn)
|
||||
- [x] [夸克网盘](https://pan.quark.cn)
|
||||
- [x] [迅雷网盘](https://pan.xunlei.com)
|
||||
- [x] [蓝奏云](https://www.lanzou.com/)
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@ -19,8 +18,7 @@ type Cloud189PC struct {
|
||||
|
||||
identity string
|
||||
|
||||
client *resty.Client
|
||||
putClient *resty.Client
|
||||
client *resty.Client
|
||||
|
||||
loginParam *LoginParam
|
||||
tokenInfo *AppSessionResp
|
||||
@ -51,9 +49,6 @@ func (y *Cloud189PC) Init(ctx context.Context) (err error) {
|
||||
"Referer": WEB_URL,
|
||||
})
|
||||
}
|
||||
if y.putClient == nil {
|
||||
y.putClient = base.NewRestyClient().SetTimeout(120 * time.Second)
|
||||
}
|
||||
|
||||
// 避免重复登陆
|
||||
identity := utils.GetMD5Encode(y.Username + y.Password)
|
||||
@ -266,8 +261,14 @@ func (y *Cloud189PC) Remove(ctx context.Context, obj model.Obj) error {
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
if y.RapidUpload {
|
||||
switch y.UploadMethod {
|
||||
case "stream":
|
||||
return y.CommonUpload(ctx, dstDir, stream, up)
|
||||
case "old":
|
||||
return y.OldUpload(ctx, dstDir, stream, up)
|
||||
case "rapid":
|
||||
return y.FastUpload(ctx, dstDir, stream, up)
|
||||
default:
|
||||
return y.CommonUpload(ctx, dstDir, stream, up)
|
||||
}
|
||||
return y.CommonUpload(ctx, dstDir, stream, up)
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import (
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strings"
|
||||
@ -131,3 +132,18 @@ func BoolToNumber(b bool) int {
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// 计算分片大小
|
||||
// 对分片数量有限制
|
||||
// 10MIB 20 MIB 999片
|
||||
// 50MIB 60MIB 70MIB 80MIB ∞MIB 1999片
|
||||
func partSize(size int64) int64 {
|
||||
const DEFAULT = 1024 * 1024 * 10 // 10MIB
|
||||
if size > DEFAULT*2*999 {
|
||||
return int64(math.Max(math.Ceil((float64(size)/1999) /*=单个切片大小*/ /float64(DEFAULT)) /*=倍率*/, 5) * DEFAULT)
|
||||
}
|
||||
if size > DEFAULT*999 {
|
||||
return DEFAULT * 2 // 20MIB
|
||||
}
|
||||
return DEFAULT
|
||||
}
|
||||
|
@ -14,13 +14,14 @@ type Addition struct {
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
||||
Type string `json:"type" type:"select" options:"personal,family" default:"personal"`
|
||||
FamilyID string `json:"family_id"`
|
||||
RapidUpload bool `json:"rapid_upload"`
|
||||
UploadMethod string `json:"upload_method" type:"select" options:"stream,rapid,old" default:"stream"`
|
||||
NoUseOcr bool `json:"no_use_ocr"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "189CloudPC",
|
||||
DefaultRoot: "-11",
|
||||
CheckStatus: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -10,20 +10,62 @@ import (
|
||||
|
||||
// 居然有四种返回方式
|
||||
type RespErr struct {
|
||||
ResCode string `json:"res_code"`
|
||||
ResCode any `json:"res_code"` // int or string
|
||||
ResMessage string `json:"res_message"`
|
||||
|
||||
Error_ string `json:"error"`
|
||||
|
||||
XMLName xml.Name `xml:"error"`
|
||||
Code string `json:"code" xml:"code"`
|
||||
Message string `json:"message" xml:"message"`
|
||||
|
||||
// Code string `json:"code"`
|
||||
Msg string `json:"msg"`
|
||||
Msg string `json:"msg"`
|
||||
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMsg string `json:"errorMsg"`
|
||||
}
|
||||
|
||||
func (e *RespErr) HasError() bool {
|
||||
switch v := e.ResCode.(type) {
|
||||
case int, int64, int32:
|
||||
return v != 0
|
||||
case string:
|
||||
return e.ResCode != ""
|
||||
}
|
||||
return (e.Code != "" && e.Code != "SUCCESS") || e.ErrorCode != "" || e.Error_ != ""
|
||||
}
|
||||
|
||||
func (e *RespErr) Error() string {
|
||||
switch v := e.ResCode.(type) {
|
||||
case int, int64, int32:
|
||||
if v != 0 {
|
||||
return fmt.Sprintf("res_code: %d ,res_msg: %s", v, e.ResMessage)
|
||||
}
|
||||
case string:
|
||||
if e.ResCode != "" {
|
||||
return fmt.Sprintf("res_code: %s ,res_msg: %s", e.ResCode, e.ResMessage)
|
||||
}
|
||||
}
|
||||
|
||||
if e.Code != "" && e.Code != "SUCCESS" {
|
||||
if e.Msg != "" {
|
||||
return fmt.Sprintf("code: %s ,msg: %s", e.Code, e.Msg)
|
||||
}
|
||||
if e.Message != "" {
|
||||
return fmt.Sprintf("code: %s ,msg: %s", e.Code, e.Message)
|
||||
}
|
||||
return "code: " + e.Code
|
||||
}
|
||||
|
||||
if e.ErrorCode != "" {
|
||||
return fmt.Sprintf("err_code: %s ,err_msg: %s", e.ErrorCode, e.ErrorMsg)
|
||||
}
|
||||
|
||||
if e.Error_ != "" {
|
||||
return fmt.Sprintf("error: %s ,message: %s", e.ErrorCode, e.Message)
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// 登陆需要的参数
|
||||
type LoginParam struct {
|
||||
// 加密后的用户名和密码
|
||||
@ -218,6 +260,42 @@ type Part struct {
|
||||
RequestHeader string `json:"requestHeader"`
|
||||
}
|
||||
|
||||
/* 第二种上传方式 */
|
||||
type CreateUploadFileResp struct {
|
||||
// 上传文件请求ID
|
||||
UploadFileId int64 `json:"uploadFileId"`
|
||||
// 上传文件数据的URL路径
|
||||
FileUploadUrl string `json:"fileUploadUrl"`
|
||||
// 上传文件完成后确认路径
|
||||
FileCommitUrl string `json:"fileCommitUrl"`
|
||||
// 文件是否已存在云盘中,0-未存在,1-已存在
|
||||
FileDataExists int `json:"fileDataExists"`
|
||||
}
|
||||
|
||||
type GetUploadFileStatusResp struct {
|
||||
CreateUploadFileResp
|
||||
|
||||
// 已上传的大小
|
||||
DataSize int64 `json:"dataSize"`
|
||||
Size int64 `json:"size"`
|
||||
}
|
||||
|
||||
func (r *GetUploadFileStatusResp) GetSize() int64 {
|
||||
return r.DataSize + r.Size
|
||||
}
|
||||
|
||||
type CommitUploadFileResp struct {
|
||||
XMLName xml.Name `xml:"file"`
|
||||
Id string `xml:"id"`
|
||||
Name string `xml:"name"`
|
||||
Size string `xml:"size"`
|
||||
Md5 string `xml:"md5"`
|
||||
CreateDate string `xml:"createDate"`
|
||||
Rev string `xml:"rev"`
|
||||
UserId string `xml:"userId"`
|
||||
}
|
||||
|
||||
/* query 加密参数*/
|
||||
type Params map[string]string
|
||||
|
||||
func (p Params) Set(k, v string) {
|
||||
|
@ -6,6 +6,7 @@ import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
@ -15,6 +16,7 @@ import (
|
||||
"os"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
@ -23,9 +25,12 @@ import (
|
||||
"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/avast/retry-go"
|
||||
"github.com/go-resty/resty/v2"
|
||||
"github.com/google/uuid"
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -47,7 +52,7 @@ const (
|
||||
CHANNEL_ID = "web_cloud.189.cn"
|
||||
)
|
||||
|
||||
func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, params Params, resp interface{}) ([]byte, error) {
|
||||
func (y *Cloud189PC) SignatureHeader(url, method, params string) map[string]string {
|
||||
dateOfGmt := getHttpDateStr()
|
||||
sessionKey := y.tokenInfo.SessionKey
|
||||
sessionSecret := y.tokenInfo.SessionSecret
|
||||
@ -56,19 +61,40 @@ func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, para
|
||||
sessionSecret = y.tokenInfo.FamilySessionSecret
|
||||
}
|
||||
|
||||
req := y.client.R().SetQueryParams(clientSuffix()).SetHeaders(map[string]string{
|
||||
header := map[string]string{
|
||||
"Date": dateOfGmt,
|
||||
"SessionKey": sessionKey,
|
||||
"X-Request-ID": uuid.NewString(),
|
||||
})
|
||||
"Signature": signatureOfHmac(sessionSecret, sessionKey, method, url, dateOfGmt, params),
|
||||
}
|
||||
return header
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) EncryptParams(params Params) string {
|
||||
sessionSecret := y.tokenInfo.SessionSecret
|
||||
if y.isFamily() {
|
||||
sessionSecret = y.tokenInfo.FamilySessionSecret
|
||||
}
|
||||
if params != nil {
|
||||
return AesECBEncrypt(params.Encode(), sessionSecret[:16])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, params Params, resp interface{}) ([]byte, error) {
|
||||
req := y.client.R().SetQueryParams(clientSuffix())
|
||||
|
||||
// 设置params
|
||||
var paramsData string
|
||||
if params != nil {
|
||||
paramsData = AesECBEncrypt(params.Encode(), sessionSecret[:16])
|
||||
paramsData := y.EncryptParams(params)
|
||||
if paramsData != "" {
|
||||
req.SetQueryParam("params", paramsData)
|
||||
}
|
||||
req.SetHeader("Signature", signatureOfHmac(sessionSecret, sessionKey, method, url, dateOfGmt, paramsData))
|
||||
|
||||
// Signature
|
||||
req.SetHeaders(y.SignatureHeader(url, method, paramsData))
|
||||
|
||||
var erron RespErr
|
||||
req.SetError(&erron)
|
||||
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
@ -80,32 +106,6 @@ func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, para
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var erron RespErr
|
||||
utils.Json.Unmarshal(res.Body(), &erron)
|
||||
|
||||
if erron.ResCode != "" {
|
||||
return nil, fmt.Errorf("res_code: %s ,res_msg: %s", erron.ResCode, erron.ResMessage)
|
||||
}
|
||||
if erron.Code != "" && erron.Code != "SUCCESS" {
|
||||
if erron.Msg != "" {
|
||||
return nil, fmt.Errorf("code: %s ,msg: %s", erron.Code, erron.Msg)
|
||||
}
|
||||
if erron.Message != "" {
|
||||
return nil, fmt.Errorf("code: %s ,msg: %s", erron.Code, erron.Message)
|
||||
}
|
||||
return nil, fmt.Errorf(res.String())
|
||||
}
|
||||
switch erron.ErrorCode {
|
||||
case "":
|
||||
break
|
||||
case "InvalidSessionKey":
|
||||
if err = y.refreshSession(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return y.request(url, method, callback, params, resp)
|
||||
default:
|
||||
return nil, fmt.Errorf("err_code: %s ,err_msg: %s", erron.ErrorCode, erron.ErrorMsg)
|
||||
}
|
||||
|
||||
if strings.Contains(res.String(), "userSessionBO is null") {
|
||||
if err = y.refreshSession(); err != nil {
|
||||
@ -114,14 +114,17 @@ func (y *Cloud189PC) request(url, method string, callback base.ReqCallback, para
|
||||
return y.request(url, method, callback, params, resp)
|
||||
}
|
||||
|
||||
resCode := utils.Json.Get(res.Body(), "res_code").ToInt64()
|
||||
message := utils.Json.Get(res.Body(), "res_message").ToString()
|
||||
switch resCode {
|
||||
case 0:
|
||||
return res.Body(), nil
|
||||
default:
|
||||
return nil, fmt.Errorf("res_code: %d ,res_msg: %s", resCode, message)
|
||||
// 处理错误
|
||||
if erron.HasError() {
|
||||
if erron.ErrorCode == "InvalidSessionKey" {
|
||||
if err = y.refreshSession(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return y.request(url, method, callback, params, resp)
|
||||
}
|
||||
return nil, &erron
|
||||
}
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) get(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
@ -132,6 +135,50 @@ func (y *Cloud189PC) post(url string, callback base.ReqCallback, resp interface{
|
||||
return y.request(url, http.MethodPost, callback, nil, resp)
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) put(ctx context.Context, url string, headers map[string]string, sign bool, file io.Reader) ([]byte, error) {
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPut, url, file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
query := req.URL.Query()
|
||||
for key, value := range clientSuffix() {
|
||||
query.Add(key, value)
|
||||
}
|
||||
req.URL.RawQuery = query.Encode()
|
||||
|
||||
for key, value := range headers {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
|
||||
if sign {
|
||||
for key, value := range y.SignatureHeader(url, http.MethodPut, "") {
|
||||
req.Header.Add(key, value)
|
||||
}
|
||||
}
|
||||
|
||||
resp, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var erron RespErr
|
||||
jsoniter.Unmarshal(body, &erron)
|
||||
xml.Unmarshal(body, &erron)
|
||||
if erron.HasError() {
|
||||
return nil, &erron
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, errors.Errorf("put fail,err:%s", string(body))
|
||||
}
|
||||
return body, nil
|
||||
}
|
||||
func (y *Cloud189PC) getFiles(ctx context.Context, fileId string) ([]model.Obj, error) {
|
||||
fullUrl := API_URL
|
||||
if y.isFamily() {
|
||||
@ -186,7 +233,7 @@ func (y *Cloud189PC) getFiles(ctx context.Context, fileId string) ([]model.Obj,
|
||||
|
||||
func (y *Cloud189PC) login() (err error) {
|
||||
// 初始化登陆所需参数
|
||||
if y.loginParam == nil || !y.NoUseOcr {
|
||||
if y.loginParam == nil {
|
||||
if err = y.initLoginParam(); err != nil {
|
||||
// 验证码也通过错误返回
|
||||
return err
|
||||
@ -197,7 +244,7 @@ func (y *Cloud189PC) login() (err error) {
|
||||
y.VCode = ""
|
||||
// 销毁登陆参数
|
||||
y.loginParam = nil
|
||||
// 遇到错误,重新加载登陆参数
|
||||
// 遇到错误,重新加载登陆参数(刷新验证码)
|
||||
if err != nil && y.NoUseOcr {
|
||||
if err1 := y.initLoginParam(); err1 != nil {
|
||||
err = fmt.Errorf("err1: %s \nerr2: %s", err, err1)
|
||||
@ -249,9 +296,8 @@ func (y *Cloud189PC) login() (err error) {
|
||||
return
|
||||
}
|
||||
|
||||
if erron.ResCode != "" {
|
||||
err = fmt.Errorf(erron.ResMessage)
|
||||
return
|
||||
if erron.HasError() {
|
||||
return &erron
|
||||
}
|
||||
if tokenInfo.ResCode != 0 {
|
||||
err = fmt.Errorf(tokenInfo.ResMessage)
|
||||
@ -304,6 +350,21 @@ func (y *Cloud189PC) initLoginParam() error {
|
||||
param.RsaPassword = encryptConf.Data.Pre + RsaEncrypt(param.jRsaKey, y.Password)
|
||||
y.loginParam = ¶m
|
||||
|
||||
// 判断是否需要验证码
|
||||
resp, err := y.client.R().
|
||||
SetFormData(map[string]string{
|
||||
"appKey": APP_ID,
|
||||
"accountType": ACCOUNT_TYPE,
|
||||
"userName": param.RsaUsername,
|
||||
}).Post(AUTH_URL + "/api/logbox/oauth2/needcaptcha.do")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if resp.String() == "0" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 拉取验证码
|
||||
imgRes, err := y.client.R().
|
||||
SetQueryParams(map[string]string{
|
||||
"token": param.CaptchaToken,
|
||||
@ -359,33 +420,23 @@ func (y *Cloud189PC) refreshSession() (err error) {
|
||||
}
|
||||
}()
|
||||
|
||||
switch erron.ResCode {
|
||||
case "":
|
||||
break
|
||||
case "UserInvalidOpenToken":
|
||||
if err = y.login(); err != nil {
|
||||
return err
|
||||
if erron.HasError() {
|
||||
if erron.ResCode == "UserInvalidOpenToken" {
|
||||
if err = y.login(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
err = fmt.Errorf("res_code: %s ,res_msg: %s", erron.ResCode, erron.ResMessage)
|
||||
return
|
||||
}
|
||||
|
||||
switch userSessionResp.ResCode {
|
||||
case 0:
|
||||
y.tokenInfo.UserSessionResp = userSessionResp
|
||||
default:
|
||||
err = fmt.Errorf("code: %d , msg: %s", userSessionResp.ResCode, userSessionResp.ResMessage)
|
||||
return &erron
|
||||
}
|
||||
y.tokenInfo.UserSessionResp = userSessionResp
|
||||
return
|
||||
}
|
||||
|
||||
// 普通上传
|
||||
func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (err error) {
|
||||
const DEFAULT int64 = 10485760
|
||||
var count = int64(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
var DEFAULT = partSize(file.GetSize())
|
||||
var count = int(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
|
||||
requestID := uuid.NewString()
|
||||
params := Params{
|
||||
"parentFolderId": dstDir.GetID(),
|
||||
"fileName": url.QueryEscape(file.GetName()),
|
||||
@ -407,7 +458,6 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo
|
||||
var initMultiUpload InitMultiUploadResp
|
||||
_, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, params, &initMultiUpload)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -417,7 +467,7 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo
|
||||
silceMd5 := md5.New()
|
||||
silceMd5Hexs := make([]string, 0, count)
|
||||
byteData := bytes.NewBuffer(make([]byte, DEFAULT))
|
||||
for i := int64(1); i <= count; i++ {
|
||||
for i := 1; i <= count; i++ {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
@ -440,7 +490,6 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo
|
||||
_, err = y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet,
|
||||
func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, Params{
|
||||
"partInfo": fmt.Sprintf("%d-%s", i, silceMd5Base64),
|
||||
"uploadFileId": initMultiUpload.Data.UploadFileID,
|
||||
@ -451,18 +500,18 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo
|
||||
|
||||
// 开始上传
|
||||
uploadData := uploadUrl.UploadUrls[fmt.Sprint("partNumber_", i)]
|
||||
res, err := y.putClient.R().
|
||||
SetContext(ctx).
|
||||
SetQueryParams(clientSuffix()).
|
||||
SetHeaders(ParseHttpHeader(uploadData.RequestHeader)).
|
||||
SetBody(byteData).
|
||||
Put(uploadData.RequestURL)
|
||||
|
||||
err = retry.Do(func() error {
|
||||
_, err := y.put(ctx, uploadData.RequestURL, ParseHttpHeader(uploadData.RequestHeader), false, bytes.NewReader(byteData.Bytes()))
|
||||
return err
|
||||
},
|
||||
retry.Context(ctx),
|
||||
retry.Attempts(3),
|
||||
retry.Delay(time.Second),
|
||||
retry.MaxDelay(5*time.Second))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode() != http.StatusOK {
|
||||
return fmt.Errorf("updload fail,msg: %s", res.String())
|
||||
}
|
||||
up(int(i * 100 / count))
|
||||
}
|
||||
|
||||
@ -476,7 +525,6 @@ func (y *Cloud189PC) CommonUpload(ctx context.Context, dstDir model.Obj, file mo
|
||||
_, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet,
|
||||
func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, Params{
|
||||
"uploadFileId": initMultiUpload.Data.UploadFileID,
|
||||
"fileMd5": fileMd5Hex,
|
||||
@ -500,7 +548,7 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
_ = os.Remove(tempFile.Name())
|
||||
}()
|
||||
|
||||
const DEFAULT int64 = 10485760
|
||||
var DEFAULT = partSize(file.GetSize())
|
||||
count := int(math.Ceil(float64(file.GetSize()) / float64(DEFAULT)))
|
||||
|
||||
// 优先计算所需信息
|
||||
@ -531,7 +579,6 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
sliceMd5Hex = strings.ToUpper(utils.GetMD5Encode(strings.Join(silceMd5Hexs, "\n")))
|
||||
}
|
||||
|
||||
requestID := uuid.NewString()
|
||||
// 检测是否支持快传
|
||||
params := Params{
|
||||
"parentFolderId": dstDir.GetID(),
|
||||
@ -554,7 +601,6 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
var uploadInfo InitMultiUploadResp
|
||||
_, err = y.request(fullUrl+"/initMultiUpload", http.MethodGet, func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, params, &uploadInfo)
|
||||
if err != nil {
|
||||
return err
|
||||
@ -566,7 +612,6 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
_, err = y.request(fullUrl+"/getMultiUploadUrls", http.MethodGet,
|
||||
func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, Params{
|
||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||
"partInfo": strings.Join(silceMd5Base64s, ","),
|
||||
@ -575,26 +620,29 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
return err
|
||||
}
|
||||
|
||||
buf := make([]byte, DEFAULT)
|
||||
for i := 1; i <= count; i++ {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
default:
|
||||
}
|
||||
|
||||
n, err := io.ReadFull(tempFile, buf)
|
||||
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
||||
return err
|
||||
}
|
||||
uploadData := uploadUrls.UploadUrls[fmt.Sprint("partNumber_", i)]
|
||||
res, err := y.putClient.R().
|
||||
SetContext(ctx).
|
||||
SetQueryParams(clientSuffix()).
|
||||
SetHeaders(ParseHttpHeader(uploadData.RequestHeader)).
|
||||
SetBody(io.LimitReader(tempFile, DEFAULT)).
|
||||
Put(uploadData.RequestURL)
|
||||
err = retry.Do(func() error {
|
||||
_, err := y.put(ctx, uploadData.RequestURL, ParseHttpHeader(uploadData.RequestHeader), false, bytes.NewReader(buf[:n]))
|
||||
return err
|
||||
},
|
||||
retry.Context(ctx),
|
||||
retry.Attempts(3),
|
||||
retry.Delay(time.Second),
|
||||
retry.MaxDelay(5*time.Second))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if res.StatusCode() != http.StatusOK {
|
||||
return fmt.Errorf("updload fail,msg: %s", res.String())
|
||||
}
|
||||
|
||||
up(int(i * 100 / count))
|
||||
}
|
||||
}
|
||||
@ -603,7 +651,6 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
_, err = y.request(fullUrl+"/commitMultiUploadFile", http.MethodGet,
|
||||
func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
req.SetHeader("X-Request-ID", requestID)
|
||||
}, Params{
|
||||
"uploadFileId": uploadInfo.Data.UploadFileID,
|
||||
"isLog": "0",
|
||||
@ -612,6 +659,137 @@ func (y *Cloud189PC) FastUpload(ctx context.Context, dstDir model.Obj, file mode
|
||||
return err
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) OldUpload(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (err error) {
|
||||
// 需要获取完整文件md5,必须支持 io.Seek
|
||||
tempFile, err := utils.CreateTempFile(file.GetReadCloser())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = tempFile.Close()
|
||||
_ = os.Remove(tempFile.Name())
|
||||
}()
|
||||
|
||||
// 计算md5
|
||||
fileMd5 := md5.New()
|
||||
if _, err := io.Copy(fileMd5, tempFile); err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err = tempFile.Seek(0, io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
fileMd5Hex := strings.ToUpper(hex.EncodeToString(fileMd5.Sum(nil)))
|
||||
|
||||
// 创建上传会话
|
||||
var uploadInfo CreateUploadFileResp
|
||||
|
||||
fullUrl := API_URL + "/createUploadFile.action"
|
||||
if y.isFamily() {
|
||||
fullUrl = API_URL + "/family/file/createFamilyFile.action"
|
||||
}
|
||||
_, err = y.post(fullUrl, func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
if y.isFamily() {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"familyId": y.FamilyID,
|
||||
"fileMd5": fileMd5Hex,
|
||||
"fileName": file.GetName(),
|
||||
"fileSize": fmt.Sprint(file.GetSize()),
|
||||
"parentId": dstDir.GetID(),
|
||||
"resumePolicy": "1",
|
||||
})
|
||||
} else {
|
||||
req.SetFormData(map[string]string{
|
||||
"parentFolderId": dstDir.GetID(),
|
||||
"fileName": file.GetName(),
|
||||
"size": fmt.Sprint(file.GetSize()),
|
||||
"md5": fileMd5Hex,
|
||||
"opertype": "3",
|
||||
"flag": "1",
|
||||
"resumePolicy": "1",
|
||||
"isLog": "0",
|
||||
// "baseFileId": "",
|
||||
// "lastWrite":"",
|
||||
// "localPath": strings.ReplaceAll(param.LocalPath, "\\", "/"),
|
||||
// "fileExt": "",
|
||||
})
|
||||
}
|
||||
}, &uploadInfo)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 网盘中不存在该文件,开始上传
|
||||
status := GetUploadFileStatusResp{CreateUploadFileResp: uploadInfo}
|
||||
for status.Size < file.GetSize() && status.FileDataExists != 1 {
|
||||
if utils.IsCanceled(ctx) {
|
||||
return ctx.Err()
|
||||
}
|
||||
|
||||
header := map[string]string{
|
||||
"ResumePolicy": "1",
|
||||
"Expect": "100-continue",
|
||||
}
|
||||
|
||||
if y.isFamily() {
|
||||
header["FamilyId"] = fmt.Sprint(y.FamilyID)
|
||||
header["UploadFileId"] = fmt.Sprint(status.UploadFileId)
|
||||
} else {
|
||||
header["Edrive-UploadFileId"] = fmt.Sprint(status.UploadFileId)
|
||||
}
|
||||
|
||||
_, err := y.put(ctx, status.FileUploadUrl, header, true, io.NopCloser(tempFile))
|
||||
if err, ok := err.(*RespErr); ok && err.Code != "InputStreamReadError" {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取断点状态
|
||||
fullUrl := API_URL + "/getUploadFileStatus.action"
|
||||
if y.isFamily() {
|
||||
fullUrl = API_URL + "/family/file/getFamilyFileStatus.action"
|
||||
}
|
||||
_, err = y.get(fullUrl, func(req *resty.Request) {
|
||||
req.SetContext(ctx).SetQueryParams(map[string]string{
|
||||
"uploadFileId": fmt.Sprint(status.UploadFileId),
|
||||
"resumePolicy": "1",
|
||||
})
|
||||
if y.isFamily() {
|
||||
req.SetQueryParam("familyId", fmt.Sprint(y.FamilyID))
|
||||
}
|
||||
}, &status)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if _, err := tempFile.Seek(status.GetSize(), io.SeekStart); err != nil {
|
||||
return err
|
||||
}
|
||||
up(int(status.Size / file.GetSize()))
|
||||
}
|
||||
|
||||
// 提交
|
||||
var resp CommitUploadFileResp
|
||||
_, err = y.post(status.FileCommitUrl, func(req *resty.Request) {
|
||||
req.SetContext(ctx)
|
||||
if y.isFamily() {
|
||||
req.SetHeaders(map[string]string{
|
||||
"ResumePolicy": "1",
|
||||
"UploadFileId": fmt.Sprint(status.UploadFileId),
|
||||
"FamilyId": fmt.Sprint(y.FamilyID),
|
||||
})
|
||||
} else {
|
||||
req.SetFormData(map[string]string{
|
||||
"opertype": "3",
|
||||
"resumePolicy": "1",
|
||||
"uploadFileId": fmt.Sprint(status.UploadFileId),
|
||||
"isLog": "0",
|
||||
})
|
||||
}
|
||||
}, &resp)
|
||||
return err
|
||||
}
|
||||
|
||||
func (y *Cloud189PC) isFamily() bool {
|
||||
return y.Type == "family"
|
||||
}
|
||||
|
@ -103,7 +103,8 @@ func (d *Alias) link(ctx context.Context, dst, sub string, args model.LinkArgs)
|
||||
}
|
||||
if common.ShouldProxy(storage, stdpath.Base(sub)) {
|
||||
return &model.Link{
|
||||
URL: fmt.Sprintf("/p%s?sign=%s",
|
||||
URL: fmt.Sprintf("%s/p%s?sign=%s",
|
||||
common.GetApiUrl(args.HttpReq),
|
||||
utils.EncodePath(reqPath, true),
|
||||
sign.Sign(reqPath)),
|
||||
}, nil
|
||||
|
@ -175,6 +175,7 @@ func (d *AListV3) Put(ctx context.Context, dstDir model.Obj, stream model.FileSt
|
||||
req.SetHeader("File-Path", path.Join(dstDir.GetPath(), stream.GetName())).
|
||||
SetHeader("Password", d.MetaPassword).
|
||||
SetHeader("Content-Length", strconv.FormatInt(stream.GetSize(), 10)).
|
||||
SetContentLength(true).
|
||||
SetBody(stream.GetReadCloser())
|
||||
})
|
||||
return err
|
||||
|
@ -9,6 +9,7 @@ import (
|
||||
"github.com/alist-org/alist/v3/pkg/utils"
|
||||
"github.com/alist-org/alist/v3/server/common"
|
||||
"github.com/go-resty/resty/v2"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func (d *AListV3) login() error {
|
||||
@ -38,6 +39,7 @@ func (d *AListV3) request(api, method string, callback base.ReqCallback, retry .
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.Debugf("[alist_v3] response body: %s", res.String())
|
||||
if res.StatusCode() >= 400 {
|
||||
return nil, fmt.Errorf("request failed, status: %s", res.Status())
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ import (
|
||||
_ "github.com/alist-org/alist/v3/drivers/onedrive_app"
|
||||
_ "github.com/alist-org/alist/v3/drivers/pikpak"
|
||||
_ "github.com/alist-org/alist/v3/drivers/pikpak_share"
|
||||
_ "github.com/alist-org/alist/v3/drivers/quark"
|
||||
_ "github.com/alist-org/alist/v3/drivers/quark_uc"
|
||||
_ "github.com/alist-org/alist/v3/drivers/s3"
|
||||
_ "github.com/alist-org/alist/v3/drivers/seafile"
|
||||
_ "github.com/alist-org/alist/v3/drivers/sftp"
|
||||
|
@ -4,7 +4,6 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
@ -16,11 +15,6 @@ func getTid() string {
|
||||
return fmt.Sprintf("3%d%.0f", time.Now().Unix(), math.Floor(9000000*rand.Float64()+1000000))
|
||||
}
|
||||
|
||||
// 检查名称
|
||||
func checkName(name string) bool {
|
||||
return len(name) <= 50 && regexp.MustCompile("[\u4e00-\u9fa5A-Za-z0-9_-]").MatchString(name)
|
||||
}
|
||||
|
||||
func toTime(t int64) *time.Time {
|
||||
tm := time.Unix(t, 0)
|
||||
return &tm
|
||||
|
@ -2,7 +2,6 @@ package baiduphoto
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
@ -22,10 +21,6 @@ const (
|
||||
FILE_API_URL_V2 = API_URL + "/file/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNotSupportName = errors.New("only chinese and english, numbers and underscores are supported, and the length is no more than 50")
|
||||
)
|
||||
|
||||
func (d *BaiduPhoto) Request(furl string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
req := base.RestyClient.R().
|
||||
SetQueryParam("access_token", d.AccessToken)
|
||||
@ -48,6 +43,8 @@ func (d *BaiduPhoto) Request(furl string, method string, callback base.ReqCallba
|
||||
return nil, fmt.Errorf("you have joined album")
|
||||
case 50820:
|
||||
return nil, fmt.Errorf("no shared albums found")
|
||||
case 50100:
|
||||
return nil, fmt.Errorf("illegal title, only supports 50 characters")
|
||||
case -6:
|
||||
if err = d.refreshToken(); err != nil {
|
||||
return nil, err
|
||||
@ -188,9 +185,6 @@ func (d *BaiduPhoto) GetAllAlbumFile(ctx context.Context, album *Album, passwd s
|
||||
|
||||
// 创建相册
|
||||
func (d *BaiduPhoto) CreateAlbum(ctx context.Context, name string) (*Album, error) {
|
||||
if !checkName(name) {
|
||||
return nil, ErrNotSupportName
|
||||
}
|
||||
var resp JoinOrCreateAlbumResp
|
||||
_, err := d.Post(ALBUM_API_URL+"/create", func(r *resty.Request) {
|
||||
r.SetContext(ctx).SetResult(&resp)
|
||||
@ -208,10 +202,6 @@ func (d *BaiduPhoto) CreateAlbum(ctx context.Context, name string) (*Album, erro
|
||||
|
||||
// 相册改名
|
||||
func (d *BaiduPhoto) SetAlbumName(ctx context.Context, album *Album, name string) (*Album, error) {
|
||||
if !checkName(name) {
|
||||
return nil, ErrNotSupportName
|
||||
}
|
||||
|
||||
_, err := d.Post(ALBUM_API_URL+"/settitle", func(r *resty.Request) {
|
||||
r.SetContext(ctx)
|
||||
r.SetFormData(map[string]string{
|
||||
|
@ -40,7 +40,7 @@ func NewRestyClient() *resty.Client {
|
||||
|
||||
func NewHttpClient() *http.Client {
|
||||
return &http.Client{
|
||||
Timeout: DefaultTimeout,
|
||||
Timeout: time.Hour * 48,
|
||||
Transport: &http.Transport{
|
||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: conf.Conf.TlsInsecureSkipVerify},
|
||||
},
|
||||
|
@ -5,6 +5,7 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
@ -30,6 +31,8 @@ func (d *Cloudreve) Init(ctx context.Context) error {
|
||||
if d.Cookie != "" {
|
||||
return nil
|
||||
}
|
||||
// removing trailing slash
|
||||
d.Address = strings.TrimSuffix(d.Address, "/")
|
||||
return d.login()
|
||||
}
|
||||
|
||||
|
@ -57,7 +57,7 @@ func (d *Cloudreve) request(method string, path string, callback base.ReqCallbac
|
||||
return d.request(method, path, callback, out)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return errors.New(r.Msg)
|
||||
}
|
||||
sess := cookie.GetCookie(resp.Cookies(), "cloudreve-session")
|
||||
|
@ -18,6 +18,7 @@ type LanZou struct {
|
||||
Addition
|
||||
model.Storage
|
||||
uid string
|
||||
vei string
|
||||
}
|
||||
|
||||
func (d *LanZou) Config() driver.Config {
|
||||
@ -28,7 +29,7 @@ func (d *LanZou) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *LanZou) Init(ctx context.Context) error {
|
||||
func (d *LanZou) Init(ctx context.Context) (err error) {
|
||||
if d.IsCookie() {
|
||||
if d.RootFolderID == "" {
|
||||
d.RootFolderID = "-1"
|
||||
@ -38,8 +39,9 @@ func (d *LanZou) Init(ctx context.Context) error {
|
||||
return fmt.Errorf("cookie does not contain ylogin")
|
||||
}
|
||||
d.uid = ylogin[1]
|
||||
d.vei, err = d.getVei()
|
||||
}
|
||||
return nil
|
||||
return
|
||||
}
|
||||
|
||||
func (d *LanZou) Drop(ctx context.Context) error {
|
||||
|
@ -22,12 +22,17 @@ var once sync.Once
|
||||
|
||||
func (d *LanZou) doupload(callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
return d.post(d.BaseUrl+"/doupload.php", func(req *resty.Request) {
|
||||
req.SetQueryParam("uid", d.uid)
|
||||
callback(req)
|
||||
req.SetQueryParams(map[string]string{
|
||||
"uid": d.uid,
|
||||
"vei": d.vei,
|
||||
})
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
}
|
||||
}, resp)
|
||||
}
|
||||
|
||||
func (d *LanZou) get(url string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
func (d *LanZou) get(url string, callback base.ReqCallback) ([]byte, error) {
|
||||
return d.request(url, http.MethodGet, callback, false)
|
||||
}
|
||||
|
||||
@ -224,7 +229,7 @@ func (d *LanZou) getShareUrlHtml(shareID string) (string, error) {
|
||||
Value: vs,
|
||||
})
|
||||
}
|
||||
}, nil)
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
@ -315,7 +320,7 @@ func (d *LanZou) getFilesByShareUrl(shareID, pwd string, sharePageData string) (
|
||||
log.Errorf("lanzou: err => not find file page param ,data => %s\n", sharePageData)
|
||||
return nil, fmt.Errorf("not find file page param")
|
||||
}
|
||||
data, err := d.get(fmt.Sprint(d.ShareUrl, urlpaths[1]), nil, nil)
|
||||
data, err := d.get(fmt.Sprint(d.ShareUrl, urlpaths[1]), nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@ -445,3 +450,22 @@ func (d *LanZou) getFileRealInfo(downURL string) (*int64, *time.Time) {
|
||||
size, _ := strconv.ParseInt(res.Header().Get("Content-Length"), 10, 64)
|
||||
return &size, &time
|
||||
}
|
||||
|
||||
func (d *LanZou) getVei() (string, error) {
|
||||
resp, err := d.get("https://pc.woozooo.com/mydisk.php", func(req *resty.Request) {
|
||||
req.SetQueryParams(map[string]string{
|
||||
"item": "files",
|
||||
"action": "index",
|
||||
"u": d.uid,
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
html := RemoveNotes(string(resp))
|
||||
data, err := htmlJsonToMap(html)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return data["vei"], nil
|
||||
}
|
||||
|
@ -1,26 +0,0 @@
|
||||
package quark
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
Cookie string `json:"cookie" required:"true"`
|
||||
driver.RootID
|
||||
OrderBy string `json:"order_by" type:"select" options:"none,file_type,file_name,updated_at" default:"none"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
||||
}
|
||||
|
||||
var config = driver.Config{
|
||||
Name: "Quark",
|
||||
OnlyLocal: true,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &Quark{}
|
||||
})
|
||||
}
|
@ -22,29 +22,31 @@ import (
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
type Quark struct {
|
||||
type QuarkOrUC struct {
|
||||
model.Storage
|
||||
Addition
|
||||
config driver.Config
|
||||
conf Conf
|
||||
}
|
||||
|
||||
func (d *Quark) Config() driver.Config {
|
||||
return config
|
||||
func (d *QuarkOrUC) Config() driver.Config {
|
||||
return d.config
|
||||
}
|
||||
|
||||
func (d *Quark) GetAddition() driver.Additional {
|
||||
func (d *QuarkOrUC) GetAddition() driver.Additional {
|
||||
return &d.Addition
|
||||
}
|
||||
|
||||
func (d *Quark) Init(ctx context.Context) error {
|
||||
func (d *QuarkOrUC) Init(ctx context.Context) error {
|
||||
_, err := d.request("/config", http.MethodGet, nil, nil)
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Quark) Drop(ctx context.Context) error {
|
||||
func (d *QuarkOrUC) Drop(ctx context.Context) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Quark) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
func (d *QuarkOrUC) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
||||
files, err := d.GetFiles(dir.GetID())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -54,12 +56,12 @@ func (d *Quark) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([
|
||||
})
|
||||
}
|
||||
|
||||
func (d *Quark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
func (d *QuarkOrUC) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
||||
data := base.Json{
|
||||
"fids": []string{file.GetID()},
|
||||
}
|
||||
var resp DownResp
|
||||
ua := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch"
|
||||
ua := d.conf.ua
|
||||
_, err := d.request("/file/download", http.MethodPost, func(req *resty.Request) {
|
||||
req.SetHeader("User-Agent", ua).
|
||||
SetBody(data)
|
||||
@ -69,61 +71,63 @@ func (d *Quark) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (
|
||||
}
|
||||
u := resp.Data[0].DownloadUrl
|
||||
start, end := int64(0), file.GetSize()
|
||||
return &model.Link{
|
||||
Handle: func(w http.ResponseWriter, r *http.Request) error {
|
||||
if rg := r.Header.Get("Range"); rg != "" {
|
||||
parseRange, err := http_range.ParseRange(rg, file.GetSize())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start, end = parseRange[0].Start, parseRange[0].Start+parseRange[0].Length
|
||||
w.Header().Set("Content-Range", parseRange[0].ContentRange(file.GetSize()))
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(parseRange[0].Length, 10))
|
||||
w.WriteHeader(http.StatusPartialContent)
|
||||
} else {
|
||||
w.Header().Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
link := model.Link{
|
||||
Header: http.Header{},
|
||||
}
|
||||
if rg := args.Header.Get("Range"); rg != "" {
|
||||
parseRange, err := http_range.ParseRange(rg, file.GetSize())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
start, end = parseRange[0].Start, parseRange[0].Start+parseRange[0].Length
|
||||
link.Header.Set("Content-Range", parseRange[0].ContentRange(file.GetSize()))
|
||||
link.Header.Set("Content-Length", strconv.FormatInt(parseRange[0].Length, 10))
|
||||
link.Status = http.StatusPartialContent
|
||||
} else {
|
||||
link.Header.Set("Content-Length", strconv.FormatInt(file.GetSize(), 10))
|
||||
link.Status = http.StatusOK
|
||||
}
|
||||
link.Writer = func(w io.Writer) error {
|
||||
// request 10 MB at a time
|
||||
chunkSize := int64(10 * 1024 * 1024)
|
||||
for start < end {
|
||||
_end := start + chunkSize
|
||||
if _end > end {
|
||||
_end = end
|
||||
}
|
||||
// request 10 MB at a time
|
||||
chunkSize := int64(10 * 1024 * 1024)
|
||||
for start < end {
|
||||
_end := start + chunkSize
|
||||
if _end > end {
|
||||
_end = end
|
||||
}
|
||||
_range := "bytes=" + strconv.FormatInt(start, 10) + "-" + strconv.FormatInt(_end-1, 10)
|
||||
start = _end
|
||||
err = func() error {
|
||||
req, err := http.NewRequest(r.Method, u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Range", _range)
|
||||
req.Header.Set("User-Agent", ua)
|
||||
req.Header.Set("Cookie", d.Cookie)
|
||||
req.Header.Set("Referer", "https://pan.quark.cn")
|
||||
resp, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
return err
|
||||
}()
|
||||
_range := "bytes=" + strconv.FormatInt(start, 10) + "-" + strconv.FormatInt(_end-1, 10)
|
||||
start = _end
|
||||
err = func() error {
|
||||
req, err := http.NewRequest(http.MethodGet, u, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Range", _range)
|
||||
req.Header.Set("User-Agent", ua)
|
||||
req.Header.Set("Cookie", d.Cookie)
|
||||
req.Header.Set("Referer", d.conf.referer)
|
||||
resp, err := base.HttpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusPartialContent {
|
||||
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
|
||||
}
|
||||
_, err = io.Copy(w, resp.Body)
|
||||
return err
|
||||
}()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return &link, nil
|
||||
}
|
||||
|
||||
func (d *Quark) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
func (d *QuarkOrUC) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) error {
|
||||
data := base.Json{
|
||||
"dir_init_lock": false,
|
||||
"dir_path": "",
|
||||
@ -139,7 +143,7 @@ func (d *Quark) MakeDir(ctx context.Context, parentDir model.Obj, dirName string
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Quark) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
func (d *QuarkOrUC) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
data := base.Json{
|
||||
"action_type": 1,
|
||||
"exclude_fids": []string{},
|
||||
@ -152,7 +156,7 @@ func (d *Quark) Move(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Quark) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
func (d *QuarkOrUC) Rename(ctx context.Context, srcObj model.Obj, newName string) error {
|
||||
data := base.Json{
|
||||
"fid": srcObj.GetID(),
|
||||
"file_name": newName,
|
||||
@ -163,11 +167,11 @@ func (d *Quark) Rename(ctx context.Context, srcObj model.Obj, newName string) er
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Quark) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
func (d *QuarkOrUC) Copy(ctx context.Context, srcObj, dstDir model.Obj) error {
|
||||
return errs.NotSupport
|
||||
}
|
||||
|
||||
func (d *Quark) Remove(ctx context.Context, obj model.Obj) error {
|
||||
func (d *QuarkOrUC) Remove(ctx context.Context, obj model.Obj) error {
|
||||
data := base.Json{
|
||||
"action_type": 1,
|
||||
"exclude_fids": []string{},
|
||||
@ -179,7 +183,7 @@ func (d *Quark) Remove(ctx context.Context, obj model.Obj) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *Quark) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
func (d *QuarkOrUC) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreamer, up driver.UpdateProgress) error {
|
||||
tempFile, err := utils.CreateTempFile(stream.GetReadCloser())
|
||||
if err != nil {
|
||||
return err
|
||||
@ -264,4 +268,4 @@ func (d *Quark) Put(ctx context.Context, dstDir model.Obj, stream model.FileStre
|
||||
return d.upFinish(pre)
|
||||
}
|
||||
|
||||
var _ driver.Driver = (*Quark)(nil)
|
||||
var _ driver.Driver = (*QuarkOrUC)(nil)
|
55
drivers/quark_uc/meta.go
Normal file
55
drivers/quark_uc/meta.go
Normal file
@ -0,0 +1,55 @@
|
||||
package quark
|
||||
|
||||
import (
|
||||
"github.com/alist-org/alist/v3/internal/driver"
|
||||
"github.com/alist-org/alist/v3/internal/op"
|
||||
)
|
||||
|
||||
type Addition struct {
|
||||
Cookie string `json:"cookie" required:"true"`
|
||||
driver.RootID
|
||||
OrderBy string `json:"order_by" type:"select" options:"none,file_type,file_name,updated_at" default:"none"`
|
||||
OrderDirection string `json:"order_direction" type:"select" options:"asc,desc" default:"asc"`
|
||||
}
|
||||
|
||||
type Conf struct {
|
||||
ua string
|
||||
referer string
|
||||
api string
|
||||
pr string
|
||||
}
|
||||
|
||||
func init() {
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &QuarkOrUC{
|
||||
config: driver.Config{
|
||||
Name: "Quark",
|
||||
OnlyLocal: true,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
conf: Conf{
|
||||
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) quark-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
|
||||
referer: "https://pan.quark.cn",
|
||||
api: "https://drive.quark.cn/1/clouddrive",
|
||||
pr: "ucpro",
|
||||
},
|
||||
}
|
||||
})
|
||||
op.RegisterDriver(func() driver.Driver {
|
||||
return &QuarkOrUC{
|
||||
config: driver.Config{
|
||||
Name: "UC",
|
||||
OnlyLocal: true,
|
||||
DefaultRoot: "0",
|
||||
NoOverwriteUpload: true,
|
||||
},
|
||||
conf: Conf{
|
||||
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) uc-cloud-drive/2.5.20 Chrome/100.0.4896.160 Electron/18.3.5.4-b478491100 Safari/537.36 Channel/pckk_other_ch",
|
||||
referer: "https://drive.uc.cn",
|
||||
api: "https://pc-api.uc.cn/1/clouddrive",
|
||||
pr: "UCBrowser",
|
||||
},
|
||||
}
|
||||
})
|
||||
}
|
@ -22,15 +22,15 @@ import (
|
||||
|
||||
// do others that not defined in Driver interface
|
||||
|
||||
func (d *Quark) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
u := "https://drive.quark.cn/1/clouddrive" + pathname
|
||||
func (d *QuarkOrUC) request(pathname string, method string, callback base.ReqCallback, resp interface{}) ([]byte, error) {
|
||||
u := d.conf.api + pathname
|
||||
req := base.RestyClient.R()
|
||||
req.SetHeaders(map[string]string{
|
||||
"Cookie": d.Cookie,
|
||||
"Accept": "application/json, text/plain, */*",
|
||||
"Referer": "https://pan.quark.cn/",
|
||||
"Referer": d.conf.referer,
|
||||
})
|
||||
req.SetQueryParam("pr", "ucpro")
|
||||
req.SetQueryParam("pr", d.conf.pr)
|
||||
req.SetQueryParam("fr", "pc")
|
||||
if callback != nil {
|
||||
callback(req)
|
||||
@ -55,7 +55,7 @@ func (d *Quark) request(pathname string, method string, callback base.ReqCallbac
|
||||
return res.Body(), nil
|
||||
}
|
||||
|
||||
func (d *Quark) GetFiles(parent string) ([]File, error) {
|
||||
func (d *QuarkOrUC) GetFiles(parent string) ([]File, error) {
|
||||
files := make([]File, 0)
|
||||
page := 1
|
||||
size := 100
|
||||
@ -85,7 +85,7 @@ func (d *Quark) GetFiles(parent string) ([]File, error) {
|
||||
return files, nil
|
||||
}
|
||||
|
||||
func (d *Quark) upPre(file model.FileStreamer, parentId string) (UpPreResp, error) {
|
||||
func (d *QuarkOrUC) upPre(file model.FileStreamer, parentId string) (UpPreResp, error) {
|
||||
now := time.Now()
|
||||
data := base.Json{
|
||||
"ccp_hash_update": true,
|
||||
@ -105,7 +105,7 @@ func (d *Quark) upPre(file model.FileStreamer, parentId string) (UpPreResp, erro
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (d *Quark) upHash(md5, sha1, taskId string) (bool, error) {
|
||||
func (d *QuarkOrUC) upHash(md5, sha1, taskId string) (bool, error) {
|
||||
data := base.Json{
|
||||
"md5": md5,
|
||||
"sha1": sha1,
|
||||
@ -119,8 +119,8 @@ func (d *Quark) upHash(md5, sha1, taskId string) (bool, error) {
|
||||
return resp.Data.Finish, err
|
||||
}
|
||||
|
||||
func (d *Quark) upPart(ctx context.Context, pre UpPreResp, mineType string, partNumber int, bytes []byte) (string, error) {
|
||||
//func (driver Quark) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account, md5Str, sha1Str string) (string, error) {
|
||||
func (d *QuarkOrUC) upPart(ctx context.Context, pre UpPreResp, mineType string, partNumber int, bytes []byte) (string, error) {
|
||||
//func (driver QuarkOrUC) UpPart(pre UpPreResp, mineType string, partNumber int, bytes []byte, account *model.Account, md5Str, sha1Str string) (string, error) {
|
||||
timeStr := time.Now().UTC().Format(http.TimeFormat)
|
||||
data := base.Json{
|
||||
"auth_info": pre.Data.AuthInfo,
|
||||
@ -169,7 +169,7 @@ x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
|
||||
return res.Header().Get("ETag"), nil
|
||||
}
|
||||
|
||||
func (d *Quark) upCommit(pre UpPreResp, md5s []string) error {
|
||||
func (d *QuarkOrUC) upCommit(pre UpPreResp, md5s []string) error {
|
||||
timeStr := time.Now().UTC().Format(http.TimeFormat)
|
||||
log.Debugf("md5s: %+v", md5s)
|
||||
bodyBuilder := strings.Builder{}
|
||||
@ -236,7 +236,7 @@ x-oss-user-agent:aliyun-sdk-js/6.6.1 Chrome 98.0.4758.80 on Windows 10 64-bit
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Quark) upFinish(pre UpPreResp) error {
|
||||
func (d *QuarkOrUC) upFinish(pre UpPreResp) error {
|
||||
data := base.Json{
|
||||
"obj_key": pre.Data.ObjKey,
|
||||
"task_id": pre.Data.TaskId,
|
@ -136,11 +136,13 @@ func (d *S3) Put(ctx context.Context, dstDir model.Obj, stream model.FileStreame
|
||||
uploader.PartSize = stream.GetSize() / (s3manager.MaxUploadParts - 1)
|
||||
}
|
||||
key := getKey(stdpath.Join(dstDir.GetPath(), stream.GetName()), false)
|
||||
contentType := stream.GetMimetype()
|
||||
log.Debugln("key:", key)
|
||||
input := &s3manager.UploadInput{
|
||||
Bucket: &d.Bucket,
|
||||
Key: &key,
|
||||
Body: stream,
|
||||
Bucket: &d.Bucket,
|
||||
Key: &key,
|
||||
Body: stream,
|
||||
ContentType: &contentType,
|
||||
}
|
||||
_, err := uploader.UploadWithContext(ctx, input)
|
||||
return err
|
||||
|
@ -36,11 +36,14 @@ func (d *Seafile) request(method string, pathname string, callback base.ReqCallb
|
||||
if len(noRedirect) > 0 && noRedirect[0] {
|
||||
req = base.NoRedirectClient.R()
|
||||
}
|
||||
var res resty.Response
|
||||
req.SetHeader("Authorization", d.authorization)
|
||||
callback(req)
|
||||
var (
|
||||
res *resty.Response
|
||||
err error
|
||||
)
|
||||
for i := 0; i < 2; i++ {
|
||||
req.SetHeader("Authorization", d.authorization)
|
||||
callback(req)
|
||||
res, err := req.Execute(method, full)
|
||||
res, err = req.Execute(method, full)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
3
go.mod
3
go.mod
@ -5,6 +5,7 @@ go 1.20
|
||||
require (
|
||||
github.com/SheltonZhu/115driver v1.0.14
|
||||
github.com/Xhofe/go-cache v0.0.0-20220723083548-714439c8af9a
|
||||
github.com/avast/retry-go v3.0.0+incompatible
|
||||
github.com/aws/aws-sdk-go v1.44.262
|
||||
github.com/blevesearch/bleve/v2 v2.3.8
|
||||
github.com/caarlos0/env/v7 v7.1.0
|
||||
@ -26,7 +27,7 @@ require (
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pkg/sftp v1.13.5
|
||||
github.com/pquerna/otp v1.4.0
|
||||
github.com/sirupsen/logrus v1.9.0
|
||||
github.com/sirupsen/logrus v1.9.2
|
||||
github.com/spf13/cobra v1.7.0
|
||||
github.com/t3rm1n4l/go-mega v0.0.0-20230228171823-a01a2cda13ca
|
||||
github.com/u2takey/ffmpeg-go v0.4.1
|
||||
|
20
go.sum
20
go.sum
@ -12,17 +12,15 @@ github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1e
|
||||
github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8=
|
||||
github.com/andreburgaud/crypt2go v1.1.0 h1:eitZxTPY1krUsxinsng3Qvt/Ud7q/aQmmYRh8p4hyPw=
|
||||
github.com/andreburgaud/crypt2go v1.1.0/go.mod h1:4qhZPzarj1dCIRmCkpdgCklwp+hBq9yEt0zPe9Ayuhc=
|
||||
github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHSxpiH9JdtuBj0=
|
||||
github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY=
|
||||
github.com/aws/aws-sdk-go v1.38.20/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
|
||||
github.com/aws/aws-sdk-go v1.44.194 h1:1ZDK+QDcc5oRbZGgRZSz561eR8XVizXCeGpoZKo33NU=
|
||||
github.com/aws/aws-sdk-go v1.44.194/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/aws/aws-sdk-go v1.44.262 h1:gyXpcJptWoNkK+DiAiaBltlreoWKQXjAIh6FRh60F+I=
|
||||
github.com/aws/aws-sdk-go v1.44.262/go.mod h1:aVsgQcEevwlmQ7qHE9I3h+dtQgpqhFB+i8Phjh7fkwI=
|
||||
github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A=
|
||||
github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
|
||||
github.com/bits-and-blooms/bitset v1.2.0 h1:Kn4yilvwNtMACtf1eYDlG8H77R07mZSPbMjLyS07ChA=
|
||||
github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA=
|
||||
github.com/blevesearch/bleve/v2 v2.3.7 h1:nIfIrhv28tvgBpbVF8Dq7/U1zW/YiwSqg/PBgE3x8bo=
|
||||
github.com/blevesearch/bleve/v2 v2.3.7/go.mod h1:2tToYD6mDeseIA13jcZiEEqYrVLg6xdk0v6+F7dWquU=
|
||||
github.com/blevesearch/bleve/v2 v2.3.8 h1:IqFyMJ73n4gY8AmVqM8Sa6EtAZ5beE8yramVqCvs2kQ=
|
||||
github.com/blevesearch/bleve/v2 v2.3.8/go.mod h1:Lh9aZEHrLKxwPnW4z4lsBEGnflZQ1V/aWP/t+htsiDw=
|
||||
github.com/blevesearch/bleve_index_api v1.0.5 h1:Lc986kpC4Z0/n1g3gg8ul7H+lxgOQPcXb9SxvQGu+tw=
|
||||
@ -53,8 +51,6 @@ github.com/blevesearch/zapx/v13 v13.3.7 h1:igIQg5eKmjw168I7av0Vtwedf7kHnQro/M+ub
|
||||
github.com/blevesearch/zapx/v13 v13.3.7/go.mod h1:yyrB4kJ0OT75UPZwT/zS+Ru0/jYKorCOOSY5dBzAy+s=
|
||||
github.com/blevesearch/zapx/v14 v14.3.7 h1:gfe+fbWslDWP/evHLtp/GOvmNM3sw1BbqD7LhycBX20=
|
||||
github.com/blevesearch/zapx/v14 v14.3.7/go.mod h1:9J/RbOkqZ1KSjmkOes03AkETX7hrXT0sFMpWH4ewC4w=
|
||||
github.com/blevesearch/zapx/v15 v15.3.9 h1:/s9zqKxFaZKQTTcMO2b/Tup0ch5MSztlvw+frVDfIBk=
|
||||
github.com/blevesearch/zapx/v15 v15.3.9/go.mod h1:m7Y6m8soYUvS7MjN9eKlz1xrLCcmqfFadmu7GhWIrLY=
|
||||
github.com/blevesearch/zapx/v15 v15.3.10 h1:bQ9ZxJCj6rKp873EuVJu2JPxQ+EWQZI1cjJGeroovaQ=
|
||||
github.com/blevesearch/zapx/v15 v15.3.10/go.mod h1:m7Y6m8soYUvS7MjN9eKlz1xrLCcmqfFadmu7GhWIrLY=
|
||||
github.com/bluele/gcache v0.0.2 h1:WcbfdXICg7G/DGBh1PFfcirkWOQV+v077yF1pSy3DGw=
|
||||
@ -260,8 +256,8 @@ github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUA
|
||||
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
|
||||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/sirupsen/logrus v1.9.2 h1:oxx1eChJGI6Uks2ZC4W1zpLlVgqB8ner4EuQwV4Ik1Y=
|
||||
github.com/sirupsen/logrus v1.9.2/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0=
|
||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M=
|
||||
github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
|
||||
@ -320,8 +316,6 @@ golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0
|
||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||
golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw=
|
||||
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
|
||||
golang.org/x/crypto v0.8.0 h1:pd9TJtTueMTVQXzk8E2XESSMQDj/U7OUu0PqJqPXQjQ=
|
||||
golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE=
|
||||
golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g=
|
||||
golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0=
|
||||
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||
@ -338,8 +332,6 @@ golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qx
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco=
|
||||
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
|
||||
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
|
||||
golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M=
|
||||
golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
@ -365,15 +357,13 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
|
||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU=
|
||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
|
||||
golang.org/x/term v0.8.0 h1:n5xxQn2i3PC0yLAbjTpNT85q/Kgzcr2gIoX9OrJUols=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -3,6 +3,7 @@ package fs
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
stdpath "path"
|
||||
"sync/atomic"
|
||||
|
||||
@ -87,7 +88,9 @@ func copyFileBetween2Storages(tsk *task.Task[uint64], srcStorage, dstStorage dri
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get src [%s] file", srcFilePath)
|
||||
}
|
||||
link, _, err := op.Link(tsk.Ctx, srcStorage, srcFilePath, model.LinkArgs{})
|
||||
link, _, err := op.Link(tsk.Ctx, srcStorage, srcFilePath, model.LinkArgs{
|
||||
Header: http.Header{},
|
||||
})
|
||||
if err != nil {
|
||||
return errors.WithMessagef(err, "failed get [%s] link", srcFilePath)
|
||||
}
|
||||
|
@ -8,32 +8,15 @@ import (
|
||||
stdpath "path"
|
||||
"strings"
|
||||
|
||||
"github.com/alist-org/alist/v3/drivers/base"
|
||||
"github.com/alist-org/alist/v3/internal/conf"
|
||||
"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/alist-org/alist/v3/server/common"
|
||||
"github.com/google/uuid"
|
||||
"github.com/pkg/errors"
|
||||
log "github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
func ClearCache(path string) {
|
||||
storage, actualPath, err := op.GetStorageAndActualPath(path)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
op.ClearCache(storage, actualPath)
|
||||
}
|
||||
|
||||
func containsByName(files []model.Obj, file model.Obj) bool {
|
||||
for _, f := range files {
|
||||
if f.GetName() == file.GetName() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream, error) {
|
||||
var rc io.ReadCloser
|
||||
mimetype := utils.GetMimeType(file.GetName())
|
||||
@ -51,6 +34,16 @@ func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream,
|
||||
return nil, errors.Wrapf(err, "failed to open file %s", *link.FilePath)
|
||||
}
|
||||
rc = f
|
||||
} else if link.Writer != nil {
|
||||
r, w := io.Pipe()
|
||||
go func() {
|
||||
err := link.Writer(w)
|
||||
err = w.CloseWithError(err)
|
||||
if err != nil {
|
||||
log.Errorf("[getFileStreamFromLink] failed to write: %v", err)
|
||||
}
|
||||
}()
|
||||
rc = r
|
||||
} else {
|
||||
req, err := http.NewRequest(http.MethodGet, link.URL, nil)
|
||||
if err != nil {
|
||||
@ -59,7 +52,7 @@ func getFileStreamFromLink(file model.Obj, link *model.Link) (*model.FileStream,
|
||||
for h, val := range link.Header {
|
||||
req.Header[h] = val
|
||||
}
|
||||
res, err := base.HttpClient.Do(req)
|
||||
res, err := common.HttpClient().Do(req)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get response for %s", link.URL)
|
||||
}
|
||||
|
@ -11,19 +11,21 @@ type ListArgs struct {
|
||||
}
|
||||
|
||||
type LinkArgs struct {
|
||||
IP string
|
||||
Header http.Header
|
||||
Type string
|
||||
IP string
|
||||
Header http.Header
|
||||
Type string
|
||||
HttpReq *http.Request
|
||||
}
|
||||
|
||||
type Link struct {
|
||||
URL string `json:"url"`
|
||||
Header http.Header `json:"header"` // needed header
|
||||
Data io.ReadCloser // return file reader directly
|
||||
Status int // status maybe 200 or 206, etc
|
||||
FilePath *string // local file, return the filepath
|
||||
Expiration *time.Duration // url expiration time
|
||||
Handle func(w http.ResponseWriter, r *http.Request) error `json:"-"` // custom handler
|
||||
URL string `json:"url"`
|
||||
Header http.Header `json:"header"` // needed header (for url) or response header(for data or writer)
|
||||
Data io.ReadCloser // return file reader directly
|
||||
Status int // status maybe 200 or 206, etc
|
||||
FilePath *string // local file, return the filepath
|
||||
Expiration *time.Duration // url expiration time
|
||||
//Handle func(w http.ResponseWriter, r *http.Request) error `json:"-"` // custom handler
|
||||
Writer WriterFunc `json:"-"` // custom writer
|
||||
}
|
||||
|
||||
type OtherArgs struct {
|
||||
@ -37,3 +39,5 @@ type FsOtherArgs struct {
|
||||
Method string `json:"method" form:"method"`
|
||||
Data interface{} `json:"data" form:"data"`
|
||||
}
|
||||
|
||||
type WriterFunc func(w io.Writer) error
|
||||
|
@ -81,8 +81,21 @@ func Proxy(w http.ResponseWriter, r *http.Request, link *model.Link, file model.
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, filename, url.PathEscape(filename)))
|
||||
http.ServeContent(w, r, file.GetName(), fileStat.ModTime(), f)
|
||||
return nil
|
||||
} else if link.Handle != nil {
|
||||
return link.Handle(w, r)
|
||||
} else if link.Writer != nil {
|
||||
if link.Header != nil {
|
||||
for h, v := range link.Header {
|
||||
w.Header()[h] = v
|
||||
}
|
||||
}
|
||||
if cd := w.Header().Get("Content-Disposition"); cd == "" {
|
||||
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"; filename*=UTF-8''%s`, file.GetName(), url.PathEscape(file.GetName())))
|
||||
}
|
||||
if link.Status == 0 {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
} else {
|
||||
w.WriteHeader(link.Status)
|
||||
}
|
||||
return link.Writer(w)
|
||||
} else {
|
||||
req, err := http.NewRequest(r.Method, link.URL, nil)
|
||||
if err != nil {
|
||||
|
@ -31,9 +31,10 @@ func Down(c *gin.Context) {
|
||||
return
|
||||
} else {
|
||||
link, _, err := fs.Link(c, rawPath, model.LinkArgs{
|
||||
IP: c.ClientIP(),
|
||||
Header: c.Request.Header,
|
||||
Type: c.Query("type"),
|
||||
IP: c.ClientIP(),
|
||||
Header: c.Request.Header,
|
||||
Type: c.Query("type"),
|
||||
HttpReq: c.Request,
|
||||
})
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
@ -84,8 +85,9 @@ func Proxy(c *gin.Context) {
|
||||
}
|
||||
}
|
||||
link, file, err := fs.Link(c, rawPath, model.LinkArgs{
|
||||
Header: c.Request.Header,
|
||||
Type: c.Query("type"),
|
||||
Header: c.Request.Header,
|
||||
Type: c.Query("type"),
|
||||
HttpReq: c.Request,
|
||||
})
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
|
@ -476,7 +476,7 @@ func Link(c *gin.Context) {
|
||||
})
|
||||
return
|
||||
}
|
||||
link, _, err := fs.Link(c, rawPath, model.LinkArgs{IP: c.ClientIP()})
|
||||
link, _, err := fs.Link(c, rawPath, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header, HttpReq: c.Request})
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
|
@ -286,7 +286,11 @@ func FsGet(c *gin.Context) {
|
||||
rawURL = url
|
||||
} else {
|
||||
// if storage is not proxy, use raw url by fs.Link
|
||||
link, _, err := fs.Link(c, reqPath, model.LinkArgs{IP: c.ClientIP(), Header: c.Request.Header})
|
||||
link, _, err := fs.Link(c, reqPath, model.LinkArgs{
|
||||
IP: c.ClientIP(),
|
||||
Header: c.Request.Header,
|
||||
HttpReq: c.Request,
|
||||
})
|
||||
if err != nil {
|
||||
common.ErrorResp(c, err, 500)
|
||||
return
|
||||
|
@ -232,7 +232,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
||||
storage, _ := fs.GetStorage(reqPath, &fs.GetStoragesArgs{})
|
||||
downProxyUrl := storage.GetStorage().DownProxyUrl
|
||||
if storage.GetStorage().WebdavNative() || (storage.GetStorage().WebdavProxy() && downProxyUrl == "") {
|
||||
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{Header: r.Header})
|
||||
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{Header: r.Header, HttpReq: r})
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
@ -249,7 +249,7 @@ func (h *Handler) handleGetHeadPost(w http.ResponseWriter, r *http.Request) (sta
|
||||
w.Header().Set("Cache-Control", "max-age=0, no-cache, no-store, must-revalidate")
|
||||
http.Redirect(w, r, u, http.StatusFound)
|
||||
} else {
|
||||
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{IP: utils.ClientIP(r)})
|
||||
link, _, err := fs.Link(ctx, reqPath, model.LinkArgs{IP: utils.ClientIP(r), Header: r.Header, HttpReq: r})
|
||||
if err != nil {
|
||||
return http.StatusInternalServerError, err
|
||||
}
|
||||
|
Reference in New Issue
Block a user