浏览代码

Added initial logic and tests for the Walker

John Episcopo 6 年之前
父节点
当前提交
e6e290ca7f
共有 3 个文件被更改,包括 245 次插入0 次删除
  1. 14 0
      ftp.go
  2. 84 0
      walker.go
  3. 147 0
      walker_test.go

+ 14 - 0
ftp.go

@@ -680,6 +680,20 @@ func (c *ServerConn) RemoveDir(path string) error {
 	return err
 }
 
+//Walk prepares the internal walk function so that the caller can begin traversing the directory
+func (c *ServerConn) Walk(root string) *Walker {
+	w := new(Walker)
+	w.serverConn = c
+
+	if !strings.HasSuffix(root, "/") {
+		root += "/"
+	}
+
+	w.root = root
+
+	return w
+}
+
 // NoOp issues a NOOP FTP command.
 // NOOP has no effects and is usually used to prevent the remote FTP server to
 // close the otherwise idle connection.

+ 84 - 0
walker.go

@@ -0,0 +1,84 @@
+package ftp
+
+import (
+	"fmt"
+	"strings"
+)
+
+//Walker traverses the directory tree of a remote FTP server
+type Walker struct {
+	serverConn *ServerConn
+	root       string
+	cur        item
+	stack      []item
+	descend    bool
+}
+
+type item struct {
+	path  string
+	entry Entry
+	err   error
+}
+
+// Step advances the Walker to the next file or directory,
+// which will then be available through the Path, Stat, and Err methods.
+// It returns false when the walk stops at the end of the tree.
+func (w *Walker) Step() bool {
+	if w.descend && w.cur.err == nil && w.cur.entry.Type == EntryTypeFolder {
+		list, err := w.serverConn.List(w.cur.path)
+		if err != nil {
+			w.cur.err = nil
+			w.stack = append(w.stack, w.cur)
+		} else {
+			for i := len(list) - 1; i >= 0; i-- {
+				if !strings.HasSuffix(w.cur.path, "/") {
+					w.cur.path += "/"
+				}
+
+				var path string
+				if list[i].Type == EntryTypeFolder {
+					path = fmt.Sprintf("%s%s", w.cur.path, list[i].Name)
+				} else {
+					path = w.cur.path
+				}
+
+				w.stack = append(w.stack, item{path, *list[i], nil})
+			}
+		}
+	}
+
+	if len(w.stack) == 0 {
+		return false
+	}
+	i := len(w.stack) - 1
+	w.cur = w.stack[i]
+	w.stack = w.stack[:i]
+	w.descend = true
+	return true
+}
+
+//SkipDir tells the step function to skip the currently processed directory
+func (w *Walker) SkipDir() {
+	w.descend = false
+}
+
+//Err returns the error, if any, for the most recent attempt by Step to
+//visit a file or a directory. If a directory has an error, the walker
+//will not descend in that directory
+func (w *Walker) Err() error {
+	return w.cur.err
+}
+
+// Stat returns info for the most recent file or directory
+// visited by a call to Step.
+func (w *Walker) Stat() Entry {
+	return w.cur.entry
+}
+
+// Path returns the path to the most recent file or directory
+// visited by a call to Step. It contains the argument to Walk
+// as a prefix; that is, if Walk is called with "dir", which is
+// a directory containing the file "a", Path will return "dir/a".
+func (w *Walker) Path() string {
+	return w.cur.path
+}

+ 147 - 0
walker_test.go

@@ -0,0 +1,147 @@
+package ftp
+
+import (
+	"fmt"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestFieldsReturnCorrectData(t *testing.T) {
+	w := Walker{
+		cur: item{
+			path: "/root/",
+			err:  fmt.Errorf("This is an error"),
+			entry: Entry{
+				Name: "root",
+				Size: 123,
+				Time: time.Now(),
+				Type: EntryTypeFolder,
+			},
+		},
+	}
+
+	assert.Equal(t, "This is an error", w.Err().Error())
+	assert.Equal(t, "/root/", w.Path())
+	assert.Equal(t, EntryTypeFolder, w.Stat().Type)
+}
+
+func TestSkipDirIsCorrectlySet(t *testing.T) {
+	w := Walker{}
+
+	w.SkipDir()
+
+	assert.Equal(t, false, w.descend)
+}
+
+func TestNoDescendDoesNotAddToStack(t *testing.T) {
+	w := new(Walker)
+	w.cur = item{
+		path: "/root/",
+		err:  nil,
+		entry: Entry{
+			Name: "root",
+			Size: 123,
+			Time: time.Now(),
+			Type: EntryTypeFolder,
+		},
+	}
+
+	w.stack = []item{
+		item{
+			path: "file",
+			err:  nil,
+			entry: Entry{
+				Name: "file",
+				Size: 123,
+				Time: time.Now(),
+				Type: EntryTypeFile,
+			},
+		},
+	}
+
+	w.SkipDir()
+
+	result := w.Step()
+
+	assert.Equal(t, true, result, "Result should return true")
+	assert.Equal(t, 1, len(w.stack))
+	assert.Equal(t, true, w.descend)
+}
+
+func TestEmptyStackReturnsFalse(t *testing.T) {
+	w := new(Walker)
+	w.cur = item{
+		path: "/root/",
+		err:  nil,
+		entry: Entry{
+			Name: "root",
+			Size: 123,
+			Time: time.Now(),
+			Type: EntryTypeFolder,
+		},
+	}
+
+	w.stack = []item{}
+
+	w.SkipDir()
+
+	result := w.Step()
+
+	assert.Equal(t, false, result, "Result should return false")
+}
+
+func TestCurAndStackSetCorrectly(t *testing.T) {
+	w := new(Walker)
+	w.cur = item{
+		path: "root/file1",
+		err:  nil,
+		entry: Entry{
+			Name: "file1",
+			Size: 123,
+			Time: time.Now(),
+			Type: EntryTypeFile,
+		},
+	}
+
+	w.stack = []item{
+		item{
+			path: "file",
+			err:  nil,
+			entry: Entry{
+				Name: "file",
+				Size: 123,
+				Time: time.Now(),
+				Type: EntryTypeFile,
+			},
+		},
+		item{
+			path: "root/file1",
+			err:  nil,
+			entry: Entry{
+				Name: "file1",
+				Size: 123,
+				Time: time.Now(),
+				Type: EntryTypeFile,
+			},
+		},
+	}
+
+	result := w.Step()
+	result = w.Step()
+
+	assert.Equal(t, true, result, "Result should return true")
+	assert.Equal(t, 0, len(w.stack))
+	assert.Equal(t, "file", w.cur.entry.Name)
+}
+
+func TestErrorsFromListAreHandledCorrectly(t *testing.T) {
+	//Get error
+	//Check w.cur.err
+	//Check stack
+}
+
+func TestStackIsPopulatedCorrectly(t *testing.T) {
+	//Check things are added to the stack correcty
+}