mirror of
https://github.com/tickstep/aliyunpan.git
synced 2025-01-23 14:32:14 +08:00
parent
9f9fb6a320
commit
bb8dad7262
@ -23,6 +23,7 @@ import (
|
||||
"github.com/tickstep/aliyunpan/internal/log"
|
||||
"github.com/tickstep/aliyunpan/internal/taskframework"
|
||||
"github.com/tickstep/aliyunpan/internal/utils"
|
||||
"github.com/tickstep/aliyunpan/library/filelocker"
|
||||
"github.com/tickstep/aliyunpan/library/requester/transfer"
|
||||
"github.com/tickstep/library-go/converter"
|
||||
"github.com/tickstep/library-go/logger"
|
||||
@ -143,7 +144,20 @@ func CmdDownload() cli.Command {
|
||||
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)
|
||||
|
||||
// 释放文件锁
|
||||
if locker != nil {
|
||||
filelocker.UnlockFile(locker)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
"github.com/tickstep/aliyunpan/internal/log"
|
||||
"github.com/tickstep/aliyunpan/internal/syncdrive"
|
||||
"github.com/tickstep/aliyunpan/internal/utils"
|
||||
"github.com/tickstep/aliyunpan/library/filelocker"
|
||||
"github.com/tickstep/library-go/converter"
|
||||
"github.com/tickstep/library-go/logger"
|
||||
"github.com/urfave/cli"
|
||||
@ -218,7 +219,20 @@ priority - 优先级,只对双向同步备份模式有效。选项支持三种
|
||||
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)
|
||||
|
||||
// 释放文件锁
|
||||
if locker != nil {
|
||||
filelocker.UnlockFile(locker)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Flags: []cli.Flag{
|
||||
|
@ -20,6 +20,7 @@ import (
|
||||
"github.com/tickstep/aliyunpan/internal/log"
|
||||
"github.com/tickstep/aliyunpan/internal/plugins"
|
||||
"github.com/tickstep/aliyunpan/internal/utils"
|
||||
"github.com/tickstep/aliyunpan/library/filelocker"
|
||||
"github.com/tickstep/library-go/requester/rio/speeds"
|
||||
"os"
|
||||
"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{
|
||||
AllParallel: c.Int("p"), // 多文件上传的时候,允许同时并行上传的文件数量
|
||||
Parallel: 1, // 一个文件同时多少个线程并发上传的数量。阿里云盘只支持单线程按顺序进行文件part数据上传,所以只能是1
|
||||
@ -188,6 +197,11 @@ func CmdUpload() cli.Command {
|
||||
ExcludeNames: c.StringSlice("exn"),
|
||||
BlockSize: int64(c.Int("bs") * 1024),
|
||||
})
|
||||
|
||||
// 释放文件锁
|
||||
if locker != nil {
|
||||
filelocker.UnlockFile(locker)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
Flags: UploadFlags,
|
||||
|
@ -351,6 +351,11 @@ func GetLogFilePath() string {
|
||||
return dirPath + "/" + "aliyunpan_verbose.log"
|
||||
}
|
||||
|
||||
// GetLockerDir 获取文件锁路径
|
||||
func GetLockerDir() string {
|
||||
return strings.TrimSuffix(GetConfigDir(), "/")
|
||||
}
|
||||
|
||||
func (c *PanConfig) ActiveUser() *PanUser {
|
||||
if c.activeUser == nil {
|
||||
if c.UserList == nil {
|
||||
|
7
library/filelocker/errors.go
Normal file
7
library/filelocker/errors.go
Normal file
@ -0,0 +1,7 @@
|
||||
package filelocker
|
||||
|
||||
import "errors"
|
||||
|
||||
var (
|
||||
ErrTimeout = errors.New("timeout")
|
||||
)
|
25
library/filelocker/file_locker.go
Normal file
25
library/filelocker/file_locker.go
Normal 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,
|
||||
}
|
||||
}
|
25
library/filelocker/file_locker_test.go
Normal file
25
library/filelocker/file_locker_test.go
Normal 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)
|
||||
}
|
||||
}
|
57
library/filelocker/locker_solaris.go
Normal file
57
library/filelocker/locker_solaris.go
Normal 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)
|
||||
}
|
50
library/filelocker/locker_unix.go
Normal file
50
library/filelocker/locker_unix.go
Normal 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)
|
||||
}
|
86
library/filelocker/locker_windows.go
Normal file
86
library/filelocker/locker_windows.go
Normal 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
|
||||
}
|
Loading…
Reference in New Issue
Block a user