~hrodude/dui

8712836f830501bf27dd67953bcaa78931f0a4a0 — Georg Kaemmert 2 months ago
safe game / funktionierender (messy) Zwischenstand
3 files changed, 448 insertions(+), 0 deletions(-)

A go.mod
A main.go
A tree/tree.go
A  => go.mod +3 -0
@@ 1,3 @@
module dui

go 1.19

A  => main.go +67 -0
@@ 1,67 @@
package main

import (
	"dui/tree"
	"flag"
	"log"
)

func listUpdates(idx1, idx2 *tree.Node) map[string]*tree.Node {
	updates := make(map[string]*tree.Node)
	for node := range idx1.Iterate() {
		updates[node.PrintPath()] = node
	}
	return updates
}

func main() {
	fileFlag := flag.String("f", "", "index file to create; defaults to the last element of the indexed directory (base).idx; if used with -c both files are compared")
	compareFileFlag := flag.String("c", "", "index file to compare; -f must also be used")
	folderFlag := flag.String("d", "", " directory to index")
	flag.Parse()

	if len(*fileFlag) > 0 && len(*compareFileFlag) == 0 {
		//create index
		if len(*folderFlag) == 0 {
			log.Fatal("-f has to be used with -c or -d")
		}
		log.Println("walking directory")
		idx, _ := tree.CreateTreeFromFS(*folderFlag)
		log.Println("writing index")
		idx.Serialize(*fileFlag)
		log.Println("finished")
	} else if len(*fileFlag) > 0 && len(*compareFileFlag) > 0 {
		//compare
		idx1, err := tree.Deserialize(*fileFlag)
		if err != nil {
			log.Fatal(err)
		}
		idx2, err := tree.Deserialize(*compareFileFlag)
		if err != nil {
			log.Fatal(err)
		}
		//idx1.PrintPaths()

		//log.Printf("TreeEquals: %t", idx1.TreeEquals(idx2))
		//log.Printf("TreeContains: %t", idx1.TreeContains(idx1))
		//log.Println(idx2.TreeEquals(idx1))

		//idx2.PrintFiles()
		//path1 := idx1.Children[0].Children[0].Children[0].Children[0].Children[0].ChainFromRoot(false)
		//path2 := idx2.Children[0].Children[0].Children[0].Children[2].Children[0].ChainFromRoot(false)
		//path1.MergeNode(path2)
		//path1.PrintFiles()

		dui := idx1.GetChanges(idx2)
		log.Println("Inserts")
		dui.Inserts.PrintFiles()
		log.Println("Updates")
		dui.Updates.PrintFiles()
		log.Println("Deletes")
		dui.Deletes.PrintFiles()

		//for k, v := range listUpdates(idx1, idx2) {
		//	log.Printf("%s, %p\n", k,v)
		//}
	}
}

A  => tree/tree.go +378 -0
@@ 1,378 @@
package tree

import (
	"encoding/gob"
	"log"
	"os"
	"path/filepath"
	"strings"
	"time"
)

type Node struct {
	Name     string
	IsFile   bool
	ModTime  time.Time
	Size     int64
	Parent   *Node
	Children []*Node
	visited  bool
}

type DUITrees struct {
	Inserts *Node
	Updates *Node
	Deletes *Node
}

////////////////////////////////////
// file system operations
////////////////////////////////////

func (node *Node) AddFile(path string, file os.FileInfo) {
	//log.Printf("addFile(%s, %s, %#v)", path, file.Name(), node)
	pathElems := strings.Split(path, string(os.PathSeparator))
	leave := &Node{Name: file.Name(), IsFile: true, ModTime: file.ModTime(), Size: file.Size()}
	node.AddNodeWithPath(pathElems[1:], leave) // first element would be empty
}

func CreateTreeFromFS(dir string) (*Node, error) {
	index := &Node{Name: "", IsFile: false}
	err := filepath.Walk(dir, func(path string, info os.FileInfo, err error) error {
		if !info.IsDir() {
			index.AddFile(filepath.Dir(path), info)
		}
		return nil
	})
	return index, err
}

////////////////////////////////////
// de/serialize
////////////////////////////////////

func (node *Node) Serialize(file string) error {
	node.UnsetParents()
	fh, err := os.Create(file)
	defer fh.Close()
	if err != nil {
		return err
	}
	encoder := gob.NewEncoder(fh)
	encoder.Encode(node)
	return nil
}

