Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
149 changes: 149 additions & 0 deletions drivers/189/torrent.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package _189

import (
"context"
"crypto/sha1"
"encoding/hex"
"fmt"
"io"
"strings"

"github.com/OpenListTeam/OpenList/v4/internal/model"
"github.com/OpenListTeam/OpenList/v4/pkg/torrent"
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
)

// GenerateTorrent 根据上传过程中收集的哈希信息生成包含 CAS 扩展的 torrent 文件
func GenerateTorrent(fileName string, fileSize int64, fileMD5 string, sliceMD5s []string, sliceSize int64, pieceHashes []byte) ([]byte, error) {
// 计算 sliceMD5
sliceMD5 := fileMD5
if len(sliceMD5s) > 1 {
joined := strings.Join(sliceMD5s, "\n")
sliceMD5 = strings.ToUpper(torrent.GetMD5Str(joined))
}

t := torrent.NewTorrent(fileName, fileSize, fileMD5)
t.Info.PieceLength = sliceSize
t.SetPieces(pieceHashes)
t.SetCASInfo(&torrent.CASInfo{
FileMD5: fileMD5,
SliceMD5: sliceMD5,
SliceMD5s: sliceMD5s,
SliceSize: sliceSize,
Cloud: "189",
})

return t.Encode()
}

// RapidUploadFromTorrent 从 torrent 文件中提取 CAS 信息进行秒传
func (d *Cloud189) RapidUploadFromTorrent(ctx context.Context, dstDir model.Obj, torrentData []byte) error {
// 解析 torrent
t, err := torrent.Decode(torrentData)
if err != nil {
return fmt.Errorf("解析 torrent 失败: %w", err)
}

// 检查是否包含 CAS 扩展信息
if !t.HasCASInfo() {
return fmt.Errorf("torrent 不包含 CAS 扩展信息,无法秒传")
}

cas := t.CAS
fileName := t.Info.Name
fileSize := t.GetTotalSize()

// 获取 sessionKey
sessionKey, err := d.getSessionKey()
if err != nil {
return err
}
d.sessionKey = sessionKey

// 初始化上传
res, err := d.uploadRequest("/person/initMultiUpload", map[string]string{
"parentFolderId": dstDir.GetID(),
"fileName": encode(fileName),
"fileSize": fmt.Sprint(fileSize),
"sliceSize": fmt.Sprint(cas.SliceSize),
"lazyCheck": "1",
}, nil)
if err != nil {
return fmt.Errorf("初始化上传失败: %w", err)
}

uploadFileId := utils.Json.Get(res, "data", "uploadFileId").ToString()

// 提交上传(使用 CAS 信息秒传)
_, err = d.uploadRequest("/person/commitMultiUploadFile", map[string]string{
"uploadFileId": uploadFileId,
"fileMd5": cas.FileMD5,
"sliceMd5": cas.SliceMD5,
"lazyCheck": "1",
"opertype": "3",
}, nil)
if err != nil {
return fmt.Errorf("秒传提交失败: %w", err)
}

return nil
}

// ComputeTorrentFromReader 从 io.Reader 计算并生成 torrent 文件
func ComputeTorrentFromReader(reader io.Reader, fileName string, fileSize int64, sliceSize int64) ([]byte, error) {
if sliceSize <= 0 {
sliceSize = torrent.DefaultPieceSize
}

hw := torrent.NewHashWriter(sliceSize, sliceSize)

buf := make([]byte, 32*1024)
for {
n, err := reader.Read(buf)
if n > 0 {
hw.Write(buf[:n])
}
if err == io.EOF {
break
}
if err != nil {
return nil, err
}
}
hw.Finish()

fileMD5 := hw.GetFileMD5()
sliceMD5s := hw.GetSliceMD5s()
pieceHashes := hw.GetPieceHashes()

return GenerateTorrent(fileName, fileSize, fileMD5, sliceMD5s, sliceSize, pieceHashes)
}

