重构:文件系统模块化架构,优化应用启动流程
This commit is contained in:
392
internal/filesystem/recycle_bin.go
Normal file
392
internal/filesystem/recycle_bin.go
Normal file
@@ -0,0 +1,392 @@
|
||||
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
|
||||
|
||||
// InitRecycleBin 初始化全局回收站
|
||||
func InitRecycleBin(binPath string) error {
|
||||
bin, err := NewRecycleBin(binPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
globalRecycleBin = bin
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetRecycleBin 获取全局回收站实例
|
||||
func GetRecycleBin() *RecycleBin {
|
||||
return globalRecycleBin
|
||||
}
|
||||
Reference in New Issue
Block a user