Files
HomeAgent/internal/tracker/changeset.go
JianFeeeee 4e70b8d5d8 refactor(module): 核心仓 Go module 路径改为 github.com/JianFeeeee/HologramAgent
批 1,不含 SDK 仓与目录改名。

- go.mod 第 1 行 module 路径
- 366 个 .go 文件的完整前缀 github.com/JianFeeeee/HomeAgent 机械替换
  (857 处,仅完整前缀;展示名/注释里的 HomeAgent 字样留给批 5)
- 未改:third_party 目录名、SDK 自身 go.mod module、go.mod replace 目标、
  homed/waiter 可执行名、.hmap 后缀
2026-10-21 12:17:19 +08:00

150 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package tracker
import (
"crypto/sha256"
"encoding/hex"
"fmt"
"os"
"path/filepath"
"time"
sdk "github.com/JianFeeeee/HologramAgent/internal/sdk"
)
// DTO 已迁入内置 SDK,此处保留别名以兼容现有调用方。
type ChangeType = sdk.ChangeType
const (
ChangeFileCreated = sdk.ChangeFileCreated
ChangeFileModified = sdk.ChangeFileModified
ChangeFileDeleted = sdk.ChangeFileDeleted
)
type FileChange = sdk.FileChange
type ChangeSet = sdk.ChangeSet
func NewChangeSet(action string) *ChangeSet {
return &ChangeSet{
ID: fmt.Sprintf("cs_%d", time.Now().UnixNano()),
Action: action,
Timestamp: time.Now(),
}
}
func fileHash(path string) (string, int64, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", 0, err
}
h := sha256.Sum256(data)
return hex.EncodeToString(h[:]), int64(len(data)), nil
}
// maxCapturedContent 回滚内容捕获上限:超大文件不保存原文(回滚时跳过并告警)。
const maxCapturedContent = 8 << 20
func fileInfo(path string) (size int64, modTime time.Time, err error) {
info, err := os.Stat(path)
if err != nil {
return 0, time.Time{}, err
}
return info.Size(), info.ModTime(), nil
}
type FSState struct {
Files map[string]FileChange `json:"files"`
Root string `json:"root"`
}
// captureFSState 仅记录哈希/尺寸(用于"之后"快照,省内存)。
func captureFSState(root string) (*FSState, error) {
state := &FSState{
Files: make(map[string]FileChange),
Root: root,
}
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil || info.IsDir() {
return err
}
rel, _ := filepath.Rel(root, path)
hash, size, err := fileHash(path)
if err != nil {
return nil
}
state.Files[rel] = FileChange{
Path: rel,
HashAfter: hash,
SizeAfter: size,
}
return nil
})
return state, err
}
// captureFSStateWithContent 额外捕获文件原文(用于"之前"基线,供回滚还原被改/被删文件)。
func captureFSStateWithContent(root string) (*FSState, error) {
state, err := captureFSState(root)
if err != nil {
return nil, err
}
for rel := range state.Files {
path := filepath.Join(root, rel)
info, err := os.Stat(path)
if err != nil || info.Size() > maxCapturedContent {
continue
}
if data, err := os.ReadFile(path); err == nil {
fc := state.Files[rel]
fc.Content = data
state.Files[rel] = fc
}
}
return state, nil
}
func diffStates(before, after *FSState) []FileChange {
var changes []FileChange
if before == nil || after == nil {
return changes
}
seen := make(map[string]bool)
for path, afterFile := range after.Files {
seen[path] = true
if beforeFile, ok := before.Files[path]; ok {
if beforeFile.HashAfter != afterFile.HashAfter {
changes = append(changes, FileChange{
Path: path,
Type: ChangeFileModified,
HashBefore: beforeFile.HashAfter,
HashAfter: afterFile.HashAfter,
SizeBefore: beforeFile.SizeAfter,
SizeAfter: afterFile.SizeAfter,
Content: beforeFile.Content, // 原始内容,供回滚还原
})
}
} else {
changes = append(changes, FileChange{
Path: path,
Type: ChangeFileCreated,
HashAfter: afterFile.HashAfter,
SizeAfter: afterFile.SizeAfter,
})
}
}
for path := range before.Files {
if !seen[path] {
changes = append(changes, FileChange{
Path: path,
Type: ChangeFileDeleted,
HashBefore: before.Files[path].HashAfter,
SizeBefore: before.Files[path].SizeAfter,
Content: before.Files[path].Content, // 原始内容,供回滚还原
})
}
}
return changes
}