func Deserialize(file string) (*Node, error) {
	fh, err := os.Open(file)
	defer fh.Close()
	if err != nil {
		return nil, err
	}
	decoder := gob.NewDecoder(fh)
	idx := &Node{}
	decoder.Decode(idx)
	idx.SetParents()
	return idx, nil
}

func (node *Node) SetParents() {
	queue := make([]*Node, 0)
	queue = append(queue, node)
	for len(queue) > 0 {
		currentNode := queue[0]
		queue = queue[1:]
		for _, child := range currentNode.Children {
			child.Parent = currentNode
		}
		queue = append(queue, currentNode.Children...)
	}
}

func (node *Node) UnsetParents() {
	queue := make([]*Node, 0)
	queue = append(queue, node)
	for len(queue) > 0 {
		currentNode := queue[0]
		queue = queue[1:]
		for _, child := range currentNode.Children {
			child.Parent = nil
		}
		queue = append(queue, currentNode.Children...)
	}
}

////////////////////////////////////
// insert
////////////////////////////////////

/*
Add new leave to path
*/
func (node *Node) AddNodeWithPath(path []string, leave *Node) {
	//log.Printf("AddNode(%v, %v)\n", path, leave)
	pathLeave := node.AddPathWithString(path)
	pathLeave.AddChild(leave)
}

func (node *Node) AddChild(child *Node) {
	child.Parent = node
	node.Children = append(node.Children, child)
}

/*
Create path inside node, return leave node of path
*/
func (node *Node) AddPathWithString(path []string) *Node {
	//log.Println("AddPath(%v)",path)
	var returnLeave *Node
	if len(path) > 0 {
		var child *Node
		var childFound bool
		childFound, child = node.HasChild(path[0])
		if !childFound {
			child = &Node{Name: path[0], IsFile: false}
			node.AddChild(child)
		}
		returnLeave = child.AddPathWithString(path[1:])
	} else {
		returnLeave = node
	}
	return returnLeave
}

/*
Merge mergeNode into node, both nodes must represent absolut paths
*/
func (node *Node) MergeNode(mergeNode *Node) {
	//root-Nodes have Name:""
	if node.Name == mergeNode.Name {
		for _, mergeChild := range mergeNode.Children {
			hasChild, child := node.HasChild(mergeChild.Name)
			if !hasChild {
				node.Children = append(node.Children, mergeChild)
			} else {
				child.MergeNode(mergeChild)
			}
		}
	}
}

////////////////////////////////////
// print tree
////////////////////////////////////

/*
prints all files/leaves Node.IsFile == true
*/
func (node *Node) PrintFiles() {
	printIndex(node, "")
}

func printIndex(node *Node, path string) {
	//      log.Printf("printIndex(%#v, %s)", node, path)
	var newPath string
	if node.Name != "" {
		newPath = strings.Join([]string{path, node.Name}, string(os.PathSeparator))
	}
	if node.IsFile {
		log.Println(newPath)
	} else {
		for _, child := range node.Children {
			printIndex(child, newPath)
		}
	}
}

/*
returns path for one node
*/
func (node *Node) PrintPath() string {
	var path string
	currentNode := node
	for currentNode.Parent != nil {
		//path += strings.Join(string(os.PathSeparator), node.Name)
		path = string(os.PathSeparator) + currentNode.Name + path
		currentNode = currentNode.Parent
	}
	return path
}

/*
returns the chain from root to node
*/
func (node *Node) ChainFromRoot(withChildren bool) *Node {
	//log.Printf("ChainFromRoot(%s, %t)",node.Name, withChildren)
	s := make([]*Node, 0)
	currentNode := node.Copy()

	//collect parents
	//log.Println("Eltern enquenen")
	for currentNode != nil {
		//log.Printf("currentNode.Parent: %s", currentNode.Parent.Name)
		s = append(s, currentNode)
		currentNode = currentNode.Parent
	}

	s = s[:len(s)-1]
	//reverse chain, modify children
	root := &Node{}
	returnRoot := root
	var copyOfCurrentNode *Node
	for len(s) > 0 {
		currentNode = s[len(s)-1]
		copyOfCurrentNode = currentNode.Copy()
		copyOfCurrentNode.Parent = nil
		if len(s) > 1 && !withChildren {
			copyOfCurrentNode.Children = nil
		}
		root.Children = []*Node{copyOfCurrentNode}
		s = s[:len(s)-1]
		root = copyOfCurrentNode
	}
	return returnRoot
}

