Private
Public Access
1
0
Files
u-desk/internal/filesystem/file_lock.go

221 lines
5.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package filesystem
import (
"fmt"
"os"
"syscall"
"time"
)
// Windows API 锁相关函数和常量
var (
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetLastError = modkernel32.NewProc("GetLastError")
procGetProcessId = modkernel32.NewProc("GetProcessId")
)
// FileLockChecker 文件锁检查器
type FileLockChecker struct{}
// NewFileLockChecker 创建文件锁检查器
func NewFileLockChecker() *FileLockChecker {
return &FileLockChecker{}
}
// IsFileLocked 检查文件是否被锁定(被其他进程占用)
// 返回: (是否锁定, 错误信息, 错误)
func (c *FileLockChecker) IsFileLocked(path string) (bool, string, error) {
// 尝试以独占写模式打开文件
file, err := os.OpenFile(path, os.O_RDWR|syscall.O_CREAT, 0666)
if err != nil {
// 检查是否是锁相关的错误
if isLockError(err) {
// 获取占用该文件的进程信息
processInfo, _ := c.getProcessInfo(path)
return true, processInfo, nil
}
return false, "", err
}
defer file.Close()
// 文件可以被打开,说明没有被锁定
return false, "", nil
}
// isLockError 判断错误是否为文件锁定错误
func isLockError(err error) bool {
if err == nil {
return false
}
// 检查错误类型
if os.IsPermission(err) {
return true
}
// Windows 特定错误检查
if pathErr, ok := err.(*os.PathError); ok {
errno, ok := pathErr.Err.(syscall.Errno)
if ok && (errno == ERROR_SHARING_VIOLATION ||
errno == ERROR_LOCK_VIOLATION ||
errno == syscall.ERROR_ACCESS_DENIED) {
return true
}
}
errStr := err.Error()
lockErrorStrings := []string{
"used by another process",
"being used",
"access is denied",
"could not be opened",
"being used by another process",
"process cannot access the file",
"used by another process",
}
for _, lockStr := range lockErrorStrings {
if contains(errStr, lockStr) {
return true
}
}
return false
}
// getProcessInfo 获取占用文件的进程信息Windows专用
func (c *FileLockChecker) getProcessInfo(path string) (string, error) {
// 在Windows上使用重启管理器API查询文件占用
// 这里提供简化版本
// 尝试打开文件获取更多信息
handle, err := syscall.Open(path, syscall.O_RDONLY, 0)
if err != nil {
// 如果打开失败,返回通用提示
return "", nil
}
defer syscall.Close(handle)
// 使用 Windows API 查询文件信息
// 注意:这需要更复杂的 Windows API 调用
// 这里返回简化的提示信息
return "文件正被其他程序使用", nil
}
// CheckFileWithRetry 带重试的文件锁检查
func (c *FileLockChecker) CheckFileWithRetry(path string, maxRetries int, retryInterval time.Duration) error {
for i := 0; i < maxRetries; i++ {
locked, processInfo, err := c.IsFileLocked(path)
if err != nil && !locked {
// 非锁相关的错误,直接返回
return err
}
if !locked {
// 文件未被锁定,可以操作
return nil
}
// 文件被锁定
if i < maxRetries-1 {
// 还有重试机会,等待后重试
time.Sleep(retryInterval)
continue
}
// 最后一次重试失败,返回错误
if processInfo != "" {
return fmt.Errorf("文件被占用: %s", processInfo)
}
return fmt.Errorf("文件被其他程序占用,请关闭相关程序后重试")
}
return fmt.Errorf("文件检查超时")
}
// SafeDeleteWithLockCheck 带锁检查的安全删除
func (c *FileLockChecker) SafeDeleteWithLockCheck(path string) error {
// 检查文件是否被锁定
locked, processInfo, err := c.IsFileLocked(path)
if err != nil && !locked {
return err
}
if locked {
if processInfo != "" {
return fmt.Errorf("无法删除文件:文件正被其他程序使用\n\n提示%s\n\n请关闭相关程序后重试", processInfo)
}
return fmt.Errorf("无法删除文件:文件正被其他程序使用\n\n请关闭相关程序后重试")
}
// 文件未被锁定,继续删除
return nil
}
// Windows 特定的结构体和常量
const (
ERROR_LOCK_VIOLATION = 33 // syscall.Errno(33)
ERROR_SHARING_VIOLATION = 32 // syscall.Errno(32)
)
// BY_HANDLE_FILE_INFORMATION 文件信息结构体
type BY_HANDLE_FILE_INFORMATION struct {
FileAttributes uint32
CreationTime syscall.Filetime
LastAccessTime syscall.Filetime
LastWriteTime syscall.Filetime
VolumeSerialNumber uint32
FileSizeHigh uint32
FileSizeLow uint32
NumberOfLinks uint32
FileIndexHigh uint32
FileIndexLow uint32
}
// contains 检查字符串是否包含子串(不区分大小写)
func contains(str, substr string) bool {
return len(str) >= len(substr) && (str == substr || len(substr) == 0 ||
(len(str) > 0 && len(substr) > 0 && containsIgnoreCase(str, substr)))
}
func containsIgnoreCase(str, substr string) bool {
// 简化版小写比较
for i := 0; i <= len(str)-len(substr); i++ {
match := true
for j := 0; j < len(substr); j++ {
c1 := str[i+j]
c2 := substr[j]
if c1 >= 'A' && c1 <= 'Z' {
c1 += 32
}
if c2 >= 'A' && c2 <= 'Z' {
c2 += 32
}
if c1 != c2 {
match = false
break
}
}
if match {
return true
}
}
return false
}
// 全局文件锁检查器
var globalLockChecker *FileLockChecker
// InitFileLockChecker 初始化全局文件锁检查器
func InitFileLockChecker() {
globalLockChecker = NewFileLockChecker()
}
// GetFileLockChecker 获取全局文件锁检查器
func GetFileLockChecker() *FileLockChecker {
if globalLockChecker == nil {
globalLockChecker = NewFileLockChecker()
}
return globalLockChecker
}