github.com/qjfoidnh/BaiduPCS-Go@v0.0.0-20231011165705-caa18a3765f3/internal/pcscommand/export.go (about)

     1  package pcscommand
     2  
     3  import (
     4  	"container/list"
     5  	"fmt"
     6  	"os"
     7  	"path"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/qjfoidnh/BaiduPCS-Go/baidupcs"
    12  	"github.com/qjfoidnh/BaiduPCS-Go/baidupcs/pcserror"
    13  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/converter"
    14  	"github.com/qjfoidnh/BaiduPCS-Go/pcsutil/pcstime"
    15  )
    16  
    17  type (
    18  	etask struct {
    19  		*ListTask
    20  		path     string
    21  		rootPath string
    22  		fd       *baidupcs.FileDirectory
    23  		err      pcserror.Error
    24  	}
    25  
    26  	// ExportOptions 导出可选项
    27  	ExportOptions struct {
    28  		RootPath   string // 根路径
    29  		SavePath   string // 输出路径
    30  		MaxRetry   int
    31  		Recursive  bool
    32  		LinkFormat bool
    33  		StdOut     bool
    34  	}
    35  )
    36  
    37  func (task *etask) handleExportTaskError(l *list.List, failedList *list.List) {
    38  	if task.err == nil {
    39  		return
    40  	}
    41  
    42  	// 不重试
    43  	switch task.err.GetError() {
    44  	case baidupcs.ErrGetRapidUploadInfoMD5NotFound, baidupcs.ErrGetRapidUploadInfoCrc32NotFound:
    45  		fmt.Printf("[%d] - [%s] 导出失败, 可能是服务器未刷新文件的md5, 请过一段时间再试一试\n", task.ID, task.path)
    46  		failedList.PushBack(task)
    47  		return
    48  	case baidupcs.ErrFileTooLarge:
    49  		fmt.Printf("[%d] - [%s] 导出失败, 文件大于20GB, 无法导出\n", task.ID, task.path)
    50  		failedList.PushBack(task)
    51  		return
    52  	}
    53  
    54  	// 未达到失败重试最大次数, 将任务推送到队列末尾
    55  	if task.retry < task.MaxRetry {
    56  		task.retry++
    57  		fmt.Printf("[%d] - [%s] 导出错误, %s, 重试 %d/%d\n", task.ID, task.path, task.err, task.retry, task.MaxRetry)
    58  		l.PushBack(task)
    59  		time.Sleep(3 * time.Duration(task.retry) * time.Second)
    60  	} else {
    61  		fmt.Printf("[%d] - [%s] 导出错误, %s\n", task.ID, task.path, task.err)
    62  		failedList.PushBack(task)
    63  	}
    64  }
    65  
    66  func changeRootPath(dstRootPath, dstPath, srcRootPath string) string {
    67  	if srcRootPath == "" {
    68  		return dstPath
    69  	}
    70  	return path.Join(srcRootPath, strings.TrimPrefix(dstPath, dstRootPath))
    71  }
    72  
    73  // GetExportFilename 获取导出路径
    74  func GetExportFilename() string {
    75  	return "BaiduPCS-Go_export_" + pcstime.BeijingTimeOption("") + ".txt"
    76  }
    77  
    78  // RunExport 执行导出文件和目录
    79  func RunExport(pcspaths []string, opt *ExportOptions) {
    80  	if opt == nil {
    81  		opt = &ExportOptions{}
    82  	}
    83  
    84  	if opt.SavePath == "" {
    85  		opt.SavePath = GetExportFilename()
    86  	}
    87  
    88  	pcspaths, err := matchPathByShellPattern(pcspaths...)
    89  	if err != nil {
    90  		fmt.Println(err)
    91  		return
    92  	}
    93  	saveFile, err := os.OpenFile(opt.SavePath, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    94  	if err != nil { // 不可写
    95  		if !opt.StdOut {
    96  			fmt.Printf("%s\n", err)
    97  			return
    98  		}
    99  	}
   100  	defer saveFile.Close()
   101  	if !opt.StdOut {
   102  		fmt.Printf("导出的信息将保存在: %s\n", opt.SavePath)
   103  	}
   104  
   105  	var (
   106  		au         = GetActiveUser()
   107  		pcs        = GetBaiduPCS()
   108  		l          = list.New()
   109  		failedList = list.New()
   110  		writeErr   error
   111  		id         int
   112  	)
   113  
   114  	for id = range pcspaths {
   115  		var rootPath string
   116  		if pcspaths[id] == au.Workdir {
   117  			rootPath = pcspaths[id]
   118  		} else {
   119  			rootPath = path.Dir(pcspaths[id])
   120  		}
   121  		// 加入队列
   122  		l.PushBack(&etask{
   123  			ListTask: &ListTask{
   124  				ID:       id,
   125  				MaxRetry: opt.MaxRetry,
   126  			},
   127  			path:     pcspaths[id],
   128  			rootPath: rootPath,
   129  		})
   130  	}
   131  
   132  	for {
   133  		e := l.Front()
   134  		if e == nil { // 结束
   135  			break
   136  		}
   137  
   138  		l.Remove(e) // 载入任务后, 移除队列
   139  
   140  		task := e.Value.(*etask)
   141  		root := task.fd == nil
   142  
   143  		// 获取文件信息
   144  		if task.fd == nil { // 第一次初始化
   145  			fd, pcsError := pcs.FilesDirectoriesMeta(task.path)
   146  			if pcsError != nil {
   147  				task.err = pcsError
   148  				task.handleExportTaskError(l, failedList)
   149  				continue
   150  			}
   151  			task.fd = fd
   152  		}
   153  
   154  		if task.fd.Isdir { // 导出目录
   155  			if !root && !opt.Recursive { // 非递归
   156  				continue
   157  			}
   158  
   159  			fds, pcsError := pcs.FilesDirectoriesList(task.path, baidupcs.DefaultOrderOptions)
   160  			if pcsError != nil {
   161  				task.err = pcsError
   162  				task.handleExportTaskError(l, failedList)
   163  				continue
   164  			}
   165  
   166  			if len(fds) == 0 && !opt.StdOut {
   167  				_, writeErr = saveFile.Write(converter.ToBytes(fmt.Sprintf("BaiduPCS-Go mkdir \"%s\"\n", changeRootPath(task.rootPath, task.path, opt.RootPath))))
   168  				if writeErr != nil {
   169  					fmt.Printf("写入文件失败: %s\n", writeErr)
   170  					return // 直接返回
   171  				}
   172  				fmt.Printf("[%d] - [%s] 导出成功\n", task.ID, task.path)
   173  				continue
   174  			}
   175  
   176  			// 加入队列
   177  			for _, fd := range fds {
   178  				// 加入队列
   179  				id++
   180  				l.PushBack(&etask{
   181  					ListTask: &ListTask{
   182  						ID:       id,
   183  						MaxRetry: opt.MaxRetry,
   184  					},
   185  					path:     fd.Path,
   186  					fd:       fd,
   187  					rootPath: task.rootPath,
   188  				})
   189  			}
   190  			continue
   191  		}
   192  
   193  		rinfo, pcsError := pcs.ExportByFileInfo(task.fd)
   194  		if pcsError != nil {
   195  			task.err = pcsError
   196  			task.handleExportTaskError(l, failedList)
   197  			continue
   198  		}
   199  		var outTemplate = fmt.Sprintf("BaiduPCS-Go rapidupload -length=%d -md5=%s -slicemd5=%s -crc32=%s \"%s\"\n", rinfo.ContentLength, rinfo.ContentMD5, rinfo.SliceMD5, rinfo.ContentCrc32, changeRootPath(task.rootPath, task.path, opt.RootPath))
   200  		if opt.LinkFormat {
   201  			outTemplate = fmt.Sprintf("%s#%s#%d#%s\n", rinfo.ContentMD5, rinfo.SliceMD5, rinfo.ContentLength, path.Base(task.path))
   202  		}
   203  		if opt.StdOut {
   204  			fmt.Print(outTemplate)
   205  		} else {
   206  			_, writeErr = saveFile.Write(converter.ToBytes(outTemplate))
   207  			if writeErr != nil {
   208  				fmt.Printf("写入文件失败: %s\n", writeErr)
   209  				return // 直接返回
   210  			}
   211  
   212  			fmt.Printf("[%d] - [%s] 导出成功\n", task.ID, task.path)
   213  		}
   214  	}
   215  	if opt.StdOut {
   216  		os.Remove(opt.SavePath)
   217  		fmt.Println("导出完毕")
   218  	}
   219  
   220  	if failedList.Len() > 0 {
   221  		fmt.Printf("\n以下目录导出失败: \n")
   222  		fmt.Printf("%s\n", strings.Repeat("-", 100))
   223  		for e := failedList.Front(); e != nil; e = e.Next() {
   224  			et := e.Value.(*etask)
   225  			fmt.Printf("[%d] %s\n", et.ID, et.path)
   226  		}
   227  	}
   228  }