func (node *Node) Iterate() <-chan *Node {
	ch := make(chan *Node, 100)
	go func() {
		queue := make([]*Node, 0)
		queue = append(queue, node)
		for len(queue) > 0 {
			n := queue[0]
			queue = queue[1:]
			queue = append(queue, n.Children...)
			ch <- n
		}
		close(ch)
	}()
	return ch
}

////////////////////////////////////
// copy
////////////////////////////////////

/*
returns new pointer to new Node-Object with same properties like the old one
*/
func (node *Node) Copy() *Node {
	return &Node{Name: node.Name, IsFile: node.IsFile, ModTime: node.ModTime, Size: node.Size, Parent: node.Parent, Children: node.Children}
}

////////////////////////////////////
// compare tree
////////////////////////////////////

/*
Check if 'node' has child with 'name'
*/
func (node *Node) HasChild(name string) (bool, *Node) {
	//log.Printf("HasChild(node:%s,name:%s)",node.Name, name)
	var returnChild *Node
	for _, child := range node.Children {
		//log.Printf("HasChild range children %s", child.Name)
		if child.Name == name {
			returnChild = child
			break
		}
	}
	//log.Printf("HasChild(return %t, %s)",(returnChild != nil), returnChild)
	return (returnChild != nil), returnChild
}

/*
Check if nodes are equal based on
Name, IsFile, Parent
*/
func (node *Node) NodeEquals(checkNode *Node) bool {
	var ret bool
	if node.Name == checkNode.Name && node.IsFile == checkNode.IsFile {
		if node.Parent == nil && checkNode.Parent == nil {
			ret = true
		} else if node.Parent.Name == checkNode.Parent.Name {
			ret = true
		} else {
			ret = false
		}
	} else {
		ret = false
	}
	return ret
}

/*
Check if node does contain everything inside checkNode
*/
func (node *Node) TreeContains(checkNode *Node) bool {
	//log.Printf("TreeEquals(%s,%s)\n", node.Name, checkNode.Name)
	var ret bool
	if node.NodeEquals(checkNode) {
		ret = true
		for _, child := range node.Children {
			//log.Printf("TreeEquals range children %s\n",child.Name)
			hasChild, checkChild := checkNode.HasChild(child.Name)
			if hasChild {
				ret = child.TreeEquals(checkChild)
			} else {
				ret = false
				break
			}
		}
	} else {
		ret = false
	}
	return ret
}

func (tree *Node) TreeEquals(checkTree *Node) bool {
	return tree.TreeContains(checkTree) && checkTree.TreeContains(tree)
}

/*
compare two trees, create new tree with differences
*/
func (node *Node) GetChanges(newNode *Node) *DUITrees {
	type nodePair struct {
		node          *Node
		corresponding *Node
	}

	dui := &DUITrees{Inserts: &Node{}, Updates: &Node{}, Deletes: &Node{}}
	workingQueue := make([]*nodePair, 0)
	workingQueue = append(workingQueue, &nodePair{node: node, corresponding: newNode})
	nextQueue := make([]*nodePair, 0)

	for len(workingQueue) > 0 {
		nextQueue = nil
		//Breitensuche, compare childs of node of same levels
		for _, nodes := range workingQueue {
			node1 := nodes.node
			node2 := nodes.corresponding
			for _, child1 := range node1.Children {
				hasChild, child2 := node2.HasChild(child1.Name)
				if hasChild {
					//UPDATE
					if child1.ModTime != child2.ModTime {
						dui.Updates.MergeNode(child2.ChainFromRoot(true))
					} else {
						nextQueue = append(nextQueue, &nodePair{node: child1, corresponding: child2})
					}
				} else {
					//DELETE
					dui.Deletes.MergeNode(child1.ChainFromRoot(true))
				}
			}
			for _, child2 := range node2.Children {
				hasChild, _ := node1.HasChild(child2.Name)
				if !hasChild {
					//INSERT
					dui.Inserts.MergeNode(child2.ChainFromRoot(true))
				}
			}
		}

		workingQueue = nextQueue
	}
	return dui
}