aliyunpan/internal/command/download.go

458 lines
14 KiB
Go
Raw Normal View History

2021-10-10 10:48:53 +08:00
// Copyright (c) 2020 tickstep.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
2021-10-10 10:48:53 +08:00
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package command
import (
"fmt"
"github.com/tickstep/aliyunpan/cmder"
"github.com/tickstep/aliyunpan/cmder/cmdtable"
"github.com/tickstep/aliyunpan/internal/config"
"github.com/tickstep/aliyunpan/internal/file/downloader"
"github.com/tickstep/aliyunpan/internal/functions/pandownload"
2022-12-19 19:50:33 +08:00
"github.com/tickstep/aliyunpan/internal/log"
2021-10-10 10:48:53 +08:00
"github.com/tickstep/aliyunpan/internal/taskframework"
"github.com/tickstep/aliyunpan/internal/utils"
2021-10-10 10:48:53 +08:00
"github.com/tickstep/aliyunpan/library/requester/transfer"
"github.com/tickstep/library-go/converter"
"github.com/tickstep/library-go/logger"
"github.com/tickstep/library-go/requester/rio/speeds"
2021-10-10 10:48:53 +08:00
"github.com/urfave/cli"
"os"
"path"
2021-10-10 10:48:53 +08:00
"path/filepath"
"runtime"
)
type (
//DownloadOptions 下载可选参数
DownloadOptions struct {
IsPrintStatus bool
IsExecutedPermission bool
IsOverwrite bool
SaveTo string
Parallel int // 文件下载最大线程数
SliceParallel int // 单个文件分片下载最大线程数
2021-10-10 10:48:53 +08:00
Load int
MaxRetry int
NoCheck bool
ShowProgress bool
DriveId string
ExcludeNames []string // 排除的文件名,包括文件夹和文件。即这些文件/文件夹不进行下载,支持正则表达式
IsMultiUserDownload bool // 是否启用多用户联合下载
2021-10-10 10:48:53 +08:00
}
// LocateDownloadOption 获取下载链接可选参数
LocateDownloadOption struct {
FromPan bool
}
)
var (
// MaxDownloadRangeSize 文件片段最大值
MaxDownloadRangeSize = 55 * converter.MB
// DownloadCacheSize 默认每个线程下载缓存大小
DownloadCacheSize = 64 * converter.KB
)
func CmdDownload() cli.Command {
return cli.Command{
Name: "download",
Aliases: []string{"d"},
Usage: "下载文件/目录",
UsageText: cmder.App().Name + " download <文件/目录路径1> <文件/目录2> <文件/目录3> ...",
Description: `
2022-07-03 11:57:18 +08:00
下载的文件默认保存到, 程序所在目录的 download/ 目录支持软链接文件包括Linux/macOS(ln命令)和Windows(mklink命令)创建的符号链接文件
通过 aliyunpan config set -savedir <savedir>, 自定义保存的目录支持多个文件或目录下载支持自动跳过下载重名的文件!
2021-10-10 10:48:53 +08:00
示例:
设置保存目录, 保存到 D:\Downloads
注意区别反斜杠 "\" 和 斜杠 "/" !!!
aliyunpan config set -savedir D:\\Downloads
或者
aliyunpan config set -savedir D:/Downloads
下载 /我的资源/1.mp4
aliyunpan download /我的资源/1.mp4
下载 /我的资源 目录下面所有的mp4文件使用通配符
aliyunpan download /我的资源/*.mp4
2021-10-10 10:48:53 +08:00
下载 /我的资源 整个目录!!
aliyunpan download /我的资源
2021-10-10 10:48:53 +08:00
下载 /我的资源 整个目录但是排除所有的jpg文件
aliyunpan download -exn "\.jpg$" /我的资源
下载 /我的资源/1.mp4 并保存下载的文件到本地的 d:/panfile
aliyunpan download --saveto d:/panfile /我的资源/1.mp4
2024-08-11 16:20:56 +08:00
使用多用户联合下载 /我的资源/1.mp4 文件必须保证所有登录的用户在相同的网盘备份盘/资源盘相同的路径下有相同的文件
aliyunpan download /我的资源/1.mp4 -md
参考
以下是典型的排除特定文件或者文件夹的例子注意参数值必须是正则表达式在正则表达式中^表示匹配开头$表示匹配结尾
1)排除@eadir文件或者文件夹-exn "^@eadir$"
2)排除.jpg文件-exn "\.jpg$"
3)排除.号开头的文件-exn "^\."
4)排除~号开头的文件-exn "^~"
5)排除 myfile.txt 文件-exn "^myfile.txt$"
2021-10-10 10:48:53 +08:00
`,
Category: "阿里云盘",
2022-12-10 14:07:28 +08:00
Before: ReloadConfigFunc,
2021-10-10 10:48:53 +08:00
Action: func(c *cli.Context) error {
if c.NArg() == 0 {
cli.ShowCommandHelp(c, c.Command.Name)
return nil
}
// 处理saveTo
var (
saveTo string
)
if c.Bool("save") {
// 使用当前工作目录
pwd, _ := os.Getwd()
saveTo = path.Clean(pwd)
2021-10-10 10:48:53 +08:00
} else if c.String("saveto") != "" {
saveTo = filepath.Clean(c.String("saveto"))
}
do := &DownloadOptions{
IsPrintStatus: c.Bool("status"),
IsExecutedPermission: c.Bool("x"),
IsOverwrite: c.Bool("ow"),
SaveTo: saveTo,
Parallel: c.Int("p"),
SliceParallel: 3,
Load: 0,
2021-10-10 10:48:53 +08:00
MaxRetry: c.Int("retry"),
NoCheck: c.Bool("nocheck"),
ShowProgress: !c.Bool("np"),
DriveId: parseDriveId(c),
ExcludeNames: c.StringSlice("exn"),
IsMultiUserDownload: c.Bool("md"),
2021-10-10 10:48:53 +08:00
}
// 获取下载文件锁,保证下载操作单实例
2022-12-27 18:58:49 +08:00
//locker := filelocker.NewFileLocker(config.GetLockerDir() + "/aliyunpan-download")
//if e := filelocker.LockFile(locker, 0755, true, 5*time.Second); e != nil {
// logger.Verboseln(e)
// fmt.Println("本应用其他实例正在执行下载,请先停止或者等待其完成")
// return nil
//}
2021-10-10 10:48:53 +08:00
RunDownload(c.Args(), do)
// 释放文件锁
2022-12-27 18:58:49 +08:00
//if locker != nil {
// filelocker.UnlockFile(locker)
//}
2021-10-10 10:48:53 +08:00
return nil
},
Flags: []cli.Flag{
cli.BoolFlag{
Name: "ow",
Usage: "overwrite, 覆盖已存在的文件",
},
cli.BoolFlag{
Name: "status",
Usage: "输出所有线程的工作状态",
},
cli.BoolFlag{
Name: "save",
Usage: "将下载的文件直接保存到当前工作目录",
},
cli.StringFlag{
Name: "saveto",
Usage: "将下载的文件直接保存到指定的目录",
},
cli.BoolFlag{
Name: "x",
Usage: "为文件加上执行权限, (windows系统无效)",
},
cli.IntFlag{
Name: "p",
Usage: "parallel,指定同时进行下载文件的数量(取值范围:1 ~ 3",
Value: 1,
},
//cli.IntFlag{
// Name: "sp",
// Usage: "slice parallel,指定单个文件下载的最大线程(分片)数(取值范围:1 ~ 3",
// Value: 1,
//},
2021-10-10 10:48:53 +08:00
cli.IntFlag{
Name: "retry",
Usage: "下载失败最大重试次数",
Value: pandownload.DefaultDownloadMaxRetry,
},
cli.BoolFlag{
Name: "nocheck",
Usage: "下载文件完成后不校验文件",
},
cli.BoolFlag{
Name: "np",
Usage: "no progress 不展示下载进度条",
},
cli.StringFlag{
Name: "driveId",
Usage: "网盘ID",
Value: "",
},
cli.StringSliceFlag{
Name: "exn",
Usage: "exclude name指定排除的文件夹或者文件的名称被排除的文件不会进行下载只支持正则表达式。支持同时排除多个名称每一个名称就是一个exn参数",
Value: nil,
},
cli.BoolFlag{
Name: "md",
Usage: "(BETA) Multi-User Download使用多用户联合下载可以对单一文件叠加所有登录用户的下载速度",
},
2021-10-10 10:48:53 +08:00
},
}
}
func downloadPrintFormat(load int) string {
if load <= 1 {
return pandownload.DefaultPrintFormat
}
return "\r[%s] ↓ %s/%s %s/s in %s, left %s ..."
}
// RunDownload 执行下载网盘内文件
func RunDownload(paths []string, options *DownloadOptions) {
activeUser := GetActiveUser()
activeUser.PanClient().OpenapiPanClient().EnableCache()
activeUser.PanClient().OpenapiPanClient().ClearCache()
defer activeUser.PanClient().OpenapiPanClient().DisableCache()
2024-03-02 22:58:41 +08:00
//// pan token expired checker
//continueFlag := int32(0)
//atomic.StoreInt32(&continueFlag, 0)
//defer func() {
// atomic.StoreInt32(&continueFlag, 1)
//}()
//go func(flag *int32) {
// for atomic.LoadInt32(flag) == 0 {
// time.Sleep(time.Duration(1) * time.Minute)
// if RefreshWebTokenInNeed(activeUser, config.Config.DeviceName) {
// logger.Verboseln("update access token for download task")
// }
// }
//}(&continueFlag)
2021-10-10 10:48:53 +08:00
if options == nil {
options = &DownloadOptions{}
}
if options.MaxRetry < 0 {
options.MaxRetry = pandownload.DefaultDownloadMaxRetry
}
if runtime.GOOS == "windows" {
// windows下不加执行权限
options.IsExecutedPermission = false
}
// 设置下载配置
cfg := &downloader.Config{
Mode: transfer.RangeGenMode_BlockSize,
CacheSize: config.Config.CacheSize,
BlockSize: MaxDownloadRangeSize,
MaxRate: config.Config.MaxDownloadRate,
InstanceStateStorageFormat: downloader.InstanceStateStorageFormatJSON,
ShowProgress: options.ShowProgress,
ExcludeNames: options.ExcludeNames,
2021-10-10 10:48:53 +08:00
}
if cfg.CacheSize == 0 {
cfg.CacheSize = int(DownloadCacheSize)
}
// 设置下载最大并发量
if options.Parallel < 1 {
options.Parallel = config.Config.MaxDownloadParallel
2022-01-15 21:50:28 +08:00
if options.Parallel == 0 {
options.Parallel = config.DefaultFileDownloadParallelNum
}
2021-10-10 10:48:53 +08:00
}
if options.Parallel > config.MaxFileDownloadParallelNum {
options.Parallel = config.MaxFileDownloadParallelNum
}
2021-10-10 10:48:53 +08:00
// 设置单个文件下载分片线程数
if options.SliceParallel < 1 {
options.SliceParallel = 1
}
// 保存文件的本地根文件夹
originSaveRootPath := ""
if options.SaveTo != "" {
originSaveRootPath = options.SaveTo
} else {
// 使用默认的保存路径
originSaveRootPath = GetActiveUser().GetSavePath("")
}
fi, err1 := os.Stat(originSaveRootPath)
if err1 != nil && !os.IsExist(err1) {
os.MkdirAll(originSaveRootPath, 0777) // 首先在本地创建目录
} else {
if !fi.IsDir() {
fmt.Println("本地保存路径不是文件夹,请删除或者创建对应的文件夹:", originSaveRootPath)
2022-07-07 11:21:01 +08:00
return
}
}
2022-12-05 16:56:17 +08:00
paths, err := makePathAbsolute(options.DriveId, paths...)
2021-10-10 10:48:53 +08:00
if err != nil {
fmt.Println(err)
return
}
// 多用户下载的辅助账号列表
var subPanClientList []*config.PanClient
if options.IsMultiUserDownload { // 多用户下载
c := config.Config
for _, u := range config.Config.UserList {
if u.UserId == activeUser.UserId {
// 当前登录用户,作为主用户,跳过
continue
}
// 初始化客户端
user, err := config.SetupUserByCookie(u.OpenapiToken, u.WebapiToken,
u.TicketId, u.UserId,
c.DeviceId, c.DeviceName,
c.ClientId, c.ClientSecret)
if err != nil {
logger.Verboseln("setup user error")
continue
}
if subPanClientList == nil {
subPanClientList = []*config.PanClient{}
}
subPanClientList = append(subPanClientList, user.PanClient())
}
if subPanClientList == nil || len(subPanClientList) == 0 {
fmt.Printf("\n当前登录用户只有一个无法启用多用户联合下载\n")
subPanClientList = nil
}
}
if subPanClientList != nil || len(subPanClientList) > 0 {
// 已启用多用户下载
userCount := len(subPanClientList) + 1
fmt.Printf("\n*** 已启用多用户联合下载,用户数: %d ***\n", userCount)
// 多用户下载并发数必须为1以获得最大下载速度
options.Parallel = 1
// 阿里OpenAPI规定文件分片下载的并发数为3即某用户使用 App 时,可以同时下载 1 个文件的 3 个分片,或者同时下载 3 个文件的各 1 个分片。
options.SliceParallel = userCount * 3
}
fmt.Printf("\n[0] 当前文件下载最大并发量为: %d, 单文件下载分片线程数为: %d, 下载缓存为: %s\n", options.Parallel, options.SliceParallel, converter.ConvertFileSize(int64(cfg.CacheSize), 2))
2021-10-10 10:48:53 +08:00
var (
panClient = activeUser.PanClient()
2021-10-10 10:48:53 +08:00
)
2022-01-15 21:50:28 +08:00
cfg.MaxParallel = options.Parallel
cfg.SliceParallel = options.SliceParallel
2021-10-10 10:48:53 +08:00
var (
executor = taskframework.TaskExecutor{
IsFailedDeque: true, // 统计失败的列表
}
statistic = &pandownload.DownloadStatistic{}
)
2022-01-15 21:50:28 +08:00
// 配置执行器任务并发数,即同时下载文件并发数
executor.SetParallel(cfg.MaxParallel)
// 全局速度统计
globalSpeedsStat := &speeds.Speeds{}
2022-12-19 19:50:33 +08:00
// 下载记录器
fileRecorder := log.NewFileRecorder(config.GetLogDir() + "/download_file_records.csv")
2021-10-10 10:48:53 +08:00
// 处理队列
for k := range paths {
// 使用通配符匹配
fileList, err2 := matchPathByShellPattern(options.DriveId, paths[k])
if err2 != nil {
fmt.Printf("获取文件出错,请稍后重试: %s\n", paths[k])
continue
}
if fileList == nil || len(fileList) == 0 {
// 文件不存在
fmt.Printf("文件不存在: %s\n", paths[k])
continue
2021-10-10 10:48:53 +08:00
}
for _, f := range fileList {
newCfg := *cfg
// 是否排除下载
if utils.IsExcludeFile(f.Path, &newCfg.ExcludeNames) {
fmt.Printf("排除文件: %s\n", f.Path)
continue
}
// 匹配的文件
unit := pandownload.DownloadTaskUnit{
Cfg: &newCfg, // 复制一份新的cfg
PanClient: panClient,
SubPanClientList: subPanClientList,
VerbosePrinter: panCommandVerbose,
PrintFormat: downloadPrintFormat(options.Load),
ParentTaskExecutor: &executor,
DownloadStatistic: statistic,
IsPrintStatus: options.IsPrintStatus,
IsExecutedPermission: options.IsExecutedPermission,
IsOverwrite: options.IsOverwrite,
NoCheck: options.NoCheck,
FilePanPath: f.Path,
DriveId: options.DriveId,
GlobalSpeedsStat: globalSpeedsStat,
2022-12-19 19:50:33 +08:00
FileRecorder: fileRecorder,
}
2021-10-10 10:48:53 +08:00
// 设置储存的路径
if options.SaveTo != "" {
unit.OriginSaveRootPath = options.SaveTo
unit.SavePath = filepath.Join(options.SaveTo, f.Path)
} else {
// 使用默认的保存路径
unit.OriginSaveRootPath = GetActiveUser().GetSavePath("")
unit.SavePath = GetActiveUser().GetSavePath(f.Path)
}
info := executor.Append(&unit, options.MaxRetry)
fmt.Printf("[%s] 加入下载队列: %s\n", info.Id(), f.Path)
2021-10-10 10:48:53 +08:00
}
}
// 开始计时
statistic.StartTimer()
// 开始执行
executor.Execute()
fmt.Printf("\n下载结束, 时间: %s, 数据总量: %s\n", utils.ConvertTime(statistic.Elapsed()), converter.ConvertFileSize(statistic.TotalSize(), 2))
2021-10-10 10:48:53 +08:00
// 输出失败的文件列表
failedList := executor.FailedDeque()
if failedList.Size() != 0 {
fmt.Printf("以下文件下载失败: \n")
tb := cmdtable.NewTable(os.Stdout)
for e := failedList.Shift(); e != nil; e = failedList.Shift() {
item := e.(*taskframework.TaskInfoItem)
tb.Append([]string{item.Info.Id(), item.Unit.(*pandownload.DownloadTaskUnit).FilePanPath})
}
tb.Render()
}
}