dive-zfs/filetree/tree.go

218 lines
5.0 KiB
Go

package filetree
import (
"errors"
"fmt"
"sort"
"strings"
)
const (
newLine = "\n"
noBranchSpace = " "
branchSpace = "│ "
middleItem = "├─"
lastItem = "└─"
whiteoutPrefix = ".wh."
uncollapsedItem = "─ "
collapsedItem = "⊕ "
)
type FileTree struct {
Root *FileNode
Size int
Name string
}
func NewFileTree() (tree *FileTree) {
tree = new(FileTree)
tree.Size = 0
tree.Root = new(FileNode)
tree.Root.Tree = tree
tree.Root.Children = make(map[string]*FileNode)
return tree
}
func (tree *FileTree) String() string {
var renderLine func(string, []bool, bool, bool) string
var walkTree func(*FileNode, []bool, int) string
renderLine = func(nodeText string, spaces []bool, last bool, collapsed bool) string {
var otherBranches string
for _, space := range spaces {
if space {
otherBranches += noBranchSpace
} else {
otherBranches += branchSpace
}
}
thisBranch := middleItem
if last {
thisBranch = lastItem
}
collapsedIndicator := uncollapsedItem
if collapsed {
collapsedIndicator = collapsedItem
}
return otherBranches + thisBranch + collapsedIndicator + nodeText + newLine
}
walkTree = func(node *FileNode, spaces []bool, depth int) string {
var result string
var keys []string
for key := range node.Children {
keys = append(keys, key)
}
sort.Strings(keys)
for idx, name := range keys {
child := node.Children[name]
last := idx == (len(node.Children) - 1)
showCollapsed := child.Collapsed && len(child.Children) > 0
result += renderLine(child.String(), spaces, last, showCollapsed)
if len(child.Children) > 0 && !child.Collapsed {
spacesChild := append(spaces, last)
result += walkTree(child, spacesChild, depth+1)
}
}
return result
}
return "." + newLine + walkTree(tree.Root, []bool{}, 0)
}
func (tree *FileTree) Copy() *FileTree {
newTree := NewFileTree()
*newTree = *tree
newTree.Root = tree.Root.Copy()
newTree.Visit(func(node *FileNode) error {
node.Tree = newTree
return nil
})
return newTree
}
type Visiter func(*FileNode) error
type VisitEvaluator func(*FileNode) bool
func (tree *FileTree) Visit(visiter Visiter) error {
return tree.Root.Visit(visiter)
}
func (tree *FileTree) VisitDepthParentFirst(visiter Visiter, evaluator VisitEvaluator) error {
return tree.Root.VisitDepthParentFirst(visiter, evaluator)
}
func (tree *FileTree) Stack(upper *FileTree) error {
graft := func(node *FileNode) error {
if node.IsWhiteout() {
err := tree.RemovePath(node.Path())
if err != nil {
return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error())
}
} else {
newNode, err := tree.AddPath(node.Path(), node.Data)
if err != nil {
return fmt.Errorf("Cannot add node %s: %v", newNode.Path(), err.Error())
}
}
return nil
}
return upper.Visit(graft)
}
func (tree *FileTree) GetNode(path string) (*FileNode, error) {
nodeNames := strings.Split(path, "/")
node := tree.Root
for _, name := range nodeNames {
if name == "" {
continue
}
if node.Children[name] == nil {
return nil, errors.New("Path does not exist")
}
node = node.Children[name]
}
return node, nil
}
func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, error) {
// fmt.Printf("ADDPATH: %s %+v\n", path, data)
nodeNames := strings.Split(path, "/")
node := tree.Root
for idx, name := range nodeNames {
if name == "" {
continue
}
// find or create node
if node.Children[name] != nil {
node = node.Children[name]
} else {
// don't attach the payload. The payload is destined for the
// Path's end node, not any intermediary node.
node = node.AddChild(name, nil)
}
// attach payload to the last specified node
if idx == len(nodeNames)-1 {
node.Data = data
}
}
return node, nil
}
func (tree *FileTree) RemovePath(path string) error {
node, err := tree.GetNode(path)
if err != nil {
return err
}
return node.Remove()
}
func (tree *FileTree) Compare(upper *FileTree) error {
graft := func(node *FileNode) error {
if node.IsWhiteout() {
err := tree.MarkRemoved(node.Path())
if err != nil {
return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error())
}
} else {
existingNode, _ := tree.GetNode(node.Path())
if existingNode == nil {
newNode, err := tree.AddPath(node.Path(), node.Data)
// fmt.Printf("added new node at %s\n", newNode.Path())
if err != nil {
return fmt.Errorf("Cannot add new node %s: %v", node.Path(), err.Error())
}
newNode.AssignDiffType(Added)
} else {
diffType := existingNode.compare(node)
// fmt.Printf("found existing node at %s\n", existingNode.Path())
existingNode.deriveDiffType(diffType)
}
}
return nil
}
return upper.Visit(graft)
}
func (tree *FileTree) MarkRemoved(path string) error {
node, err := tree.GetNode(path)
if err != nil {
return err
}
return node.AssignDiffType(Removed)
}
func StackRange(trees []*FileTree, index int) *FileTree {
tree := trees[0].Copy()
for idx := 0; idx <= index; idx++ {
tree.Stack(trees[idx])
}
return tree
}