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
}
29 changes: 28 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 Down Expand Up @@ -332,6 +333,10 @@ func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.F
var byteSize int64
md5s := make([]string, 0)
md5Sum := md5.New()

// 额外计算 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 @@ -353,6 +358,11 @@ func (d *Cloud189) newUpload(ctx context.Context, dstDir model.Obj, file model.F
md5Base64 := base64.StdEncoding.EncodeToString(md5Bytes)
md5s = append(md5s, strings.ToUpper(md5Hex))
md5Sum.Write(byteData)

// 计算 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 @@ -393,7 +403,24 @@ 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 文件
go func() {
fileMD5Upper := strings.ToUpper(fileMd5)
torrentData, err := GenerateTorrent(file.GetName(), file.GetSize(), fileMD5Upper, md5s, DEFAULT, pieceSHA1Hashes)
if err != nil {
log.Warnf("生成 torrent 失败: %v", err)
return
}
infoHash, _ := GetInfoHashHex(torrentData)
log.Infof("已生成 torrent: %s.torrent (info_hash: %s, size: %d bytes)",
file.GetName(), infoHash, len(torrentData))
}()

Comment on lines +410 to +422
return nil
}

func (d *Cloud189) getCapacityInfo(ctx context.Context) (*CapacityResp, error) {
Expand Down
19 changes: 10 additions & 9 deletions drivers/189pc/meta.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,16 @@ type Addition struct {
VCode string `json:"validate_code"`
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