make download/upload/sync command singleton #208 #207

This commit is contained in:
xiaoyaofenfen 2022-12-21 15:30:35 +08:00
parent 9f9fb6a320
commit bb8dad7262
10 changed files with 297 additions and 0 deletions

View File

@ -23,6 +23,7 @@ import (
"github.com/tickstep/aliyunpan/internal/log" "github.com/tickstep/aliyunpan/internal/log"
"github.com/tickstep/aliyunpan/internal/taskframework" "github.com/tickstep/aliyunpan/internal/taskframework"
"github.com/tickstep/aliyunpan/internal/utils" "github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/aliyunpan/library/filelocker"
"github.com/tickstep/aliyunpan/library/requester/transfer" "github.com/tickstep/aliyunpan/library/requester/transfer"
"github.com/tickstep/library-go/converter" "github.com/tickstep/library-go/converter"
"github.com/tickstep/library-go/logger" "github.com/tickstep/library-go/logger"
@ -143,7 +144,20 @@ func CmdDownload() cli.Command {
ExcludeNames: c.StringSlice("exn"), ExcludeNames: c.StringSlice("exn"),
} }
// 获取下载文件锁,保证下载操作单实例
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
}
RunDownload(c.Args(), do) RunDownload(c.Args(), do)
// 释放文件锁
if locker != nil {
filelocker.UnlockFile(locker)
}
return nil return nil
}, },
Flags: []cli.Flag{ Flags: []cli.Flag{

View File

@ -21,6 +21,7 @@ import (
"github.com/tickstep/aliyunpan/internal/log" "github.com/tickstep/aliyunpan/internal/log"
"github.com/tickstep/aliyunpan/internal/syncdrive" "github.com/tickstep/aliyunpan/internal/syncdrive"
"github.com/tickstep/aliyunpan/internal/utils" "github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/aliyunpan/library/filelocker"
"github.com/tickstep/library-go/converter" "github.com/tickstep/library-go/converter"
"github.com/tickstep/library-go/logger" "github.com/tickstep/library-go/logger"
"github.com/urfave/cli" "github.com/urfave/cli"
@ -218,7 +219,20 @@ priority - 优先级,只对双向同步备份模式有效。选项支持三种
task.UserId = activeUser.UserId task.UserId = activeUser.UserId
} }
// 获取同步文件锁,保证同步操作单实例
locker := filelocker.NewFileLocker(config.GetLockerDir() + "/aliyunpan-sync")
if e := filelocker.LockFile(locker, 0755, true, 5*time.Second); e != nil {
logger.Verboseln(e)
fmt.Println("本应用其他实例正在执行同步,请先停止或者等待其完成")
return nil
}
RunSync(task, dp, up, downloadBlockSize, uploadBlockSize, syncOpt, c.Int("ldt"), step) RunSync(task, dp, up, downloadBlockSize, uploadBlockSize, syncOpt, c.Int("ldt"), step)
// 释放文件锁
if locker != nil {
filelocker.UnlockFile(locker)
}
return nil return nil
}, },
Flags: []cli.Flag{ Flags: []cli.Flag{

View File

@ -20,6 +20,7 @@ import (
"github.com/tickstep/aliyunpan/internal/log" "github.com/tickstep/aliyunpan/internal/log"
"github.com/tickstep/aliyunpan/internal/plugins" "github.com/tickstep/aliyunpan/internal/plugins"
"github.com/tickstep/aliyunpan/internal/utils" "github.com/tickstep/aliyunpan/internal/utils"
"github.com/tickstep/aliyunpan/library/filelocker"
"github.com/tickstep/library-go/requester/rio/speeds" "github.com/tickstep/library-go/requester/rio/speeds"
"os" "os"
"path" "path"
@ -176,6 +177,14 @@ func CmdUpload() cli.Command {
} }
} }
// 获取上传文件锁,保证上传操作单实例
locker := filelocker.NewFileLocker(config.GetLockerDir() + "/aliyunpan-upload")
if e := filelocker.LockFile(locker, 0755, true, 5*time.Second); e != nil {
logger.Verboseln(e)
fmt.Println("本应用其他实例正在执行上传,请先停止或者等待其完成")
return nil
}
RunUpload(subArgs[:c.NArg()-1], subArgs[c.NArg()-1], &UploadOptions{ RunUpload(subArgs[:c.NArg()-1], subArgs[c.NArg()-1], &UploadOptions{
AllParallel: c.Int("p"), // 多文件上传的时候,允许同时并行上传的文件数量 AllParallel: c.Int("p"), // 多文件上传的时候,允许同时并行上传的文件数量
Parallel: 1, // 一个文件同时多少个线程并发上传的数量。阿里云盘只支持单线程按顺序进行文件part数据上传所以只能是1 Parallel: 1, // 一个文件同时多少个线程并发上传的数量。阿里云盘只支持单线程按顺序进行文件part数据上传所以只能是1
@ -188,6 +197,11 @@ func CmdUpload() cli.Command {
ExcludeNames: c.StringSlice("exn"), ExcludeNames: c.StringSlice("exn"),
BlockSize: int64(c.Int("bs") * 1024), BlockSize: int64(c.Int("bs") * 1024),
}) })
// 释放文件锁
if locker != nil {
filelocker.UnlockFile(locker)
}
return nil return nil
}, },
Flags: UploadFlags, Flags: UploadFlags,

