package filesystem import ( "crypto/rand" "encoding/json" "fmt" "io" "math/big" "os" "path/filepath" "time" ) // RecycleBinEntry 回收站条目 type RecycleBinEntry struct { OriginalPath string `json:"original_path"` // 原始路径 DeletedPath string `json:"deleted_path"` // 回收站中的路径 DeletedTime time.Time `json:"deleted_time"` // 删除时间 Size int64 `json:"size"` // 文件大小 IsDirectory bool `json:"is_directory"` // 是否为目录 OriginalDevice string `json:"original_device"` // 原始设备(盘符) } // RecycleBin 回收站管理器 type RecycleBin struct { binPath string metadataFile string entries []RecycleBinEntry } // NewRecycleBin 创建回收站管理器 func NewRecycleBin(binPath string) (*RecycleBin, error) { // 创建回收站目录 if err := os.MkdirAll(binPath, 0755); err != nil { return nil, fmt.Errorf("创建回收站目录失败: %v", err) } bin := &RecycleBin{ binPath: binPath, metadataFile: filepath.Join(binPath, "metadata.json"), entries: make([]RecycleBinEntry, 0), } // 加载元数据 if err := bin.loadMetadata(); err != nil { // 如果文件不存在,这是正常的,忽略错误 if !os.IsNotExist(err) { return nil, fmt.Errorf("加载回收站元数据失败: %v", err) } } // 启动自动清理协程 go bin.autoCleanup() return bin, nil } // MoveToRecycleBin 移动文件到回收站 func (rb *RecycleBin) MoveToRecycleBin(path string) error { // 获取文件信息 info, err := os.Stat(path) if err != nil { return fmt.Errorf("获取文件信息失败: %v", err) } // 生成唯一的回收站文件名 timestamp := time.Now().Format("20060102_150405") randomSuffix := generateRandomString(6) baseName := filepath.Base(path) var recycleName string if info.IsDir() { recycleName = fmt.Sprintf("%s_%s_%s", timestamp, randomSuffix, baseName) } else { ext := filepath.Ext(baseName) nameWithoutExt := baseName[:len(baseName)-len(ext)] recycleName = fmt.Sprintf("%s_%s_%s%s", timestamp, randomSuffix, nameWithoutExt, ext) } recyclePath := filepath.Join(rb.binPath, recycleName) // 移动文件到回收站 if err := os.Rename(path, recyclePath); err != nil { // 如果跨设备移动失败,尝试复制后删除 if err := copyRecursively(path, recyclePath); err != nil { return fmt.Errorf("移动到回收站失败: %v", err) } os.RemoveAll(path) } // 创建元数据条目 entry := RecycleBinEntry{ OriginalPath: path, DeletedPath: recyclePath, DeletedTime: time.Now(), Size: info.Size(), IsDirectory: info.IsDir(), OriginalDevice: getDevice(path), } // 添加到元数据 rb.entries = append(rb.entries, entry) // 保存元数据 if err := rb.saveMetadata(); err != nil { return fmt.Errorf("保存回收站元数据失败: %v", err) } return nil } // RestoreFromRecycleBin 从回收站恢复文件 func (rb *RecycleBin) RestoreFromRecycleBin(recyclePath string) error { // 查找对应的元数据条目 var entry *RecycleBinEntry for i := range rb.entries { if rb.entries[i].DeletedPath == recyclePath { entry = &rb.entries[i] // 从列表中移除 rb.entries = append(rb.entries[:i], rb.entries[i+1:]...) break } } if entry == nil { return fmt.Errorf("回收站中未找到该文件") } // 检查原始路径的父目录是否存在 parentDir := filepath.Dir(entry.OriginalPath) if err := os.MkdirAll(parentDir, 0755); err != nil { return fmt.Errorf("创建父目录失败: %v", err) } // 检查原始位置是否已有文件 if _, err := os.Stat(entry.OriginalPath); err == nil { return fmt.Errorf("原始位置已存在同名文件,请先删除或重命名") } // 移回文件 if err := os.Rename(recyclePath, entry.OriginalPath); err != nil { // 如果跨设备移动失败,尝试复制后删除 if err := copyRecursively(recyclePath, entry.OriginalPath); err != nil { return fmt.Errorf("恢复文件失败: %v", err) } os.RemoveAll(recyclePath) } // 保存元数据 if err := rb.saveMetadata(); err != nil { return fmt.Errorf("保存回收站元数据失败: %v", err) } return nil } // DeletePermanently 永久删除回收站中的文件 func (rb *RecycleBin) DeletePermanently(recyclePath string) error { // 查找元数据条目 for i, entry := range rb.entries { if entry.DeletedPath == recyclePath { // 从列表中移除 rb.entries = append(rb.entries[:i], rb.entries[i+1:]...) break } } // 删除文件 if err := os.RemoveAll(recyclePath); err != nil { return fmt.Errorf("永久删除失败: %v", err) } // 保存元数据 if err := rb.saveMetadata(); err != nil { return fmt.Errorf("保存回收站元数据失败: %v", err) } return nil } // ListEntries 列出回收站中的所有条目 func (rb *RecycleBin) ListEntries() []RecycleBinEntry { return rb.entries } // Empty 清空回收站 func (rb *RecycleBin) Empty() error { // 删除所有文件 for _, entry := range rb.entries { if err := os.RemoveAll(entry.DeletedPath); err != nil { return fmt.Errorf("删除文件失败: %s", err) } } // 清空元数据 rb.entries = make([]RecycleBinEntry, 0) // 保存元数据 if err := rb.saveMetadata(); err != nil { return fmt.Errorf("保存回收站元数据失败: %v", err) } return nil } // autoCleanup 自动清理超过30天的文件 func (rb *RecycleBin) autoCleanup() { ticker := time.NewTicker(24 * time.Hour) // 每天检查一次 defer ticker.Stop() for range ticker.C { rb.cleanupExpiredEntries() } } // cleanupExpiredEntries 清理过期的条目 func (rb *RecycleBin) cleanupExpiredEntries() { now := time.Now() expiredEntries := make([]int, 0) // 找出所有过期的条目(超过30天) for i, entry := range rb.entries { if now.Sub(entry.DeletedTime) > 30*24*time.Hour { expiredEntries = append(expiredEntries, i) } } // 从后往前删除(避免索引问题) for i := len(expiredEntries) - 1; i >= 0; i-- { idx := expiredEntries[i] entry := rb.entries[idx] // 删除文件 _ = os.RemoveAll(entry.DeletedPath) // 从列表中移除 rb.entries = append(rb.entries[:idx], rb.entries[idx+1:]...) } // 保存元数据 if len(expiredEntries) > 0 { _ = rb.saveMetadata() } } // loadMetadata 加载元数据 func (rb *RecycleBin) loadMetadata() error { data, err := os.ReadFile(rb.metadataFile) if err != nil { return err } return json.Unmarshal(data, &rb.entries) } // saveMetadata 保存元数据 func (rb *RecycleBin) saveMetadata() error { data, err := json.MarshalIndent(rb.entries, "", " ") if err != nil { return err } return os.WriteFile(rb.metadataFile, data, 0644) } // copyRecursively 递归复制文件或目录 func copyRecursively(src, dst string) error { info, err := os.Stat(src) if err != nil { return err } if info.IsDir() { return copyDirectory(src, dst) } return copyFile(src, dst) } // copyDirectory 复制目录 func copyDirectory(src, dst string) error { // 创建目标目录 if err := os.MkdirAll(dst, 0755); err != nil { return err } // 读取源目录 entries, err := os.ReadDir(src) if err != nil { return err } // 复制每个条目 for _, entry := range entries { srcPath := filepath.Join(src, entry.Name()) dstPath := filepath.Join(dst, entry.Name()) if entry.IsDir() { if err := copyDirectory(srcPath, dstPath); err != nil { return err } } else { if err := copyFile(srcPath, dstPath); err != nil { return err } } } return nil } // copyFile 复制文件 func copyFile(src, dst string) error { // 打开源文件 srcFile, err := os.Open(src) if err != nil { return err } defer srcFile.Close() // 创建目标文件 dstFile, err := os.Create(dst) if err != nil { return err } defer dstFile.Close() // 复制内容 if _, err := io.Copy(dstFile, srcFile); err != nil { return err } // 复制文件权限 srcInfo, err := os.Stat(src) if err != nil { return err } return os.Chmod(dst, srcInfo.Mode()) } // getDevice 获取文件所在设备(盘符) func getDevice(path string) string { absPath, err := filepath.Abs(path) if err != nil { return "" } if len(absPath) >= 2 { return absPath[:2] // 返回 "C:" 这样的盘符 } return "" } // generateRandomString 生成随机字符串 // 使用加密安全的随机数生成器,保证随机性和性能 func generateRandomString(length int) string { const charset = "abcdefghijklmnopqrstuvwxyz0123456789" b := make([]byte, length) // 使用 crypto/rand 生成安全的随机数 for i := range b { n, err := rand.Int(rand.Reader, big.NewInt(int64(len(charset)))) if err != nil { // 如果加密随机数生成失败,回退到时间戳(极低概率) b[i] = charset[time.Now().UnixNano()%int64(len(charset))] continue } b[i] = charset[n.Int64()] } return string(b) } // 全局回收站实例 var globalRecycleBin *RecycleBin // GetRecycleBin 获取全局回收站实例 func GetRecycleBin() *RecycleBin { return globalRecycleBin }