Skip to content

Commit

Permalink
Separate file/directory types
Browse files Browse the repository at this point in the history
  • Loading branch information
magik6k committed Nov 29, 2018
1 parent b0b422e commit 97852d8
Show file tree
Hide file tree
Showing 10 changed files with 127 additions and 163 deletions.
29 changes: 18 additions & 11 deletions file.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
)

var (
ErrNotDirectory = errors.New("couldn't call NextFile(), this isn't a directory")
ErrNotReader = errors.New("this file is a directory, can't use Reader functions")
ErrNotDirectory = errors.New("file isn't a directory")
ErrNotReader = errors.New("file isn't a regular file")

ErrNotSupported = errors.New("operation not supported")
)
Expand All @@ -20,22 +20,29 @@ var (
// Read/Seek methods are only valid for files
// NextFile method is only valid for directories
type File interface {
io.Reader
io.Closer
io.Seeker

// Size returns size of the
// Size returns size of this file (if this file is a directory, total size of
// all files stored in the tree should be returned). Some implementations may
// choose not to implement this
Size() (int64, error)
}

// Regular represents the regular Unix file
type Regular interface {
File

// IsDirectory returns true if the File is a directory (and therefore
// supports calling `Files`/`Walk`) and false if the File is a normal file
// (and therefore supports calling `Read`/`Close`/`Seek`)
IsDirectory() bool
io.Reader
io.Seeker
}

// Directory is a special file which can link to any number of files
type Directory interface {
File

// NextFile returns the next child file available (if the File is a
// directory). It will return io.EOF if no more files are
// available. If the file is a regular file (not a directory), NextFile
// will return a non-nil error.
// available.
//
// Note:
// - Some implementations may only allow reading in order - if a
Expand Down
66 changes: 24 additions & 42 deletions file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,23 +18,15 @@ func TestSliceFiles(t *testing.T) {

sf := NewSliceFile(files)

if !sf.IsDirectory() {
t.Fatal("SliceFile should always be a directory")
}

if n, err := sf.Read(buf); n > 0 || err != ErrNotReader {
t.Fatal("Shouldn't be able to read data from a SliceFile")
}

if err := sf.Close(); err != nil {
t.Fatal("Should be able to call `Close` on a SliceFile")
}

_, file, err := sf.NextFile()
if file == nil || err != nil {
t.Fatal("Expected a file and nil error")
}
read, err := file.Read(buf)
rf, ok := file.(Regular)
if !ok {
t.Fatal("Expected a regular file")
}
read, err := rf.Read(buf)
if read != 11 || err != nil {
t.Fatal("NextFile got a file in the wrong order")
}
Expand All @@ -52,21 +44,17 @@ func TestSliceFiles(t *testing.T) {
if file != nil || err != io.EOF {
t.Fatal("Expected a nil file and io.EOF")
}

if err := sf.Close(); err != nil {
t.Fatal("Should be able to call `Close` on a SliceFile")
}
}

func TestReaderFiles(t *testing.T) {
message := "beep boop"
rf := NewReaderFile(ioutil.NopCloser(strings.NewReader(message)), nil)
buf := make([]byte, len(message))

if rf.IsDirectory() {
t.Fatal("ReaderFile should never be a directory")
}
_, file, err := rf.NextFile()
if file != nil || err != ErrNotDirectory {
t.Fatal("Expected a nil file and ErrNotDirectory")
}

if n, err := rf.Read(buf); n == 0 || err != nil {
t.Fatal("Expected to be able to read")
}
Expand Down Expand Up @@ -117,19 +105,17 @@ anotherfile
if mpf == nil || err != nil {
t.Fatal("Expected non-nil MultipartFile, nil error")
}
if mpf.IsDirectory() {
mf, ok := mpf.(Regular)
if !ok {
t.Fatal("Expected file to not be a directory")
}
if mpname != "name" {
t.Fatal("Expected filename to be \"name\"")
}
if _, file, err := mpf.NextFile(); file != nil || err != ErrNotDirectory {
t.Fatal("Expected a nil file and ErrNotDirectory")
}
if n, err := mpf.Read(buf); n != 4 || !(err == io.EOF || err == nil) {
if n, err := mf.Read(buf); n != 4 || !(err == io.EOF || err == nil) {
t.Fatal("Expected to be able to read 4 bytes", n, err)
}
if err := mpf.Close(); err != nil {
if err := mf.Close(); err != nil {
t.Fatal("Expected to be able to close file")
}

Expand All @@ -142,16 +128,14 @@ anotherfile
if mpf == nil || err != nil {
t.Fatal("Expected non-nil MultipartFile, nil error")
}
if !mpf.IsDirectory() {
md, ok := mpf.(Directory)
if !ok {
t.Fatal("Expected file to be a directory")
}
if mpname != "dir" {
t.Fatal("Expected filename to be \"dir\"")
}
if n, err := mpf.Read(buf); n > 0 || err != ErrNotReader {
t.Fatal("Shouldn't be able to call `Read` on a directory")
}
if err := mpf.Close(); err != nil {
if err := md.Close(); err != nil {
t.Fatal("Should be able to call `Close` on a directory")
}

Expand All @@ -164,13 +148,14 @@ anotherfile
if mpf == nil || err != nil {
t.Fatal("Expected non-nil MultipartFile, nil error")
}
if mpf.IsDirectory() {
t.Fatal("Expected file, got directory")
mf, ok = mpf.(Regular)
if !ok {
t.Fatal("Expected file to not be a directory")
}
if mpname != "nested" {
t.Fatalf("Expected filename to be \"nested\", got %s", mpname)
}
if n, err := mpf.Read(buf); n != 12 || !(err == nil || err == io.EOF) {
if n, err := mf.Read(buf); n != 12 || !(err == nil || err == io.EOF) {
t.Fatalf("expected to be able to read 12 bytes from file: %s (got %d)", err, n)
}
if err := mpf.Close(); err != nil {
Expand All @@ -186,17 +171,14 @@ anotherfile
if mpf == nil || err != nil {
t.Fatal("Expected non-nil MultipartFile, nil error")
}
if mpf.IsDirectory() {
t.Fatal("Expected file to be a symlink")
ms, ok := mpf.(*Symlink)
if !ok {
t.Fatal("Expected file to not be a directory")
}
if mpname != "simlynk" {
t.Fatal("Expected filename to be \"dir/simlynk\"")
}
slink, ok := mpf.(*Symlink)
if !ok {
t.Fatalf("expected file to be a symlink")
}
if slink.Target != "anotherfile" {
if ms.Target != "anotherfile" {
t.Fatal("expected link to point to anotherfile")
}
}
14 changes: 3 additions & 11 deletions linkfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,20 @@ import (
)

type Symlink struct {
path string
Target string
stat os.FileInfo

reader io.Reader
}

func NewLinkFile(path, target string, stat os.FileInfo) File {
func NewLinkFile(target string, stat os.FileInfo) Regular {
return &Symlink{
path: path,
Target: target,
stat: stat,
reader: strings.NewReader(target),
}
}

func (lf *Symlink) IsDirectory() bool {
return false
}

func (lf *Symlink) NextFile() (string, File, error) {
return "", nil, ErrNotDirectory
}

func (lf *Symlink) Close() error {
if c, ok := lf.reader.(io.Closer); ok {
return c.Close()
Expand All @@ -54,3 +44,5 @@ func (lf *Symlink) Seek(offset int64, whence int) (int64, error) {
func (lf *Symlink) Size() (int64, error) {
return 0, ErrNotSupported
}

var _ Regular = &Symlink{}
36 changes: 22 additions & 14 deletions multifilereader.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ type MultiFileReader struct {
io.Reader

// directory stack for NextFile
files []File
files []Directory
path []string

currentFile File
Expand All @@ -31,12 +31,12 @@ type MultiFileReader struct {
form bool
}

// NewMultiFileReader constructs a MultiFileReader. `file` can be any `commands.File`.
// NewMultiFileReader constructs a MultiFileReader. `file` can be any `commands.Directory`.
// If `form` is set to true, the multipart data will have a Content-Type of 'multipart/form-data',
// if `form` is false, the Content-Type will be 'multipart/mixed'.
func NewMultiFileReader(file File, form bool) *MultiFileReader {
func NewMultiFileReader(file Directory, form bool) *MultiFileReader {
mfr := &MultiFileReader{
files: []File{file},
files: []Directory{file},
path: []string{""},
form: form,
mutex: &sync.Mutex{},
Expand Down Expand Up @@ -91,15 +91,19 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) {
header.Set("Content-Disposition", fmt.Sprintf("file; filename=\"%s\"", filename))

var contentType string
if _, ok := file.(*Symlink); ok {

switch f := file.(type) {
case *Symlink:
contentType = "application/symlink"
} else if file.IsDirectory() {
mfr.files = append(mfr.files, file)
case Directory:
mfr.files = append(mfr.files, f)
mfr.path = append(mfr.path, name)
contentType = "application/x-directory"
} else {
case Regular:
// otherwise, use the file as a reader to read its contents
contentType = "application/octet-stream"
default:
return 0, ErrNotSupported
}

header.Set("Content-Type", contentType)
Expand All @@ -120,16 +124,20 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) {
}

// otherwise, read from file data
written, err = mfr.currentFile.Read(buf)
if err == io.EOF || err == ErrNotReader {
if err := mfr.currentFile.Close(); err != nil {
switch f := mfr.currentFile.(type) {
case Regular:
written, err = f.Read(buf)
if err != io.EOF {
return written, err
}
}

mfr.currentFile = nil
return written, nil
if err := mfr.currentFile.Close(); err != nil {
return written, err
}
return written, err

mfr.currentFile = nil
return written, nil
}

// Boundary returns the boundary string to be used to separate files in the multipart data
Expand Down
Loading

0 comments on commit 97852d8

Please sign in to comment.