View File

@ -351,6 +351,11 @@ func GetLogFilePath() string {
return dirPath + "/" + "aliyunpan_verbose.log" return dirPath + "/" + "aliyunpan_verbose.log"
} }
// GetLockerDir 获取文件锁路径
func GetLockerDir() string {
return strings.TrimSuffix(GetConfigDir(), "/")
}
func (c *PanConfig) ActiveUser() *PanUser { func (c *PanConfig) ActiveUser() *PanUser {
if c.activeUser == nil { if c.activeUser == nil {
if c.UserList == nil { if c.UserList == nil {

View File

@ -0,0 +1,7 @@
package filelocker
import "errors"
var (
ErrTimeout = errors.New("timeout")
)

View File

@ -0,0 +1,25 @@
package filelocker
import (
"os"
)
const (
lockExt = ".lock"
)
type (
FileLocker struct {
FilePath string
LockFilePath string
lockFile *os.File
}
)
func NewFileLocker(path string) *FileLocker {
return &FileLocker{
FilePath: path,
LockFilePath: path + lockExt,
lockFile: nil,
}
}

View File

@ -0,0 +1,25 @@
package filelocker
import (
"fmt"
"log"
"testing"
"time"
)
func TestFlocker(t *testing.T) {
// lock file first time - success
locker := NewFileLocker("D:\\smb\\feny\\goprojects\\dev\\aliyunpan")
e := LockFile(locker, 0755, true, 5*time.Second)
fmt.Println(e)
// lock file again - fail
//time.Sleep(5 * time.Second)
//e = flock(locker, 0755, true, 5*time.Second)
//fmt.Println(e)
// Unlock the file.
if err := UnlockFile(locker); err != nil {
log.Printf("funlock error: %s", err)
}
}

View File

@ -0,0 +1,57 @@
package filelocker
import (
"os"
"syscall"
"time"
)
// LockFile acquires an advisory lock on a file descriptor.
func LockFile(locker *FileLocker, mode os.FileMode, exclusive bool, timeout time.Duration) error {
f, err := os.OpenFile(locker.LockFilePath, os.O_CREATE, mode)
if err != nil {
return err
}
locker.lockFile = f
var t time.Time
for {
// If we're beyond our timeout then return an error.
// This can only occur after we've attempted a flock once.
if t.IsZero() {
t = time.Now()
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Pid = 0
lock.Whence = 0
lock.Pid = 0
if exclusive {
lock.Type = syscall.F_WRLCK
} else {
lock.Type = syscall.F_RDLCK
}
err := syscall.FcntlFlock(locker.lockFile.Fd(), syscall.F_SETLK, &lock)
if err == nil {
return nil
} else if err != syscall.EAGAIN {
return err
}
// Wait for a bit and try again.
time.Sleep(50 * time.Millisecond)
}
}
// UnlockFile releases an advisory lock on a file descriptor.
func UnlockFile(locker *FileLocker) error {
var lock syscall.Flock_t
lock.Start = 0
lock.Len = 0
lock.Type = syscall.F_UNLCK
lock.Whence = 0
return syscall.FcntlFlock(uintptr(locker.lockFile.Fd()), syscall.F_SETLK, &lock)
}

View File

@ -0,0 +1,50 @@
//go:build !windows && !plan9 && !solaris
// +build !windows,!plan9,!solaris
package filelocker
import (
"os"
"syscall"
"time"
)
// LockFile acquires an advisory lock on a file descriptor.
func LockFile(locker *FileLocker, mode os.FileMode, exclusive bool, timeout time.Duration) error {
f, err := os.OpenFile(locker.LockFilePath, os.O_CREATE, mode)
if err != nil {
return err
}
locker.lockFile = f
var t time.Time
for {
// If we're beyond our timeout then return an error.
// This can only occur after we've attempted a flock once.
if t.IsZero() {
t = time.Now()
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
flag := syscall.LOCK_SH
if exclusive {
flag = syscall.LOCK_EX
}
// Otherwise attempt to obtain an exclusive lock.
err := syscall.Flock(int(locker.lockFile.Fd()), flag|syscall.LOCK_NB)
if err == nil {
return nil
} else if err != syscall.EWOULDBLOCK {
return err
}
// Wait for a bit and try again.
time.Sleep(50 * time.Millisecond)
}
}
// UnlockFile releases an advisory lock on a file descriptor.
func UnlockFile(locker *FileLocker) error {
return syscall.Flock(int(locker.lockFile.Fd()), syscall.LOCK_UN)
}

View File

@ -0,0 +1,86 @@
package filelocker
import (
"os"
"syscall"
"time"
"unsafe"
)
// LockFileEx code derived from golang build filemutex_windows.go @ v1.5.1
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procLockFileEx = modkernel32.NewProc("LockFileEx")
procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
)
const (
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
flagLockExclusive = 2
flagLockFailImmediately = 1
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
errLockViolation syscall.Errno = 0x21
)
func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
if r == 0 {
return err
}
return nil
}
func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0)
if r == 0 {
return err
}
return nil
}
// LockFile acquires an advisory lock on a file descriptor.
func LockFile(locker *FileLocker, mode os.FileMode, exclusive bool, timeout time.Duration) error {
// Create a separate lock file on windows because a process
// cannot share an exclusive lock on the same file. This is
// needed during Tx.WriteTo().
f, err := os.OpenFile(locker.LockFilePath, os.O_CREATE, mode)
if err != nil {
return err
}
locker.lockFile = f
var t time.Time
for {
// If we're beyond our timeout then return an error.
// This can only occur after we've attempted a flock once.
if t.IsZero() {
t = time.Now()
} else if timeout > 0 && time.Since(t) > timeout {
return ErrTimeout
}
var flag uint32 = flagLockFailImmediately
if exclusive {
flag |= flagLockExclusive
}
err := lockFileEx(syscall.Handle(locker.lockFile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
if err == nil {
return nil
} else if err != errLockViolation {
return err
}
// Wait for a bit and try again.
time.Sleep(50 * time.Millisecond)
}
}
// UnlockFile releases an advisory lock on a file descriptor.
func UnlockFile(locker *FileLocker) error {
err := unlockFileEx(syscall.Handle(locker.lockFile.Fd()), 0, 1, 0, &syscall.Overlapped{})
locker.lockFile.Close()
os.Remove(locker.LockFilePath)
return err
}