- 最大上传单文件支持至128G
- 上传速度优化
- 下载取消文件预分配
- 因官方接口变动上传文件强制计算秒传
- transfer命令修复--download参数
This commit is contained in:
qjfoidnh
2025-08-30 13:20:52 +08:00
parent 572655de25
commit 7853849409
17 changed files with 156 additions and 70 deletions

View File

@@ -88,12 +88,19 @@ iikira/BaiduPCS-Go was largely inspired by [GangZhuo/BaiduPCS](https://github.co
[下载](#下载文件目录)网盘内文件, 支持多个文件或目录下载, 支持断点续传和单文件并行下载;
[上传](#上传文件目录)本地文件, 支持上传大文件, 支持多个文件或目录上传;
[上传](#上传文件目录)本地文件, 支持上传最大128G大文件, 支持多个文件或目录上传;
[转存](#转存文件目录)其他用户分享的文件, 支持带密码的分享链接;
[离线下载](#离线下载), 支持http/https/ftp/电驴/磁力链协议.
# 版本更新
**2025.08.29** v3.9.9
- 最大上传单文件支持至128G
- 上传速度优化
- 下载取消文件预分配
- 因官方接口变动上传文件强制计算秒传
- transfer命令修复--download参数
**2025.08.29** v3.9.8
- 全面修复了上传文件的问题
- 全面修复了下载文件的问题
@@ -910,7 +917,7 @@ c4.pcs.baidu.com
c5.pcs.baidu.com
d.pcs.baidu.com
```
最新版上传时支持动态获取pcs服务器, 理论上不需要手动配置
v3.9.8后上传时支持动态获取pcs服务器, 理论上不需要手动配置. 如希望使用静态pcs服务器, 可配置打开`fix_pcs_addr`
`cache_size` 的值支持可选设置单位了, 单位不区分大小写, `b``B` 均表示字节的意思, 如 `64KB`, `1MB`, `32kb`, `65536b`, `65536`.

View File

@@ -146,6 +146,7 @@ type (
pcsAddr string
panUA string
isSetPanUA bool
fixPCSAddr bool
ph *panhome.PanHome
cacheOpMap cachemap.CacheOpMap
}
@@ -377,6 +378,10 @@ func (pcs *BaiduPCS) SetHTTPS(https bool) {
pcs.isHTTPS = https
}
func (pcs *BaiduPCS) SetStaticPCSAddr(static bool) {
pcs.fixPCSAddr = static
}
// URL 返回 url
func (pcs *BaiduPCS) URL() *url.URL {
host := pcs.pcsAddr

View File

@@ -66,7 +66,7 @@ func (pcs *BaiduPCS) getLocateDownloadLink(pcspath string) (link string, pcsErro
func (pcs *BaiduPCS) ExportByFileInfo(finfo *FileDirectory) (rinfo *RapidUploadInfo, pcsError pcserror.Error) {
errInfo := pcserror.NewPCSErrorInfo(OperationExportFileInfo)
errInfo.ErrType = pcserror.ErrTypeOthers
if finfo.Size > MaxRapidUploadSize {
if finfo.Size > MaxUploadSize {
errInfo.Err = ErrFileTooLarge
return nil, errInfo
}
@@ -108,7 +108,7 @@ func (pcs *BaiduPCS) GetRapidUploadInfoByFileInfo(finfo *FileDirectory) (rinfo *
})
// 如果是没获取到MD5, 可尝试新接口(测试中), 新接口调用频率有限制且文件大小不能超过约3.9G
if pcsError != nil && pcsError.GetError() == ErrGetRapidUploadInfoMD5NotFound && finfo.Size < 4 * converter.GB {
if pcsError != nil && pcsError.GetError() == ErrGetRapidUploadInfoMD5NotFound && finfo.Size < 4*converter.GB {
link, pcsError = pcs.GetDirectDownloadLink(finfo.Path)
rinfo, pcsError = pcs.GetRapidUploadInfoByLink(link, &RapidUploadInfo{
ContentLength: finfo.Size,
@@ -121,12 +121,11 @@ func (pcs *BaiduPCS) GetRapidUploadInfoByFileInfo(finfo *FileDirectory) (rinfo *
func (pcs *BaiduPCS) GetDirectDownloadLink(path string) (link string, pcsError pcserror.Error) {
var header = pcs.getPanUAHeader()
header["Range"] = "bytes=0-" + strconv.FormatInt(SliceMD5Size-1, 10)
RawQuery := map[string] string{"path": path}
RawQuery := map[string]string{"path": path}
pcsURL := pcs.generatePCSURL("file", "download", RawQuery)
return pcsURL.String(), nil
}
// GetRapidUploadInfoByLink 通过下载链接, 获取文件秒传信息
func (pcs *BaiduPCS) GetRapidUploadInfoByLink(link string, compareRInfo *RapidUploadInfo) (rinfo *RapidUploadInfo, pcsError pcserror.Error) {
errInfo := pcserror.NewPCSErrorInfo(OperationGetRapidUploadInfo)
@@ -277,7 +276,7 @@ func (pcs *BaiduPCS) FixMD5ByFileInfo(finfo *FileDirectory) (pcsError pcserror.E
return errInfo
}
if finfo.Size > MaxRapidUploadSize { // 文件大于20GB
if finfo.Size > MaxUploadSize {
errInfo.Err = ErrFileTooLarge
return errInfo
}

View File

@@ -118,7 +118,10 @@ func (pcs *BaiduPCS) PrepareUK() (dataReadCloser io.ReadCloser, pcsError pcserro
// PreparePCSServers 获取推荐的pcs服务器URL
func (pcs *BaiduPCS) PreparePCSServers() (dataReadCloser io.ReadCloser, pcsError pcserror.Error) {
pcs.lazyInit()
pcsURL := pcs.generatePCSURL("file", "locateupload")
pcsURL := pcs.generatePCSURL("file", "locateupload", map[string]string{
"upload_version": "2.0",
"app_id": PanAppID,
})
baiduPCSVerbose.Infof("%s URL: %s\n", OperationGetPCSServer, pcsURL)
dataReadCloser, pcsError = pcs.sendReqReturnReadCloser(reqTypePCS, OperationGetPCSServer, http.MethodGet, pcsURL.String(), nil, nil)

View File

@@ -5,19 +5,30 @@ import (
"github.com/qjfoidnh/BaiduPCS-Go/baidupcs/pcserror"
"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/converter"
"net/http"
"net/url"
"path"
"strings"
)
const (
// MinUploadBlockSize 最小的上传的文件分片大小
// MaxUploadBlockSize 上传的文件分片最大大小
MaxUploadBlockSize = 64 * converter.MB
// MiddleUploadBlockSize 上传的文件分片中等大小
MiddleUploadBlockSize = 16 * converter.MB
// MinUploadBlockSize 上传的文件分片最小大小
MinUploadBlockSize = 4 * converter.MB
// MaxRapidUploadSize 秒传文件支持的最文件大小
MaxRapidUploadSize = 20 * converter.GB
// RecommendedUploadSize 推荐的最文件上传大小
RecommendedUploadSize = 32 * converter.GB
// MaxUploadSize 目前支持的最大文件大小
MaxUploadSize = 128 * converter.GB
// SliceMD5Size 计算 slice-md5 所需的长度
SliceMD5Size = 256 * converter.KB
// EmptyContentMD5 空串的md5
EmptyContentMD5 = "d41d8cd98f00b204e9800998ecf8427e"
// MiddleUploadThreshold 中等分片对应的文件大小
MiddleUploadThreshold = 8 * converter.GB
// MaxUploadThreshold 最大分片对应的文件大小
MaxUploadThreshold = 32 * converter.GB
)
var (
@@ -71,6 +82,17 @@ type (
UploadID string
UploadSeqList []*UploadSeq
}
PCSServer struct {
ServerAddr string `json:"server"`
}
PCSInfo struct {
*pcserror.PCSErrInfo
Host string `json:"host"`
Server []string `json:"server"`
Servers []*PCSServer `json:"servers"`
}
)
// RapidUpload 秒传文件
@@ -235,3 +257,38 @@ func (pcs *BaiduPCS) UploadPrecreate(targetPath, contentMD5, sliceMD5, crc32 str
panic("unknown returntype")
}
}
// GetRandomPCSHost 随机获取一个可用的pcs地址
func (pcs *BaiduPCS) GetRandomPCSHost() (pcsError pcserror.Error, pcsHost string) {
if pcs.fixPCSAddr {
return
}
dataReadCloser, pcsError := pcs.PreparePCSServers()
if pcsError != nil {
return
}
defer dataReadCloser.Close()
pcsInfo := &PCSInfo{
PCSErrInfo: pcserror.NewPCSErrorInfo(OperationGetPCSServer),
}
pcsError = pcserror.HandleJSONParse(OperationGetPCSServer, dataReadCloser, pcsInfo)
if pcsError != nil {
return
}
pcsHostList := make([]string, 0)
if len(pcsInfo.Servers) > 0 {
for _, server := range pcsInfo.Servers {
if strings.Contains(server.ServerAddr, "-") {
parsedURL, err := url.Parse(server.ServerAddr)
if err != nil {
continue
}
pcsHostList = append(pcsHostList, parsedURL.Hostname())
}
}
} else if len(pcsInfo.Server) > 0 {
pcsHostList = pcsInfo.Server
}
pcsHost = RandomElement(pcsHostList)
return
}

View File

@@ -8,6 +8,7 @@ import (
"github.com/qjfoidnh/BaiduPCS-Go/pcsutil"
"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/converter"
"io"
"math/rand"
"path"
"regexp"
"sort"
@@ -152,3 +153,11 @@ func DecryptMD5(rawMD5 string) string {
}
return sliceThird[8:16] + sliceThird[0:8] + sliceThird[24:32] + sliceThird[16:24]
}
func RandomElement[T any](s []T) T {
if len(s) == 0 {
var zero T // 对于空slice返回类型的零值
return zero
}
return s[rand.Intn(len(s))]
}

View File

@@ -81,6 +81,7 @@ func (c *PCSConfig) PrintTable() {
[]string{"user_agent", c.UserAgent, requester.DefaultUserAgent, "浏览器标识"},
[]string{"pcs_ua", c.PCSUA, "", "PCS 浏览器标识"},
[]string{"pcs_addr", c.PCSAddr, "pcs.baidu.com", "PCS 服务器地址"},
[]string{"fix_pcs_addr", fmt.Sprint(c.FixPCSAddr), "false", "不使用动态PCS服务器地址, 通常情况保持默认即可"},
[]string{"pan_ua", c.PanUA, baidupcs.NetdiskUA, "Pan 浏览器标识"},
[]string{"proxy", c.Proxy, "", "设置代理, 支持 http/socks5 代理"},
[]string{"local_addrs", c.LocalAddrs, "", "设置本地网卡地址, 多个地址用逗号隔开"},

View File

@@ -83,7 +83,7 @@ func (c *PCSConfig) manipUser(op string, baiduBase *BaiduBase) (*Baidu, error) {
return nil, ErrBaiduUserNotFound
}
//setupNewUser 从已有用户中, 设置新的当前登录用户
// setupNewUser 从已有用户中, 设置新的当前登录用户
func (c *PCSConfig) setupNewUser(user *Baidu) {
if user == nil {
return
@@ -216,6 +216,14 @@ func (c *PCSConfig) SETPCSAddr(pcsaddr string) bool {
return match
}
// SetStaticPCSAddr 设置上传时是否关闭动态PCS域名
func (c *PCSConfig) SetStaticPCSAddr(static bool) {
c.FixPCSAddr = static
if c.pcs != nil {
c.pcs.SetStaticPCSAddr(static)
}
}
// SetEnableHTTPS 设置是否启用https
func (c *PCSConfig) SetEnableHTTPS(https bool) {
c.EnableHTTPS = https
@@ -253,4 +261,4 @@ func (c *PCSConfig) SetIgnoreIllegal(ignore bool) {
// SetForceLogin 设置强制登录
func (c *PCSConfig) SetForceLogin(username string) {
c.ForceLogin = username
}
}

View File

@@ -52,6 +52,7 @@ type PCSConfig struct {
PanUA string `json:"pan_ua"` // PAN浏览器标识
SaveDir string `json:"savedir"` // 下载储存路径
EnableHTTPS bool `json:"enable_https"` // 启用https
FixPCSAddr bool `json:"fix_pcs_addr"` //上传不使用动态PCS服务器域名
ForceLogin string `json:"force_login_username"` // 强制登录
Proxy string `json:"proxy"` // 代理
LocalAddrs string `json:"local_addrs"` // 本地网卡地址

View File

@@ -23,14 +23,10 @@ type (
}
)
type PCSInfo struct {
*pcserror.PCSErrInfo
Host string `json:"host"`
Server []string `json:"server"`
}
var client = pcsconfig.Config.PCSHTTPClient()
var pcsPeriod = 256 // 上传多少个分片更换一次pcsHost
func (e EmptyReaderLen64) Read(p []byte) (n int, err error) {
return 0, io.EOF
}
@@ -53,43 +49,29 @@ func (pu *PCSUpload) lazyInit() {
}
// Precreate 检查网盘的目标路径是否已存在同名文件及路径合法性, 顺便获取本次上传用的pcs服务器
func (pu *PCSUpload) Precreate(fileSize int64, policy string) (pcsHost string, pcsError pcserror.Error) {
func (pu *PCSUpload) Precreate(fileSize int64, policy string) (originPCSHost string, pcsError pcserror.Error) {
pcsError = pu.pcs.CheckIsdir(baidupcs.OperationUpload, pu.targetPath, policy, fileSize)
if pcsError != nil {
return
}
dataReadCloser, pcsError := pu.pcs.PreparePCSServers()
if pcsError != nil {
return
}
defer dataReadCloser.Close()
pcsInfo := &PCSInfo{
PCSErrInfo: pcserror.NewPCSErrorInfo(baidupcs.OperationGetPCSServer),
}
pcsError = pcserror.HandleJSONParse(baidupcs.OperationGetPCSServer, dataReadCloser, pcsInfo)
if pcsError != nil {
return
}
if len(pcsInfo.Server) > 0 {
pcsHost = pcsInfo.Server[0]
} else {
pcsHost = pcsInfo.Host
}
originPCSHost = pu.pcs.GetPCSAddr()
_, newPCSHost := pu.pcs.GetRandomPCSHost()
pu.pcs.SetPCSAddr(newPCSHost)
return
}
func (pu *PCSUpload) TmpFile(ctx context.Context, pcsHost, uploadId, targetPath string, partSeq int, partOffset int64, r rio.ReaderLen64) (checksum string, uperr error) {
func (pu *PCSUpload) TmpFile(ctx context.Context, uploadId, targetPath string, partSeq int, partOffset int64, r rio.ReaderLen64) (checksum string, uperr error) {
pu.lazyInit()
var respErr *uploader.MultiError
// 临时切换为动态pcs addr
originPCSHost := pu.pcs.GetPCSAddr()
defer pu.pcs.SetPCSAddr(originPCSHost)
pu.pcs.SetPCSAddr(pcsHost)
if partSeq%pcsPeriod == pcsPeriod-1 {
go func() {
_, newPCSHost := pu.pcs.GetRandomPCSHost()
pu.pcs.SetPCSAddr(newPCSHost)
}()
}
checksum, pcsError := pu.pcs.UploadTmpFile(uploadId, targetPath, partSeq, partOffset, func(uploadURL string, jar http.CookieJar) (resp *http.Response, err error) {
client.SetCookiejar(jar)
@@ -132,7 +114,8 @@ func (pu *PCSUpload) TmpFile(ctx context.Context, pcsHost, uploadId, targetPath
return checksum, pcsError
}
func (pu *PCSUpload) CreateSuperFile(uploadId string, fileSize int64, checksumMap map[int]string) (err error) {
func (pu *PCSUpload) CreateSuperFile(pcsHost, uploadId string, fileSize int64, checksumMap map[int]string) (err error) {
pu.lazyInit()
pu.pcs.SetPCSAddr(pcsHost) // 恢复默认pcs服务器
return pu.pcs.UploadCreateSuperFile(uploadId, fileSize, pu.targetPath, checksumMap)
}

View File

@@ -83,12 +83,10 @@ func (utu *UploadTaskUnit) prepareFile() {
}
// 秒传不分文件大小一律进行
if utu.LocalFileChecksum.Length >= baidupcs.RecommendedUploadSize {
fmt.Printf("[%s] 文件超过32GB, 上传有可能失败, 建议分割文件...\n", utu.taskInfo.Id())
}
//if utu.LocalFileChecksum.Length > baidupcs.MaxRapidUploadSize {
// fmt.Printf("[%s] 文件超过20GB, 无法使用秒传功能, 跳过秒传...\n", utu.taskInfo.Id())
// utu.Step = StepUploadUpload
// return
//}
// 下一步: 秒传
utu.Step = StepUploadRapidUpload
}
@@ -123,7 +121,7 @@ func (utu *UploadTaskUnit) rapidUpload() (isContinue bool, result *taskframework
}
}
fmt.Printf("[%s] 检测秒传中, 请稍候...\n", utu.taskInfo.Id())
fmt.Printf("[%s] 开始计算文件元信息, 请稍候...\n", utu.taskInfo.Id())
// 经测试, 文件的 crc32 值并非秒传文件所必需
err := utu.LocalFileChecksum.Sum(checksum.CHECKSUM_MD5 | checksum.CHECKSUM_SLICE_MD5)
@@ -170,9 +168,9 @@ func (utu *UploadTaskUnit) rapidUpload() (isContinue bool, result *taskframework
}
b64Content := strings.TrimRight(base64.StdEncoding.EncodeToString(dataContent), "=")
blockSize := getBlockSize()
blockSize := getBlockSize(utu.LocalFileChecksum.Length)
fmt.Printf("[%s] 开始计算文件分块md5...\n", utu.taskInfo.Id())
fmt.Printf("[%s] 开始计算文件分块md5, 请稍候...\n", utu.taskInfo.Id())
err = utu.LocalFileChecksum.CalculateChunkedSum(blockSize)
if err != nil {
// 不重试
@@ -206,7 +204,7 @@ func (utu *UploadTaskUnit) rapidUpload() (isContinue bool, result *taskframework
}
}
fmt.Printf("[%s] 秒传失败, 开始上传文件...\n\n", utu.taskInfo.Id())
fmt.Printf("[%s] 开始上传文件...\n\n", utu.taskInfo.Id())
// 保存秒传信息
utu.UploadingDatabase.UpdateUploading(&utu.LocalFileChecksum.LocalFileMeta, nil)
@@ -219,7 +217,7 @@ func (utu *UploadTaskUnit) rapidUpload() (isContinue bool, result *taskframework
func (utu *UploadTaskUnit) upload() (result *taskframework.TaskUnitRunResult) {
utu.Step = StepUploadUpload
blockSize := getBlockSize()
blockSize := getBlockSize(utu.LocalFileChecksum.Length)
muer := uploader.NewMultiUploader(NewPCSUpload(utu.PCS, utu.SavePath), rio.NewFileReaderAtLen64(utu.LocalFileChecksum.GetFile()), &uploader.MultiUploaderConfig{
Parallel: utu.Parallel,
@@ -372,6 +370,11 @@ func (utu *UploadTaskUnit) RetryWait() time.Duration {
func (utu *UploadTaskUnit) Run() (result *taskframework.TaskUnitRunResult) {
fmt.Printf("[%s] 准备上传: %s\n", utu.taskInfo.Id(), utu.LocalFileChecksum.Path)
if utu.LocalFileChecksum.Length > baidupcs.MaxUploadSize {
fmt.Printf("[%s] 文件大小超过128G, 无法上传, 跳过...\n", utu.taskInfo.Id())
return
}
err := utu.LocalFileChecksum.OpenPath()
if err != nil {
fmt.Printf("[%s] 文件不可读, 错误信息: %s, 跳过...\n", utu.taskInfo.Id(), err)

View File

@@ -8,8 +8,15 @@ import (
"strconv"
)
func getBlockSize() int64 {
return baidupcs.MinUploadBlockSize
func getBlockSize(fileSize int64) int64 {
blockSize := baidupcs.MinUploadBlockSize
if fileSize >= baidupcs.MiddleUploadThreshold {
blockSize = baidupcs.MiddleUploadBlockSize
}
if fileSize >= baidupcs.MaxUploadThreshold {
blockSize = baidupcs.MaxUploadBlockSize
}
return blockSize
}
func creaetDataOffset(contentMD5 string, uk, dataTime, fileSize, subSize int64) (offset int64, err error) {

View File

@@ -55,7 +55,7 @@ const (
var (
// Version 版本号
Version = "v3.9.8-devel"
Version = "v3.9.9-devel"
historyFilePath = filepath.Join(pcsconfig.GetConfigDir(), "pcs_command_history.txt")
reloadFn = func(c *cli.Context) error {
@@ -154,7 +154,7 @@ func main() {
lineArgs = args.Parse(line)
numArgs = len(lineArgs)
acceptCompleteFileCommands = []string{
"cd", "cp", "download", "export", "fixmd5", "locate", "ls", "meta", "mkdir", "mv", "rapidupload", "rm", "setastoken", "share", "transfer", "tree", "upload",
"cd", "cp", "download", "export", "locate", "ls", "meta", "mkdir", "mv", "rm", "setastoken", "share", "transfer", "tree", "upload",
}
closed = strings.LastIndex(line, " ") == len(line)-1
)
@@ -1803,6 +1803,9 @@ func main() {
return nil
}
}
if c.IsSet("fix_pcs_addr") {
pcsconfig.Config.SetStaticPCSAddr(c.Bool("fix_pcs_addr"))
}
if c.IsSet("pan_ua") {
pcsconfig.Config.SetPanUA(c.String("pan_ua"))
}

View File

@@ -14,7 +14,7 @@ import (
const (
// DefaultBufSize 默认的bufSize
DefaultBufSize = int(256 * converter.KB)
DefaultBufSize = int(1 * converter.MB)
)
const (
@@ -260,7 +260,7 @@ func (lfc *LocalFileChecksum) CalculateChunkedSum(chunkSize int64) (err error) {
lfc.BlocksList = make([]string, 0, chunkCount)
// 分块处理
buffer := make([]byte, 64*1024) // 64KB读取缓冲区
buffer := make([]byte, 4*converter.MB) // 4MB读取缓冲区
chunkMD5 := md5.New()
for offset := int64(0); offset < fileSize; offset += chunkSize {
// 计算当前分块的实际大小(最后一块可能较小)

View File

@@ -16,8 +16,8 @@ type (
// MultiUpload 支持多线程的上传, 可用于断点续传
MultiUpload interface {
Precreate(fileSize int64, policy string) (pcsHost string, err pcserror.Error)
TmpFile(ctx context.Context, pcsHost, uploadid, targetPath string, partseq int, partOffset int64, readerlen64 rio.ReaderLen64) (checksum string, terr error)
CreateSuperFile(uploadId string, fileSize int64, checksumMap map[int]string) (cerr error)
TmpFile(ctx context.Context, uploadid, targetPath string, partseq int, partOffset int64, readerlen64 rio.ReaderLen64) (checksum string, terr error)
CreateSuperFile(pcsHost, uploadId string, fileSize int64, checksumMap map[int]string) (cerr error)
}
// MultiUploader 多线程上传

View File

@@ -39,7 +39,7 @@ func (werl *workerList) Readed() int64 {
}
func (muer *MultiUploader) upload() (uperr error) {
pcsHost, err := muer.multiUpload.Precreate(muer.file.Len(), muer.config.Policy)
originPCSHost, err := muer.multiUpload.Precreate(muer.file.Len(), muer.config.Policy)
if err != nil {
return err
}
@@ -79,7 +79,7 @@ func (muer *MultiUploader) upload() (uperr error) {
terr error
)
go func() {
checksum, terr = muer.multiUpload.TmpFile(ctx, pcsHost, muer.uploadid, muer.targetPath, wer.id, wer.partOffset, wer.splitUnit)
checksum, terr = muer.multiUpload.TmpFile(ctx, muer.uploadid, muer.targetPath, wer.id, wer.partOffset, wer.splitUnit)
close(doneChan)
}()
select {
@@ -135,7 +135,7 @@ func (muer *MultiUploader) upload() (uperr error) {
default:
}
cerr := muer.multiUpload.CreateSuperFile(muer.uploadid, muer.file.Len(), checksumMap)
cerr := muer.multiUpload.CreateSuperFile(originPCSHost, muer.uploadid, muer.file.Len(), checksumMap)
if cerr != nil {
return cerr
}

View File

@@ -3,13 +3,13 @@
"FileVersion": {
"Major": 3,
"Minor": 9,
"Patch": 8,
"Patch": 9,
"Build": 0
},
"ProductVersion": {
"Major": 3,
"Minor": 9,
"Patch": 8,
"Patch": 9,
"Build": 0
},
"FileFlagsMask": "3f",
@@ -22,14 +22,14 @@
"Comments": "",
"CompanyName": "qjfoidnh",
"FileDescription": "百度网盘客户端(加强版)",
"FileVersion": "v3.9.8",
"FileVersion": "v3.9.9",
"InternalName": "",
"LegalCopyright": "© 2018-2025 qjfoidnh.",
"LegalTrademarks": "",
"OriginalFilename": "",
"PrivateBuild": "",
"ProductName": "BaiduPCS-Go",
"ProductVersion": "v3.9.8",
"ProductVersion": "v3.9.9",
"SpecialBuild": ""
},
"VarFileInfo": {