重构:文件系统模块化架构,优化应用启动流程
This commit is contained in:
220
internal/filesystem/file_lock.go
Normal file
220
internal/filesystem/file_lock.go
Normal file
@@ -0,0 +1,220 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user