@@ 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)
+ //}
+ }
+}
@@ 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
+}