mirror of
https://github.com/OpenListTeam/OpenList.git
synced 2025-11-25 03:15:19 +08:00
* feat(stream): enhance GetRangeReaderFromLink rate limiting * refactor(stream): update GetRangeReaderFromMFile to return *model.FileRangeReader * refactor(stream): simplify context error handling in RateLimitReader, RateLimitWriter, and RateLimitFile * refactor(net): replace custom LimitedReadCloser with readers.NewLimitedReadCloser * fix(model): update Link.ContentLength JSON tag for correct serialization * docs(model): add clarification to FileRangeReader usage comment
289 lines
7.3 KiB
Go
289 lines
7.3 KiB
Go
package protondrive
|
|
|
|
/*
|
|
Package protondrive
|
|
Author: Da3zKi7<da3zki7@duck.com>
|
|
Date: 2025-09-18
|
|
|
|
Thanks to @henrybear327 for modded go-proton-api & Proton-API-Bridge
|
|
|
|
The power of open-source, the force of teamwork and the magic of reverse engineering!
|
|
|
|
|
|
D@' 3z K!7 - The King Of Cracking
|
|
|
|
Да здравствует Родина))
|
|
*/
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
"github.com/OpenListTeam/OpenList/v4/internal/conf"
|
|
"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/setting"
|
|
"github.com/OpenListTeam/OpenList/v4/internal/stream"
|
|
"github.com/OpenListTeam/OpenList/v4/pkg/http_range"
|
|
"github.com/OpenListTeam/OpenList/v4/pkg/utils"
|
|
"github.com/ProtonMail/gopenpgp/v2/crypto"
|
|
proton_api_bridge "github.com/henrybear327/Proton-API-Bridge"
|
|
"github.com/henrybear327/Proton-API-Bridge/common"
|
|
"github.com/henrybear327/go-proton-api"
|
|
)
|
|
|
|
type ProtonDrive struct {
|
|
model.Storage
|
|
Addition
|
|
|
|
protonDrive *proton_api_bridge.ProtonDrive
|
|
|
|
apiBase string
|
|
appVersion string
|
|
protonJson string
|
|
userAgent string
|
|
sdkVersion string
|
|
webDriveAV string
|
|
|
|
c *proton.Client
|
|
|
|
// userKR *crypto.KeyRing
|
|
addrKRs map[string]*crypto.KeyRing
|
|
addrData map[string]proton.Address
|
|
|
|
MainShare *proton.Share
|
|
|
|
DefaultAddrKR *crypto.KeyRing
|
|
MainShareKR *crypto.KeyRing
|
|
}
|
|
|
|
func (d *ProtonDrive) Config() driver.Config {
|
|
return config
|
|
}
|
|
|
|
func (d *ProtonDrive) GetAddition() driver.Additional {
|
|
return &d.Addition
|
|
}
|
|
|
|
func (d *ProtonDrive) Init(ctx context.Context) (err error) {
|
|
defer func() {
|
|
if r := recover(); err == nil && r != nil {
|
|
err = fmt.Errorf("ProtonDrive initialization panic: %v", r)
|
|
}
|
|
}()
|
|
|
|
if d.Email == "" {
|
|
return fmt.Errorf("email is required")
|
|
}
|
|
if d.Password == "" {
|
|
return fmt.Errorf("password is required")
|
|
}
|
|
|
|
config := &common.Config{
|
|
AppVersion: d.appVersion,
|
|
UserAgent: d.userAgent,
|
|
FirstLoginCredential: &common.FirstLoginCredentialData{
|
|
Username: d.Email,
|
|
Password: d.Password,
|
|
TwoFA: d.TwoFACode,
|
|
},
|
|
EnableCaching: true,
|
|
ConcurrentBlockUploadCount: setting.GetInt(conf.TaskUploadThreadsNum, conf.Conf.Tasks.Upload.Workers),
|
|
//ConcurrentFileCryptoCount: 2,
|
|
UseReusableLogin: d.UseReusableLogin && d.ReusableCredential != (common.ReusableCredentialData{}),
|
|
ReplaceExistingDraft: true,
|
|
ReusableCredential: &d.ReusableCredential,
|
|
}
|
|
|
|
protonDrive, _, err := proton_api_bridge.NewProtonDrive(
|
|
ctx,
|
|
config,
|
|
d.authHandler,
|
|
func() {},
|
|
)
|
|
|
|
if err != nil && config.UseReusableLogin {
|
|
config.UseReusableLogin = false
|
|
protonDrive, _, err = proton_api_bridge.NewProtonDrive(ctx,
|
|
config,
|
|
d.authHandler,
|
|
func() {},
|
|
)
|
|
if err == nil {
|
|
op.MustSaveDriverStorage(d)
|
|
}
|
|
}
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("failed to initialize ProtonDrive: %w", err)
|
|
}
|
|
|
|
if err := d.initClient(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
d.protonDrive = protonDrive
|
|
d.MainShare = protonDrive.MainShare
|
|
if d.RootFolderID == "root" || d.RootFolderID == "" {
|
|
d.RootFolderID = protonDrive.RootLink.LinkID
|
|
}
|
|
d.MainShareKR = protonDrive.MainShareKR
|
|
d.DefaultAddrKR = protonDrive.DefaultAddrKR
|
|
|
|
return nil
|
|
}
|
|
|
|
func (d *ProtonDrive) Drop(ctx context.Context) error {
|
|
return nil
|
|
}
|
|
|
|
func (d *ProtonDrive) List(ctx context.Context, dir model.Obj, args model.ListArgs) ([]model.Obj, error) {
|
|
entries, err := d.protonDrive.ListDirectory(ctx, dir.GetID())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list directory: %w", err)
|
|
}
|
|
|
|
objects := make([]model.Obj, 0, len(entries))
|
|
for _, entry := range entries {
|
|
obj := &model.Object{
|
|
ID: entry.Link.LinkID,
|
|
Name: entry.Name,
|
|
Size: entry.Link.Size,
|
|
Modified: time.Unix(entry.Link.ModifyTime, 0),
|
|
IsFolder: entry.IsFolder,
|
|
}
|
|
objects = append(objects, obj)
|
|
}
|
|
|
|
return objects, nil
|
|
}
|
|
|
|
func (d *ProtonDrive) Link(ctx context.Context, file model.Obj, args model.LinkArgs) (*model.Link, error) {
|
|
link, err := d.getLink(ctx, file.GetID())
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed get file link: %+v", err)
|
|
}
|
|
fileSystemAttrs, err := d.protonDrive.GetActiveRevisionAttrs(ctx, link)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed get file revision: %+v", err)
|
|
}
|
|
// 解密后的文件大小
|
|
size := fileSystemAttrs.Size
|
|
|
|
rangeReaderFunc := func(rangeCtx context.Context, httpRange http_range.Range) (io.ReadCloser, error) {
|
|
length := httpRange.Length
|
|
if length < 0 || httpRange.Start+length > size {
|
|
length = size - httpRange.Start
|
|
}
|
|
reader, _, _, err := d.protonDrive.DownloadFile(rangeCtx, link, httpRange.Start)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed start download: %+v", err)
|
|
}
|
|
return utils.ReadCloser{
|
|
Reader: io.LimitReader(reader, length),
|
|
Closer: reader,
|
|
}, nil
|
|
}
|
|
|
|
expiration := time.Minute
|
|
return &model.Link{
|
|
RangeReader: stream.RateLimitRangeReaderFunc(rangeReaderFunc),
|
|
ContentLength: size,
|
|
Expiration: &expiration,
|
|
}, nil
|
|
}
|
|
|
|
func (d *ProtonDrive) MakeDir(ctx context.Context, parentDir model.Obj, dirName string) (model.Obj, error) {
|
|
id, err := d.protonDrive.CreateNewFolderByID(ctx, parentDir.GetID(), dirName)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to create directory: %w", err)
|
|
}
|
|
|
|
newDir := &model.Object{
|
|
ID: id,
|
|
Name: dirName,
|
|
IsFolder: true,
|
|
Modified: time.Now(),
|
|
}
|
|
return newDir, nil
|
|
}
|
|
|
|
func (d *ProtonDrive) Move(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
|
return d.DirectMove(ctx, srcObj, dstDir)
|
|
}
|
|
|
|
func (d *ProtonDrive) Rename(ctx context.Context, srcObj model.Obj, newName string) (model.Obj, error) {
|
|
if d.protonDrive == nil {
|
|
return nil, fmt.Errorf("protonDrive bridge is nil")
|
|
}
|
|
|
|
return d.DirectRename(ctx, srcObj, newName)
|
|
}
|
|
|
|
func (d *ProtonDrive) Copy(ctx context.Context, srcObj, dstDir model.Obj) (model.Obj, error) {
|
|
if srcObj.IsDir() {
|
|
return nil, fmt.Errorf("directory copy not supported")
|
|
}
|
|
|
|
srcLink, err := d.getLink(ctx, srcObj.GetID())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
reader, linkSize, fileSystemAttrs, err := d.protonDrive.DownloadFile(ctx, srcLink, 0)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to download source file: %w", err)
|
|
}
|
|
defer reader.Close()
|
|
|
|
actualSize := linkSize
|
|
if fileSystemAttrs != nil && fileSystemAttrs.Size > 0 {
|
|
actualSize = fileSystemAttrs.Size
|
|
}
|
|
|
|
file := &stream.FileStream{
|
|
Ctx: ctx,
|
|
Obj: &model.Object{
|
|
Name: srcObj.GetName(),
|
|
// Use the accurate and real size
|
|
Size: actualSize,
|
|
Modified: srcObj.ModTime(),
|
|
},
|
|
Reader: reader,
|
|
}
|
|
defer file.Close()
|
|
return d.Put(ctx, dstDir, file, func(percentage float64) {})
|
|
}
|
|
|
|
func (d *ProtonDrive) Remove(ctx context.Context, obj model.Obj) error {
|
|
if obj.IsDir() {
|
|
return d.protonDrive.MoveFolderToTrashByID(ctx, obj.GetID(), false)
|
|
} else {
|
|
return d.protonDrive.MoveFileToTrashByID(ctx, obj.GetID())
|
|
}
|
|
}
|
|
|
|
func (d *ProtonDrive) Put(ctx context.Context, dstDir model.Obj, file model.FileStreamer, up driver.UpdateProgress) (model.Obj, error) {
|
|
return d.uploadFile(ctx, dstDir.GetID(), file, up)
|
|
}
|
|
|
|
func (d *ProtonDrive) GetDetails(ctx context.Context) (*model.StorageDetails, error) {
|
|
about, err := d.protonDrive.About(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
total := uint64(about.MaxSpace)
|
|
free := total - uint64(about.UsedSpace)
|
|
return &model.StorageDetails{
|
|
DiskUsage: model.DiskUsage{
|
|
TotalSpace: total,
|
|
FreeSpace: free,
|
|
},
|
|
}, nil
|
|
}
|
|
|
|
var _ driver.Driver = (*ProtonDrive)(nil)
|