- 工具栏:面包屑与右侧组件像素级等高(:deep 34px)、合并重复search handler、统一分隔符样式、删除死代码 - 面板对齐:三面板header统一padding/font-size、文件列表分页固定底部(自定义紧凑)、表头默认隐藏、滚动条统一样式 - 预览区:始终显示空白预览面板、重启自动恢复上次打开文件 - 收藏夹:简化计数显示(共N项) - 远程连接:ConnectionIndicator自适应UI(无远程显示mini云图标)、ConnectionDialog支持编辑配置、transport抽象层(本地Wails/远程HTTP双模式)、agent后端模块
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|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
|
||
}
|
||
|
||
// 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
|
||
}
|
||
|