// ComputePieceSHA1 计算单个分片的 SHA-1 哈希
func ComputePieceSHA1(data []byte) []byte {
h := sha1.Sum(data)
return h[:]
}

// ExtractCASFromTorrent 从 torrent 数据中提取 CAS 信息
func ExtractCASFromTorrent(torrentData []byte) (*torrent.CASInfo, string, int64, error) {
t, err := torrent.Decode(torrentData)
if err != nil {
return nil, "", 0, fmt.Errorf("解析 torrent 失败: %w", err)
}

if !t.HasCASInfo() {
return nil, "", 0, fmt.Errorf("torrent 不包含 CAS 扩展信息")
}

return t.CAS, t.Info.Name, t.GetTotalSize(), nil
}

// GetInfoHashHex 获取 torrent 的 info_hash(十六进制字符串)
func GetInfoHashHex(torrentData []byte) (string, error) {
t, err := torrent.Decode(torrentData)
if err != nil {
return "", err
}
return hex.EncodeToString(t.InfoHash), nil
}
55 changes: 54 additions & 1 deletion drivers/189/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"context"
"crypto/md5"
sha1Pkg "crypto/sha1"
"encoding/base64"
"encoding/hex"
"errors"
Expand All @@ -18,6 +19,8 @@ import (
"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/op"
"github.com/OpenListTeam/OpenList/v4/internal/stream"
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
myrand "github.com/OpenListTeam/OpenList/v4/pkg/utils/random"
"github.com/go-resty/resty/v2"
Expand Down Expand Up @@ -388,6 +391,10 @@ func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.F
var finish int64 = 0
var i int64
var byteSize int64

// 额外计算 SHA-1 piece hash 用于生成 torrent
pieceSHA1Hashes := make([]byte, 0, int(count)*20)

for i = 1; i <= count; i++ {
if utils.IsCanceled(ctx) {
return ctx.Err()
Expand All @@ -404,6 +411,10 @@ func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.F
finish += int64(n)
md5Bytes := getMd5(byteData)
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)

// 计算 SHA-1 piece hash
sha1Hash := sha1Pkg.Sum(byteData)
pieceSHA1Hashes = append(pieceSHA1Hashes, sha1Hash[:]...)
var resp UploadUrlsResp
res, err = d.uploadRequest("/person/getMultiUploadUrls", map[string]string{
"partInfo": fmt.Sprintf("%s-%s", strconv.FormatInt(i, 10), md5Base64),
Expand Down Expand Up @@ -439,7 +450,49 @@ func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.F
"lazyCheck": "1",
"opertype": "3",
}, nil)
return err
if err != nil {
return err
}

// 生成 torrent 文件(异步,不影响上传结果)
capturedDstDir := dstDir
capturedFileName := file.GetName()
capturedFileSize := fileSize
capturedFileMd5Hex := fileMd5Hex
capturedMd5s := md5s
go func() {
fileMD5Upper := strings.ToUpper(capturedFileMd5Hex)
torrentData, err := GenerateTorrent(capturedFileName, capturedFileSize, fileMD5Upper, capturedMd5s, DEFAULT, pieceSHA1Hashes)
if err != nil {
log.Warnf("生成 torrent 失败: %v", err)
return
}
infoHash, _ := GetInfoHashHex(torrentData)
torrentName := capturedFileName + ".cas.torrent"
log.Infof("已生成 torrent: %s (info_hash: %s, size: %d bytes)",
torrentName, infoHash, len(torrentData))

// 将 torrent 文件上传到同一目录
torrentFileStream := &stream.FileStream{
Ctx: context.Background(),
Obj: &model.Object{
Name: torrentName,
Size: int64(len(torrentData)),
IsFolder: false,
},
Reader: bytes.NewReader(torrentData),
Mimetype: "application/x-bittorrent",
}
uploadErr := d.oldUpload(capturedDstDir, torrentFileStream)
if uploadErr != nil {
log.Warnf("上传 torrent 文件失败: %v", uploadErr)
} else {
log.Infof("torrent 文件已上传: %s", torrentName)
op.Cache.DeleteDirectory(d, capturedDstDir.GetPath())
}
}()

return nil
}

func (d *Cloud189) getCapacityInfo(ctx context.Context) (*CapacityResp, error) {
Expand Down
23 changes: 6 additions & 17 deletions drivers/189pc/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,7 @@ func (y *Cloud189PC) Put(ctx context.Context, dstDir model.Obj, stream model.Fil

// 响应时间长,按需启用
if y.Addition.RapidUpload && !stream.IsForceStreamUpload() {
// 尝试妙传
if newObj, err := y.RapidUpload(ctx, dstDir, stream, isFamily, overwrite); err == nil {
return newObj, nil
}
Expand All @@ -351,10 +352,11 @@ func (y *Cloud189PC) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
uploadMethod := y.UploadMethod
if stream.IsForceStreamUpload() {
uploadMethod = "stream"
}

// 旧版上传家庭云也有限制
if uploadMethod == "old" {
} else if y.Addition.RapidUpload && stream.GetFile() != nil {
// 文件流支持随机读取,走FastUpload计算MD5并尝试秒传
uploadMethod = "rapid"
} else if uploadMethod == "old" {
// 旧版上传家庭云也有限制
return y.OldUpload(ctx, dstDir, stream, up, isFamily, overwrite)
}

Expand Down Expand Up @@ -416,19 +418,6 @@ func (y *Cloud189PC) Put(ctx context.Context, dstDir model.Obj, stream model.Fil
if stream.GetSize() == 0 {
return y.FastUpload(ctx, dstDir, stream, up, isFamily, overwrite)
}
// 尝试秒传:如果已有MD5且启用了RapidUpload则用RapidUpload,否则走FastUpload(会计算MD5并尝试秒传)
if !stream.IsForceStreamUpload() {
fileMd5 := stream.GetHash().GetHash(utils.MD5)
if len(fileMd5) >= utils.MD5.Width && y.Addition.RapidUpload {
// 源文件已有MD5且启用了RapidUpload配置,尝试快速秒传
if newObj, err := y.RapidUpload(ctx, dstDir, stream, isFamily, overwrite); err == nil {
return newObj, nil
}
} else if len(fileMd5) < utils.MD5.Width {
// 源文件无MD5(如从本地复制),走FastUpload计算MD5并尝试秒传
return y.FastUpload(ctx, dstDir, stream, up, isFamily, overwrite)
}
}
fallthrough
default:
return y.StreamUpload(ctx, dstDir, stream, up, isFamily, overwrite)
Expand Down
19 changes: 10 additions & 9 deletions drivers/189pc/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,16 @@ type Addition struct {
AccessToken string `json:"access_token" required:"false"`
RefreshToken string `json:"refresh_token" help:"To switch accounts, please clear this field"`
driver.RootID
OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"`
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"`
UploadMethod string `json:"upload_method" type:"select" options:"stream,rapid,old" default:"stream"`
UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"`
FamilyTransfer bool `json:"family_transfer"`
RapidUpload bool `json:"rapid_upload"`
NoUseOcr bool `json:"no_use_ocr"`
OrderBy string `json:"order_by" type:"select" options:"filename,filesize,lastOpTime" default:"filename"`
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"`
UploadMethod string `json:"upload_method" type:"select" options:"stream,rapid,old" default:"stream"`
UploadThread string `json:"upload_thread" default:"3" help:"1<=thread<=32"`
FamilyTransfer bool `json:"family_transfer"`
RapidUpload bool `json:"rapid_upload"`
NoUseOcr bool `json:"no_use_ocr"`
GenerateTorrent bool `json:"generate_torrent" help:"Generate torrent file with CAS extension after upload"`
}

var config = driver.Config{
Expand Down
Loading
Loading