重构: App拆分10域文件+并发安全+结构化日志+依赖升级
This commit is contained in:
+55
-2
@@ -2,9 +2,62 @@
|
|||||||
|
|
||||||
> 本文档记录所有技术细节,包括代码重构、构建优化等内部改动
|
> 本文档记录所有技术细节,包括代码重构、构建优化等内部改动
|
||||||
|
|
||||||
## [0.5.0] - 2026-05-01 (fs-only-v3)
|
## [0.4.1] - 2026-05-01 ~ 05-16 (fs-only-v3)
|
||||||
|
|
||||||
### Wails v3 迁移 🏗️
|
---
|
||||||
|
|
||||||
|
### 2026-05-16 增量更新
|
||||||
|
|
||||||
|
#### App 结构体拆分 🏗️
|
||||||
|
|
||||||
|
app.go 从 1281 行拆为 10 个域文件:
|
||||||
|
|
||||||
|
| 文件 | 方法数 | 职责 |
|
||||||
|
|------|--------|------|
|
||||||
|
| `app.go` | 5 | 结构体定义、生命周期、请求类型 |
|
||||||
|
| `app_config.go` | 12 | 配置管理、BGM、Profile CRUD |
|
||||||
|
| `app_filesystem.go` | 23 | 文件操作、回收站、文件监听 |
|
||||||
|
| `app_sftp.go` | 17 | SFTP 连接管理 |
|
||||||
|
| `app_oss.go` | 17 | OSS 对象存储 |
|
||||||
|
| `app_window.go` | 8 | 窗口控制 |
|
||||||
|
| `app_system.go` | 7 | 系统信息 |
|
||||||
|
| `app_update.go` | 11 | 版本更新管理 |
|
||||||
|
| `app_pdf.go` | 2 | PDF 导出 |
|
||||||
|
| `app_windows.go` | 4 | Windows 平台专用(`//go:build windows`) |
|
||||||
|
|
||||||
|
#### 结构化日志 📝
|
||||||
|
|
||||||
|
`fmt.Print*` → `log/slog`(45 处):app.go、filesystem/service.go、storage/download_cache.go
|
||||||
|
|
||||||
|
#### Bug 修复 🐛
|
||||||
|
|
||||||
|
- **预览状态级联失败**: `clearContent()` 未重置 `isBinaryFile`,模板 `v-if` 链被阻断 → 加 `isBinaryFile.value = false`
|
||||||
|
- **Tab 切换状态不同步**: `activateTab` 未更新 `filePath` → 加 `getParentPath` + `loadDirectory`
|
||||||
|
- **AsyncCodeEditor 永久死亡**: `defineAsyncComponent` 无 `onError` → 添加重试(限 2 次)
|
||||||
|
- **FileLock 误创建文件**: `O_RDWR|O_CREAT` → `O_RDWR`,`mode 0666` → `0`
|
||||||
|
- **OSS 目录重命名**: `c.Delete()` 返回值未检查,目录标记删除添加容错
|
||||||
|
|
||||||
|
#### 并发安全 🔒
|
||||||
|
|
||||||
|
- **RecycleBin**: 所有公开方法添加 `sync.Mutex`,`ListEntries()` 返回深拷贝副本
|
||||||
|
|
||||||
|
#### 缓存优化 📦
|
||||||
|
|
||||||
|
- **contentDetectCache**: 添加 200 条上限,超限全量清空
|
||||||
|
|
||||||
|
#### 依赖升级 ⬆️
|
||||||
|
|
||||||
|
Wails alpha.80→alpha.92, echo v4.15.1→v4.15.2, go-sqlite v1.22, modernc/sqlite v1.50, go-git v5.19
|
||||||
|
|
||||||
|
#### 代码审查 (15/17 修复)
|
||||||
|
|
||||||
|
🔴 构建标签 · 死参数 · 重复逻辑 · 并发锁 | 🟡 错误处理 · 方法归属 · 日志 | ⚪ 注释 · 去重
|
||||||
|
|
||||||
|
**暂不处理**: ⚪⑬ fallback 路径 · ⚪⑯ ensure 风格 · ⚪⑰ ensure 模式
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 2026-05-01 Wails v3 迁移
|
||||||
|
|
||||||
#### 框架升级
|
#### 框架升级
|
||||||
- **Wails v2.12 → v3 alpha.80**: 全面迁移至 Wails v3 架构
|
- **Wails v2.12 → v3 alpha.80**: 全面迁移至 Wails v3 架构
|
||||||
|
|||||||
+31
-29
@@ -1,40 +1,42 @@
|
|||||||
# 更新日志
|
# 更新日志
|
||||||
|
|
||||||
## [0.5.0] - 2026-05-05 (fs-only-v3)
|
## [0.4.1] - 2026-05-16 (fs-only-v3)
|
||||||
|
|
||||||
### 新增 ✨
|
### 新增 ✨
|
||||||
- **云 OSS 存储**: 七牛云/阿里云双厂商支持,AK/SK 认证连接
|
- **云 OSS 存储**: 七牛云/阿里云双厂商,AK/SK 认证,桶级客户端懒创建+缓存
|
||||||
- **多桶导航**: 根目录自动列出所有桶,点进桶浏览文件,桶级客户端懒创建+缓存
|
|
||||||
- **OSS 全功能 CRUD**: 列目录/读文件/写文件/创建/删除/重命名/预签名 URL
|
- **OSS 全功能 CRUD**: 列目录/读文件/写文件/创建/删除/重命名/预签名 URL
|
||||||
- **GBK 编码自动转换**: 文件预览智能检测编码(UTF-8/GBK),解决 LRC 等中文文件乱码
|
- **OSS 区域自动嗅探**: 连接时自动探测桶区域,无需手动填写
|
||||||
- **桶图标 🪣**: OSS 桶与普通文件夹图标区分
|
- **七牛云桶管理**: 查询桶域名、真实区域、设置访问权限(公开/私有)
|
||||||
- **连接对话框 OSS 分类**: 「云OSS」父分类 + 厂商子选择(七牛云/阿里云)
|
- **HTML 网站预览**: OSS/SFTP 上的 HTML 文件自动下载关联资源(JS/CSS/图片),逐级嗅探网站根目录,完整预览静态站点
|
||||||
- **Sidebar 折叠架构**: 收藏夹和帮助文档独立区块,各自支持折叠/展开
|
- **BGM 音乐播放器**: 编辑器底部控制条,支持播放/暂停/上下首/进度拖拽/四种播放模式(单曲/顺序/随机/循环),播放列表持久化,失败自动跳过
|
||||||
- **帮助文档区块**: 静态快捷键参考面板,默认展示
|
- **LRC 歌词同步显示**: 播放时自动查找同名 .lrc 文件,实时高亮滚动歌词,点击歌词行跳转
|
||||||
- **收藏夹内部滚动**: 收藏内容多时列表区域独立滚动,帮助区块固定底部不溢出
|
- **全局热键**: Ctrl+Shift+B 系统级快捷键,应用最小化时快速唤起
|
||||||
|
- **文件变更监听**: 文件外部被修改时自动刷新,避免编辑冲突
|
||||||
|
- **下载缓存**: 远程文件预览缓存到本地 SQLite(24 小时过期),重启后仍有效
|
||||||
|
- **远程服务器监控**: 侧边栏显示磁盘/CPU/内存使用率,超 90% 红色警告,15 秒自动刷新
|
||||||
|
- **连接指示器快速导航**: 悬停弹出根目录子文件夹列表,可递归展开,快速跳转
|
||||||
|
- **侧边栏自定义**: 设置面板支持服务器/收藏夹/帮助区块拖拽排序和显隐控制
|
||||||
|
|
||||||
### 重构 🔧
|
### 优化 🚀
|
||||||
- **Wails v3 迁移**: 从 Wails v2 升级至 v3 alpha.80,全面重构项目架构
|
- **侧边栏折叠架构**: 服务器、收藏夹、帮助独立可折叠区块,收藏夹内部独立滚动
|
||||||
- **前端目录规范化**: `web/` → `frontend/`,对齐 Wails v3 标准目录结构
|
- **七牛云目录层级**: ListFiles 支持 delimiter,正确展示文件夹层级而非平铺
|
||||||
- **跨平台构建配置**: 新增 Android/iOS/Linux/macOS/Docker 构建模板(Taskfile.yml)
|
- **七牛云签名 URL**: 修正私有空间下载/预览签名格式
|
||||||
- **v3 Bindings**: 自动生成的 TypeScript 绑定替代手动维护的 wailsjs
|
- **七牛云下载域名**: 智能解析策略:临时域名→自定义域名→CDN 兜底,失败自动重试
|
||||||
|
- **GBK 编码自动转换**: 文件预览智能检测编码(UTF-8/GBK),中文文件不再乱码
|
||||||
|
- **SFTP 功能对齐**: 补全粘贴图片、网站预览、系统监控、常用路径等能力
|
||||||
|
- **预览缓存迁移**: 前端 LRU → 后端 SQLite,重启后缓存仍有效
|
||||||
|
- **Wails v3 迁移**: 框架升级至 v3 alpha.92,核心依赖全面更新
|
||||||
|
|
||||||
### 修复 🐛
|
### 修复 🐛
|
||||||
- **MP3 误报加载失败**: 音频 @canplay 清除错误状态 + previewUrl watcher 重置
|
- **二进制文件后预览失效**: 打开二进制文件后图片/视频无法预览
|
||||||
- **启动路径恢复错误**: 本地模式跳过 Linux/OSS 路径残留,避免 `open /bucket` 报错
|
- **跨服务器 Tab 切换**: 面包屑和文件列表未同步更新
|
||||||
- **阿里云签名修复**: ListFiles 签名不含 list 查询参数(prefix/delimiter/marker/max-keys 非子资源)
|
- **代码编辑器加载失败**: 异步组件失败后自动重试
|
||||||
- **阿里云 XML 解析**: `<Contents>` 直接映射文件字段,修正 `Contents.Object` 嵌套错误
|
- **OSS 收藏打开失败**: provider 字段映射修正
|
||||||
- **阿里云 LastModified**: 宽容时间解析(4 种格式兼容)
|
- **阿里云 OSS 签名/解析**: 文件列表获取失败、时间格式不兼容等问题
|
||||||
- **临时文件白名单放行**: OSS/SFTP 预览文件绕过文件类型限制
|
- **启动路径错误**: 本地模式跳过无效的 OSS 路径残留
|
||||||
- **custom.js 404**: AssetOptions Middleware 拦截返回空响应,消除控制台报错
|
- **OSS 目录重命名**: 单文件失败不中断整体操作
|
||||||
- **longPressTimer TypeError**: `const` → `let` 修复重复赋值错误
|
- **文件锁定检查**: 不再误创建空文件
|
||||||
- **Arco Tabs padding**: 覆盖默认 16px padding-top
|
- **长期运行内存增长**: 文件类型检测缓存添加上限
|
||||||
- **DevTools 可用性**: production 构建带 devtools tag + 延迟 OpenDevTools() 调用
|
|
||||||
|
|
||||||
### 变更说明
|
|
||||||
- 分支: `feature/fs-only` → `fs-only-v3`
|
|
||||||
- 入口: main.go 新增 Middleware 中间件模式
|
|
||||||
- build/config.yml executes 流水线对齐官方模板(once → background → blocking → primary)
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4,52 +4,57 @@
|
|||||||
|
|
||||||
## 功能
|
## 功能
|
||||||
|
|
||||||
- 文件浏览 / 编辑 / 预览(文本、Markdown、图片、Office、PDF)
|
- **本地文件管理**: 浏览、编辑、重命名、删除、回收站
|
||||||
- 收藏夹管理(折叠/展开、拖拽排序、置顶)
|
- **SFTP 远程连接**: SSH 密钥/密码认证,文件上传下载
|
||||||
- Markdown 编辑器(实时预览、语法高亮、Mermaid 图表)
|
- **云 OSS 存储**: 七牛云/阿里云,多桶浏览
|
||||||
- 远程文件服务器连接
|
- **多格式预览**: 图片/视频/音频/PDF/Office/HTML
|
||||||
- 主题切换(亮色/暗色)
|
- **Markdown 编辑器**: 实时预览、Mermaid 图表、语法高亮
|
||||||
- 版本更新检查
|
- **收藏夹管理**: 折叠/展开、拖拽排序、置顶
|
||||||
|
- **BGM 播放**: 音频播放列表、全局热键 Ctrl+Shift+B
|
||||||
|
- **PDF 导出**: Markdown/HTML 导出为 PDF
|
||||||
|
- **版本更新检查**: 自动检测新版本
|
||||||
|
- **主题切换**: 亮色/暗色
|
||||||
|
|
||||||
## 技术栈
|
## 技术栈
|
||||||
|
|
||||||
| 层 | 技术 |
|
| 层 | 技术 |
|
||||||
|---|------|
|
|---|------|
|
||||||
| 桌面框架 | Wails v3 (alpha.80) |
|
| 桌面框架 | Wails v3 (alpha.82) |
|
||||||
| 后端 | Go 1.22+ |
|
| 后端 | Go 1.24 |
|
||||||
| 前端 | Vue 3 + TypeScript |
|
| 前端 | Vue 3 + TypeScript |
|
||||||
| UI 组件库 | Arco Design Vue |
|
| UI 组件库 | Arco Design Vue |
|
||||||
| 编辑器 | CodeMirror 6 |
|
| 编辑器 | CodeMirror 6 |
|
||||||
| 构建 | Vite 7 + Taskfile |
|
| 构建 | Vite 7 + Taskfile |
|
||||||
|
| 数据库 | SQLite (配置/历史) |
|
||||||
|
| 文件服务 | 内置 HTTP 文件服务器 (端口 2652) |
|
||||||
|
|
||||||
## 项目结构
|
## 项目结构
|
||||||
|
|
||||||
```
|
```
|
||||||
├── main.go # 入口:窗口配置、中间件、DevTools
|
├── main.go # 入口:窗口配置、中间件
|
||||||
├── app.go # 应用逻辑:文件系统、更新检查等
|
├── app.go # App 结构体定义、生命周期、请求类型
|
||||||
├── internal/ # 内部模块
|
├── app_*.go # 按功能域拆分(config/sftp/oss/...)
|
||||||
│ ├── filesystem/ # 文件操作、锁、预览服务
|
├── internal/
|
||||||
│ └── api/ # API 处理器
|
│ ├── api/ # Wails 绑定 API 层
|
||||||
├── frontend/ # 前端代码 (Vue 3)
|
│ ├── filesystem/ # 文件系统服务 + HTTP 文件服务器
|
||||||
│ ├── src/
|
│ ├── ossdrv/ # OSS 驱动(七牛/阿里云)
|
||||||
│ │ ├── components/FileSystem/ # 文件管理主组件
|
│ ├── sftp/ # SFTP 客户端
|
||||||
│ │ ├── stores/ # Pinia 状态管理
|
│ ├── storage/ # SQLite 存储
|
||||||
│ │ ├── api/ # 后端调用封装
|
│ └── service/ # 业务服务
|
||||||
│ │ └── utils/ # 工具函数
|
├── frontend/
|
||||||
│ └── vite.config.js
|
│ └── src/
|
||||||
|
│ ├── components/FileSystem/ # 文件管理主组件
|
||||||
|
│ ├── api/ # 后端调用封装
|
||||||
|
│ ├── composables/ # Vue Composables
|
||||||
|
│ └── stores/ # Pinia 状态管理
|
||||||
├── build/ # 构建配置(跨平台)
|
├── build/ # 构建配置(跨平台)
|
||||||
│ ├── config.yml # Wails 项目配置
|
|
||||||
│ └── windows/ # Windows 构建脚本
|
|
||||||
└── configs/ # 运行时配置
|
└── configs/ # 运行时配置
|
||||||
```
|
```
|
||||||
|
|
||||||
## 开发
|
## 开发
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 安装依赖
|
# 启动开发模式
|
||||||
wails3 task common:install:frontend:deps
|
|
||||||
|
|
||||||
# 启动开发模式(热重载)
|
|
||||||
wails3 dev
|
wails3 dev
|
||||||
|
|
||||||
# 生产构建
|
# 生产构建
|
||||||
@@ -59,16 +64,16 @@ wails3 build
|
|||||||
### 构建标签
|
### 构建标签
|
||||||
|
|
||||||
- `production` — 生产模式,使用嵌入的 frontend dist
|
- `production` — 生产模式,使用嵌入的 frontend dist
|
||||||
- `devtools` — 在生产构建中保留 DevTools(F12)
|
- `devtools` — 在生产构建中保留 DevTools
|
||||||
|
|
||||||
## 快捷键
|
## 快捷键
|
||||||
|
|
||||||
| 快捷键 | 功能 |
|
| 快捷键 | 功能 |
|
||||||
|--------|------|
|
|--------|------|
|
||||||
| Ctrl+B | 切换侧边栏 |
|
| Ctrl+B | 切换侧边栏 |
|
||||||
| Ctrl+H | 历史记录 |
|
| Ctrl+Shift+B | BGM 播放栏 |
|
||||||
| Ctrl+F | 聚焦搜索 |
|
| F11 | 全屏预览 |
|
||||||
|
|
||||||
## 版本历史
|
## 版本历史
|
||||||
|
|
||||||
详见 [CHANGELOG.md](./CHANGELOG.md)
|
详见 [CHANGELOG.md](./CHANGELOG.md) | 内部技术细节见 [CHANGELOG.internal.md](./CHANGELOG.internal.md)
|
||||||
|
|||||||
+215
@@ -0,0 +1,215 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"u-desk/internal/api"
|
||||||
|
"u-desk/internal/common"
|
||||||
|
"u-desk/internal/service"
|
||||||
|
"u-desk/internal/storage"
|
||||||
|
"u-desk/internal/storage/models"
|
||||||
|
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ========== 应用配置接口 ==========
|
||||||
|
|
||||||
|
// GetAppConfig 获取应用配置
|
||||||
|
func (a *App) GetAppConfig() (map[string]interface{}, error) {
|
||||||
|
if a.configAPI == nil {
|
||||||
|
return nil, fmt.Errorf("配置服务正在初始化中")
|
||||||
|
}
|
||||||
|
return a.configAPI.GetAppConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveAppConfig 保存应用配置
|
||||||
|
func (a *App) SaveAppConfig(req SaveAppConfigRequest) (map[string]interface{}, error) {
|
||||||
|
if a.configAPI == nil {
|
||||||
|
return nil, fmt.Errorf("配置服务正在初始化中")
|
||||||
|
}
|
||||||
|
|
||||||
|
oldConfig, _ := a.configAPI.GetAppConfig()
|
||||||
|
var oldVisibleTabs []string
|
||||||
|
if success, ok := oldConfig["success"].(bool); ok && success {
|
||||||
|
if data, ok := oldConfig["data"].(map[string]interface{}); ok {
|
||||||
|
if vtInterface, ok := data["visibleTabs"].([]interface{}); ok {
|
||||||
|
oldVisibleTabs = common.InterfaceSliceToStringSlice(vtInterface)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
apiReq := api.SaveAppConfigRequest{
|
||||||
|
Tabs: req.Tabs,
|
||||||
|
VisibleTabs: req.VisibleTabs,
|
||||||
|
DefaultTab: req.DefaultTab,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := a.configAPI.SaveAppConfig(apiReq)
|
||||||
|
if err != nil {
|
||||||
|
return result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if success, ok := result["success"].(bool); ok && success {
|
||||||
|
a.handleNewlyEnabledModules(oldVisibleTabs, req.VisibleTabs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) LoadConnectionProfiles() ([]map[string]interface{}, error) {
|
||||||
|
list, err := a.ensureProfileSvc().ListProfiles()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
var result []map[string]interface{}
|
||||||
|
data, _ := json.Marshal(list)
|
||||||
|
if err := json.Unmarshal(data, &result); err != nil {
|
||||||
|
return nil, fmt.Errorf("解析连接配置失败: %w", err)
|
||||||
|
}
|
||||||
|
return result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) SaveConnectionProfile(req SaveProfileRequest) (map[string]interface{}, error) {
|
||||||
|
p := &models.ConnectionProfile{
|
||||||
|
Name: req.Name, Host: req.Host, Port: req.Port,
|
||||||
|
Username: req.Username, Password: req.Password,
|
||||||
|
KeyPath: req.KeyPath, Type: req.Type, Provider: req.Provider, Token: req.Token,
|
||||||
|
AccessKey: req.AccessKey, SecretKey: req.SecretKey,
|
||||||
|
Bucket: req.Bucket, Region: req.Region, Endpoint: req.Endpoint,
|
||||||
|
}
|
||||||
|
if req.LastConnected != nil {
|
||||||
|
t := time.Unix(*req.LastConnected, 0)
|
||||||
|
p.LastConnected = &t
|
||||||
|
}
|
||||||
|
if req.ID != nil {
|
||||||
|
p.ID = *req.ID
|
||||||
|
}
|
||||||
|
if err := a.ensureProfileSvc().SaveProfile(p); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return map[string]interface{}{"id": float64(p.ID), "success": true}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) DeleteConnectionProfile(id uint) error {
|
||||||
|
return a.ensureProfileSvc().DeleteProfile(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// BgmGetPlaylist 获取播放列表
|
||||||
|
func (a *App) BgmGetPlaylist() ([]BgmPlaylistItem, error) {
|
||||||
|
db := storage.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return nil, fmt.Errorf("数据库未初始化")
|
||||||
|
}
|
||||||
|
var rows []models.BgmPlaylist
|
||||||
|
if err := db.Order("sort ASC").Find(&rows).Error; err != nil {
|
||||||
|
return nil, fmt.Errorf("查询播放列表失败: %w", err)
|
||||||
|
}
|
||||||
|
items := make([]BgmPlaylistItem, len(rows))
|
||||||
|
for i, r := range rows {
|
||||||
|
items[i] = BgmPlaylistItem{Name: r.Name, Path: r.Path, ProfileID: r.ProfileID}
|
||||||
|
}
|
||||||
|
return items, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// BgmSavePlaylist 全量保存播放列表(前端调用时传完整列表)
|
||||||
|
func (a *App) BgmSavePlaylist(items []BgmPlaylistItem) error {
|
||||||
|
db := storage.GetDB()
|
||||||
|
if db == nil {
|
||||||
|
return fmt.Errorf("数据库未初始化")
|
||||||
|
}
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
if err := tx.Exec("DELETE FROM bgm_playlist").Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sort := uint(0)
|
||||||
|
for _, item := range items {
|
||||||
|
if item.Name == "" || item.Path == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if err := tx.Create(&models.BgmPlaylist{Name: item.Name, Path: item.Path, ProfileID: item.ProfileID, Sort: sort}).Error; err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sort++
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) ensureProfileSvc() *service.ProfileService {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
if a.profileSvc == nil {
|
||||||
|
a.profileSvc = service.NewProfileService()
|
||||||
|
}
|
||||||
|
return a.profileSvc
|
||||||
|
}
|
||||||
|
|
||||||
|
// getVisibleTabs 获取配置中的可见 Tabs
|
||||||
|
func (a *App) getVisibleTabs() []string {
|
||||||
|
config, err := a.configAPI.GetAppConfig()
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("读取配置失败,使用默认配置", "error", err)
|
||||||
|
return common.DefaultVisibleTabs
|
||||||
|
}
|
||||||
|
|
||||||
|
success, ok := config["success"].(bool)
|
||||||
|
if !ok || !success {
|
||||||
|
slog.Warn("配置读取失败,使用默认配置")
|
||||||
|
return common.DefaultVisibleTabs
|
||||||
|
}
|
||||||
|
|
||||||
|
data, ok := config["data"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return common.DefaultVisibleTabs
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleTabsInterface, ok := data["visibleTabs"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return common.DefaultVisibleTabs
|
||||||
|
}
|
||||||
|
|
||||||
|
visibleTabs := common.InterfaceSliceToStringSlice(visibleTabsInterface)
|
||||||
|
|
||||||
|
if len(visibleTabs) == 0 {
|
||||||
|
return common.DefaultVisibleTabs
|
||||||
|
}
|
||||||
|
|
||||||
|
return visibleTabs
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleNewlyEnabledModules 处理新启用的模块
|
||||||
|
func (a *App) handleNewlyEnabledModules(oldTabs, newTabs []string) {
|
||||||
|
newlyEnabled := common.Difference(newTabs, oldTabs)
|
||||||
|
|
||||||
|
if len(newlyEnabled) == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("检测到新启用的模块", "modules", newlyEnabled)
|
||||||
|
|
||||||
|
for _, tab := range newlyEnabled {
|
||||||
|
switch tab {
|
||||||
|
case common.TabFileSystem:
|
||||||
|
a.initFilesystemModule()
|
||||||
|
case common.TabDevice:
|
||||||
|
slog.Info("设备测试模块已启用")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initModulesByConfig 根据配置初始化模块
|
||||||
|
func (a *App) initModulesByConfig(visibleTabs []string) error {
|
||||||
|
if common.Contains(visibleTabs, common.TabFileSystem) {
|
||||||
|
a.initFilesystemModule()
|
||||||
|
if a.filesystem == nil {
|
||||||
|
return fmt.Errorf("文件系统服务初始化失败")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
slog.Info("跳过文件系统模块(未启用)")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,174 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
|
||||||
|
"u-desk/internal/filesystem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ReadFile 读取文件
|
||||||
|
func (a *App) ReadFile(path string) (string, error) {
|
||||||
|
return a.filesystem.ReadFile(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WatchFile 开始监听指定文件的变化,变化时发送 file-changed 事件
|
||||||
|
func (a *App) WatchFile(path string) error {
|
||||||
|
if a.fileWatcher == nil {
|
||||||
|
return fmt.Errorf("文件监听器未初始化")
|
||||||
|
}
|
||||||
|
return a.fileWatcher.WatchFile(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnwatchFile 停止监听文件变化
|
||||||
|
func (a *App) UnwatchFile() {
|
||||||
|
if a.fileWatcher != nil {
|
||||||
|
a.fileWatcher.UnwatchFile()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteFile 写入文件
|
||||||
|
func (a *App) WriteFile(req WriteFileRequest) error {
|
||||||
|
return a.filesystem.WriteFile(req.Path, req.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveBase64File 将 base64 内容解码后写入文件(用于图片等二进制数据)
|
||||||
|
func (a *App) SaveBase64File(req SaveBase64FileRequest) error {
|
||||||
|
return a.filesystem.SaveBase64File(req.Path, req.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListDir 列出目录
|
||||||
|
func (a *App) ListDir(path string) ([]map[string]interface{}, error) {
|
||||||
|
return a.filesystem.ListDir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateDir 创建目录
|
||||||
|
func (a *App) CreateDir(path string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.filesystem.CreateDir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateFile 创建文件
|
||||||
|
func (a *App) CreateFile(path string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.filesystem.CreateFile(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePath 删除文件或目录
|
||||||
|
func (a *App) DeletePath(path string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.filesystem.DeletePath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenamePath 重命名文件或目录
|
||||||
|
func (a *App) RenamePath(req RenamePathRequest) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.filesystem.RenamePath(req.OldPath, req.NewPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileInfo 获取文件信息
|
||||||
|
func (a *App) GetFileInfo(path string) (map[string]interface{}, error) {
|
||||||
|
return a.filesystem.GetFileInfo(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenPath 使用系统默认程序打开文件或目录
|
||||||
|
func (a *App) OpenPath(path string) error {
|
||||||
|
return a.filesystem.OpenPath(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== Zip 文件操作接口 ==========
|
||||||
|
|
||||||
|
// ListZipContents 列出 zip 文件内容
|
||||||
|
func (a *App) ListZipContents(zipPath string) ([]map[string]interface{}, error) {
|
||||||
|
return a.filesystem.ListZipContents(zipPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractFileFromZip 从 zip 文件中提取单个文件内容
|
||||||
|
func (a *App) ExtractFileFromZip(zipPath, filePath string) (string, error) {
|
||||||
|
return a.filesystem.ExtractFileFromZip(zipPath, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExtractFileFromZipToTemp 从 zip 文件中提取单个文件到临时目录
|
||||||
|
func (a *App) ExtractFileFromZipToTemp(zipPath, filePath string) (string, error) {
|
||||||
|
return a.filesystem.ExtractFileFromZipToTemp(zipPath, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetZipFileInfo 获取 zip 文件中特定文件的信息
|
||||||
|
func (a *App) GetZipFileInfo(zipPath, filePath string) (map[string]interface{}, error) {
|
||||||
|
return a.filesystem.GetZipFileInfo(zipPath, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResolveShortcut 解析快捷方式文件,返回目标路径信息
|
||||||
|
func (a *App) ResolveShortcut(lnkPath string) (map[string]interface{}, error) {
|
||||||
|
targetPath, err := a.filesystem.ResolveShortcut(lnkPath)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
fileInfo, err := a.filesystem.GetFileInfo(targetPath)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"targetPath": targetPath,
|
||||||
|
"targetExists": false,
|
||||||
|
"targetAccessible": false,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"targetPath": targetPath,
|
||||||
|
"targetExists": true,
|
||||||
|
"targetAccessible": true,
|
||||||
|
"targetInfo": fileInfo,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// startFileServer 启动文件服务器
|
||||||
|
func (a *App) startFileServer() {
|
||||||
|
if _, err := filesystem.StartLocalFileServer(); err != nil {
|
||||||
|
slog.Error("文件服务器启动失败", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
slog.Info("文件服务器已启动", "addr", "http://"+filesystem.GetLocalFileServerAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 文件服务器接口 ==========
|
||||||
|
|
||||||
|
// GetFileServerURL 获取本地文件服务器的URL
|
||||||
|
func (a *App) GetFileServerURL() string {
|
||||||
|
return fmt.Sprintf("http://%s", filesystem.GetLocalFileServerAddr())
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectFileTypeByContent 通过文件内容检测文件类型
|
||||||
|
func (a *App) DetectFileTypeByContent(path string) (map[string]interface{}, error) {
|
||||||
|
return filesystem.DetectFileTypeByContentSimple(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// initFilesystemModule 延迟初始化文件系统模块
|
||||||
|
func (a *App) initFilesystemModule() {
|
||||||
|
if a.filesystem != nil {
|
||||||
|
slog.Info("文件系统模块已初始化,跳过")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
slog.Info("延迟初始化文件系统模块")
|
||||||
|
fsConfig := filesystem.DefaultConfig()
|
||||||
|
|
||||||
|
var err error
|
||||||
|
a.filesystem, err = filesystem.NewFileSystemService(fsConfig)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("文件系统模块初始化失败", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
go a.startFileServer()
|
||||||
|
|
||||||
|
slog.Info("文件系统模块初始化完成")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 审计日志接口 ==========
|
||||||
|
|
||||||
|
// GetAuditLogs 获取审计日志
|
||||||
|
func (a *App) GetAuditLogs(limit int) ([]map[string]interface{}, error) {
|
||||||
|
return a.filesystem.GetAuditLogs(limit)
|
||||||
|
}
|
||||||
+101
@@ -0,0 +1,101 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"u-desk/internal/filesystem"
|
||||||
|
osssvc "u-desk/internal/ossdrv"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ========== OSS 接口 ==========
|
||||||
|
|
||||||
|
func (a *App) ensureOssService() *osssvc.Service {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
if a.ossService == nil {
|
||||||
|
a.ossService = osssvc.NewService()
|
||||||
|
}
|
||||||
|
return a.ossService
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssConnect 连接 OSS 存储
|
||||||
|
func (a *App) OssConnect(req OssConnectRequest) (string, error) {
|
||||||
|
if err := a.ensureOssService().GetManager().Connect(req.Provider, req.AccessKey, req.SecretKey, req.Endpoint); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return req.Provider, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssDisconnect 断开 OSS 连接
|
||||||
|
func (a *App) OssDisconnect(connID string) error {
|
||||||
|
osssvc.GetManager().Disconnect(connID)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssListDir OSS 列出目录
|
||||||
|
func (a *App) OssListDir(connID string, prefix string) ([]map[string]interface{}, error) {
|
||||||
|
return a.ensureOssService().ListDir(connID, prefix)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssDownloadToTemp OSS 下载到临时文件
|
||||||
|
func (a *App) OssDownloadToTemp(connID string, key string) (string, error) {
|
||||||
|
return a.ensureOssService().DownloadToTemp(connID, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssDownloadSiteForPreview OSS 下载 HTML 及其引用的资源到临时目录
|
||||||
|
func (a *App) OssDownloadSiteForPreview(connID string, key string) (string, error) {
|
||||||
|
return a.ensureOssService().DownloadSiteForPreview(connID, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssDownloadToTempCached 带缓存的 OSS 下载(命中缓存直接返回本地路径)
|
||||||
|
func (a *App) OssDownloadToTempCached(connID string, key string, fileSize int64, modTime string) (string, error) {
|
||||||
|
return a.ensureOssService().DownloadToTempCached(connID, key, fileSize, modTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssReadFile OSS 读取文件
|
||||||
|
func (a *App) OssReadFile(connID string, key string) (string, error) {
|
||||||
|
return a.ensureOssService().ReadFile(connID, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssWriteFile OSS 写入文件
|
||||||
|
func (a *App) OssWriteFile(connID string, key string, content string) error {
|
||||||
|
return a.ensureOssService().WriteFile(connID, key, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssWriteBase64File OSS 写入 base64 编码文件
|
||||||
|
func (a *App) OssWriteBase64File(connID string, key string, base64Content string) error {
|
||||||
|
return a.ensureOssService().WriteBase64File(connID, key, base64Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssGetFileInfo OSS 获取文件信息
|
||||||
|
func (a *App) OssGetFileInfo(connID string, key string) (map[string]interface{}, error) {
|
||||||
|
return a.ensureOssService().GetFileInfo(connID, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssCreateDir OSS 创建目录
|
||||||
|
func (a *App) OssCreateDir(connID string, dirPath string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.ensureOssService().CreateDir(connID, dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssCreateFile OSS 创建文件
|
||||||
|
func (a *App) OssCreateFile(connID string, filePath string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.ensureOssService().CreateFile(connID, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssDeletePath OSS 删除
|
||||||
|
func (a *App) OssDeletePath(connID string, key string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.ensureOssService().DeletePath(connID, key)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssRenamePath OSS 重命名
|
||||||
|
func (a *App) OssRenamePath(req OssRenamePathRequest) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.ensureOssService().RenamePath(req.ConnID, req.OldPath, req.NewPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssGetCommonPaths OSS 获取常用路径
|
||||||
|
func (a *App) OssGetCommonPaths(connID string) (map[string]string, error) {
|
||||||
|
return a.ensureOssService().GetCommonPaths(connID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OssGetSignedURL OSS 获取预签名 URL
|
||||||
|
func (a *App) OssGetSignedURL(connID string, key string) (string, error) {
|
||||||
|
return a.ensureOssService().GetSignedURL(connID, key)
|
||||||
|
}
|
||||||
+50
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"u-desk/internal/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ExportPDF 导出PDF文件
|
||||||
|
func (a *App) ExportPDF(content string, title string, fileName string, fontSize int, pageWidth int, pageHeight int) (map[string]interface{}, error) {
|
||||||
|
if a.pdfAPI == nil {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"message": "PDF导出功能未初始化",
|
||||||
|
}, fmt.Errorf("PDF导出功能未初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
req := api.PdfExportRequest{
|
||||||
|
Content: content,
|
||||||
|
Title: title,
|
||||||
|
FileName: fileName,
|
||||||
|
FontSize: fontSize,
|
||||||
|
PageWidth: pageWidth,
|
||||||
|
PageHeight: pageHeight,
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := a.pdfAPI.ExportMarkdownToPDF(req)
|
||||||
|
if err != nil {
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": false,
|
||||||
|
"message": err.Error(),
|
||||||
|
}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return map[string]interface{}{
|
||||||
|
"success": result.Success,
|
||||||
|
"message": result.Message,
|
||||||
|
"path": result.Path,
|
||||||
|
"size": result.Size,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SelectPDFSaveDirectory 选择PDF保存目录
|
||||||
|
func (a *App) SelectPDFSaveDirectory() (string, error) {
|
||||||
|
if a.pdfAPI == nil {
|
||||||
|
return "", fmt.Errorf("PDF导出功能未初始化")
|
||||||
|
}
|
||||||
|
|
||||||
|
return a.pdfAPI.SelectDirectory()
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
// ========== 回收站接口 ==========
|
||||||
|
|
||||||
|
// GetRecycleBinEntries 获取回收站条目
|
||||||
|
func (a *App) GetRecycleBinEntries() ([]map[string]interface{}, error) {
|
||||||
|
return a.filesystem.GetRecycleBinEntries()
|
||||||
|
}
|
||||||
|
|
||||||
|
// RestoreFromRecycleBin 从回收站恢复文件
|
||||||
|
func (a *App) RestoreFromRecycleBin(recyclePath string) error {
|
||||||
|
return a.filesystem.RestoreFromRecycleBin(recyclePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeletePermanently 永久删除回收站中的文件
|
||||||
|
func (a *App) DeletePermanently(recyclePath string) error {
|
||||||
|
return a.filesystem.DeletePermanently(recyclePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// EmptyRecycleBin 清空回收站
|
||||||
|
func (a *App) EmptyRecycleBin() error {
|
||||||
|
return a.filesystem.EmptyRecycleBin()
|
||||||
|
}
|
||||||
+135
@@ -0,0 +1,135 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"u-desk/internal/filesystem"
|
||||||
|
"u-desk/internal/sftp"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ========== SFTP 接口 ==========
|
||||||
|
|
||||||
|
func (a *App) ensureSftpService() *sftp.Service {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
if a.sftpService == nil {
|
||||||
|
a.sftpService = sftp.NewService()
|
||||||
|
}
|
||||||
|
return a.sftpService
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpConnect 建立 SFTP 连接,返回连接标识符 connID
|
||||||
|
func (a *App) SftpConnect(req SftpConnectRequest) (string, error) {
|
||||||
|
config := &sftp.Config{
|
||||||
|
Host: req.Host,
|
||||||
|
Port: req.Port,
|
||||||
|
Username: req.Username,
|
||||||
|
Password: req.Password,
|
||||||
|
KeyPath: req.KeyPath,
|
||||||
|
KeyPassphrase: req.KeyPassphrase,
|
||||||
|
}
|
||||||
|
if config.Port == 0 {
|
||||||
|
config.Port = 22
|
||||||
|
}
|
||||||
|
if config.Timeout == 0 {
|
||||||
|
config.Timeout = 15 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
svc := a.ensureSftpService()
|
||||||
|
_, err := svc.GetManager().Connect(config)
|
||||||
|
if err != nil {
|
||||||
|
return "", sftp.ToUserMessage(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
connID := sftp.ConnID(config.Host, config.Port)
|
||||||
|
return connID, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpDisconnect 断开 SFTP 连接
|
||||||
|
func (a *App) SftpDisconnect(connID string) error {
|
||||||
|
parts := strings.SplitN(connID, ":", 2)
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return fmt.Errorf("无效的连接标识符")
|
||||||
|
}
|
||||||
|
host := parts[0]
|
||||||
|
port, err := strconv.Atoi(parts[1])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("无效的端口号")
|
||||||
|
}
|
||||||
|
|
||||||
|
sftp.GetManager().Disconnect(host, port)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpListDir SFTP 列出目录
|
||||||
|
func (a *App) SftpListDir(connID string, dirPath string) ([]map[string]interface{}, error) {
|
||||||
|
return a.ensureSftpService().ListDir(connID, dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpReadFile SFTP 读取文件内容
|
||||||
|
func (a *App) SftpReadFile(connID string, filePath string) (string, error) {
|
||||||
|
return a.ensureSftpService().ReadFile(connID, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpWriteFile SFTP 写入文件
|
||||||
|
func (a *App) SftpWriteFile(req SftpWriteFileRequest) error {
|
||||||
|
return a.ensureSftpService().WriteFile(req.SessionID, req.Path, req.Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpWriteBase64File SFTP 写入 base64 编码的二进制文件(粘贴图片等)
|
||||||
|
func (a *App) SftpWriteBase64File(sessionID, filePath, base64Content string) error {
|
||||||
|
return a.ensureSftpService().WriteBase64File(sessionID, filePath, base64Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpGetFileInfo SFTP 获取文件信息
|
||||||
|
func (a *App) SftpGetFileInfo(connID string, filePath string) (map[string]interface{}, error) {
|
||||||
|
return a.ensureSftpService().GetFileInfo(connID, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpCreateDir SFTP 创建目录
|
||||||
|
func (a *App) SftpCreateDir(connID string, dirPath string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.ensureSftpService().CreateDir(connID, dirPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpCreateFile SFTP 创建文件
|
||||||
|
func (a *App) SftpCreateFile(connID string, filePath string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.ensureSftpService().CreateFile(connID, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpDeletePath SFTP 删除文件或目录
|
||||||
|
func (a *App) SftpDeletePath(connID string, filePath string) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.ensureSftpService().DeletePath(connID, filePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpRenamePath SFTP 重命名文件或目录
|
||||||
|
func (a *App) SftpRenamePath(req SftpRenamePathRequest) (*filesystem.FileOperationResult, error) {
|
||||||
|
return a.ensureSftpService().RenamePath(req.SessionID, req.OldPath, req.NewPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpDownloadToTemp 下载远程文件到本地临时目录(用于预览)
|
||||||
|
func (a *App) SftpDownloadToTemp(connID string, remotePath string) (string, error) {
|
||||||
|
return a.ensureSftpService().DownloadToTemp(connID, remotePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpDownloadSiteForPreview 下载 HTML 及其网站资源到本地临时目录
|
||||||
|
func (a *App) SftpDownloadSiteForPreview(connID string, remotePath string) (string, error) {
|
||||||
|
return a.ensureSftpService().DownloadSiteForPreview(connID, remotePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpDownloadToTempCached 带缓存的 SFTP 下载(命中缓存直接返回本地路径)
|
||||||
|
func (a *App) SftpDownloadToTempCached(connID string, remotePath string, fileSize int64, modTime string) (string, error) {
|
||||||
|
return a.ensureSftpService().DownloadToTempCached(connID, remotePath, fileSize, modTime)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpGetCommonPaths 获取 SFTP 远程主机常用路径
|
||||||
|
func (a *App) SftpGetCommonPaths(connID string) (map[string]string, error) {
|
||||||
|
return a.ensureSftpService().GetCommonPaths(connID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SftpGetSystemInfo 获取 SFTP 远程主机系统信息(CPU/内存/磁盘)
|
||||||
|
func (a *App) SftpGetSystemInfo(connID string) (map[string]interface{}, error) {
|
||||||
|
return a.ensureSftpService().GetSystemInfo(connID)
|
||||||
|
}
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"u-desk/internal/system"
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetSystemInfo 获取系统信息
|
||||||
|
func (a *App) GetSystemInfo() (map[string]interface{}, error) {
|
||||||
|
return system.GetSystemInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCPUInfo 获取 CPU 信息
|
||||||
|
func (a *App) GetCPUInfo() (map[string]interface{}, error) {
|
||||||
|
return system.GetCPUInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMemoryInfo 获取内存信息
|
||||||
|
func (a *App) GetMemoryInfo() (map[string]interface{}, error) {
|
||||||
|
return system.GetMemoryInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDiskInfo 获取磁盘信息
|
||||||
|
func (a *App) GetDiskInfo() ([]map[string]interface{}, error) {
|
||||||
|
return system.GetDiskInfo()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *App) GetLocalSystemInfo() (map[string]interface{}, error) {
|
||||||
|
info := make(map[string]interface{})
|
||||||
|
|
||||||
|
cpuInfo, err := system.GetCPUInfo()
|
||||||
|
if err == nil && cpuInfo != nil {
|
||||||
|
if v, ok := cpuInfo["usage"].(string); ok {
|
||||||
|
info["cpu_usage"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
memInfo, err := system.GetMemoryInfo()
|
||||||
|
if err == nil && memInfo != nil {
|
||||||
|
if v, ok := memInfo["usage"].(string); ok {
|
||||||
|
info["mem_usage"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diskInfos, err := system.GetDiskInfo()
|
||||||
|
if err == nil && len(diskInfos) > 0 {
|
||||||
|
if v, ok := diskInfos[0]["usage"].(string); ok {
|
||||||
|
info["disk_usage"] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCommonPaths 获取常用系统路径
|
||||||
|
func (a *App) GetCommonPaths() (map[string]string, error) {
|
||||||
|
homeDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
paths := map[string]string{
|
||||||
|
"home": homeDir,
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range resolveFolderPaths(homeDir) {
|
||||||
|
paths[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetEnvVars 获取环境变量
|
||||||
|
func (a *App) GetEnvVars() (map[string]string, error) {
|
||||||
|
envVars := make(map[string]string)
|
||||||
|
for _, env := range os.Environ() {
|
||||||
|
if key, value, found := strings.Cut(env, "="); found {
|
||||||
|
envVars[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return envVars, nil
|
||||||
|
}
|
||||||
+172
@@ -0,0 +1,172 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"u-desk/internal/api"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ========== 版本更新管理接口 ==========
|
||||||
|
|
||||||
|
// requireUpdateAPI 检查 updateAPI 是否已初始化
|
||||||
|
func (a *App) requireUpdateAPI() (*api.UpdateAPI, error) {
|
||||||
|
a.mu.Lock()
|
||||||
|
defer a.mu.Unlock()
|
||||||
|
if a.updateAPI == nil {
|
||||||
|
return nil, fmt.Errorf("更新功能正在初始化中")
|
||||||
|
}
|
||||||
|
return a.updateAPI, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckUpdate 检查更新
|
||||||
|
func (a *App) CheckUpdate() (map[string]interface{}, error) {
|
||||||
|
api, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.CheckUpdate()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentVersion 获取当前版本号
|
||||||
|
func (a *App) GetCurrentVersion() (map[string]interface{}, error) {
|
||||||
|
api, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.GetCurrentVersion()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUpdateConfig 获取更新配置
|
||||||
|
func (a *App) GetUpdateConfig() (map[string]interface{}, error) {
|
||||||
|
api, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.GetUpdateConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetUpdateConfig 设置更新配置
|
||||||
|
func (a *App) SetUpdateConfig(autoCheckEnabled bool, checkIntervalMinutes int, checkURL string) (map[string]interface{}, error) {
|
||||||
|
api, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.SetUpdateConfig(autoCheckEnabled, checkIntervalMinutes, checkURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DownloadUpdate 下载更新包
|
||||||
|
func (a *App) DownloadUpdate(downloadURL string) (map[string]interface{}, error) {
|
||||||
|
api, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.DownloadUpdate(downloadURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallUpdate 安装更新包
|
||||||
|
func (a *App) InstallUpdate(installerPath string, autoRestart bool) (map[string]interface{}, error) {
|
||||||
|
api, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.InstallUpdate(installerPath, autoRestart)
|
||||||
|
}
|
||||||
|
|
||||||
|
// InstallUpdateWithHash 安装更新包(带哈希验证)
|
||||||
|
func (a *App) InstallUpdateWithHash(installerPath string, autoRestart bool, expectedHash string, hashType string) (map[string]interface{}, error) {
|
||||||
|
api, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.InstallUpdateWithHash(installerPath, autoRestart, expectedHash, hashType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// VerifyUpdateFile 验证更新文件哈希值
|
||||||
|
func (a *App) VerifyUpdateFile(filePath string, expectedHash string, hashType string) (map[string]interface{}, error) {
|
||||||
|
api, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return api.VerifyUpdateFile(filePath, expectedHash, hashType)
|
||||||
|
}
|
||||||
|
|
||||||
|
// startAutoUpdateCheck 启动自动更新检查
|
||||||
|
func (a *App) startAutoUpdateCheck() {
|
||||||
|
updateAPI, err := a.requireUpdateAPI()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
config, err := updateAPI.GetUpdateConfig()
|
||||||
|
if err != nil {
|
||||||
|
slog.Warn("获取更新配置失败", "error", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
success, ok := config["success"].(bool)
|
||||||
|
if !ok || !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
configData, ok := config["data"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
autoCheckEnabled, ok := configData["auto_check_enabled"].(bool)
|
||||||
|
if !ok || !autoCheckEnabled {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
interval, ok := configData["check_interval_minutes"].(int)
|
||||||
|
if !ok || interval <= 0 {
|
||||||
|
interval = 5
|
||||||
|
}
|
||||||
|
|
||||||
|
go a.checkUpdate()
|
||||||
|
|
||||||
|
a.updateTicker = time.NewTicker(time.Duration(interval) * time.Minute)
|
||||||
|
go func() {
|
||||||
|
for range a.updateTicker.C {
|
||||||
|
a.checkUpdate()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// checkUpdate 执行更新检查
|
||||||
|
func (a *App) checkUpdate() {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
slog.Error("自动检查更新发生错误", "error", r)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
a.mu.Lock()
|
||||||
|
api := a.updateAPI
|
||||||
|
a.mu.Unlock()
|
||||||
|
|
||||||
|
if api == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := api.CheckUpdate()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
success, ok := result["success"].(bool)
|
||||||
|
if !ok || !success {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
data, ok := result["data"].(map[string]interface{})
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
hasUpdate, ok := data["has_update"].(bool)
|
||||||
|
if ok && hasUpdate && a.mainWindow != nil {
|
||||||
|
a.mainWindow.EmitEvent("update-available", data)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/application"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ========== 窗口控制方法 ==========
|
||||||
|
|
||||||
|
// WindowMinimize 最小化窗口
|
||||||
|
func (a *App) WindowMinimize() {
|
||||||
|
if a.mainWindow != nil {
|
||||||
|
a.mainWindow.Minimise()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowMaximize 最大化/还原窗口
|
||||||
|
func (a *App) WindowMaximize() {
|
||||||
|
if a.mainWindow == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if a.mainWindow.IsMaximised() {
|
||||||
|
a.mainWindow.UnMaximise()
|
||||||
|
} else {
|
||||||
|
a.mainWindow.Maximise()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowClose 关闭窗口
|
||||||
|
func (a *App) WindowClose() {
|
||||||
|
application.Get().Quit()
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowIsMaximized 检查窗口是否最大化
|
||||||
|
func (a *App) WindowIsMaximized() bool {
|
||||||
|
if a.mainWindow != nil {
|
||||||
|
return a.mainWindow.IsMaximised()
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// WindowToggleAlwaysOnTop 切换窗口置顶
|
||||||
|
func (a *App) WindowToggleAlwaysOnTop() bool {
|
||||||
|
if a.mainWindow == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
a.mu.Lock()
|
||||||
|
a.isAlwaysOnTop = !a.isAlwaysOnTop
|
||||||
|
a.mu.Unlock()
|
||||||
|
a.mainWindow.SetAlwaysOnTop(a.isAlwaysOnTop)
|
||||||
|
return a.isAlwaysOnTop
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetWindowTitleBarColor 设置原生标题栏颜色 + 主题模式(0x00BBGGRR 格式)
|
||||||
|
func (a *App) SetWindowTitleBarColor(color uint32, isDark bool) {
|
||||||
|
if a.mainWindow == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hwnd := uintptr(a.mainWindow.NativeWindow())
|
||||||
|
if hwnd == 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
setWindowTitleBar(hwnd, color, isDark)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload 重新加载窗口(用于菜单项)
|
||||||
|
func (a *App) Reload() {
|
||||||
|
if a.mainWindow != nil {
|
||||||
|
a.mainWindow.Reload()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCache 清理本地缓存(用于菜单项)
|
||||||
|
func (a *App) ClearCache() {
|
||||||
|
if a.mainWindow != nil {
|
||||||
|
a.mainWindow.EmitEvent("clear-cache")
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"golang.org/x/sys/windows/registry"
|
||||||
|
|
||||||
|
"github.com/wailsapp/wails/v3/pkg/w32"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getWindowsSpecialFolder 从注册表读取 Windows 特殊文件夹的真实路径
|
||||||
|
func getWindowsSpecialFolder(guid string) string {
|
||||||
|
key, err := registry.OpenKey(registry.CURRENT_USER,
|
||||||
|
`Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders`,
|
||||||
|
registry.READ)
|
||||||
|
if err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
defer key.Close()
|
||||||
|
|
||||||
|
val, _, err := key.GetStringValue(guid)
|
||||||
|
if err != nil || val == "" {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
path := os.ExpandEnv(val)
|
||||||
|
if _, err := os.Stat(path); err != nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// getWindowsDrivePaths 返回所有可用盘符路径
|
||||||
|
func getWindowsDrivePaths() map[string]string {
|
||||||
|
paths := make(map[string]string)
|
||||||
|
for _, drive := range "ABCDEFGHIJKLMNOPQRSTUVWXYZ" {
|
||||||
|
path := string(drive) + ":\\"
|
||||||
|
if _, err := os.Stat(path); err == nil {
|
||||||
|
key := string(drive)
|
||||||
|
paths[key] = path
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
|
|
||||||
|
// setWindowTitleBar 设置原生标题栏颜色 + 主题模式
|
||||||
|
func setWindowTitleBar(hwnd uintptr, color uint32, isDark bool) {
|
||||||
|
w32.SetTheme(hwnd, isDark)
|
||||||
|
w32.SetTitleBarColour(hwnd, color)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolveFolderPaths 解析特殊文件夹路径
|
||||||
|
func resolveFolderPaths(homeDir string) map[string]string {
|
||||||
|
paths := make(map[string]string)
|
||||||
|
folderGUIDs := map[string]string{
|
||||||
|
"desktop": "{B4BFCC3A-DB2C-424C-B029-7FE99A87C641}",
|
||||||
|
"documents": "{D20B4C7F-5EA7-424C-B25E-039F6F1FCC8A}",
|
||||||
|
"downloads": "{374DE290-123F-4565-9164-39C4925E467B}",
|
||||||
|
}
|
||||||
|
for name, guid := range folderGUIDs {
|
||||||
|
if p := getWindowsSpecialFolder(guid); p != "" {
|
||||||
|
paths[name] = p
|
||||||
|
} else {
|
||||||
|
paths[name] = filepath.Join(homeDir, strings.ToUpper(name[:1])+name[1:])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for k, v := range getWindowsDrivePaths() {
|
||||||
|
paths["root_"+k] = v
|
||||||
|
}
|
||||||
|
return paths
|
||||||
|
}
|
||||||
+38
-4
@@ -15,9 +15,43 @@
|
|||||||
|
|
||||||
## 待处理
|
## 待处理
|
||||||
|
|
||||||
- [ ] ④ App 结构体拆分 — 1344 行,应拆为 FilesystemService / ProfileService / BgmService / UpdateService 等 v3 Service
|
- [x] ④ App 结构体拆分 — 1281行 app.go 拆为 9 个域文件:app_update/filesystem/sftp/oss/window/system/config/recycle/pdf + app_windows.go
|
||||||
- [ ] ⑦ `internal/api/pdf_api.go:371` SelectDirectory 改用 Wails 原生对话框 `application.Get().Dialog.OpenFile()`
|
- [ ] ⑦ `internal/api/pdf_api.go:371` SelectDirectory 改用 Wails 原生对话框 `application.Get().Dialog.OpenFile()`
|
||||||
- [ ] ⑧ `app.go:176-189` HWND 轮询改事件驱动 — v3 alpha 暂无对应 API,后续跟进
|
- [ ] ⑧ `app.go:176-189` HWND 轮询改事件驱动 — v3 alpha 暂无对应 API,后续跟进
|
||||||
- [ ] ⑩ `app.go:29` Windows 专用导入 `golang.org/x/sys/windows/registry` 加构建标签拆到 `*_windows.go`
|
- [x] ⑩ `app.go:29` Windows 专用导入拆到 `app_windows.go`(registry/w32 包隔离)
|
||||||
- [ ] ⑪ 全局结构化日志 — `fmt.Println` 替换为 `log/slog`,按优先级分批替换
|
- [x] ⑪ 全局结构化日志 — `fmt.Println/Printf` 全部替换为 `log/slog`(app.go + internal/ 共 45 处)
|
||||||
- [ ] ⑬ `app.go:158` 更新检查 URL `https://c.1216.top/last-version.json` 移入配置
|
- [x] ⑬ `app.go` 更新检查 URL 改从 `update_config.json` 动态读取,不再硬编码
|
||||||
|
|
||||||
|
## 代码审查修复 (2026-05-16)
|
||||||
|
|
||||||
|
> 基于 App 拆分后全量审查,17 项问题已修复 15 项
|
||||||
|
|
||||||
|
### 已修复
|
||||||
|
|
||||||
|
- [x] 🔴① app_windows.go 添加 `//go:build windows` 构建标签
|
||||||
|
- [x] 🔴② app_windows.go 移除 `fallbackName` 死参数
|
||||||
|
- [x] 🔴③ initModulesByConfig 复用 initFilesystemModule,消除重复初始化逻辑
|
||||||
|
- [x] 🔴④ WindowToggleAlwaysOnTop 的 isAlwaysOnTop 修改加 mu 锁保护
|
||||||
|
- [x] 🟡⑤ GetEnvVars 从 app_filesystem.go 移至 app_system.go(归属修正)
|
||||||
|
- [x] 🟡⑥ GetAuditLogs 从 app_config.go 移至 app_filesystem.go(归属修正)
|
||||||
|
- [x] 🟡⑦ BgmGetPlaylist 处理 db.Find 错误
|
||||||
|
- [x] 🟡⑧ LoadConnectionProfiles 处理 json.Unmarshal 错误
|
||||||
|
- [x] 🟡⑨ startAutoUpdateCheck 改用 requireUpdateAPI 统一加锁
|
||||||
|
- [x] 🟡⑩ startAutoUpdateCheck 配置读取失败添加 slog.Warn 日志
|
||||||
|
- [x] 🟡⑪ MigrateTabConfig 错误从 `_ =` 改为 slog.Warn
|
||||||
|
- [x] 🟡⑫ app_recycle.go 移除空 import 块
|
||||||
|
- [x] ⚪⑭ OssConnect 补充 godoc 注释
|
||||||
|
- [x] ⚪⑮ EmitEvent 闭包提取为 app.emitEvent 方法,消除重复
|
||||||
|
|
||||||
|
## Bug 修复 (2026-05-16)
|
||||||
|
|
||||||
|
- [x] 🔴 预览状态级联失败: `clearContent()` 未重置 `isBinaryFile`,打开二进制文件后所有图片/视频预览失效
|
||||||
|
- [x] 🔴 Tab 切换状态不同步: 跨服务器 Tab 切换时面包屑和文件列表未更新
|
||||||
|
- [x] 🟡 AsyncCodeEditor 永久死亡: `defineAsyncComponent` 添加 `onError` 重试(限 2 次)
|
||||||
|
- [x] 🟡 loadDirectory 静默吞异常: 切换 Tab 加载目录失败时添加用户提示
|
||||||
|
|
||||||
|
### 暂不处理
|
||||||
|
|
||||||
|
- [ ] ⚪⑬ fallback 路径在非英文 Windows 上不匹配 — 边缘场景,影响有限
|
||||||
|
- [ ] ⚪⑯ SftpDisconnect 未走 ensureSftpService — 功能正确,风格统一待后续
|
||||||
|
- [ ] ⚪⑰ ensure 模式 5 种写法统一 — 大规模重构,独立迭代
|
||||||
|
|||||||
@@ -325,6 +325,9 @@ export function OpenPath(path: string): $CancellablePromise<void> {
|
|||||||
return $Call.ByID(1591734570, path);
|
return $Call.ByID(1591734570, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OssConnect 连接 OSS 存储
|
||||||
|
*/
|
||||||
export function OssConnect(req: $models.OssConnectRequest): $CancellablePromise<string> {
|
export function OssConnect(req: $models.OssConnectRequest): $CancellablePromise<string> {
|
||||||
return $Call.ByID(3667022538, req);
|
return $Call.ByID(3667022538, req);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -445,10 +445,15 @@ import { parseLrc, type LrcLine } from '@/utils/lrcParser'
|
|||||||
import BgmBar, { type BgmTrack } from './FileEditor/BgmBar.vue'
|
import BgmBar, { type BgmTrack } from './FileEditor/BgmBar.vue'
|
||||||
|
|
||||||
// 异步加载 CodeEditor 组件,减少初始包大小
|
// 异步加载 CodeEditor 组件,减少初始包大小
|
||||||
|
let editorRetryCount = 0
|
||||||
const AsyncCodeEditor = defineAsyncComponent({
|
const AsyncCodeEditor = defineAsyncComponent({
|
||||||
loader: () => import('@/components/CodeEditor.vue'),
|
loader: () => import('@/components/CodeEditor.vue'),
|
||||||
delay: 200,
|
delay: 200,
|
||||||
timeout: 10000
|
timeout: 10000,
|
||||||
|
onError(error, retry) {
|
||||||
|
console.error('[AsyncCodeEditor] 加载失败:', error)
|
||||||
|
if (++editorRetryCount <= 2) retry()
|
||||||
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// Office 预览容器引用
|
// Office 预览容器引用
|
||||||
|
|||||||
@@ -499,6 +499,7 @@ ${ext ? `文件类型: ${fileTypeDesc}\n` : ''}
|
|||||||
const clearContent = () => {
|
const clearContent = () => {
|
||||||
fileContent.value = ''
|
fileContent.value = ''
|
||||||
originalContent.value = ''
|
originalContent.value = ''
|
||||||
|
isBinaryFile.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ const CONTENT_DETECT_MAX_SIZE = 500 * 1024 // 500KB
|
|||||||
// 缓存检测结果
|
// 缓存检测结果
|
||||||
const contentDetectCache = new Map<string, { timestamp: number; result: any }>()
|
const contentDetectCache = new Map<string, { timestamp: number; result: any }>()
|
||||||
const CACHE_TTL = 60000 // 1分钟缓存
|
const CACHE_TTL = 60000 // 1分钟缓存
|
||||||
|
const CACHE_MAX_SIZE = 200
|
||||||
|
|
||||||
// ====== URL 工具函数(导出供外部使用) ======
|
// ====== URL 工具函数(导出供外部使用) ======
|
||||||
|
|
||||||
@@ -122,6 +123,7 @@ export function useFilePreview() {
|
|||||||
try {
|
try {
|
||||||
const result = await detectFileTypeByContent(path)
|
const result = await detectFileTypeByContent(path)
|
||||||
const data = { category: result.category, ext: result.extension }
|
const data = { category: result.category, ext: result.extension }
|
||||||
|
if (contentDetectCache.size >= CACHE_MAX_SIZE) contentDetectCache.clear()
|
||||||
contentDetectCache.set(path, { timestamp: Date.now(), result: data })
|
contentDetectCache.set(path, { timestamp: Date.now(), result: data })
|
||||||
return data
|
return data
|
||||||
} catch {
|
} catch {
|
||||||
|
|||||||
@@ -1158,6 +1158,14 @@ const activateTab = async (tab: PreviewTab) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
selectedFileItem.value = tab.fileItem
|
selectedFileItem.value = tab.fileItem
|
||||||
|
|
||||||
|
// 更新面包屑和文件列表到文件所在目录
|
||||||
|
const dir = getParentPath(tab.fileItem.path)
|
||||||
|
if (dir && dir !== filePath.value) {
|
||||||
|
filePath.value = dir
|
||||||
|
loadDirectory(dir).catch(e => Message.error(`目录加载失败: ${e}`))
|
||||||
|
}
|
||||||
|
|
||||||
if (tab.loaded) {
|
if (tab.loaded) {
|
||||||
restoreTabState(tab)
|
restoreTabState(tab)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -7,14 +7,14 @@ require (
|
|||||||
github.com/chromedp/chromedp v0.15.1
|
github.com/chromedp/chromedp v0.15.1
|
||||||
github.com/fsnotify/fsnotify v1.10.1
|
github.com/fsnotify/fsnotify v1.10.1
|
||||||
github.com/glebarez/sqlite v1.11.0
|
github.com/glebarez/sqlite v1.11.0
|
||||||
github.com/labstack/echo/v4 v4.15.1
|
github.com/labstack/echo/v4 v4.15.2
|
||||||
github.com/pkg/sftp v1.13.10
|
github.com/pkg/sftp v1.13.10
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5
|
github.com/shirou/gopsutil/v3 v3.24.5
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.80
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.92
|
||||||
github.com/yuin/goldmark v1.7.16
|
github.com/yuin/goldmark v1.8.2
|
||||||
golang.org/x/crypto v0.47.0
|
golang.org/x/crypto v0.51.0
|
||||||
golang.org/x/sys v0.42.0
|
golang.org/x/sys v0.44.0
|
||||||
golang.org/x/text v0.33.0
|
golang.org/x/text v0.37.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gorm.io/gorm v1.31.1
|
gorm.io/gorm v1.31.1
|
||||||
)
|
)
|
||||||
@@ -22,7 +22,7 @@ require (
|
|||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
dario.cat/mergo v1.0.2 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
github.com/ProtonMail/go-crypto v1.4.1 // indirect
|
||||||
github.com/adrg/xdg v0.5.3 // indirect
|
github.com/adrg/xdg v0.5.3 // indirect
|
||||||
github.com/bep/debounce v1.2.1 // indirect
|
github.com/bep/debounce v1.2.1 // indirect
|
||||||
github.com/chromedp/sysutil v1.1.0 // indirect
|
github.com/chromedp/sysutil v1.1.0 // indirect
|
||||||
@@ -30,13 +30,13 @@ require (
|
|||||||
github.com/coder/websocket v1.8.14 // indirect
|
github.com/coder/websocket v1.8.14 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
github.com/cyphar/filepath-securejoin v0.6.1 // indirect
|
||||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
github.com/ebitengine/purego v0.9.1 // indirect
|
github.com/ebitengine/purego v0.10.0 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/glebarez/go-sqlite v1.21.2 // indirect
|
github.com/glebarez/go-sqlite v1.22.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.7.0 // indirect
|
github.com/go-git/go-billy/v5 v5.9.0 // indirect
|
||||||
github.com/go-git/go-git/v5 v5.16.4 // indirect
|
github.com/go-git/go-git/v5 v5.19.0 // indirect
|
||||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 // indirect
|
github.com/go-json-experiment/json v0.0.0-20260505212615-e40f80bf6836 // indirect
|
||||||
github.com/go-ole/go-ole v1.3.0 // indirect
|
github.com/go-ole/go-ole v1.3.0 // indirect
|
||||||
github.com/gobwas/httphead v0.1.0 // indirect
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
github.com/gobwas/pool v0.2.1 // indirect
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
@@ -48,39 +48,40 @@ require (
|
|||||||
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
github.com/jchv/go-winloader v0.0.0-20250406163304-c1995be93bd1 // indirect
|
||||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||||
github.com/jinzhu/now v1.1.5 // indirect
|
github.com/jinzhu/now v1.1.5 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.4.0 // indirect
|
github.com/kevinburke/ssh_config v1.6.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
||||||
github.com/kr/fs v0.1.0 // indirect
|
github.com/kr/fs v0.1.0 // indirect
|
||||||
github.com/labstack/gommon v0.4.2 // indirect
|
github.com/labstack/gommon v0.5.0 // indirect
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
|
||||||
github.com/leaanthony/u v1.1.1 // indirect
|
github.com/leaanthony/u v1.1.1 // indirect
|
||||||
github.com/lmittmann/tint v1.1.2 // indirect
|
github.com/lmittmann/tint v1.1.3 // indirect
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
|
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.22 // indirect
|
||||||
github.com/ncruces/go-strftime v1.0.0 // indirect
|
github.com/ncruces/go-strftime v1.0.0 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.5.0 // indirect
|
github.com/pjbgf/sha1cd v0.6.0 // indirect
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
github.com/rivo/uniseg v0.4.7 // indirect
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
github.com/samber/lo v1.52.0 // indirect
|
github.com/samber/lo v1.53.0 // indirect
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
github.com/sergi/go-diff v1.4.0 // indirect
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 // indirect
|
github.com/shoenig/go-m1cpu v0.2.1 // indirect
|
||||||
github.com/skeema/knownhosts v1.3.2 // indirect
|
github.com/skeema/knownhosts v1.3.2 // indirect
|
||||||
github.com/tklauser/go-sysconf v0.3.12 // indirect
|
github.com/tklauser/go-sysconf v0.4.0 // indirect
|
||||||
github.com/tklauser/numcpus v0.6.1 // indirect
|
github.com/tklauser/numcpus v0.12.0 // indirect
|
||||||
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
github.com/valyala/bytebufferpool v1.0.0 // indirect
|
||||||
github.com/valyala/fasttemplate v1.2.2 // indirect
|
github.com/valyala/fasttemplate v1.2.2 // indirect
|
||||||
github.com/wailsapp/go-webview2 v1.0.23 // indirect
|
github.com/wailsapp/wails/webview2 v1.0.24 // indirect
|
||||||
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
github.com/xanzy/ssh-agent v0.3.3 // indirect
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
github.com/yusufpapurcu/wmi v1.2.4 // indirect
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 // indirect
|
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a // indirect
|
||||||
golang.org/x/net v0.49.0 // indirect
|
golang.org/x/net v0.54.0 // indirect
|
||||||
golang.org/x/time v0.14.0 // indirect
|
golang.org/x/time v0.15.0 // indirect
|
||||||
|
golang.org/x/tools v0.45.0 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
modernc.org/libc v1.67.6 // indirect
|
modernc.org/libc v1.72.3 // indirect
|
||||||
modernc.org/mathutil v1.7.1 // indirect
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
modernc.org/memory v1.11.0 // indirect
|
modernc.org/memory v1.11.0 // indirect
|
||||||
modernc.org/sqlite v1.44.3 // indirect
|
modernc.org/sqlite v1.50.1 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
|||||||
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
|
||||||
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
|
||||||
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM=
|
||||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo=
|
||||||
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
github.com/adrg/xdg v0.5.3 h1:xRnxJXne7+oWDatRhR1JLnvuccuIeCoBu2rtuLqQB78=
|
||||||
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
github.com/adrg/xdg v0.5.3/go.mod h1:nlTsY+NNiCBGCK2tpm09vRqfVzrc2fLmXGpBLF0zlTQ=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
|
||||||
@@ -30,30 +30,30 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
|
|||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A=
|
github.com/ebitengine/purego v0.10.0 h1:QIw4xfpWT6GWTzaW5XEKy3HXoqrJGx1ijYHzTF0/ISU=
|
||||||
github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
github.com/ebitengine/purego v0.10.0/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
|
||||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||||
github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9gAXWo=
|
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||||
github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k=
|
github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc=
|
||||||
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw=
|
||||||
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-billy/v5 v5.7.0 h1:83lBUJhGWhYp0ngzCMSgllhUSuoHP1iEWYjsPl9nwqM=
|
github.com/go-git/go-billy/v5 v5.9.0 h1:jItGXszUDRtR/AlferWPTMN4j38BQ88XnXKbilmmBPA=
|
||||||
github.com/go-git/go-billy/v5 v5.7.0/go.mod h1:/1IUejTKH8xipsAcdfcSAlUlo2J7lkYV8GTKxAT/L3E=
|
github.com/go-git/go-billy/v5 v5.9.0/go.mod h1:jCnQMLj9eUgGU7+ludSTYoZL/GGmii14RxKFj7ROgHw=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.16.4 h1:7ajIEZHZJULcyJebDLo99bGgS0jRrOxzZG4uCk2Yb2Y=
|
github.com/go-git/go-git/v5 v5.19.0 h1:+WkVUQZSy/F1Gb13udrMKjIM2PrzsNfDKFSfo5tkMtc=
|
||||||
github.com/go-git/go-git/v5 v5.16.4/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
github.com/go-git/go-git/v5 v5.19.0/go.mod h1:Pb1v0c7/g8aGQJwx9Us09W85yGoyvSwuhEGMH7zjDKQ=
|
||||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433 h1:vymEbVwYFP/L05h5TKQxvkXoKxNvTpjxYKdF1Nlwuao=
|
github.com/go-json-experiment/json v0.0.0-20260505212615-e40f80bf6836 h1:5KGUhXZFTN1PrCY4zUZLe1J8n7uBNmPDbCLCn78EbPQ=
|
||||||
github.com/go-json-experiment/json v0.0.0-20260214004413-d219187c3433/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
|
github.com/go-json-experiment/json v0.0.0-20260505212615-e40f80bf6836/go.mod h1:tphK2c80bpPhMOI4v6bIc2xWywPfbqi1Z06+RcrMkDg=
|
||||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||||
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE=
|
||||||
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78=
|
||||||
@@ -67,7 +67,6 @@ github.com/godbus/dbus/v5 v5.2.2 h1:TUR3TgtSVDmjiXOgAAyaZbYmIeP3DPkld3jgKGV8mXQ=
|
|||||||
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
github.com/godbus/dbus/v5 v5.2.2/go.mod h1:3AAv2+hPq5rdnr5txxxRwiGjPXamgoIHgz9FPBfOp3c=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ=
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
@@ -84,8 +83,8 @@ github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD
|
|||||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ=
|
||||||
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
github.com/kevinburke/ssh_config v1.6.0 h1:J1FBfmuVosPHf5GRdltRLhPJtJpTlMdKTBjRgTaQBFY=
|
||||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
github.com/kevinburke/ssh_config v1.6.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
||||||
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8=
|
||||||
@@ -97,35 +96,35 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
|||||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/labstack/echo/v4 v4.15.1 h1:S9keusg26gZpjMmPqB5hOEvNKnmd1lNmcHrbbH2lnFs=
|
github.com/labstack/echo/v4 v4.15.2 h1:nnh2sCzGCVYnU+wCisMPiYapEg/QVo/gcI9ePKg5/T4=
|
||||||
github.com/labstack/echo/v4 v4.15.1/go.mod h1:xmw1clThob0BSVRX1CRQkGQ/vjwcpOMjQZSZa9fKA/c=
|
github.com/labstack/echo/v4 v4.15.2/go.mod h1:Xzp1Ns1RA2c9fY7nSgUJkpkUZGNbEIVHZbtbOMPktBI=
|
||||||
github.com/labstack/gommon v0.4.2 h1:F8qTUNXgG1+6WQmqoUWnz8WiEU60mXVVw0P4ht1WRA0=
|
github.com/labstack/gommon v0.5.0 h1:6VSQ2NOzsnEJ5W6+84E0RbcaDDmgB6NIAzWCczTEe6c=
|
||||||
github.com/labstack/gommon v0.4.2/go.mod h1:QlUFxVM+SNXhDL/Z7YhocGIBYOiwB0mXm1+1bAPHPyU=
|
github.com/labstack/gommon v0.5.0/go.mod h1:Rzlg7HHy1maLfzBYGg9NZcVuz1sA68HHhLjhcEllYE0=
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
github.com/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
|
||||||
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
|
||||||
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
github.com/leaanthony/u v1.1.1 h1:TUFjwDGlNX+WuwVEzDqQwC2lOv0P4uhTQw7CMFdiK7M=
|
||||||
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
github.com/leaanthony/u v1.1.1/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80 h1:6Yzfa6GP0rIo/kULo2bwGEkFvCePZ3qHDDTC3/J9Swo=
|
||||||
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
github.com/ledongthuc/pdf v0.0.0-20220302134840-0c2507a12d80/go.mod h1:imJHygn/1yfhB7XSJJKlFZKl/J+dCPAknuiaGOshXAs=
|
||||||
github.com/lmittmann/tint v1.1.2 h1:2CQzrL6rslrsyjqLDwD11bZ5OpLBPU+g3G/r5LSfS8w=
|
github.com/lmittmann/tint v1.1.3 h1:Hv4EaHWXQr+GTFnOU4VKf8UvAtZgn0VuKT+G0wFlO3I=
|
||||||
github.com/lmittmann/tint v1.1.2/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
github.com/lmittmann/tint v1.1.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
|
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e h1:Q6MvJtQK/iRcRtzAscm/zF23XxJlbECiGPyRicsX+Ak=
|
||||||
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
|
github.com/lufia/plan9stats v0.0.0-20260330125221-c963978e514e/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg=
|
||||||
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
github.com/matryer/is v1.4.1 h1:55ehd8zaGABKLXQUe2awZ99BD/PTc2ls+KV/dXphgEQ=
|
||||||
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
github.com/matryer/is v1.4.1/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.22 h1:j8l17JJ9i6VGPUFUYoTUKPSgKe/83EYU2zBC7YNKMw4=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.22/go.mod h1:ZXfXG4SQHsB/w3ZeOYbR0PrPwLy+n6xiMrJlRFqopa4=
|
||||||
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
github.com/ncruces/go-strftime v1.0.0 h1:HMFp8mLCTPp341M/ZnA4qaf7ZlsbTc+miZjCLOFAw7w=
|
||||||
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
github.com/ncruces/go-strftime v1.0.0/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
github.com/onsi/gomega v1.34.1 h1:EUMJIKUjM8sKjYbtxQI9A4z2o+rruxnzNvpknOXie6k=
|
||||||
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
github.com/onsi/gomega v1.34.1/go.mod h1:kU1QgUvBDLXBJq618Xvm2LUX6rSAfRaFRTcdOeDLwwY=
|
||||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde h1:x0TT0RDC7UhAVbbWWBzr41ElhJx5tXPWkIHA2HWPRuw=
|
||||||
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
github.com/orisano/pixelmatch v0.0.0-20220722002657-fb0b55479cde/go.mod h1:nZgzbfBr3hhjoZnS66nKrHmduYNpc34ny7RK4z5/HM0=
|
||||||
github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
github.com/pjbgf/sha1cd v0.6.0 h1:3WJ8Wz8gvDz29quX1OcEmkAlUg9diU4GxJHqs0/XiwU=
|
||||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
github.com/pjbgf/sha1cd v0.6.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
|
||||||
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
|
||||||
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
@@ -134,8 +133,8 @@ github.com/pkg/sftp v1.13.10 h1:+5FbKNTe5Z9aspU88DPIKJ9z2KZoaGCu6Sr6kKR/5mU=
|
|||||||
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
|
github.com/pkg/sftp v1.13.10/go.mod h1:bJ1a7uDhrX/4OII+agvy28lzRvQrmIQuaHrcI1HbeGA=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU=
|
||||||
github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
@@ -143,16 +142,16 @@ github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
|||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
||||||
github.com/samber/lo v1.52.0 h1:Rvi+3BFHES3A8meP33VPAxiBZX/Aws5RxrschYGjomw=
|
github.com/samber/lo v1.53.0 h1:t975lj2py4kJPQ6haz1QMgtId2gtmfktACxIXArw3HM=
|
||||||
github.com/samber/lo v1.52.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
github.com/samber/lo v1.53.0/go.mod h1:4+MXEGsJzbKGaUEQFKBq2xtfuznW9oz/WrgyzMzRoM0=
|
||||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
|
||||||
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
|
github.com/shoenig/go-m1cpu v0.2.1 h1:yqRB4fvOge2+FyRXFkXqsyMoqPazv14Yyy+iyccT2E4=
|
||||||
github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
|
github.com/shoenig/go-m1cpu v0.2.1/go.mod h1:KkDOw6m3ZJQAPHbrzkZki4hnx+pDRR1Lo+ldA56wD5w=
|
||||||
github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
|
github.com/shoenig/test v1.7.0 h1:eWcHtTXa6QLnBvm0jgEabMRN/uJ4DMV3M8xUGgRkZmk=
|
||||||
github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k=
|
github.com/shoenig/test v1.7.0/go.mod h1:UxJ6u/x2v/TNs/LoLxBNJRV9DiwBBKYxXSyczsBHFoI=
|
||||||
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
|
||||||
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
|
||||||
@@ -161,36 +160,36 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf
|
|||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
|
github.com/tklauser/go-sysconf v0.4.0 h1:7H0uAN+7RkwWRaxhYXDLqa5V3LPrJeV8wmD9dRUgPQU=
|
||||||
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
|
github.com/tklauser/go-sysconf v0.4.0/go.mod h1:8mTNWyog7H+MpKijp4VmKJAd2bbYQ2zuUwkYRbUArPI=
|
||||||
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
|
github.com/tklauser/numcpus v0.12.0 h1:NR85qdvHA9pFse3x3weVZ0r0ST8R6l5RHbZrlRaqob4=
|
||||||
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
|
github.com/tklauser/numcpus v0.12.0/go.mod h1:ABHeXzJnr/qqwguhClkZKT1/8VABcYrsyUiUGobwWJg=
|
||||||
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
|
||||||
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
|
||||||
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo=
|
||||||
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
|
||||||
github.com/wailsapp/go-webview2 v1.0.23 h1:jmv8qhz1lHibCc79bMM/a/FqOnnzOGEisLav+a0b9P0=
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.92 h1:LYVe5RcGDB/x1QTWE0JbonXZc7+BmCtjb3+mHa1riao=
|
||||||
github.com/wailsapp/go-webview2 v1.0.23/go.mod h1:qJmWAmAmaniuKGZPWwne+uor3AHMB5PFhqiK0Bbj8kc=
|
github.com/wailsapp/wails/v3 v3.0.0-alpha.92/go.mod h1:4cKvtUppwqYC9tVtvgHWzEmXfUnuLEV3q8d0Jh6xkQQ=
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.80 h1:AeJGPYEf4MnEzNAqHzmhWTuvv7ZyKL51hFQsDxEWBks=
|
github.com/wailsapp/wails/webview2 v1.0.24 h1:uULnjCSaRfMlU84mS3kjLgPsRosEOIusVK1nFOHZHzs=
|
||||||
github.com/wailsapp/wails/v3 v3.0.0-alpha.80/go.mod h1:4saK4A4K9970X+X7RkMwP2lyGbLogcUz54wVeq4C/V8=
|
github.com/wailsapp/wails/webview2 v1.0.24/go.mod h1:sdf+s0nAdxlzVWf9SCxC15XaxnQPJeY+uU1Ucn3jHQM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
|
||||||
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
|
||||||
github.com/yuin/goldmark v1.7.16 h1:n+CJdUxaFMiDUNnWC3dMWCIQJSkxH4uz3ZwQBkAlVNE=
|
github.com/yuin/goldmark v1.8.2 h1:kEGpgqJXdgbkhcOgBxkC0X0PmoPG1ZyoZ117rDVp4zE=
|
||||||
github.com/yuin/goldmark v1.7.16/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
github.com/yuin/goldmark v1.8.2/go.mod h1:ip/1k0VRfGynBgxOz0yCqHrbZXhcjxyuS66Brc7iBKg=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
|
||||||
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
|
||||||
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96 h1:Z/6YuSHTLOHfNFdb8zVZomZr7cqNgTJvA8+Qz75D8gU=
|
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a h1:+3jdDGGB8NGb1Zktc737jlt3/A5f6UlwSzmvqUuufxw=
|
||||||
golang.org/x/exp v0.0.0-20260112195511-716be5621a96/go.mod h1:nzimsREAkjBCIEFtHiYkrJyT+2uy9YZJB7H1k68CXZU=
|
golang.org/x/exp v0.0.0-20260508232706-74f9aab9d74a/go.mod h1:d2fgXJLVs4dYDHUk5lwMIfzRzSrWCfGZb0ZqeLa/Vcw=
|
||||||
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
golang.org/x/mod v0.36.0 h1:JJjpVx6myfUsUdAzZuOSTTmRE0PfZeNWzzvKrP7amb4=
|
||||||
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
golang.org/x/mod v0.36.0/go.mod h1:moc6ELqsWcOw5Ef3xVprK5ul/MvtVvkIXLziUOICjUQ=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
golang.org/x/net v0.54.0 h1:2zJIZAxAHV/OHCDTCOHAYehQzLfSXuf/5SoL/Dv6w/w=
|
||||||
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ=
|
||||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
golang.org/x/sync v0.20.0 h1:e0PTpb7pjO8GAtTs2dQ6jYa5BWYlMuX047Dco/pItO4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0=
|
||||||
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
@@ -202,22 +201,19 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
|
||||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||||
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
|
|
||||||
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
|
golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
|
||||||
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
|
golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
|
||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
golang.org/x/text v0.37.0 h1:Cqjiwd9eSg8e0QAkyCaQTNHFIIzWtidPahFWR83rTrc=
|
||||||
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38=
|
||||||
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
|
golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U=
|
||||||
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
golang.org/x/tools v0.45.0 h1:18qN3FAooORvApf5XjCXgsuayZOEtXf6JK18I3+ONa8=
|
||||||
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
golang.org/x/tools v0.45.0/go.mod h1:LuUGqqaXcXMEFEruIVJVm5mgDD8vww/z/SR1gQ4uE/0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||||
@@ -230,30 +226,30 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
|||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
gorm.io/gorm v1.31.1 h1:7CA8FTFz/gRfgqgpeKIBcervUn3xSyPUmr6B2WXJ7kg=
|
||||||
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
gorm.io/gorm v1.31.1/go.mod h1:XyQVbO2k6YkOis7C2437jSit3SsDK72s7n7rsSHd+Gs=
|
||||||
modernc.org/cc/v4 v4.27.1 h1:9W30zRlYrefrDV2JE2O8VDtJ1yPGownxciz5rrbQZis=
|
modernc.org/cc/v4 v4.28.2 h1:3tQ0lf2ADtoby2EtSP+J7IE2SHwEJdP8ioR59wx7XpY=
|
||||||
modernc.org/cc/v4 v4.27.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
modernc.org/cc/v4 v4.28.2/go.mod h1:OnovgIhbbMXMu1aISnJ0wvVD1KnW+cAUJkIrAWh+kVI=
|
||||||
modernc.org/ccgo/v4 v4.30.1 h1:4r4U1J6Fhj98NKfSjnPUN7Ze2c6MnAdL0hWw6+LrJpc=
|
modernc.org/ccgo/v4 v4.34.0 h1:yRLPFZieg532OT4rp4JFNIVcquwalMX26G95WQDqwCQ=
|
||||||
modernc.org/ccgo/v4 v4.30.1/go.mod h1:bIOeI1JL54Utlxn+LwrFyjCx2n2RDiYEaJVSrgdrRfM=
|
modernc.org/ccgo/v4 v4.34.0/go.mod h1:AS5WYMyBakQ+fhsHhtP8mWB82KTGPkNNJDGfGQCe0/A=
|
||||||
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
modernc.org/fileutil v1.4.0 h1:j6ZzNTftVS054gi281TyLjHPp6CPHr2KCxEXjEbD6SM=
|
||||||
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
modernc.org/fileutil v1.4.0/go.mod h1:EqdKFDxiByqxLk8ozOxObDSfcVOv/54xDs/DUHdvCUU=
|
||||||
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
modernc.org/gc/v3 v3.1.1 h1:k8T3gkXWY9sEiytKhcgyiZ2L0DTyCQ/nvX+LoCljoRE=
|
modernc.org/gc/v3 v3.1.2 h1:ZtDCnhonXSZexk/AYsegNRV1lJGgaNZJuKjJSWKyEqo=
|
||||||
modernc.org/gc/v3 v3.1.1/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
modernc.org/gc/v3 v3.1.2/go.mod h1:HFK/6AGESC7Ex+EZJhJ2Gni6cTaYpSMmU/cT9RmlfYY=
|
||||||
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
modernc.org/libc v1.67.6 h1:eVOQvpModVLKOdT+LvBPjdQqfrZq+pC39BygcT+E7OI=
|
modernc.org/libc v1.72.3 h1:ZnDF4tXn4NBXFutMMQC4vtbTFSXhhKzR73fv0beZEAU=
|
||||||
modernc.org/libc v1.67.6/go.mod h1:JAhxUVlolfYDErnwiqaLvUqc8nfb2r6S6slAgZOnaiE=
|
modernc.org/libc v1.72.3/go.mod h1:dn0dZNnnn1clLyvRxLxYExxiKRZIRENOfqQ8XEeg4Qs=
|
||||||
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
modernc.org/opt v0.2.0 h1:tGyef5ApycA7FSEOMraay9SaTk5zmbx7Tu+cJs4QKZg=
|
||||||
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
modernc.org/opt v0.2.0/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
modernc.org/sqlite v1.44.3 h1:+39JvV/HWMcYslAwRxHb8067w+2zowvFOUrOWIy9PjY=
|
modernc.org/sqlite v1.50.1 h1:l+cQvn0sd0zJJtfygGHuQJ5AjlrwXmWPw4KP3ZMwr9w=
|
||||||
modernc.org/sqlite v1.44.3/go.mod h1:CzbrU2lSB1DKUusvwGz7rqEKIq+NUd8GWuBBZDs9/nA=
|
modernc.org/sqlite v1.50.1/go.mod h1:tcNzv5p84E0skkmJn038y+hWJbLQXQqEnQfeh5r2JLM=
|
||||||
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
|||||||
@@ -20,8 +20,8 @@ func NewFileLockChecker() *FileLockChecker {
|
|||||||
// IsFileLocked 检查文件是否被锁定(被其他进程占用)
|
// IsFileLocked 检查文件是否被锁定(被其他进程占用)
|
||||||
// 返回: (是否锁定, 错误信息, 错误)
|
// 返回: (是否锁定, 错误信息, 错误)
|
||||||
func (c *FileLockChecker) IsFileLocked(path string) (bool, string, error) {
|
func (c *FileLockChecker) IsFileLocked(path string) (bool, string, error) {
|
||||||
// 尝试以独占写模式打开文件
|
// 尝试以独占写模式打开文件(不创建新文件)
|
||||||
file, err := os.OpenFile(path, os.O_RDWR|syscall.O_CREAT, 0666)
|
file, err := os.OpenFile(path, os.O_RDWR, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// 检查是否是锁相关的错误
|
// 检查是否是锁相关的错误
|
||||||
if isLockError(err) {
|
if isLockError(err) {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"math/big"
|
"math/big"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -23,6 +24,7 @@ type RecycleBinEntry struct {
|
|||||||
|
|
||||||
// RecycleBin 回收站管理器
|
// RecycleBin 回收站管理器
|
||||||
type RecycleBin struct {
|
type RecycleBin struct {
|
||||||
|
mu sync.Mutex
|
||||||
binPath string
|
binPath string
|
||||||
metadataFile string
|
metadataFile string
|
||||||
entries []RecycleBinEntry
|
entries []RecycleBinEntry
|
||||||
@@ -57,6 +59,8 @@ func NewRecycleBin(binPath string) (*RecycleBin, error) {
|
|||||||
|
|
||||||
// MoveToRecycleBin 移动文件到回收站
|
// MoveToRecycleBin 移动文件到回收站
|
||||||
func (rb *RecycleBin) MoveToRecycleBin(path string) error {
|
func (rb *RecycleBin) MoveToRecycleBin(path string) error {
|
||||||
|
rb.mu.Lock()
|
||||||
|
defer rb.mu.Unlock()
|
||||||
// 获取文件信息
|
// 获取文件信息
|
||||||
info, err := os.Stat(path)
|
info, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -111,6 +115,8 @@ func (rb *RecycleBin) MoveToRecycleBin(path string) error {
|
|||||||
|
|
||||||
// RestoreFromRecycleBin 从回收站恢复文件
|
// RestoreFromRecycleBin 从回收站恢复文件
|
||||||
func (rb *RecycleBin) RestoreFromRecycleBin(recyclePath string) error {
|
func (rb *RecycleBin) RestoreFromRecycleBin(recyclePath string) error {
|
||||||
|
rb.mu.Lock()
|
||||||
|
defer rb.mu.Unlock()
|
||||||
// 查找对应的元数据条目
|
// 查找对应的元数据条目
|
||||||
var entry *RecycleBinEntry
|
var entry *RecycleBinEntry
|
||||||
for i := range rb.entries {
|
for i := range rb.entries {
|
||||||
@@ -156,6 +162,8 @@ func (rb *RecycleBin) RestoreFromRecycleBin(recyclePath string) error {
|
|||||||
|
|
||||||
// DeletePermanently 永久删除回收站中的文件
|
// DeletePermanently 永久删除回收站中的文件
|
||||||
func (rb *RecycleBin) DeletePermanently(recyclePath string) error {
|
func (rb *RecycleBin) DeletePermanently(recyclePath string) error {
|
||||||
|
rb.mu.Lock()
|
||||||
|
defer rb.mu.Unlock()
|
||||||
// 查找元数据条目
|
// 查找元数据条目
|
||||||
for i, entry := range rb.entries {
|
for i, entry := range rb.entries {
|
||||||
if entry.DeletedPath == recyclePath {
|
if entry.DeletedPath == recyclePath {
|
||||||
@@ -180,11 +188,15 @@ func (rb *RecycleBin) DeletePermanently(recyclePath string) error {
|
|||||||
|
|
||||||
// ListEntries 列出回收站中的所有条目
|
// ListEntries 列出回收站中的所有条目
|
||||||
func (rb *RecycleBin) ListEntries() []RecycleBinEntry {
|
func (rb *RecycleBin) ListEntries() []RecycleBinEntry {
|
||||||
return rb.entries
|
rb.mu.Lock()
|
||||||
|
defer rb.mu.Unlock()
|
||||||
|
return append([]RecycleBinEntry(nil), rb.entries...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Empty 清空回收站
|
// Empty 清空回收站
|
||||||
func (rb *RecycleBin) Empty() error {
|
func (rb *RecycleBin) Empty() error {
|
||||||
|
rb.mu.Lock()
|
||||||
|
defer rb.mu.Unlock()
|
||||||
// 删除所有文件
|
// 删除所有文件
|
||||||
for _, entry := range rb.entries {
|
for _, entry := range rb.entries {
|
||||||
if err := os.RemoveAll(entry.DeletedPath); err != nil {
|
if err := os.RemoveAll(entry.DeletedPath); err != nil {
|
||||||
@@ -215,6 +227,8 @@ func (rb *RecycleBin) autoCleanup() {
|
|||||||
|
|
||||||
// cleanupExpiredEntries 清理过期的条目
|
// cleanupExpiredEntries 清理过期的条目
|
||||||
func (rb *RecycleBin) cleanupExpiredEntries() {
|
func (rb *RecycleBin) cleanupExpiredEntries() {
|
||||||
|
rb.mu.Lock()
|
||||||
|
defer rb.mu.Unlock()
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
expiredEntries := make([]int, 0)
|
expiredEntries := make([]int, 0)
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -248,7 +249,7 @@ func (s *FileSystemService) DeletePathWithContext(ctx context.Context, path stri
|
|||||||
if !isInRecycleBin(path) {
|
if !isInRecycleBin(path) {
|
||||||
if err := s.recycleBin.MoveToRecycleBin(path); err != nil {
|
if err := s.recycleBin.MoveToRecycleBin(path); err != nil {
|
||||||
// 回收站失败,记录但继续
|
// 回收站失败,记录但继续
|
||||||
fmt.Printf("[警告] 移动到回收站失败: %v\n", err)
|
slog.Warn("移动到回收站失败", "error", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -617,13 +617,18 @@ func (s *Service) RenamePath(connID string, oldPath string, newPath string) (*fi
|
|||||||
if err := c.Copy(ctx, f.Key, newPrefix+relativeKey); err != nil {
|
if err := c.Copy(ctx, f.Key, newPrefix+relativeKey); err != nil {
|
||||||
return nil, fmt.Errorf("复制失败: %w", err)
|
return nil, fmt.Errorf("复制失败: %w", err)
|
||||||
}
|
}
|
||||||
c.Delete(ctx, f.Key)
|
if err := c.Delete(ctx, f.Key); err != nil {
|
||||||
|
return nil, fmt.Errorf("删除源文件失败: %w", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if !listResult.IsTruncated {
|
if !listResult.IsTruncated {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.Delete(ctx, oldKey) // marker
|
if err := c.Delete(ctx, oldKey); err != nil { // marker
|
||||||
|
// 目录标记删除失败不影响重命名结果
|
||||||
|
_ = err
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if err := c.Copy(ctx, oldKey, newKey); err != nil {
|
if err := c.Copy(ctx, oldKey, newKey); err != nil {
|
||||||
return nil, fmt.Errorf("复制失败: %w", err)
|
return nil, fmt.Errorf("复制失败: %w", err)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package storage
|
|||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
"crypto/sha256"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
@@ -94,7 +95,7 @@ func CleanupExpiredCache() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(expired) > 0 {
|
if len(expired) > 0 {
|
||||||
fmt.Printf("[下载缓存] 清理 %d 条过期记录\n", len(expired))
|
slog.Info("清理过期下载缓存", "count", len(expired))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user