decoupled node data scopes

This commit is contained in:
Alex Goodman 2018-06-16 19:32:38 -04:00
parent 2a67ef9cbb
commit a47105cc7d
No known key found for this signature in database
GPG Key ID: 05328C611D8A520E
9 changed files with 217 additions and 172 deletions

View File

@ -1,84 +0,0 @@
package filetree
import (
"archive/tar"
"bytes"
"crypto/md5"
"fmt"
"io"
)
type FileChangeInfo struct {
Path string
Typeflag byte
MD5sum [16]byte
DiffType DiffType
}
type DiffType int
// enum to show whether a file has changed
const (
Unchanged DiffType = iota
Changed
Added
Removed
)
func NewFileChangeInfo(reader *tar.Reader, header *tar.Header, path string) FileChangeInfo {
if header.Typeflag == tar.TypeDir {
return FileChangeInfo{
Path: path,
Typeflag: header.Typeflag,
MD5sum: [16]byte{},
}
}
fileBytes := make([]byte, header.Size)
_, err := reader.Read(fileBytes)
if err != nil && err != io.EOF {
panic(err)
}
return FileChangeInfo{
Path: path,
Typeflag: header.Typeflag,
MD5sum: md5.Sum(fileBytes),
DiffType: Unchanged,
}
}
func (d DiffType) String() string {
switch d {
case Unchanged:
return "Unchanged"
case Changed:
return "Changed"
case Added:
return "Added"
case Removed:
return "Removed"
default:
return fmt.Sprintf("%d", int(d))
}
}
func (a DiffType) merge(b DiffType) DiffType {
if a == b {
return a
}
return Changed
}
func (a *FileChangeInfo) getDiffType(b *FileChangeInfo) DiffType {
if a == nil && b == nil {
return Unchanged
}
if a == nil || b == nil {
return Changed
}
if a.Typeflag == b.Typeflag {
if bytes.Compare(a.MD5sum[:], b.MD5sum[:]) == 0 {
return Unchanged
}
}
return Changed
}

134
filetree/data.go Normal file
View File

@ -0,0 +1,134 @@
package filetree
import (
"archive/tar"
"bytes"
"crypto/md5"
"fmt"
"io"
)
// enum to show whether a file has changed
const (
Unchanged DiffType = iota
Changed
Added
Removed
)
type NodeData struct {
ViewInfo ViewInfo
FileInfo *FileInfo
DiffType DiffType
}
type ViewInfo struct {
Collapsed bool
Hidden bool
}
type FileInfo struct {
Path string
Typeflag byte
MD5sum [16]byte
}
type DiffType int
func NewNodeData() (*NodeData) {
return &NodeData{
ViewInfo: *NewViewInfo(),
FileInfo: nil,
DiffType: Unchanged,
}
}
func (data *NodeData) Copy() (*NodeData) {
return &NodeData{
ViewInfo: *data.ViewInfo.Copy(),
FileInfo: data.FileInfo.Copy(),
DiffType: data.DiffType,
}
}
func NewViewInfo() (view *ViewInfo) {
return &ViewInfo{
Collapsed: false,
Hidden: false,
}
}
func (view *ViewInfo) Copy() (newView *ViewInfo) {
newView = NewViewInfo()
*newView = *view
return newView
}
func NewFileInfo(reader *tar.Reader, header *tar.Header, path string) FileInfo {
if header.Typeflag == tar.TypeDir {
return FileInfo{
Path: path,
Typeflag: header.Typeflag,
MD5sum: [16]byte{},
}
}
fileBytes := make([]byte, header.Size)
_, err := reader.Read(fileBytes)
if err != nil && err != io.EOF {
panic(err)
}
return FileInfo{
Path: path,
Typeflag: header.Typeflag,
MD5sum: md5.Sum(fileBytes),
}
}
func (d DiffType) String() string {
switch d {
case Unchanged:
return "Unchanged"
case Changed:
return "Changed"
case Added:
return "Added"
case Removed:
return "Removed"
default:
return fmt.Sprintf("%d", int(d))
}
}
func (a DiffType) merge(b DiffType) DiffType {
if a == b {
return a
}
return Changed
}
func (data *FileInfo) Copy() *FileInfo {
if data == nil {
return nil
}
return &FileInfo{
Path: data.Path,
Typeflag: data.Typeflag,
MD5sum: data.MD5sum,
}
}
func (data *FileInfo) getDiffType(other *FileInfo) DiffType {
if data == nil && other == nil {
return Unchanged
}
if data == nil || other == nil {
return Changed
}
if data.Typeflag == other.Typeflag {
if bytes.Compare(data.MD5sum[:], other.MD5sum[:]) == 0 {
return Unchanged
}
}
return Changed
}

