浏览代码

Merge pull request #141 from johnepiscopo/add-walk

Adds the ability to walk through a directory tree
Julien Laffaye 5 年之前
父节点
当前提交
041eae9a3d
共有 3 个文件被更改,包括 289 次插入0 次删除
  1. 14 0
      ftp.go
  2. 84 0
      walker.go
  3. 191 0
      walker_test.go

+ 14 - 0
ftp.go

@@ -701,6 +701,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 (
+	pa "path"
+	"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
+}
+
+// Next 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) Next() 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 = err
+			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 = pa.Join(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 Next 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 Next 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 Next. 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
+}

+ 191 - 0
walker_test.go

@@ -0,0 +1,191 @@
+package ftp
+
+import (
+	"fmt"
+	"strings"
+	"testing"
+	"time"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestWalkReturnsCorrectlyPopulatedWalker(t *testing.T) {
+	mock, err := newFtpMock(t, "127.0.0.1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer mock.Close()
+
+	c, cErr := Connect(mock.Addr())
+	if cErr != nil {
+		t.Fatal(err)
+	}
+
+	w := c.Walk("root")
+
+	assert.Equal(t, "root/", w.root)
+	assert.Equal(t, &c, &w.serverConn)
+}
+
+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.Next()
+
+	assert.Equal(t, true, result, "Result should return true")
+	assert.Equal(t, 0, 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.Next()
+
+	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.Next()
+	result = w.Next()
+
+	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 TestStackIsPopulatedCorrectly(t *testing.T) {
+
+	mock, err := newFtpMock(t, "127.0.0.1")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer mock.Close()
+
+	c, cErr := Connect(mock.Addr())
+	if cErr != nil {
+		t.Fatal(err)
+	}
+
+	w := Walker{
+		cur: item{
+			path: "/root",
+			entry: Entry{
+				Name: "root",
+				Size: 123,
+				Time: time.Now(),
+				Type: EntryTypeFolder,
+			},
+		},
+		serverConn: c,
+	}
+
+	w.descend = true
+
+	w.Next()
+
+	assert.Equal(t, 0, len(w.stack))
+	assert.Equal(t, "lo", w.cur.entry.Name)
+	assert.Equal(t, true, strings.HasSuffix(w.cur.path, "/"))
+}