add download action for sync

This commit is contained in:
tickstep
2022-06-01 21:55:03 +08:00
parent 437c2e31b9
commit f2bcc977cc
16 changed files with 1287 additions and 94 deletions
+31 -50
View File
@@ -24,6 +24,10 @@ type (
}
)
const (
DirBucketPrefix string = "##:@@"
)
func NewBoltDb(dbFilePath string) *BoltDb {
return &BoltDb{
Path: dbFilePath,
@@ -47,26 +51,22 @@ func (b *BoltDb) addOneItem(rootBucket *bolt.Bucket, item *BoltItem) (bool, erro
if p == "" {
continue
}
bkt, _ = bkt.CreateBucketIfNotExists([]byte(p))
bkt, _ = bkt.CreateBucketIfNotExists([]byte(DirBucketPrefix + p))
}
if bkt == nil {
return false, fmt.Errorf("create or get bucket error")
}
fileName := path.Base(item.FilePath)
if e := bkt.Put([]byte(fileName), []byte(item.Data)); e != nil {
return false, e
}
var err error
if item.IsFolder {
bkt, err = bkt.CreateBucketIfNotExists([]byte(fileName))
bkt, err = bkt.CreateBucketIfNotExists([]byte(DirBucketPrefix + fileName))
if err != nil {
return false, err
}
if e := bkt.Put([]byte(DefaultDirKeyName), []byte(item.Data)); e != nil {
return false, e
}
} else {
if e := bkt.Put([]byte(fileName), []byte(item.Data)); e != nil {
return false, e
}
}
return true, nil
}
@@ -168,7 +168,7 @@ func (b *BoltDb) Get(filePath string) (string, error) {
return "", ErrItemNotExisted
}
for _, p := range parts[:len(parts)-1] {
bkt = bkt.Bucket([]byte(p))
bkt = bkt.Bucket([]byte(DirBucketPrefix + p))
if bkt == nil {
return "", ErrItemNotExisted
}
@@ -177,14 +177,7 @@ func (b *BoltDb) Get(filePath string) (string, error) {
return "", ErrItemNotExisted
}
dirBucket := bkt.Bucket([]byte(parts[len(parts)-1]))
var data []byte
if dirBucket != nil {
// is dir
data = dirBucket.Get([]byte(DefaultDirKeyName))
} else {
data = bkt.Get([]byte(parts[len(parts)-1]))
}
data := bkt.Get([]byte(parts[len(parts)-1]))
if data != nil {
return string(data), nil
}
@@ -218,26 +211,25 @@ func (b *BoltDb) GetFileList(filePath string) ([]string, error) {
if bkt == nil {
return dataList, ErrItemNotExisted
}
for _, p := range parts[:len(parts)-1] {
bkt = bkt.Bucket([]byte(p))
if len(parts) > 0 {
for _, p := range parts[:len(parts)-1] {
bkt = bkt.Bucket([]byte(DirBucketPrefix + p))
if bkt == nil {
return dataList, ErrItemNotExisted
}
}
bkt = bkt.Bucket([]byte(DirBucketPrefix + parts[len(parts)-1]))
if bkt == nil {
return dataList, ErrItemNotExisted
}
}
if bkt == nil {
return dataList, ErrItemNotExisted
}
dirBucket := bkt.Bucket([]byte(parts[len(parts)-1]))
if dirBucket == nil {
return dataList, ErrItemNotExisted
}
c := dirBucket.Cursor()
c := bkt.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
if string(k) == DefaultDirKeyName {
continue
}
dataList = append(dataList, string(v))
}
return dataList, nil
@@ -273,7 +265,7 @@ func (b *BoltDb) Delete(filePath string) (bool, error) {
return false, ErrItemNotExisted
}
for _, p := range parts[:len(parts)-1] {
bkt = bkt.Bucket([]byte(p))
bkt = bkt.Bucket([]byte(DirBucketPrefix + p))
if bkt == nil {
return false, ErrItemNotExisted
}
@@ -282,16 +274,16 @@ func (b *BoltDb) Delete(filePath string) (bool, error) {
return false, ErrItemNotExisted
}
targetName := []byte(parts[len(parts)-1])
dirBucket := bkt.Bucket(targetName)
targetName := parts[len(parts)-1]
dirBucket := bkt.Bucket([]byte(DirBucketPrefix + targetName))
var er error
if dirBucket != nil {
// is dir, delete bucket
er = bkt.DeleteBucket(targetName)
} else {
// is file, delete item
er = bkt.Delete(targetName)
er = bkt.DeleteBucket([]byte(DirBucketPrefix + targetName))
}
// delete item
er = bkt.Delete([]byte(targetName))
// Commit the transaction and check for error.
if err := tx.Commit(); err != nil {
return false, err
@@ -300,7 +292,7 @@ func (b *BoltDb) Delete(filePath string) (bool, error) {
}
// Update 更新数据项,数据项不存在返回错误
func (b *BoltDb) Update(filePath string, isFolder bool, data string) (bool, error) {
func (b *BoltDb) Update(filePath string, data string) (bool, error) {
filePath = FormatFilePath(filePath)
b.locker.Lock()
defer b.locker.Unlock()
@@ -334,19 +326,8 @@ func (b *BoltDb) Update(filePath string, isFolder bool, data string) (bool, erro
// update content
fileName := path.Base(filePath)
if isFolder {
bkt = bkt.Bucket([]byte(fileName))
if bkt == nil {
return false, ErrItemNotExisted
}
if e := bkt.Put([]byte(DefaultDirKeyName), []byte(data)); e != nil {
return false, e
}
} else {
// is file
if e := bkt.Put([]byte(fileName), []byte(data)); e != nil {
return false, e
}
if e := bkt.Put([]byte(fileName), []byte(data)); e != nil {
return false, e
}
// Commit the transaction and check for error.
+36 -2
View File
@@ -3,14 +3,38 @@ package syncdrive
import (
"context"
"fmt"
"path"
"github.com/tickstep/bolt"
"strings"
"testing"
"time"
)
func TestPath(t *testing.T) {
fmt.Println(path.Dir("/dsgsdghsd/dsgsdh/eg34/dghsh"))
db, err := bolt.Open("D:\\smb\\feny\\goprojects\\dev\\sync_drive\\5b2d7c10-e927-4e72-8f9d-5abb3bb04814\\test.db", 0600, &bolt.Options{Timeout: 5 * time.Second})
if err != nil {
return
}
defer db.Close()
// add item
// Start a writable transaction.
tx, err := db.Begin(true)
if err != nil {
return
}
defer tx.Rollback()
rootBucket, _ := tx.CreateBucketIfNotExists([]byte("/"))
if e := rootBucket.Put([]byte("test"), []byte("test value")); e != nil {
return
}
_, e := rootBucket.CreateBucketIfNotExists([]byte("test"))
println(e)
// Commit the transaction and check for error.
if err := tx.Commit(); err != nil {
return
}
}
func dosomething(ctx context.Context) {
@@ -39,6 +63,16 @@ func TestBolt(t *testing.T) {
localFileDb := NewLocalSyncDb("D:\\smb\\feny\\goprojects\\dev\\sync_drive\\local.db")
localFileDb.Open()
defer localFileDb.Close()
localFileDb.Add(&LocalFileItem{
FileName: "dev",
FileSize: 0,
FileType: "folder",
CreatedAt: "2022-05-12 10:21:14",
UpdatedAt: "2022-05-12 10:21:14",
FileExtension: ".db",
Sha1Hash: "",
Path: "D:\\smb\\feny\\goprojects\\dev",
})
localFileDb.Add(&LocalFileItem{
FileName: "file1.db",
FileSize: 0,
+182 -20
View File
@@ -1,37 +1,199 @@
package syncdrive
import (
"context"
"fmt"
"github.com/tickstep/aliyunpan-api/aliyunpan"
"github.com/tickstep/aliyunpan/internal/file/downloader"
"github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/aliyunpan/library/requester/transfer"
"github.com/tickstep/library-go/logger"
"github.com/tickstep/library-go/requester"
"os"
"path"
"strings"
"sync"
"time"
)
type (
FileAction string
FileActionTask struct {
Action FileAction
LocalFile *LocalFileItem
PanFile *PanFileItem
localFileDb LocalSyncDb
panFileDb PanSyncDb
syncFileDb SyncFileDb
panClient *aliyunpan.PanClient
blockSize int64
syncItem *SyncFileItem
}
)
const (
DownloadFile FileAction = "DownloadFile"
UploadFile FileAction = "UploadFile"
DeleteLocalFile FileAction = "DeleteLocalFile"
DeletePanFile FileAction = "DeletePanFile"
)
func (f *FileActionTask) HashCode() string {
postfix := ""
if f.syncItem.Action == SyncFileActionDownload {
postfix = strings.ReplaceAll(f.syncItem.PanFile.Path, "\\", "/")
} else if f.syncItem.Action == SyncFileActionUpload {
postfix = strings.ReplaceAll(f.syncItem.LocalFile.Path, "\\", "/")
}
return string(f.syncItem.Action) + postfix
}
func (f *FileActionTask) DoAction() error {
func (f *FileActionTask) DoAction(ctx context.Context) error {
fmt.Println("\nfile action task")
fmt.Println(f.syncItem)
if f.syncItem.Action == SyncFileActionUpload {
if e := f.uploadFile(); e != nil {
return e
}
}
if f.syncItem.Action == SyncFileActionDownload {
if e := f.downloadFile(); e != nil {
// TODO: retry / cleanup downloading file
return e
} else {
if b, er := utils.PathExists(f.syncItem.getLocalFileFullPath()); er == nil && b {
// file existed
// remove old local file
logger.Verbosef("delete local old file")
os.Remove(f.syncItem.getLocalFileFullPath())
time.Sleep(200 * time.Millisecond)
}
// rename downloading file into local file
os.Rename(f.syncItem.getLocalFileDownloadingFullPath(), f.syncItem.getLocalFileFullPath())
time.Sleep(200 * time.Millisecond)
// change modify time of local file
if err := os.Chtimes(f.syncItem.getLocalFileFullPath(), f.syncItem.PanFile.UpdateTime(), f.syncItem.PanFile.UpdateTime()); err != nil {
logger.Verbosef(err.Error())
}
time.Sleep(200 * time.Millisecond)
// save local file info
if file, er := os.Stat(f.syncItem.getLocalFileFullPath()); er == nil {
f.localFileDb.Add(&LocalFileItem{
FileName: file.Name(),
FileSize: file.Size(),
FileType: "file",
CreatedAt: file.ModTime().Format("2006-01-02 15:04:05"),
UpdatedAt: file.ModTime().Format("2006-01-02 15:04:05"),
FileExtension: path.Ext(file.Name()),
Sha1Hash: f.syncItem.PanFile.Sha1Hash,
Path: f.syncItem.getLocalFileFullPath(),
})
}
}
}
return nil
}
func (f *FileActionTask) DownloadFile() error {
func (f *FileActionTask) downloadFile() error {
// check local file existed or not
if b, e := utils.PathExists(f.syncItem.getLocalFileFullPath()); e == nil && b {
// file existed
logger.Verbosef("delete local old file")
os.Remove(f.syncItem.getLocalFileFullPath())
time.Sleep(200 * time.Millisecond)
}
durl, apierr := f.panClient.GetFileDownloadUrl(&aliyunpan.GetFileDownloadUrlParam{
DriveId: f.syncItem.PanFile.DriveId,
FileId: f.syncItem.PanFile.FileId,
})
time.Sleep(time.Duration(200) * time.Millisecond)
if apierr != nil {
logger.Verbosef("ERROR: get download url error: %s\n", f.syncItem.PanFile.FileId)
return apierr
}
if durl == nil || durl.Url == "" {
logger.Verbosef("无法获取有效的下载链接: %+v\n", durl)
f.syncItem.Status = SyncFileStatusFailed
f.syncItem.StatusUpdateTime = utils.NowTimeStr()
f.syncFileDb.Update(f.syncItem)
return fmt.Errorf("无法获取有效的下载链接")
}
if durl.Url == aliyunpan.IllegalDownloadUrl {
logger.Verbosef("无法获取有效的下载链接: %+v\n", durl)
f.syncItem.Status = SyncFileStatusIllegal
f.syncItem.StatusUpdateTime = utils.NowTimeStr()
f.syncFileDb.Update(f.syncItem)
return fmt.Errorf("文件非法,无法下载")
}
localDir := path.Dir(f.syncItem.getLocalFileFullPath())
if b, e := utils.PathExists(localDir); e == nil && !b {
os.MkdirAll(localDir, 0600)
time.Sleep(200 * time.Millisecond)
}
writer, file, err := downloader.NewDownloaderWriterByFilename(f.syncItem.getLocalFileDownloadingFullPath(), os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
return fmt.Errorf("%s, %s", "初始化下载发生错误", err)
}
defer file.Close()
if f.syncItem.PanFile.FileSize == 0 {
// zero file
f.syncItem.Status = SyncFileStatusSuccess
f.syncItem.StatusUpdateTime = utils.NowTimeStr()
f.syncFileDb.Update(f.syncItem)
return nil
}
worker := downloader.NewWorker(0, f.syncItem.PanFile.DriveId, f.syncItem.PanFile.FileId, durl.Url, writer, nil)
client := requester.NewHTTPClient()
client.SetKeepAlive(true)
client.SetTimeout(10 * time.Minute)
worker.SetClient(client)
worker.SetPanClient(f.panClient)
writeMu := &sync.Mutex{}
worker.SetWriteMutex(writeMu)
worker.SetTotalSize(f.syncItem.PanFile.FileSize)
worker.SetAcceptRange("bytes")
if f.syncItem.DownloadRange == nil {
f.syncItem.DownloadRange = &transfer.Range{
Begin: 0,
End: f.blockSize,
}
}
worker.SetRange(f.syncItem.DownloadRange) // 分片
// update status
f.syncItem.Status = SyncFileStatusDownloading
f.syncItem.StatusUpdateTime = utils.NowTimeStr()
f.syncFileDb.Update(f.syncItem)
for {
if f.syncItem.DownloadRange.End > f.syncItem.PanFile.FileSize {
f.syncItem.DownloadRange.End = f.syncItem.PanFile.FileSize
}
// 下载分片
// TODO: 分片重试策略
worker.Execute()
if worker.GetStatus().StatusCode() == downloader.StatusCodeSuccessed {
if f.syncItem.DownloadRange.End == f.syncItem.PanFile.FileSize {
// finished
f.syncItem.Status = SyncFileStatusSuccess
f.syncItem.StatusUpdateTime = utils.NowTimeStr()
f.syncFileDb.Update(f.syncItem)
break
}
// 下一个分片
f.syncItem.DownloadRange.Begin = f.syncItem.DownloadRange.End
f.syncItem.DownloadRange.End += f.blockSize
// 存储状态
f.syncFileDb.Update(f.syncItem)
}
}
return nil
}
func (f *FileActionTask) UploadFile() error {
return nil
}
func (f *FileActionTask) DeleteLocalFile() error {
return nil
}
func (f *FileActionTask) DeletePanFile() error {
func (f *FileActionTask) uploadFile() error {
return nil
}
+459 -10
View File
@@ -1,28 +1,477 @@
package syncdrive
import (
"context"
"fmt"
mapset "github.com/deckarep/golang-set"
"github.com/tickstep/aliyunpan/internal/localfile"
"github.com/tickstep/aliyunpan/internal/waitgroup"
"github.com/tickstep/aliyunpan/library/collection"
"github.com/tickstep/library-go/logger"
"path"
"strings"
"sync"
"time"
)
type (
FileActionTaskExecutor struct {
localFileDb LocalSyncDb
panFileDb PanSyncDb
}
FileActionTaskList []*FileActionTask
FileActionTaskManager struct {
FileActionTaskList []*FileActionTask
task *SyncTask
mutex *sync.Mutex
task *SyncTask
wg *waitgroup.WaitGroup
ctx context.Context
cancelFunc context.CancelFunc
}
localFileSet struct {
items LocalFileList
localFolderPath string
}
panFileSet struct {
items PanFileList
panFolderPath string
}
)
func NewFileActionTaskManager(task *SyncTask) *FileActionTaskManager {
return &FileActionTaskManager{
task: task,
FileActionTaskList: []*FileActionTask{},
mutex: &sync.Mutex{},
task: task,
}
}
func (f *FileActionTaskManager) StartSync() error {
func (f *FileActionTaskManager) Start() error {
if f.ctx != nil {
return fmt.Errorf("task have starting")
}
f.wg = waitgroup.NewWaitGroup(2)
var cancel context.CancelFunc
f.ctx, cancel = context.WithCancel(context.Background())
f.cancelFunc = cancel
go f.doFileDiffRoutine(f.ctx)
go f.fileActionTaskExecutor(f.ctx)
return nil
}
func (f *FileActionTaskManager) StopSync() error {
func (f *FileActionTaskManager) Stop() error {
if f.ctx == nil {
return nil
}
// cancel all sub task & process
f.cancelFunc()
// wait for finished
f.wg.Wait()
f.ctx = nil
f.cancelFunc = nil
return nil
}
// getPanPathFromLocalPath 通过本地文件路径获取网盘文件的对应路径
func (f *FileActionTaskManager) getPanPathFromLocalPath(localPath string) string {
localPath = strings.ReplaceAll(localPath, "\\", "/")
localRootPath := strings.ReplaceAll(f.task.LocalFolderPath, "\\", "/")
relativePath := strings.TrimPrefix(localPath, localRootPath)
return path.Join(path.Clean(f.task.PanFolderPath), relativePath)
}
// getLocalPathFromPanPath 通过网盘文件路径获取对应的本地文件的对应路径
func (f *FileActionTaskManager) getLocalPathFromPanPath(panPath string) string {
panPath = strings.ReplaceAll(panPath, "\\", "/")
panRootPath := strings.ReplaceAll(f.task.PanFolderPath, "\\", "/")
relativePath := strings.TrimPrefix(panPath, panRootPath)
return path.Join(path.Clean(f.task.LocalFolderPath), relativePath)
}
// doFileDiffRoutine 对比网盘文件和本地文件信息,差异化上传或者下载文件
func (f *FileActionTaskManager) doFileDiffRoutine(ctx context.Context) {
localFolderQueue := collection.NewFifoQueue()
panFolderQueue := collection.NewFifoQueue()
// init root folder
if localRootFolder, e := f.task.localFileDb.Get(f.task.LocalFolderPath); e == nil {
localFolderQueue.Push(localRootFolder)
} else {
logger.Verboseln(e)
return
}
if panRootFolder, e := f.task.panFileDb.Get(f.task.PanFolderPath); e == nil {
panFolderQueue.Push(panRootFolder)
} else {
logger.Verboseln(e)
return
}
f.wg.AddDelta()
defer f.wg.Done()
for {
select {
case <-ctx.Done():
// cancel routine & done
logger.Verboseln("file diff routine done")
return
default:
logger.Verboseln("do file diff process")
localFiles := LocalFileList{}
panFiles := PanFileList{}
var err error
// iterator local folder
objLocal := localFolderQueue.Pop()
if objLocal != nil {
localItem := objLocal.(*LocalFileItem)
localFiles, err = f.task.localFileDb.GetFileList(localItem.Path)
if err != nil {
localFiles = LocalFileList{}
}
panFiles, err = f.task.panFileDb.GetFileList(f.getPanPathFromLocalPath(localItem.Path))
if err != nil {
panFiles = PanFileList{}
}
} else {
// iterator pan folder
objPan := panFolderQueue.Pop()
if objPan != nil {
panItem := objPan.(*PanFileItem)
panFiles, err = f.task.panFileDb.GetFileList(panItem.Path)
if err != nil {
panFiles = PanFileList{}
}
localFiles, err = f.task.localFileDb.GetFileList(f.getLocalPathFromPanPath(panItem.Path))
if err != nil {
localFiles = LocalFileList{}
}
}
}
// empty loop
if len(panFiles) == 0 && len(localFiles) == 0 {
time.Sleep(100 * time.Millisecond)
continue
}
localFilesSet := &localFileSet{
items: localFiles,
localFolderPath: f.task.LocalFolderPath,
}
panFilesSet := &panFileSet{
items: panFiles,
panFolderPath: f.task.PanFolderPath,
}
localFilesNeedToUpload := localFilesSet.Difference(panFilesSet)
panFilesNeedToDownload := panFilesSet.Difference(localFilesSet)
localFilesNeedToCheck, panFilesNeedToCheck := localFilesSet.Intersection(panFilesSet)
// download file from pan drive
if f.task.Mode != UploadOnly {
for _, file := range panFilesNeedToDownload {
if file.IsFolder() {
panFolderQueue.PushUnique(file)
continue
}
fileActionTask := &FileActionTask{
syncItem: &SyncFileItem{
Action: SyncFileActionDownload,
Status: SyncFileStatusCreate,
LocalFile: nil,
PanFile: file,
StatusUpdateTime: "",
PanFolderPath: f.task.PanFolderPath,
LocalFolderPath: f.task.LocalFolderPath,
},
}
f.addToQueue(fileActionTask)
}
}
// upload file to pan drive
if f.task.Mode != DownloadOnly {
for _, file := range localFilesNeedToUpload {
if file.IsFolder() {
localFolderQueue.PushUnique(file)
continue
}
fileActionTask := &FileActionTask{
syncItem: &SyncFileItem{
Action: SyncFileActionUpload,
Status: SyncFileStatusCreate,
LocalFile: file,
PanFile: nil,
StatusUpdateTime: "",
PanFolderPath: f.task.PanFolderPath,
LocalFolderPath: f.task.LocalFolderPath,
},
}
f.addToQueue(fileActionTask)
}
}
// compare file to decide download / upload
for idx, _ := range localFilesNeedToCheck {
localFile := localFilesNeedToCheck[idx]
panFile := panFilesNeedToCheck[idx]
if localFile.IsFolder() {
localFolderQueue.PushUnique(localFile)
continue
}
if localFile.Sha1Hash == "" {
// calc sha1
fileSum := localfile.NewLocalFileEntity(localFile.Path)
fileSum.Sum(localfile.CHECKSUM_SHA1) // block operation
localFile.Sha1Hash = fileSum.SHA1
fileSum.Close()
// save sha1
f.task.localFileDb.Update(localFile)
}
if strings.ToLower(panFile.Sha1Hash) == strings.ToLower(localFile.Sha1Hash) {
// do nothing
logger.Verboseln("no need to update file: ", localFile.Path)
continue
}
if localFile.UpdateTimeUnix() > panFile.UpdateTimeUnix() { // upload file
uploadLocalFile := &FileActionTask{
syncItem: &SyncFileItem{
Action: SyncFileActionUpload,
Status: SyncFileStatusCreate,
LocalFile: localFile,
PanFile: nil,
StatusUpdateTime: "",
PanFolderPath: f.task.PanFolderPath,
LocalFolderPath: f.task.LocalFolderPath,
},
}
f.addToQueue(uploadLocalFile)
} else if localFile.UpdateTimeUnix() < panFile.UpdateTimeUnix() { // download file
downloadPanFile := &FileActionTask{
syncItem: &SyncFileItem{
Action: SyncFileActionDownload,
Status: SyncFileStatusCreate,
LocalFile: nil,
PanFile: panFile,
StatusUpdateTime: "",
PanFolderPath: f.task.PanFolderPath,
LocalFolderPath: f.task.LocalFolderPath,
},
}
f.addToQueue(downloadPanFile)
}
}
time.Sleep(100 * time.Millisecond)
}
}
}
func (f *FileActionTaskManager) addToQueue(fileTask *FileActionTask) {
f.mutex.Lock()
defer f.mutex.Unlock()
if itemInDb, e := f.task.syncFileDb.Get(fileTask.syncItem.Id()); e == nil && itemInDb != nil {
if itemInDb.Status == SyncFileStatusCreate || itemInDb.Status == SyncFileStatusDownloading || itemInDb.Status == SyncFileStatusUploading {
return
}
if itemInDb.Status == SyncFileStatusSuccess {
if (time.Now().Unix() - itemInDb.StatusUpdateTimeUnix()) < TimeSecondsOf5Minute {
// 少于5分钟,不同步,减少同步频次
return
}
}
}
// 进入任务队列
f.task.syncFileDb.Add(fileTask.syncItem)
}
func (f *FileActionTaskManager) getFromQueue(act SyncFileAction) *FileActionTask {
f.mutex.Lock()
defer f.mutex.Unlock()
if act == SyncFileActionDownload {
if files, e := f.task.syncFileDb.GetFileList(SyncFileStatusDownloading); e == nil {
if len(files) > 0 {
return &FileActionTask{
localFileDb: f.task.localFileDb,
panFileDb: f.task.panFileDb,
syncFileDb: f.task.syncFileDb,
panClient: f.task.panClient,
blockSize: int64(10485760),
syncItem: files[0],
}
}
}
} else if act == SyncFileActionUpload {
if files, e := f.task.syncFileDb.GetFileList(SyncFileStatusUploading); e == nil {
if len(files) > 0 {
return &FileActionTask{
localFileDb: f.task.localFileDb,
panFileDb: f.task.panFileDb,
syncFileDb: f.task.syncFileDb,
panClient: f.task.panClient,
blockSize: int64(10485760),
syncItem: files[0],
}
}
}
}
if files, e := f.task.syncFileDb.GetFileList(SyncFileStatusCreate); e == nil {
if len(files) > 0 {
for _, file := range files {
if file.Action == act {
return &FileActionTask{
localFileDb: f.task.localFileDb,
panFileDb: f.task.panFileDb,
syncFileDb: f.task.syncFileDb,
panClient: f.task.panClient,
blockSize: int64(10485760),
syncItem: file,
}
}
}
}
}
// TODO: failed file retry?
return nil
}
// fileActionTaskExecutor 异步执行文件操作
func (f *FileActionTaskManager) fileActionTaskExecutor(ctx context.Context) {
f.wg.AddDelta()
defer f.wg.Done()
for {
select {
case <-ctx.Done():
// cancel routine & done
logger.Verboseln("file executor routine done")
return
default:
logger.Verboseln("do file executor process")
// do upload
uploadItem := f.getFromQueue(SyncFileActionUpload)
if uploadItem != nil {
uploadItem.DoAction(ctx)
}
// do download
downloadItem := f.getFromQueue(SyncFileActionDownload)
if downloadItem != nil {
downloadItem.DoAction(ctx)
}
// delay
time.Sleep(500 * time.Millisecond)
}
}
}
// getRelativePath 获取文件的相对路径
func (l *localFileSet) getRelativePath(localPath string) string {
localPath = strings.ReplaceAll(localPath, "\\", "/")
localRootPath := strings.ReplaceAll(l.localFolderPath, "\\", "/")
relativePath := strings.TrimPrefix(localPath, localRootPath)
return path.Clean(relativePath)
}
// Intersection 交集
func (l *localFileSet) Intersection(other *panFileSet) (LocalFileList, PanFileList) {
localFilePathSet := mapset.NewThreadUnsafeSet()
relativePathLocalMap := map[string]*LocalFileItem{}
for _, item := range l.items {
rp := l.getRelativePath(item.Path)
relativePathLocalMap[rp] = item
localFilePathSet.Add(rp)
}
localFileList := LocalFileList{}
panFileList := PanFileList{}
for _, item := range other.items {
rp := other.getRelativePath(item.Path)
if localFilePathSet.Contains(rp) {
localFileList = append(localFileList, relativePathLocalMap[rp])
panFileList = append(panFileList, item)
}
}
return localFileList, panFileList
}
// Difference 差集
func (l *localFileSet) Difference(other *panFileSet) LocalFileList {
panFilePathSet := mapset.NewThreadUnsafeSet()
for _, item := range other.items {
rp := other.getRelativePath(item.Path)
panFilePathSet.Add(rp)
}
localFileList := LocalFileList{}
for _, item := range l.items {
rp := l.getRelativePath(item.Path)
if !panFilePathSet.Contains(rp) {
localFileList = append(localFileList, item)
}
}
return localFileList
}
// getRelativePath 获取文件的相对路径
func (p *panFileSet) getRelativePath(panPath string) string {
panPath = strings.ReplaceAll(panPath, "\\", "/")
panRootPath := strings.ReplaceAll(p.panFolderPath, "\\", "/")
relativePath := strings.TrimPrefix(panPath, panRootPath)
return path.Clean(relativePath)
}
// Intersection 交集
func (p *panFileSet) Intersection(other *localFileSet) (PanFileList, LocalFileList) {
localFilePathSet := mapset.NewThreadUnsafeSet()
relativePathLocalMap := map[string]*LocalFileItem{}
for _, item := range other.items {
rp := other.getRelativePath(item.Path)
relativePathLocalMap[rp] = item
localFilePathSet.Add(rp)
}
localFileList := LocalFileList{}
panFileList := PanFileList{}
for _, item := range p.items {
rp := p.getRelativePath(item.Path)
if localFilePathSet.Contains(rp) {
localFileList = append(localFileList, relativePathLocalMap[rp])
panFileList = append(panFileList, item)
}
}
return panFileList, localFileList
}
// Difference 差集
func (p *panFileSet) Difference(other *localFileSet) PanFileList {
localFilePathSet := mapset.NewThreadUnsafeSet()
for _, item := range other.items {
rp := other.getRelativePath(item.Path)
localFilePathSet.Add(rp)
}
panFileList := PanFileList{}
for _, item := range p.items {
rp := p.getRelativePath(item.Path)
if !localFilePathSet.Contains(rp) {
panFileList = append(panFileList, item)
}
}
return panFileList
}
@@ -0,0 +1,60 @@
package syncdrive
import (
"fmt"
"github.com/tickstep/aliyunpan-api/aliyunpan"
"github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/library-go/logger"
"os"
"testing"
"time"
)
func TestFileActionMgrStart(t *testing.T) {
refreshToken := "ac1010f6396...585338b533bd4ab"
webToken, err := aliyunpan.GetAccessTokenFromRefreshToken(refreshToken)
if err != nil {
fmt.Println("get acccess token error")
return
}
// pan client
panClient := aliyunpan.NewPanClient(*webToken, aliyunpan.AppLoginToken{})
user, _ := panClient.GetUserInfo()
task := &SyncTask{
Id: "5b2d7c10-e927-4e72-8f9d-5abb3bb04814",
DriveId: user.FileDriveId,
LocalFolderPath: "/Volumes/Downloads/dev/folder",
PanFolderPath: "/sync_drive",
Mode: "sync",
LastSyncTime: "2022-05-26 19:29:19",
syncDbFolderPath: "/Volumes/Downloads/dev/sync_drive",
panClient: panClient,
}
task.setup()
ft := NewFileActionTaskManager(task)
ft.Start()
//go func() {
// time.Sleep(10 * time.Second)
// task.Stop()
//}()
time.Sleep(50 * time.Minute)
ft.Stop()
}
func TestFileTime(t *testing.T) {
ts := utils.ParseTimeStr("2013-01-11 12:15:07")
//ts = time.Now()
if err := os.Chtimes("/Volumes/Downloads/dev/upload/password.key", ts, ts); err != nil {
logger.Verbosef(err.Error())
}
}
func TestLocalFileTime(t *testing.T) {
if file, er := os.Stat("/Volumes/Downloads/dev/upload/password.key"); er == nil {
fmt.Println(file.ModTime().Format("2006-01-02 15:04:05"))
}
}
+12
View File
@@ -0,0 +1,12 @@
package syncdrive
const (
// DownloadingFileSuffix 下载中文件后缀
DownloadingFileSuffix string = ".aliyunpan"
// TimeSecondsOfOneMinute 一分钟秒数
TimeSecondsOfOneMinute int64 = 60
// TimeSecondsOf5Minute 5分钟秒数
TimeSecondsOf5Minute int64 = 5 * TimeSecondsOfOneMinute
)
+150
View File
@@ -3,6 +3,11 @@ package syncdrive
import (
"fmt"
"github.com/tickstep/aliyunpan-api/aliyunpan"
"github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/aliyunpan/library/requester/transfer"
"path"
"strings"
"time"
)
type (
@@ -99,6 +104,51 @@ type (
// Close 关闭数据库
Close() (bool, error)
}
SyncFileAction string
SyncFileStatus string
SyncFileItem struct {
Action SyncFileAction `json:"action"`
Status SyncFileStatus `json:"status"`
LocalFile *LocalFileItem `json:"localFile"`
PanFile *PanFileItem `json:"panFile"`
// LocalFolderPath 本地目录
LocalFolderPath string `json:"localFolderPath"`
// PanFolderPath 云盘目录
PanFolderPath string `json:"panFolderPath"`
DownloadRange *transfer.Range `json:"downloadRange"`
StatusUpdateTime string `json:"statusUpdateTime"`
}
SyncFileList []*SyncFileItem
SyncFileDb interface {
// Open 打开并准备数据库
Open() (bool, error)
// Add 存储一个数据项
Add(item *SyncFileItem) (bool, error)
// Get 获取一个数据项
Get(id string) (*SyncFileItem, error)
// GetFileList 获取文件夹下的所有的文件列表
GetFileList(Status SyncFileStatus) (SyncFileList, error)
// Delete 删除一个数据项,如果是文件夹,则会删除文件夹下面所有的文件列表
Delete(id string) (bool, error)
// Update 更新一个数据项数据
Update(item *SyncFileItem) (bool, error)
// Close 关闭数据库
Close() (bool, error)
}
)
const (
SyncFileStatusCreate SyncFileStatus = "create"
SyncFileStatusUploading SyncFileStatus = "uploading"
SyncFileStatusDownloading SyncFileStatus = "downloading"
SyncFileStatusFailed SyncFileStatus = "failed"
SyncFileStatusSuccess SyncFileStatus = "success"
SyncFileStatusIllegal SyncFileStatus = "illegal"
SyncFileActionDownload SyncFileAction = "download"
SyncFileActionUpload SyncFileAction = "upload"
)
var (
@@ -125,6 +175,12 @@ func NewPanFileItem(fe *aliyunpan.FileEntity) *PanFileItem {
}
}
func (item *PanFileItem) Id() string {
sb := &strings.Builder{}
fmt.Fprintf(sb, "%s%s", strings.ReplaceAll(item.Path, "\\", "/"), item.UpdatedAt)
return utils.Md5Str(sb.String())
}
func (item *PanFileItem) FormatFileName() string {
return item.FileName
}
@@ -137,10 +193,28 @@ func (item *PanFileItem) IsFolder() bool {
return item.FileType == "folder"
}
func (item *PanFileItem) UpdateTimeUnix() int64 {
return item.UpdateTime().Unix()
}
func (item *PanFileItem) UpdateTime() time.Time {
return utils.ParseTimeStr(item.UpdatedAt)
}
func (item *PanFileItem) HashCode() string {
return item.Path
}
func NewPanSyncDb(dbFilePath string) PanSyncDb {
return interface{}(newPanSyncDbBolt(dbFilePath)).(PanSyncDb)
}
func (item *LocalFileItem) Id() string {
sb := &strings.Builder{}
fmt.Fprintf(sb, "%s%s", strings.ReplaceAll(item.Path, "\\", "/"), item.UpdatedAt)
return utils.Md5Str(sb.String())
}
func (item *LocalFileItem) FormatFileName() string {
return item.FileName
}
@@ -153,6 +227,82 @@ func (item *LocalFileItem) IsFolder() bool {
return item.FileType == "folder"
}
func (item *LocalFileItem) UpdateTimeUnix() int64 {
return item.UpdateTime().Unix()
}
func (item *LocalFileItem) UpdateTime() time.Time {
return utils.ParseTimeStr(item.UpdatedAt)
}
func (item *LocalFileItem) HashCode() string {
return item.Path
}
func NewLocalSyncDb(dbFilePath string) LocalSyncDb {
return interface{}(newLocalSyncDbBolt(dbFilePath)).(LocalSyncDb)
}
func (l LocalFileList) FindFileByPath(filePath string) *LocalFileItem {
for _, item := range l {
if filePath == item.Path {
return item
}
}
return nil
}
func (p PanFileList) FindFileByPath(filePath string) *PanFileItem {
for _, item := range p {
if strings.ReplaceAll(filePath, "\\", "/") == item.Path {
return item
}
}
return nil
}
func (item *SyncFileItem) Id() string {
sb := &strings.Builder{}
if item.Action == SyncFileActionDownload {
fmt.Fprintf(sb, "%s%s", string(item.Action), item.PanFile.Id())
} else if item.Action == SyncFileActionUpload {
fmt.Fprintf(sb, "%s%s", string(item.Action), item.LocalFile.Id())
}
return utils.Md5Str(sb.String())
}
func (item *SyncFileItem) StatusUpdateTimeUnix() int64 {
if ts, er := time.Parse("2006-01-02 15:04:05", item.StatusUpdateTime); er != nil {
return ts.Unix()
}
return 0
}
// getPanFullPath 获取网盘文件的路径
func (item *SyncFileItem) getPanFileFullPath() string {
localPath := item.LocalFile.Path
localPath = strings.ReplaceAll(localPath, "\\", "/")
localRootPath := strings.ReplaceAll(item.LocalFolderPath, "\\", "/")
relativePath := strings.TrimPrefix(localPath, localRootPath)
return path.Join(path.Clean(item.PanFolderPath), relativePath)
}
// getLocalFullPath 获取本地文件的路径
func (item *SyncFileItem) getLocalFileFullPath() string {
panPath := item.PanFile.Path
panPath = strings.ReplaceAll(panPath, "\\", "/")
panRootPath := strings.ReplaceAll(item.PanFolderPath, "\\", "/")
relativePath := strings.TrimPrefix(panPath, panRootPath)
return path.Join(path.Clean(item.LocalFolderPath), relativePath)
}
// getLocalFileDownloadingFullPath 获取本地文件下载时的路径
func (item *SyncFileItem) getLocalFileDownloadingFullPath() string {
return item.getLocalFileFullPath() + DownloadingFileSuffix
}
func NewSyncFileDb(dbFilePath string) SyncFileDb {
return interface{}(newSyncFileDbBolt(dbFilePath)).(SyncFileDb)
}
+139 -5
View File
@@ -21,10 +21,13 @@ type (
db *BoltDb
locker *sync.Mutex
}
)
const (
DefaultDirKeyName string = "."
// SyncFileDbBolt 存储同步文件状态信息的数据库
SyncFileDbBolt struct {
Path string
db *BoltDb
locker *sync.Mutex
}
)
func newPanSyncDbBolt(dbFilePath string) *PanSyncDbBolt {
@@ -174,7 +177,7 @@ func (p *PanSyncDbBolt) Update(item *PanFileItem) (bool, error) {
if err != nil {
return false, err
}
return p.db.Update(item.Path, item.IsFolder(), string(data))
return p.db.Update(item.Path, string(data))
}
// Close 关闭数据库
@@ -327,10 +330,141 @@ func (p *LocalSyncDbBolt) Update(item *LocalFileItem) (bool, error) {
if err != nil {
return false, err
}
return p.db.Update(item.Path, item.IsFolder(), string(data))
return p.db.Update(item.Path, string(data))
}
// Close 关闭数据库
func (p *LocalSyncDbBolt) Close() (bool, error) {
return true, nil
}
func newSyncFileDbBolt(dbFilePath string) *SyncFileDbBolt {
return &SyncFileDbBolt{
Path: dbFilePath,
locker: &sync.Mutex{},
}
}
// Open 打开并准备数据库
func (s *SyncFileDbBolt) Open() (bool, error) {
return true, nil
}
// Add 存储一个数据项
func (s *SyncFileDbBolt) Add(item *SyncFileItem) (bool, error) {
if item == nil {
return false, fmt.Errorf("item is nil")
}
s.locker.Lock()
defer s.locker.Unlock()
s.db = NewBoltDb(s.Path)
s.db.Open()
defer s.db.Close()
data, err := json.Marshal(item)
if err != nil {
return false, err
}
return s.db.Add(&BoltItem{
FilePath: "/" + item.Id(),
IsFolder: false,
Data: string(data),
})
}
// Get 获取一个数据项
func (s *SyncFileDbBolt) Get(id string) (*SyncFileItem, error) {
filePath := "/" + id
if filePath == "" {
return nil, fmt.Errorf("item is nil")
}
s.locker.Lock()
defer s.locker.Unlock()
s.db = NewBoltDb(s.Path)
s.db.Open()
defer s.db.Close()
data, err := s.db.Get(filePath)
if err == nil && data != "" {
item := &SyncFileItem{}
if err := json.Unmarshal([]byte(data), item); err != nil {
return nil, err
}
return item, nil
}
return nil, err
}
// GetFileList 获取文件夹下的所有的文件列表
func (s *SyncFileDbBolt) GetFileList(Status SyncFileStatus) (SyncFileList, error) {
filePath := "/"
if filePath == "" {
return nil, fmt.Errorf("item is nil")
}
s.locker.Lock()
defer s.locker.Unlock()
s.db = NewBoltDb(s.Path)
s.db.Open()
defer s.db.Close()
panFileList := SyncFileList{}
dataList, err := s.db.GetFileList(filePath)
if err == nil && len(dataList) > 0 {
for _, data := range dataList {
if data == "" {
continue
}
item := &SyncFileItem{}
if err := json.Unmarshal([]byte(data), item); err != nil {
return nil, err
}
if item.Status == Status {
panFileList = append(panFileList, item)
}
}
return panFileList, nil
}
return nil, err
}
// Delete 删除一个数据项,如果是文件夹,则会删除文件夹下面所有的文件列表
func (s *SyncFileDbBolt) Delete(id string) (bool, error) {
filePath := "/" + id
if filePath == "" {
return false, fmt.Errorf("item is nil")
}
s.locker.Lock()
defer s.locker.Unlock()
s.db = NewBoltDb(s.Path)
s.db.Open()
defer s.db.Close()
return s.db.Delete(filePath)
}
// Update 更新一个数据项数据
func (s *SyncFileDbBolt) Update(item *SyncFileItem) (bool, error) {
if item == nil {
return false, fmt.Errorf("item is nil")
}
s.locker.Lock()
defer s.locker.Unlock()
s.db = NewBoltDb(s.Path)
s.db.Open()
defer s.db.Close()
data, err := json.Marshal(item)
if err != nil {
return false, err
}
return s.db.Update("/"+item.Id(), string(data))
}
// Close 关闭数据库
func (s *SyncFileDbBolt) Close() (bool, error) {
return true, nil
}
+115
View File
@@ -197,3 +197,118 @@ func TestLocalGetFileList(t *testing.T) {
fmt.Println(b.GetFileList("D:/smb/feny/goprojects/dl/a761171495"))
}
func TestSyncDbAdd(t *testing.T) {
b := NewSyncFileDb("D:\\smb\\feny\\goprojects\\dev\\sync_drive\\sync.db")
b.Open()
defer b.Close()
b.Add(&SyncFileItem{
Action: SyncFileActionDownload,
Status: SyncFileStatusCreate,
LocalFile: nil,
PanFile: &PanFileItem{
Category: "others",
Crc64Hash: "16173291050517323365",
CreatedAt: "2021-07-09 23:17:52",
DomainId: "bj29",
DriveId: "19519221",
FileExtension: "apk",
FileId: "60e868a0a005315fe2b149b4ade47b2df8bdccee",
FileName: "10000996@yunpan-release.apk",
FileSize: 61029089,
FileType: "file",
ParentFileId: "60f29e3ac420ae8c08e645db8b80b881e2a3633c",
Path: "/sync_drive/10000996@yunpan-release.apk",
Sha1Hash: "05A7597967C3C947D84564A7D55309DEA56ED985",
UpdatedAt: "2021-07-21 17:05:49",
UploadId: "rapid-9141d56d-21c3-4fcc-b41d-5df7bf47540f",
},
StatusUpdateTime: "",
})
b.Add(&SyncFileItem{
Action: SyncFileActionDownload,
Status: SyncFileStatusCreate,
LocalFile: nil,
PanFile: &PanFileItem{
Category: "others",
Crc64Hash: "16173291050517323365",
CreatedAt: "2021-07-09 23:17:52",
DomainId: "bj29",
DriveId: "19519221",
FileExtension: "apk",
FileId: "60e868a0a005315fe2b149b4ade47b2df8bdccee",
FileName: "10000996@yunpan-release-1.apk",
FileSize: 61029089,
FileType: "file",
ParentFileId: "60f29e3ac420ae8c08e645db8b80b881e2a3633c",
Path: "/sync_drive/10000996@yunpan-release-1.apk",
Sha1Hash: "05A7597967C3C947D84564A7D55309DEA56ED985",
UpdatedAt: "2021-07-21 17:05:49",
UploadId: "rapid-9141d56d-21c3-4fcc-b41d-5df7bf47540f",
},
StatusUpdateTime: "",
})
b.Add(&SyncFileItem{
Action: SyncFileActionUpload,
Status: SyncFileStatusCreate,
LocalFile: &LocalFileItem{
FileName: "f1.txt",
FileSize: 1542,
FileType: "file",
CreatedAt: "2020-12-12 12:51:12",
UpdatedAt: "2020-12-12 12:51:12",
FileExtension: ".txt",
Sha1Hash: "",
Path: "D:\\smb\\feny\\goprojects\\dev\\fo\\f1.txt",
},
PanFile: nil,
StatusUpdateTime: "",
})
//b.AddUnique(&SyncFileItem{
// Action: SyncFileActionDownload,
// Status: SyncFileStatusCreate,
// LocalFile: nil,
// PanFile: &PanFileItem{
// Category: "others",
// Crc64Hash: "16173291050517323365",
// CreatedAt: "2021-07-09 23:17:52",
// DomainId: "bj29",
// DriveId: "19519221",
// FileExtension: "apk",
// FileId: "60e868a0a005315fe2b149b4ade47b2df8bdccee",
// FileName: "10000996@yunpan-release-1.apk",
// FileSize: 61029089,
// FileType: "file",
// ParentFileId: "60f29e3ac420ae8c08e645db8b80b881e2a3633c",
// Path: "/sync_drive/10000996@yunpan-release-1.apk",
// Sha1Hash: "05A7597967C3C947D84564A7D55309DEA56ED985",
// UpdatedAt: "2021-07-21 17:05:49",
// UploadId: "rapid-9141d56d-21c3-4fcc-b41d-5df7bf47540f",
// },
// StatusUpdateTime: "",
//})
//b.AddUnique(&SyncFileItem{
// Action: SyncFileActionDownload,
// Status: SyncFileStatusCreate,
// LocalFile: nil,
// PanFile: &PanFileItem{
// Category: "others",
// Crc64Hash: "16173291050517323365",
// CreatedAt: "2021-07-09 23:17:52",
// DomainId: "bj29",
// DriveId: "19519221",
// FileExtension: "apk",
// FileId: "60e868a0a005315fe2b149b4ade47b2df8bdccee",
// FileName: "10000996@yunpan-release-2.apk",
// FileSize: 61029089,
// FileType: "file",
// ParentFileId: "60f29e3ac420ae8c08e645db8b80b881e2a3633c",
// Path: "/sync_drive/10000996@yunpan-release-2.apk",
// Sha1Hash: "05A7597967C3C947D84564A7D55309DEA56ED985",
// UpdatedAt: "2021-07-21 17:05:49",
// UploadId: "rapid-9141d56d-21c3-4fcc-b41d-5df7bf47540f",
// },
// StatusUpdateTime: "",
//})
}
+50 -1
View File
@@ -38,12 +38,15 @@ type (
syncDbFolderPath string
localFileDb LocalSyncDb
panFileDb PanSyncDb
syncFileDb SyncFileDb
wg *waitgroup.WaitGroup
ctx context.Context
cancelFunc context.CancelFunc
panClient *aliyunpan.PanClient
fileActionTaskManager *FileActionTaskManager
}
)
@@ -76,19 +79,40 @@ func (t *SyncTask) String() string {
return builder.String()
}
func (t *SyncTask) setup() error {
t.localFileDb = NewLocalSyncDb(t.localSyncDbFullPath())
t.panFileDb = NewPanSyncDb(t.panSyncDbFullPath())
t.syncFileDb = NewSyncFileDb(t.syncFileDbFullPath())
if _, e := t.localFileDb.Open(); e != nil {
return e
}
if _, e := t.panFileDb.Open(); e != nil {
return e
}
return nil
}
// Start 启动同步任务
func (t *SyncTask) Start() error {
if t.ctx != nil {
return fmt.Errorf("task have starting")
}
t.localFileDb = NewLocalSyncDb(t.localSyncDbFullPath())
t.panFileDb = newPanSyncDbBolt(t.panSyncDbFullPath())
t.panFileDb = NewPanSyncDb(t.panSyncDbFullPath())
t.syncFileDb = NewSyncFileDb(t.syncFileDbFullPath())
if _, e := t.localFileDb.Open(); e != nil {
return e
}
if _, e := t.panFileDb.Open(); e != nil {
return e
}
if _, e := t.syncFileDb.Open(); e != nil {
return e
}
if t.fileActionTaskManager == nil {
t.fileActionTaskManager = NewFileActionTaskManager(t)
}
t.wg = waitgroup.NewWaitGroup(2)
@@ -98,6 +122,8 @@ func (t *SyncTask) Start() error {
go t.scanLocalFile(t.ctx)
go t.scanPanFile(t.ctx)
//t.fileActionTaskManager.Start()
return nil
}
@@ -115,6 +141,9 @@ func (t *SyncTask) Stop() error {
t.ctx = nil
t.cancelFunc = nil
// stop file action task (block routine)
t.fileActionTaskManager.Stop()
// release resources
if t.localFileDb != nil {
t.localFileDb.Close()
@@ -122,6 +151,9 @@ func (t *SyncTask) Stop() error {
if t.panFileDb != nil {
t.panFileDb.Close()
}
if t.syncFileDb != nil {
t.syncFileDb.Close()
}
// record the sync time
t.LastSyncTime = utils.NowTimeStr()
@@ -146,6 +178,15 @@ func (t *SyncTask) localSyncDbFullPath() string {
return path.Join(dir, "local.bolt")
}
// syncFileDbFullPath 同步文件过程数据库
func (t *SyncTask) syncFileDbFullPath() string {
dir := path.Join(t.syncDbFolderPath, t.Id)
if b, _ := utils.PathExists(dir); !b {
os.MkdirAll(dir, 0600)
}
return path.Join(dir, "sync.bolt")
}
func newLocalFileItem(file os.FileInfo, fullPath string) *LocalFileItem {
ft := "file"
if file.IsDir() {
@@ -227,6 +268,11 @@ func (t *SyncTask) scanLocalFile(ctx context.Context) {
}
localFileAppendList := LocalFileList{}
for _, file := range files {
if strings.HasSuffix(file.Name(), DownloadingFileSuffix) {
// 下载中文件,跳过
continue
}
localFile := newLocalFileItem(file, item.path+"/"+file.Name())
localFileInDb, _ := t.localFileDb.Get(localFile.Path)
if localFileInDb == nil {
@@ -234,6 +280,9 @@ func (t *SyncTask) scanLocalFile(ctx context.Context) {
localFileAppendList = append(localFileAppendList, localFile)
} else {
// update newest info into DB
if localFile.UpdateTimeUnix() > localFileInDb.UpdateTimeUnix() {
localFileInDb.Sha1Hash = ""
}
localFileInDb.UpdatedAt = localFile.UpdatedAt
localFileInDb.CreatedAt = localFile.CreatedAt
localFileInDb.FileSize = localFile.FileSize
+2
View File
@@ -112,6 +112,8 @@ func (m *SyncTaskManager) Start() (bool, error) {
}
// save config file
ioutil.WriteFile(m.configFilePath(), []byte(utils.ObjectToJsonStr(m.syncDriveConfig, true)), 0600)
// TODO: refresh panClient token to keep access token alive
return true, nil
}
+2 -2
View File
@@ -8,7 +8,7 @@ import (
)
func TestStart(t *testing.T) {
refreshToken := "927e2a3da30646d8a787e7f11c0fdf1f"
refreshToken := "ac1010f63...9585338b533bd4ab"
webToken, err := aliyunpan.GetAccessTokenFromRefreshToken(refreshToken)
if err != nil {
fmt.Println("get acccess token error")
@@ -26,6 +26,6 @@ func TestStart(t *testing.T) {
)
manager.Start()
time.Sleep(1 * time.Minute)
time.Sleep(5 * time.Minute)
manager.Stop()
}
+3 -3
View File
@@ -8,7 +8,7 @@ import (
)
func TestSyncTask(t *testing.T) {
refreshToken := "84c6499b7c9344109a2fe4f6733c3afd"
refreshToken := "84c6499b7...9a2fe4f6733c3afd"
webToken, err := aliyunpan.GetAccessTokenFromRefreshToken(refreshToken)
if err != nil {
fmt.Println("get acccess token error")
@@ -19,9 +19,9 @@ func TestSyncTask(t *testing.T) {
panClient := aliyunpan.NewPanClient(*webToken, aliyunpan.AppLoginToken{})
user, _ := panClient.GetUserInfo()
task := SyncTask{
Id: "840f28af799747848c0b3155e0bdfeab",
Id: "5b2d7c10-e927-4e72-8f9d-5abb3bb04814",
DriveId: user.FileDriveId,
LocalFolderPath: "D:\\smb\\feny\\goprojects\\dev\\upload",
LocalFolderPath: "D:\\smb\\feny\\goprojects\\dev\\NS游戏备份",
PanFolderPath: "/sync_drive",
Mode: "sync",
LastSyncTime: "",
+20
View File
@@ -15,6 +15,7 @@ package utils
import (
"compress/gzip"
"crypto/md5"
"flag"
"fmt"
jsoniter "github.com/json-iterator/go"
@@ -202,3 +203,22 @@ func UuidStr() string {
func NowTimeStr() string {
return time.Now().Format("2006-01-02 15:04:05")
}
// ParseTimeStr 反解析时间字符串
func ParseTimeStr(value string) time.Time {
cz := time.FixedZone("CST", 8*3600) // 东8区
if t, e := time.ParseInLocation("2006-01-02 15:04:05", value, cz); e == nil {
return t
}
return time.Now()
}
// Md5Str MD5哈希计算
func Md5Str(text string) string {
h := md5.New()
h.Write([]byte(text))
re := h.Sum(nil)
sb := &strings.Builder{}
fmt.Fprintf(sb, "%x\n", re)
return strings.ToLower(sb.String())
}
+4
View File
@@ -23,3 +23,7 @@ func TestConvertTime(t *testing.T) {
func TestUuidStr(t *testing.T) {
fmt.Println(UuidStr())
}
func TestMd5Str(t *testing.T) {
fmt.Println(Md5Str("123456"))
}
+22 -1
View File
@@ -1,6 +1,12 @@
package collection
import "sync"
import (
"sync"
)
type QueueItem interface {
HashCode() string
}
type Queue struct {
queueList []interface{}
@@ -21,6 +27,21 @@ func (q *Queue) Push(item interface{}) {
q.queueList = append(q.queueList, item)
}
func (q *Queue) PushUnique(item interface{}) {
q.mutex.Lock()
defer q.mutex.Unlock()
if q.queueList == nil {
q.queueList = []interface{}{}
} else {
for _, qItem := range q.queueList {
if item.(QueueItem).HashCode() == qItem.(QueueItem).HashCode() {
return
}
}
}
q.queueList = append(q.queueList, item)
}
func (q *Queue) Pop() interface{} {
q.mutex.Lock()
defer q.mutex.Unlock()