View File

@ -28,23 +28,10 @@ func TestMergeDiffTypes(t *testing.T) {
}
}
func TestDiffTypeFromChildren(t *testing.T) {
tree := NewFileTree()
tree.AddPath("/usr", BlankFileChangeInfo("/usr", Unchanged))
info1 := BlankFileChangeInfo("/usr/bin", Added)
tree.AddPath("/usr/bin", info1)
info2 := BlankFileChangeInfo("/usr/bin2", Removed)
tree.AddPath("/usr/bin2", info2)
tree.Root.Children["usr"].deriveDiffType(Unchanged)
if tree.Root.Children["usr"].Data.DiffType != Changed {
t.Errorf("Expected Changed but got %v", tree.Root.Children["usr"].Data.DiffType)
}
}
func AssertDiffType(node *FileNode, expectedDiffType DiffType, t *testing.T) error {
if node.Data == nil {
t.Errorf("Expected *FileChangeInfo but got nil at Path %s", node.Path())
return fmt.Errorf("expected *FileChangeInfo but got nil at Path %s", node.Path())
if node.Data.FileInfo == nil {
t.Errorf("Expected *FileInfo but got nil at Path %s", node.Path())
return fmt.Errorf("expected *FileInfo but got nil at Path %s", node.Path())
}
if node.Data.DiffType != expectedDiffType {
t.Errorf("Expecting node at %s to have DiffType %v, but had %v", node.Path(), expectedDiffType, node.Data.DiffType)
@ -53,12 +40,11 @@ func AssertDiffType(node *FileNode, expectedDiffType DiffType, t *testing.T) err
return nil
}
func BlankFileChangeInfo(path string, diffType DiffType) (f *FileChangeInfo) {
result := FileChangeInfo{
func BlankFileChangeInfo(path string, diffType DiffType) (f *FileInfo) {
result := FileInfo{
Path: path,
Typeflag: 1,
MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
DiffType: diffType,
}
return &result
}

View File

@ -11,19 +11,17 @@ type FileNode struct {
Tree *FileTree
Parent *FileNode
Name string
Collapsed bool
Hidden bool
Data *FileChangeInfo
Data NodeData
Children map[string]*FileNode
}
func NewNode(parent *FileNode, name string, data *FileChangeInfo) (node *FileNode) {
func NewNode(parent *FileNode, name string, data *FileInfo) (node *FileNode) {
node = new(FileNode)
node.Name = name
if data == nil {
data = &FileChangeInfo{}
node.Data = *NewNodeData()
if data != nil {
node.Data.FileInfo = data.Copy()
}
node.Data = data
node.Children = make(map[string]*FileNode)
node.Parent = parent
if parent != nil {
@ -33,21 +31,19 @@ func NewNode(parent *FileNode, name string, data *FileChangeInfo) (node *FileNod
}
func (node *FileNode) Copy() *FileNode {
// newNode := new(FileNode)
// *newNode = *node
// return newNode
newNode := NewNode(node.Parent, node.Name, node.Data)
newNode := NewNode(node.Parent, node.Name, node.Data.FileInfo)
for name, child := range node.Children {
newNode.Children[name] = child.Copy()
child.Parent = newNode
}
return newNode
}
func (node *FileNode) AddChild(name string, data *FileChangeInfo) (child *FileNode) {
func (node *FileNode) AddChild(name string, data *FileInfo) (child *FileNode) {
child = NewNode(node, name, data)
if node.Children[name] != nil {
// tree node already exists, replace the payload, keep the children
node.Children[name].Data = data
node.Children[name].Data.FileInfo = data.Copy()
} else {
node.Children[name] = child
node.Tree.Size++
@ -68,7 +64,7 @@ func (node *FileNode) String() string {
var style *color.Color
if node == nil {
return ""
} else if node.Data == nil {
} else if node.Data.FileInfo == nil {
return node.Name
}
switch node.Data.DiffType {
@ -86,7 +82,7 @@ func (node *FileNode) String() string {
return style.Sprint(node.Name)
}
func (node *FileNode) Visit(visiter Visiter) error {
func (node *FileNode) VisitDepthChildFirst(visiter Visiter) error {
var keys []string
for key := range node.Children {
keys = append(keys, key)
@ -94,7 +90,7 @@ func (node *FileNode) Visit(visiter Visiter) error {
sort.Strings(keys)
for _, name := range keys {
child := node.Children[name]
err := child.Visit(visiter)
err := child.VisitDepthChildFirst(visiter)
if err != nil {
return err
}
@ -165,8 +161,7 @@ func (node *FileNode) deriveDiffType(diffType DiffType) error {
myDiffType := diffType
for _, v := range node.Children {
vData := v.Data
myDiffType = myDiffType.merge(vData.DiffType)
myDiffType = myDiffType.merge(v.Data.DiffType)
}
node.AssignDiffType(myDiffType)
@ -217,5 +212,5 @@ func (a *FileNode) compare(b *FileNode) DiffType {
}
// TODO: fails on nil
return a.Data.getDiffType(b.Data)
return a.Data.FileInfo.getDiffType(b.Data.FileInfo)
}

View File

@ -1,12 +1,14 @@
package filetree
import "testing"
import (
"testing"
)
func TestAddChild(t *testing.T) {
var expected, actual int
tree := NewFileTree()
payload := FileChangeInfo{
payload := FileInfo{
Path: "stufffffs",
}
@ -34,16 +36,16 @@ func TestAddChild(t *testing.T) {
t.Errorf("Expected 'twos' number of children to be %d got %d.", expected, actual)
}
expectedFC := &FileChangeInfo{
expectedFC := &FileInfo{
Path: "stufffffs",
}
actualFC := one.Data
actualFC := one.Data.FileInfo
if *expectedFC != *actualFC {
t.Errorf("Expected 'ones' payload to be %+v got %+v.", expectedFC, actualFC)
}
if *two.Data != *new(FileChangeInfo) {
t.Errorf("Expected 'twos' payload to be nil got %+v.", two.Data)
if two.Data.FileInfo != nil {
t.Errorf("Expected 'twos' payload to be nil got %+v.", two.Data.FileInfo)
}
}
@ -106,3 +108,20 @@ func TestIsWhiteout(t *testing.T) {
t.Errorf("Expected Path '%s' to be a whiteout file", p2.Name)
}
}
func TestDiffTypeFromChildren(t *testing.T) {
tree := NewFileTree()
tree.AddPath("/usr", BlankFileChangeInfo("/usr", Unchanged))
info1 := BlankFileChangeInfo("/usr/bin", Added)
tree.AddPath("/usr/bin", info1)
info2 := BlankFileChangeInfo("/usr/bin2", Removed)
tree.AddPath("/usr/bin2", info2)
tree.Root.Children["usr"].deriveDiffType(Unchanged)
if tree.Root.Children["usr"].Data.DiffType != Changed {
t.Errorf("Expected Changed but got %v", tree.Root.Children["usr"].Data.DiffType)
}
}

View File

@ -69,13 +69,13 @@ func (tree *FileTree) String() string {
sort.Strings(keys)
for idx, name := range keys {
child := node.Children[name]
if child.Hidden {
if child.Data.ViewInfo.Hidden {
continue
}
last := idx == (len(node.Children) - 1)
showCollapsed := child.Collapsed && len(child.Children) > 0
showCollapsed := child.Data.ViewInfo.Collapsed && len(child.Children) > 0
result += renderLine(child.String(), spaces, last, showCollapsed)
if len(child.Children) > 0 && !child.Collapsed {
if len(child.Children) > 0 && !child.Data.ViewInfo.Collapsed {
spacesChild := append(spaces, last)
result += walkTree(child, spacesChild, depth+1)
}
@ -90,7 +90,7 @@ func (tree *FileTree) Copy() *FileTree {
newTree := NewFileTree()
*newTree = *tree
newTree.Root = tree.Root.Copy()
newTree.Visit(func(node *FileNode) error {
newTree.VisitDepthChildFirst(func(node *FileNode) error {
node.Tree = newTree
return nil
})
@ -101,10 +101,12 @@ func (tree *FileTree) Copy() *FileTree {
type Visiter func(*FileNode) error
type VisitEvaluator func(*FileNode) bool
func (tree *FileTree) Visit(visiter Visiter) error {
return tree.Root.Visit(visiter)
// DFS bubble up
func (tree *FileTree) VisitDepthChildFirst(visiter Visiter) error {
return tree.Root.VisitDepthChildFirst(visiter)
}
// DFS sink down
func (tree *FileTree) VisitDepthParentFirst(visiter Visiter, evaluator VisitEvaluator) error {
return tree.Root.VisitDepthParentFirst(visiter, evaluator)
}
@ -117,14 +119,14 @@ func (tree *FileTree) Stack(upper *FileTree) error {
return fmt.Errorf("Cannot remove node %s: %v", node.Path(), err.Error())
}
} else {
newNode, err := tree.AddPath(node.Path(), node.Data)
newNode, err := tree.AddPath(node.Path(), node.Data.FileInfo)
if err != nil {
return fmt.Errorf("Cannot add node %s: %v", newNode.Path(), err.Error())
}
}
return nil
}
return upper.Visit(graft)
return upper.VisitDepthChildFirst(graft)
}
func (tree *FileTree) GetNode(path string) (*FileNode, error) {
@ -142,7 +144,7 @@ func (tree *FileTree) GetNode(path string) (*FileNode, error) {
return node, nil
}
func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, error) {
func (tree *FileTree) AddPath(path string, data *FileInfo) (*FileNode, error) {
// fmt.Printf("ADDPATH: %s %+v\n", path, data)
nodeNames := strings.Split(path, "/")
node := tree.Root
@ -161,7 +163,7 @@ func (tree *FileTree) AddPath(path string, data *FileChangeInfo) (*FileNode, err
// attach payload to the last specified node
if idx == len(nodeNames)-1 {
node.Data = data
node.Data.FileInfo = data
}
}
@ -186,7 +188,7 @@ func (tree *FileTree) Compare(upper *FileTree) error {
} else {
existingNode, _ := tree.GetNode(node.Path())
if existingNode == nil {
newNode, err := tree.AddPath(node.Path(), node.Data)
newNode, err := tree.AddPath(node.Path(), node.Data.FileInfo)
// 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())
@ -200,7 +202,7 @@ func (tree *FileTree) Compare(upper *FileTree) error {
}
return nil
}
return upper.Visit(graft)
return upper.VisitDepthChildFirst(graft)
}
func (tree *FileTree) MarkRemoved(path string) error {

View File

@ -86,7 +86,7 @@ func TestRemovePath(t *testing.T) {
func TestStack(t *testing.T) {
payloadKey := "/var/run/systemd"
payloadValue := FileChangeInfo{
payloadValue := FileInfo{
Path: "yup",
}
@ -128,8 +128,8 @@ func TestStack(t *testing.T) {
t.Errorf("Expected '%s' to still exist, but it doesn't", payloadKey)
}
if *node.Data != payloadValue {
t.Errorf("Expected '%s' value to be %+v but got %+v", payloadKey, payloadValue, node.Data)
if *node.Data.FileInfo != payloadValue {
t.Errorf("Expected '%s' value to be %+v but got %+v", payloadKey, payloadValue, node.Data.FileInfo)
}
actual := tree1.String()
@ -177,11 +177,10 @@ func TestCompareWithNoChanges(t *testing.T) {
paths := [...]string{"/etc", "/etc/sudoers", "/etc/hosts", "/usr/bin", "/usr/bin/bash", "/usr"}
for _, value := range paths {
fakeData := FileChangeInfo{
fakeData := FileInfo{
Path: value,
Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
DiffType: Unchanged,
}
lowerTree.AddPath(value, &fakeData)
upperTree.AddPath(value, &fakeData)
@ -191,16 +190,16 @@ func TestCompareWithNoChanges(t *testing.T) {
if n.Path() == "/" {
return nil
}
if n.Data == nil {
t.Errorf("Expected *FileChangeInfo but got nil")
return fmt.Errorf("expected *FileChangeInfo but got nil")
if n.Data.FileInfo == nil {
t.Errorf("Expected *FileInfo but got nil")
return fmt.Errorf("expected *FileInfo but got nil")
}
if (n.Data.DiffType) != Unchanged {
t.Errorf("Expecting node at %s to have DiffType unchanged, but had %v", n.Path(), n.Data.DiffType)
}
return nil
}
err := lowerTree.Visit(asserter)
err := lowerTree.VisitDepthChildFirst(asserter)
if err != nil {
t.Error(err)
}
@ -213,21 +212,19 @@ func TestCompareWithAdds(t *testing.T) {
upperPaths := [...]string{"/etc", "/etc/sudoers", "/usr", "/etc/hosts", "/usr/bin", "/usr/bin/bash"}
for _, value := range lowerPaths {
fakeData := FileChangeInfo{
fakeData := FileInfo{
Path: value,
Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
DiffType: Unchanged,
}
lowerTree.AddPath(value, &fakeData)
}
for _, value := range upperPaths {
fakeData := FileChangeInfo{
fakeData := FileInfo{
Path: value,
Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
DiffType: Unchanged,
}
upperTree.AddPath(value, &fakeData)
}
@ -251,7 +248,7 @@ func TestCompareWithAdds(t *testing.T) {
}
return AssertDiffType(n, Unchanged, t)
}
err := lowerTree.Visit(asserter)
err := lowerTree.VisitDepthChildFirst(asserter)
if err != nil {
t.Error(err)
}
@ -264,21 +261,19 @@ func TestCompareWithChanges(t *testing.T) {
upperPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"}
for _, value := range lowerPaths {
fakeData := FileChangeInfo{
fakeData := FileInfo{
Path: value,
Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
DiffType: Unchanged,
}
lowerTree.AddPath(value, &fakeData)
}
for _, value := range upperPaths {
fakeData := FileChangeInfo{
fakeData := FileInfo{
Path: value,
Typeflag: 1,
MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
DiffType: Unchanged,
}
upperTree.AddPath(value, &fakeData)
}
@ -291,7 +286,7 @@ func TestCompareWithChanges(t *testing.T) {
}
return AssertDiffType(n, Changed, t)
}
err := lowerTree.Visit(asserter)
err := lowerTree.VisitDepthChildFirst(asserter)
if err != nil {
t.Error(err)
}
@ -315,21 +310,19 @@ func TestStackRange(t *testing.T) {
upperPaths := [...]string{"/etc", "/usr", "/etc/hosts", "/etc/sudoers", "/usr/bin"}
for _, value := range lowerPaths {
fakeData := FileChangeInfo{
fakeData := FileInfo{
Path: value,
Typeflag: 1,
MD5sum: [16]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0},
DiffType: Unchanged,
}
lowerTree.AddPath(value, &fakeData)
}
for _, value := range upperPaths {
fakeData := FileChangeInfo{
fakeData := FileInfo{
Path: value,
Typeflag: 1,
MD5sum: [16]byte{1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0},
DiffType: Unchanged,
}
upperTree.AddPath(value, &fakeData)
}

View File

@ -184,8 +184,8 @@ func saveImage(imageID string) (string, string) {
return imageTarPath, tmpDir
}
func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileChangeInfo {
var files []filetree.FileChangeInfo
func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileInfo {
var files []filetree.FileInfo
var tarredBytes = make([]byte, h.Size)
_, err := parentReader.Read(tarredBytes)
@ -214,7 +214,7 @@ func getFileList(parentReader *tar.Reader, h *tar.Header) []filetree.FileChangeI
case tar.TypeXHeader:
fmt.Printf("ERRG: XHeader: %v: %s\n", header.Typeflag, name)
default:
files = append(files, filetree.NewFileChangeInfo(tarReader, header, name))
files = append(files, filetree.NewFileInfo(tarReader, header, name))
}
}
return files

View File

@ -81,11 +81,11 @@ func (view *FileTreeView) setLayer(layerIndex int) error {
visitor := func(node *filetree.FileNode) error {
newNode, err := newTree.GetNode(node.Path())
if err == nil {
newNode.Collapsed = node.Collapsed
newNode.Data.ViewInfo.Collapsed = node.Data.ViewInfo.Collapsed
}
return nil
}
view.Tree.Visit(visitor)
view.Tree.VisitDepthChildFirst(visitor)
// now that the tree has been rebuilt, keep the view seleciton in parity with the previous selection
view.setHiddenFromDiffTypes()
@ -132,7 +132,7 @@ func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) {
}
evaluator = func(curNode *filetree.FileNode) bool {
return !curNode.Collapsed && !curNode.Hidden
return !curNode.Data.ViewInfo.Collapsed && !curNode.Data.ViewInfo.Hidden
}
err := view.Tree.VisitDepthParentFirst(visiter, evaluator)
@ -145,16 +145,16 @@ func (view *FileTreeView) getAbsPositionNode() (node *filetree.FileNode) {
func (view *FileTreeView) toggleCollapse() error {
node := view.getAbsPositionNode()
node.Collapsed = !node.Collapsed
node.Data.ViewInfo.Collapsed = !node.Data.ViewInfo.Collapsed
return view.Render()
}
func (view *FileTreeView) setHiddenFromDiffTypes() error {
visitor := func(node *filetree.FileNode) error {
node.Hidden = view.HiddenDiffTypes[node.Data.DiffType]
node.Data.ViewInfo.Hidden = view.HiddenDiffTypes[node.Data.DiffType]
return nil
}
view.Tree.Visit(visitor)
view.Tree.VisitDepthChildFirst(visitor)
return view.Render()
}