add sync policy for download&upload mode

This commit is contained in:
tickstep 2024-03-20 18:04:30 +08:00
parent f7b8d26964
commit 9962e63c81
4 changed files with 114 additions and 36 deletions

View File

@ -72,6 +72,7 @@ func CmdSync() cli.Command {
"localFolderPath": "D:/tickstep/Documents/设计文档", "localFolderPath": "D:/tickstep/Documents/设计文档",
"panFolderPath": "/sync_drive/我的文档", "panFolderPath": "/sync_drive/我的文档",
"mode": "upload", "mode": "upload",
"policy" "increment"
"driveName": "backup" "driveName": "backup"
} }
] ]
@ -81,6 +82,7 @@ name - 任务名称
localFolderPath - 本地目录 localFolderPath - 本地目录
panFolderPath - 网盘目录 panFolderPath - 网盘目录
mode - 模式支持两种: upload(备份本地文件到云盘),download(备份云盘文件到本地) mode - 模式支持两种: upload(备份本地文件到云盘),download(备份云盘文件到本地)
policy - 备份策略, 支持两种: exclusive(排他备份文件目标目录多余的文件会被删除),increment(增量备份文件目标目录多余的文件不会被删除)
driveName - 网盘名称backup(备份盘)resource(资源盘) driveName - 网盘名称backup(备份盘)resource(资源盘)
例子: 例子:
@ -160,6 +162,7 @@ driveName - 网盘名称backup(备份盘)resource(资源盘)
localDir := c.String("ldir") localDir := c.String("ldir")
panDir := c.String("pdir") panDir := c.String("pdir")
mode := c.String("mode") mode := c.String("mode")
policy := c.String("policy")
driveName := c.String("drive") driveName := c.String("drive")
if localDir != "" && panDir != "" { if localDir != "" && panDir != "" {
// make path absolute // make path absolute
@ -188,15 +191,22 @@ driveName - 网盘名称backup(备份盘)resource(资源盘)
task = &syncdrive.SyncTask{} task = &syncdrive.SyncTask{}
task.LocalFolderPath = path.Clean(strings.ReplaceAll(localDir, "\\", "/")) task.LocalFolderPath = path.Clean(strings.ReplaceAll(localDir, "\\", "/"))
task.PanFolderPath = panDir task.PanFolderPath = panDir
task.Mode = syncdrive.UploadOnly task.Mode = syncdrive.Upload
if mode == string(syncdrive.UploadOnly) { if mode == string(syncdrive.Upload) {
task.Mode = syncdrive.UploadOnly task.Mode = syncdrive.Upload
} else if mode == string(syncdrive.DownloadOnly) { } else if mode == string(syncdrive.Download) {
task.Mode = syncdrive.DownloadOnly task.Mode = syncdrive.Download
} else if mode == string(syncdrive.SyncTwoWay) { } else if mode == string(syncdrive.SyncTwoWay) {
task.Mode = syncdrive.SyncTwoWay task.Mode = syncdrive.SyncTwoWay
} else { } else {
task.Mode = syncdrive.UploadOnly task.Mode = syncdrive.Upload
}
if policy == string(syncdrive.SyncPolicyExclusive) {
task.Policy = syncdrive.SyncPolicyExclusive
} else if policy == string(syncdrive.SyncPolicyIncrement) {
task.Policy = syncdrive.SyncPolicyIncrement
} else {
task.Policy = syncdrive.SyncPolicyIncrement
} }
task.Name = path.Base(task.LocalFolderPath) task.Name = path.Base(task.LocalFolderPath)
task.Id = utils.Md5Str(task.LocalFolderPath) task.Id = utils.Md5Str(task.LocalFolderPath)
@ -239,6 +249,11 @@ driveName - 网盘名称backup(备份盘)resource(资源盘)
Usage: "备份模式, 支持两种: upload(备份本地文件到云盘),download(备份云盘文件到本地)", Usage: "备份模式, 支持两种: upload(备份本地文件到云盘),download(备份云盘文件到本地)",
Value: "upload", Value: "upload",
}, },
cli.StringFlag{
Name: "policy",
Usage: "备份策略, 支持两种: exclusive(排他备份文件,目标目录多余的文件会被删除),increment(增量备份文件,目标目录多余的文件不会被删除)",
Value: "increment",
},
cli.IntFlag{ cli.IntFlag{
Name: "dp", Name: "dp",
Usage: "download parallel, 下载并发数量即可以同时并发下载多少个文件。0代表跟从配置文件设置取值范围:1 ~ 10", Usage: "download parallel, 下载并发数量即可以同时并发下载多少个文件。0代表跟从配置文件设置取值范围:1 ~ 10",

View File

@ -4,6 +4,8 @@ import (
"context" "context"
"fmt" "fmt"
mapset "github.com/deckarep/golang-set" mapset "github.com/deckarep/golang-set"
"github.com/tickstep/aliyunpan-api/aliyunpan"
"github.com/tickstep/aliyunpan-api/aliyunpan/apierror"
"github.com/tickstep/aliyunpan/internal/config" "github.com/tickstep/aliyunpan/internal/config"
"github.com/tickstep/aliyunpan/internal/plugins" "github.com/tickstep/aliyunpan/internal/plugins"
"github.com/tickstep/aliyunpan/internal/utils" "github.com/tickstep/aliyunpan/internal/utils"
@ -171,7 +173,7 @@ func (f *FileActionTaskManager) doFileDiffRoutine(localFiles LocalFileList, panF
// download file from pan drive // download file from pan drive
if panFilesNeedToDownload != nil { if panFilesNeedToDownload != nil {
for _, file := range panFilesNeedToDownload { for _, file := range panFilesNeedToDownload {
if f.task.Mode == DownloadOnly { if f.task.Mode == Download {
syncItem := &SyncFileItem{ syncItem := &SyncFileItem{
Action: SyncFileActionDownload, Action: SyncFileActionDownload,
Status: SyncFileStatusCreate, Status: SyncFileStatusCreate,
@ -195,6 +197,13 @@ func (f *FileActionTaskManager) doFileDiffRoutine(localFiles LocalFileList, panF
} }
f.addToSyncDb(fileActionTask) f.addToSyncDb(fileActionTask)
} }
} else if f.task.Mode == Upload {
if f.task.Policy == SyncPolicyExclusive {
// 需要删除云盘多余的文件
if f.deletePanFile(file) == nil {
PromptPrintln("成功删除云盘多余文件:" + file.Path)
}
}
} }
} }
} }
@ -202,7 +211,7 @@ func (f *FileActionTaskManager) doFileDiffRoutine(localFiles LocalFileList, panF
// upload file to pan drive // upload file to pan drive
if localFilesNeedToUpload != nil { if localFilesNeedToUpload != nil {
for _, file := range localFilesNeedToUpload { for _, file := range localFilesNeedToUpload {
if f.task.Mode == UploadOnly { if f.task.Mode == Upload {
// check local file modified or not // check local file modified or not
if file.IsFile() { if file.IsFile() {
if f.syncOption.LocalFileModifiedCheckIntervalSec > 0 { if f.syncOption.LocalFileModifiedCheckIntervalSec > 0 {
@ -238,6 +247,13 @@ func (f *FileActionTaskManager) doFileDiffRoutine(localFiles LocalFileList, panF
} }
f.addToSyncDb(fileActionTask) f.addToSyncDb(fileActionTask)
} }
} else if f.task.Mode == Download {
if f.task.Policy == SyncPolicyExclusive {
// 需要删除云盘多余的文件
if f.deleteLocalFile(file) == nil {
PromptPrintln("成功删除本地多余文件:" + file.Path)
}
}
} }
} }
} }
@ -254,7 +270,7 @@ func (f *FileActionTaskManager) doFileDiffRoutine(localFiles LocalFileList, panF
// 本地文件和云盘文件SHA1不一样 // 本地文件和云盘文件SHA1不一样
// 不同模式同步策略不一样 // 不同模式同步策略不一样
if f.task.Mode == UploadOnly { if f.task.Mode == Upload {
// 不再这里计算SHA1待到上传的时候再计算 // 不再这里计算SHA1待到上传的时候再计算
//if localFile.Sha1Hash == "" { //if localFile.Sha1Hash == "" {
@ -298,7 +314,7 @@ func (f *FileActionTaskManager) doFileDiffRoutine(localFiles LocalFileList, panF
}, },
} }
f.addToSyncDb(uploadLocalFile) f.addToSyncDb(uploadLocalFile)
} else if f.task.Mode == DownloadOnly { } else if f.task.Mode == Download {
// 校验SHA1是否相同 // 校验SHA1是否相同
if strings.ToLower(panFile.Sha1Hash) == strings.ToLower(localFile.Sha1Hash) { if strings.ToLower(panFile.Sha1Hash) == strings.ToLower(localFile.Sha1Hash) {
// do nothing // do nothing
@ -327,7 +343,7 @@ func (f *FileActionTaskManager) doFileDiffRoutine(localFiles LocalFileList, panF
} }
} }
// 创建本地文件夹 // createLocalFolder 创建本地文件夹
func (f *FileActionTaskManager) createLocalFolder(panFileItem *PanFileItem) error { func (f *FileActionTaskManager) createLocalFolder(panFileItem *PanFileItem) error {
panPath := panFileItem.Path panPath := panFileItem.Path
panPath = strings.ReplaceAll(panPath, "\\", "/") panPath = strings.ReplaceAll(panPath, "\\", "/")
@ -368,6 +384,37 @@ func (f *FileActionTaskManager) createPanFolder(localFileItem *LocalFileItem) er
} }
} }
// deleteLocalFile 删除本地文件
func (f *FileActionTaskManager) deleteLocalFile(localFileItem *LocalFileItem) error {
localFilePath := localFileItem.Path
logger.Verbosef("正在删除本地文件: %s\n", localFilePath)
var e error
if localFileItem.IsFolder() {
e = os.RemoveAll(localFilePath)
} else {
e = os.Remove(localFilePath)
}
if e == nil {
logger.Verbosef("删除本地文件成功: %s\n", localFilePath)
return nil
}
return e
}
// deletePanFile 删除云盘文件
func (f *FileActionTaskManager) deletePanFile(panFileItem *PanFileItem) error {
logger.Verbosef("正在删除云盘文件: %s\n", panFileItem.Path)
var fileDeleteResult *aliyunpan.FileBatchActionResult
var err *apierror.ApiError = nil
fileDeleteResult, err = f.task.panClient.OpenapiPanClient().FileDeleteCompletely(&aliyunpan.FileBatchActionParam{DriveId: panFileItem.DriveId, FileId: panFileItem.FileId})
time.Sleep(1 * time.Second)
if err == nil && fileDeleteResult.Success {
logger.Verbosef("删除云盘文件成功: %s\n", panFileItem.Path)
return nil
}
return err
}
func (f *FileActionTaskManager) addToSyncDb(fileTask *FileActionTask) { func (f *FileActionTaskManager) addToSyncDb(fileTask *FileActionTask) {
f.mutex.Lock() f.mutex.Lock()
defer f.mutex.Unlock() defer f.mutex.Unlock()
@ -553,9 +600,9 @@ func (f *FileActionTaskManager) fileActionTaskExecutor(ctx context.Context) {
f.setExecuteLoopFlag(true) f.setExecuteLoopFlag(true)
logger.Verboseln("file execute task is finish, exit normally") logger.Verboseln("file execute task is finish, exit normally")
prompt := "" prompt := ""
if f.task.Mode == UploadOnly { if f.task.Mode == Upload {
prompt = "完成全部文件的同步上传,等待下一次扫描" prompt = "完成全部文件的同步上传,等待下一次扫描"
} else if f.task.Mode == DownloadOnly { } else if f.task.Mode == Download {
prompt = "完成全部文件的同步下载,等待下一次扫描" prompt = "完成全部文件的同步下载,等待下一次扫描"
} else { } else {
prompt = "完成全部文件的同步,等待下一次扫描" prompt = "完成全部文件的同步,等待下一次扫描"

View File

@ -20,9 +20,9 @@ import (
) )
type ( type (
TaskStep string SyncMode string
SyncMode string SyncPolicy string
CycleMode string CycleMode string
// SyncTask 同步任务 // SyncTask 同步任务
SyncTask struct { SyncTask struct {
@ -40,8 +40,10 @@ type (
LocalFolderPath string `json:"localFolderPath"` LocalFolderPath string `json:"localFolderPath"`
// PanFolderPath 云盘目录 // PanFolderPath 云盘目录
PanFolderPath string `json:"panFolderPath"` PanFolderPath string `json:"panFolderPath"`
// Mode 同步模式 // Mode 备份模式
Mode SyncMode `json:"mode"` Mode SyncMode `json:"mode"`
// Policy 备份策略
Policy SyncPolicy `json:"policy"`
// CycleMode 循环模式OneTime-运行一次InfiniteLoop-无限循环模式 // CycleMode 循环模式OneTime-运行一次InfiniteLoop-无限循环模式
CycleModeType CycleMode `json:"-"` CycleModeType CycleMode `json:"-"`
// Priority 优先级选项 // Priority 优先级选项
@ -73,24 +75,22 @@ type (
) )
const ( const (
// UploadOnly 单向上传,即备份本地文件到云盘 // Upload 上传,即备份本地文件到云盘
UploadOnly SyncMode = "upload" Upload SyncMode = "upload"
// DownloadOnly 下载,即备份云盘文件到本地 // Download 下载,即备份云盘文件到本地
DownloadOnly SyncMode = "download" Download SyncMode = "download"
// SyncTwoWay 双向同步,本地和云盘文件完全保持一致 // SyncTwoWay 双向同步,本地和云盘文件完全保持一致
SyncTwoWay SyncMode = "sync" SyncTwoWay SyncMode = "sync"
// SyncPolicyExclusive 备份策略,排他备份,保证本地和云盘一比一备份,目标目录多余的文件会被删除
SyncPolicyExclusive SyncPolicy = "exclusive"
// SyncPolicyIncrement 备份策略,增量备份,只会增量备份文件,目标目录多余的(旧的)文件不会被删除
SyncPolicyIncrement SyncPolicy = "increment"
// CycleOneTime 只运行一次 // CycleOneTime 只运行一次
CycleOneTime CycleMode = "OneTime" CycleOneTime CycleMode = "OneTime"
// CycleInfiniteLoop 无限循环模式 // CycleInfiniteLoop 无限循环模式
CycleInfiniteLoop CycleMode = "InfiniteLoop" CycleInfiniteLoop CycleMode = "InfiniteLoop"
// StepScanFile 任务步骤,扫描文件建立同步数据库
StepScanFile TaskStep = "scan"
// StepDiffFile 任务步骤,对比文件
StepDiffFile TaskStep = "diff"
// StepSyncFile 任务步骤,同步文件
StepSyncFile TaskStep = "sync"
) )
func (t *SyncTask) NameLabel() string { func (t *SyncTask) NameLabel() string {
@ -101,11 +101,11 @@ func (t *SyncTask) String() string {
builder := &strings.Builder{} builder := &strings.Builder{}
builder.WriteString("任务: " + t.NameLabel() + "\n") builder.WriteString("任务: " + t.NameLabel() + "\n")
mode := "双向备份" mode := "双向备份"
if t.Mode == UploadOnly { if t.Mode == Upload {
mode = "备份本地文件(上传)" mode = "备份本地文件(上传)"
} }
if t.Mode == DownloadOnly { if t.Mode == Download {
mode = "备份云盘文件(下载)" mode = "备份云盘文件(下载)"
} }
builder.WriteString("同步模式: " + mode + "\n") builder.WriteString("同步模式: " + mode + "\n")
if t.Mode == SyncTwoWay { if t.Mode == SyncTwoWay {
@ -119,6 +119,14 @@ func (t *SyncTask) String() string {
} }
builder.WriteString("优先选项: " + priority + "\n") builder.WriteString("优先选项: " + priority + "\n")
} }
policy := "增量备份"
if t.Policy == SyncPolicyExclusive {
policy = "排他备份(上传&删除)"
}
if t.Policy == SyncPolicyIncrement {
policy = "增量备份(只上传)"
}
builder.WriteString("同步策略: " + policy + "\n")
builder.WriteString("本地目录: " + t.LocalFolderPath + "\n") builder.WriteString("本地目录: " + t.LocalFolderPath + "\n")
builder.WriteString("云盘目录: " + t.PanFolderPath + "\n") builder.WriteString("云盘目录: " + t.PanFolderPath + "\n")
driveName := "备份盘" driveName := "备份盘"
@ -209,11 +217,16 @@ func (t *SyncTask) Start() error {
return e return e
} }
// 策略
if t.Policy == "" {
t.Policy = SyncPolicyIncrement
}
// 启动文件扫描进程 // 启动文件扫描进程
t.SetScanLoopFlag(false) t.SetScanLoopFlag(false)
if t.Mode == UploadOnly { if t.Mode == Upload {
go t.scanLocalFile(t.ctx) go t.scanLocalFile(t.ctx)
} else if t.Mode == DownloadOnly { } else if t.Mode == Download {
go t.scanPanFile(t.ctx) go t.scanPanFile(t.ctx)
} else { } else {
return fmt.Errorf("异常:暂不支持该模式。") return fmt.Errorf("异常:暂不支持该模式。")
@ -333,7 +346,7 @@ func (t *SyncTask) discardLocalFileDb(filePath string, startTimeUnix int64) bool
} }
for _, file := range files { for _, file := range files {
if file.ScanTimeAt == "" || file.ScanTimeUnix() < startTimeUnix { if file.ScanTimeAt == "" || file.ScanTimeUnix() < startTimeUnix {
if t.Mode == DownloadOnly { if t.Mode == Download {
// delete discard local file info directly // delete discard local file info directly
t.localFileDb.Delete(file.Path) t.localFileDb.Delete(file.Path)
logger.Verboseln("label discard local file from DB: ", utils.ObjectToJsonStr(file, false)) logger.Verboseln("label discard local file from DB: ", utils.ObjectToJsonStr(file, false))
@ -572,7 +585,7 @@ func (t *SyncTask) discardPanFileDb(filePath string, startTimeUnix int64) bool {
} }
for _, file := range files { for _, file := range files {
if file.ScanTimeUnix() < startTimeUnix { if file.ScanTimeUnix() < startTimeUnix {
if t.Mode == UploadOnly { if t.Mode == Upload {
// delete discard pan file info directly // delete discard pan file info directly
t.panFileDb.Delete(file.Path) t.panFileDb.Delete(file.Path)
logger.Verboseln("delete discard pan file from DB: ", utils.ObjectToJsonStr(file, false)) logger.Verboseln("delete discard pan file from DB: ", utils.ObjectToJsonStr(file, false))

View File

@ -180,6 +180,9 @@ func (m *SyncTaskManager) Start(tasks []*SyncTask) (bool, error) {
task.Priority = SyncPriorityTimestampFirst task.Priority = SyncPriorityTimestampFirst
task.syncOption.SyncPriority = SyncPriorityTimestampFirst task.syncOption.SyncPriority = SyncPriorityTimestampFirst
} }
if task.Policy == "" {
task.Policy = SyncPolicyIncrement
}
task.LocalFolderPath = path.Clean(task.LocalFolderPath) task.LocalFolderPath = path.Clean(task.LocalFolderPath)
task.PanFolderPath = path.Clean(task.PanFolderPath) task.PanFolderPath = path.Clean(task.PanFolderPath)
if e := task.Start(); e != nil { if e := task.Start(); e != nil {