156 lines
3.7 KiB
Go
156 lines
3.7 KiB
Go
//go:build windows
|
|
// +build windows
|
|
|
|
package filesystem
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"syscall"
|
|
)
|
|
|
|
// 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, 0)
|
|
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
|
|
}
|
|
|
|
// 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)
|
|
)
|
|
|
|
// 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
|
|
}
|
|
|