Files
RawMark/internal/vault/trash.go
T

82 lines
2.2 KiB
Go

package vault
import (
"encoding/json"
"errors"
"fmt"
"os"
"path/filepath"
"time"
)
// 调用方持有写锁并已读取验证元数据。先删除内容,失败时保留条目的管理依据。
func (v *Vault) purgeTrashItemLocked(metadata trashMetadata) error {
dir := filepath.Join(v.trash, metadata.ID)
entries, err := os.ReadDir(dir)
if err != nil {
return err
}
for _, entry := range entries {
if entry.Name() == "meta.json" {
continue
}
if err := os.RemoveAll(filepath.Join(dir, entry.Name())); err != nil {
return err
}
}
metaPath := filepath.Join(dir, "meta.json")
if err := os.Remove(metaPath); err != nil {
return err
}
if err := os.Remove(dir); err != nil {
// 父目录不可写等原因仍可能阻止最后的 rmdir,恢复元数据以便继续清理。
data, _ := json.Marshal(metadata)
return errors.Join(err, atomicWrite(metaPath, data))
}
return nil
}
type TrashFailure struct {
ID string `json:"id"`
Error string `json:"error"`
}
type TrashPurgeResult struct {
Deleted []string `json:"deleted"`
Failed []TrashFailure `json:"failed"`
}
// PurgeTrash 在与恢复相同的写锁内读取元数据并清理。days=0 清空所有;
// 自动清理的“永不删除”由调用方处理,不调用本方法。
func (v *Vault) PurgeTrash(days int64, now time.Time) (TrashPurgeResult, error) {
v.mu.Lock()
defer v.mu.Unlock()
result := TrashPurgeResult{Deleted: []string{}, Failed: []TrashFailure{}}
if days < 0 {
return result, fmt.Errorf("保留天数不能为负数")
}
dirs, err := os.ReadDir(v.trash)
if err != nil {
return result, err
}
for _, dir := range dirs {
id := dir.Name()
if !trashIDPattern.MatchString(id) {
continue // 不删除非本程序管理的文件。
}
metadata, err := v.readTrashMetadata(id)
if err == nil && days > 0 && (metadata.DeletedAt > now.UnixMilli() || (now.UnixMilli()-metadata.DeletedAt)/int64(24*time.Hour/time.Millisecond) < days) {
continue
}
if err == nil {
err = v.purgeTrashItemLocked(metadata)
}
if err != nil {
result.Failed = append(result.Failed, TrashFailure{ID: id, Error: err.Error()})
} else {
result.Deleted = append(result.Deleted, id)
}
}
return result, nil
}