Commit de02f8e0 authored by Jan Winkelmann's avatar Jan Winkelmann

move files/ to go-ipfs-cmdkit

parent 181797cc
package files
import (
"errors"
"io"
"os"
)
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")
)
// File is an interface that provides functionality for handling
// files/directories as values that can be supplied to commands. For
// directories, child files are accessed serially by calling `NextFile()`.
type File interface {
// Files implement ReadCloser, but can only be read from or closed if
// they are not directories
io.ReadCloser
// FileName returns a filename associated with this file
FileName() string
// FullPath returns the full path used when adding with this file
FullPath() string
// IsDirectory returns true if the File is a directory (and therefore
// supports calling `NextFile`) and false if the File is a normal file
// (and therefor supports calling `Read` and `Close`)
IsDirectory() bool
// NextFile returns the next child file available (if the File is a
// directory). It will return (nil, 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.
NextFile() (File, error)
}
type StatFile interface {
File
Stat() os.FileInfo
}
type PeekFile interface {
SizeFile
Peek(n int) File
Length() int
}
type SizeFile interface {
File
Size() (int64, error)
}
type FileInfo interface {
AbsPath() string
Stat() os.FileInfo
}
package files
import (
"io"
"io/ioutil"
"mime/multipart"
"strings"
"testing"
)
func TestSliceFiles(t *testing.T) {
name := "testname"
files := []File{
NewReaderFile("file.txt", "file.txt", ioutil.NopCloser(strings.NewReader("Some text!\n")), nil),
NewReaderFile("beep.txt", "beep.txt", ioutil.NopCloser(strings.NewReader("beep")), nil),
NewReaderFile("boop.txt", "boop.txt", ioutil.NopCloser(strings.NewReader("boop")), nil),
}
buf := make([]byte, 20)
sf := NewSliceFile(name, name, files)
if !sf.IsDirectory() {
t.Fatal("SliceFile should always be a directory")
}
if n, err := sf.Read(buf); n > 0 || err != io.EOF {
t.Fatal("Shouldn't be able to read data from a SliceFile")
}
if err := sf.Close(); err != ErrNotReader {
t.Fatal("Shouldn't 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)
if read != 11 || err != nil {
t.Fatal("NextFile got a file in the wrong order")
}
file, err = sf.NextFile()
if file == nil || err != nil {
t.Fatal("Expected a file and nil error")
}
file, err = sf.NextFile()
if file == nil || err != nil {
t.Fatal("Expected a file and nil error")
}
file, err = sf.NextFile()
if file != nil || err != io.EOF {
t.Fatal("Expected a nil file and io.EOF")
}
}
func TestReaderFiles(t *testing.T) {
message := "beep boop"
rf := NewReaderFile("file.txt", "file.txt", 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")
}
if err := rf.Close(); err != nil {
t.Fatal("Should be able to close")
}
if n, err := rf.Read(buf); n != 0 || err != io.EOF {
t.Fatal("Expected EOF when reading after close")
}
}
func TestMultipartFiles(t *testing.T) {
data := `
--Boundary!
Content-Type: text/plain
Content-Disposition: file; filename="name"
Some-Header: beep
beep
--Boundary!
Content-Type: application/x-directory
Content-Disposition: file; filename="dir"
--Boundary!
Content-Type: text/plain
Content-Disposition: file; filename="dir/nested"
some content
--Boundary!
Content-Type: application/symlink
Content-Disposition: file; filename="dir/simlynk"
anotherfile
--Boundary!--
`
reader := strings.NewReader(data)
mpReader := multipart.NewReader(reader, "Boundary!")
buf := make([]byte, 20)
// test properties of a file created from the first part
part, err := mpReader.NextPart()
if part == nil || err != nil {
t.Fatal("Expected non-nil part, nil error")
}
mpf, err := NewFileFromPart(part)
if mpf == nil || err != nil {
t.Fatal("Expected non-nil MultipartFile, nil error")
}
if mpf.IsDirectory() {
t.Fatal("Expected file to not be a directory")
}
if mpf.FileName() != "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 != nil {
t.Fatal("Expected to be able to read 4 bytes")
}
if err := mpf.Close(); err != nil {
t.Fatal("Expected to be able to close file")
}
// test properties of file created from second part (directory)
part, err = mpReader.NextPart()
if part == nil || err != nil {
t.Fatal("Expected non-nil part, nil error")
}
mpf, err = NewFileFromPart(part)
if mpf == nil || err != nil {
t.Fatal("Expected non-nil MultipartFile, nil error")
}
if !mpf.IsDirectory() {
t.Fatal("Expected file to be a directory")
}
if mpf.FileName() != "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 != ErrNotReader {
t.Fatal("Shouldn't be able to call `Close` on a directory")
}
// test properties of file created from third part (nested file)
part, err = mpReader.NextPart()
if part == nil || err != nil {
t.Fatal("Expected non-nil part, nil error")
}
mpf, err = NewFileFromPart(part)
if mpf == nil || err != nil {
t.Fatal("Expected non-nil MultipartFile, nil error")
}
if mpf.IsDirectory() {
t.Fatal("Expected file, got directory")
}
if mpf.FileName() != "dir/nested" {
t.Fatalf("Expected filename to be \"nested\", got %s", mpf.FileName())
}
if n, err := mpf.Read(buf); n != 12 || err != nil {
t.Fatalf("expected to be able to read 12 bytes from file: %s (got %d)", err, n)
}
if err := mpf.Close(); err != nil {
t.Fatalf("should be able to close file: %s", err)
}
// test properties of symlink created from fourth part (symlink)
part, err = mpReader.NextPart()
if part == nil || err != nil {
t.Fatal("Expected non-nil part, nil error")
}
mpf, err = NewFileFromPart(part)
if mpf == nil || err != nil {
t.Fatal("Expected non-nil MultipartFile, nil error")
}
if mpf.IsDirectory() {
t.Fatal("Expected file to be a symlink")
}
if mpf.FileName() != "dir/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" {
t.Fatal("expected link to point to anotherfile")
}
}
// +build !windows
package files
import (
"path/filepath"
"strings"
)
func IsHidden(f File) bool {
fName := filepath.Base(f.FileName())
if strings.HasPrefix(fName, ".") && len(fName) > 1 {
return true
}
return false
}
// +build windows
package files
import (
"path/filepath"
"strings"
"syscall"
)
func IsHidden(f File) bool {
fName := filepath.Base(f.FileName())
if strings.HasPrefix(fName, ".") && len(fName) > 1 {
return true
}
p, e := syscall.UTF16PtrFromString(f.FileName())
if e != nil {
return false
}
attrs, e := syscall.GetFileAttributes(p)
if e != nil {
return false
}
return attrs&syscall.FILE_ATTRIBUTE_HIDDEN != 0
}
package files
import (
"io"
"os"
"strings"
)
type Symlink struct {
name string
path string
Target string
stat os.FileInfo
reader io.Reader
}
func NewLinkFile(name, path, target string, stat os.FileInfo) File {
return &Symlink{
name: name,
path: path,
Target: target,
stat: stat,
reader: strings.NewReader(target),
}
}
func (lf *Symlink) IsDirectory() bool {
return false
}
func (lf *Symlink) NextFile() (File, error) {
return nil, io.EOF
}
func (f *Symlink) FileName() string {
return f.name
}
func (f *Symlink) Close() error {
return nil
}
func (f *Symlink) FullPath() string {
return f.path
}
func (f *Symlink) Read(b []byte) (int, error) {
return f.reader.Read(b)
}
package files
import (
"io"
"io/ioutil"
"mime"
"mime/multipart"
"net/url"
)
const (
multipartFormdataType = "multipart/form-data"
multipartMixedType = "multipart/mixed"
applicationDirectory = "application/x-directory"
applicationSymlink = "application/symlink"
applicationFile = "application/octet-stream"
contentTypeHeader = "Content-Type"
)
// MultipartFile implements File, and is created from a `multipart.Part`.
// It can be either a directory or file (checked by calling `IsDirectory()`).
type MultipartFile struct {
File
Part *multipart.Part
Reader *multipart.Reader
Mediatype string
}
func NewFileFromPart(part *multipart.Part) (File, error) {
f := &MultipartFile{
Part: part,
}
contentType := part.Header.Get(contentTypeHeader)
switch contentType {
case applicationSymlink:
out, err := ioutil.ReadAll(part)
if err != nil {
return nil, err
}
return &Symlink{
Target: string(out),
name: f.FileName(),
}, nil
case applicationFile:
return &ReaderFile{
reader: part,
filename: f.FileName(),
abspath: part.Header.Get("abspath"),
fullpath: f.FullPath(),
}, nil
}
var err error
f.Mediatype, _, err = mime.ParseMediaType(contentType)
if err != nil {
return nil, err
}
return f, nil
}
func (f *MultipartFile) IsDirectory() bool {
return f.Mediatype == multipartFormdataType || f.Mediatype == applicationDirectory
}
func (f *MultipartFile) NextFile() (File, error) {
if !f.IsDirectory() {
return nil, ErrNotDirectory
}
if f.Reader != nil {
part, err := f.Reader.NextPart()
if err != nil {
return nil, err
}
return NewFileFromPart(part)
}
return nil, io.EOF
}
func (f *MultipartFile) FileName() string {
if f == nil || f.Part == nil {
return ""
}
filename, err := url.QueryUnescape(f.Part.FileName())
if err != nil {
// if there is a unescape error, just treat the name as unescaped
return f.Part.FileName()
}
return filename
}
func (f *MultipartFile) FullPath() string {
return f.FileName()
}
func (f *MultipartFile) Read(p []byte) (int, error) {
if f.IsDirectory() {
return 0, ErrNotReader
}
return f.Part.Read(p)
}
func (f *MultipartFile) Close() error {
if f.IsDirectory() {
return ErrNotReader
}
return f.Part.Close()
}
package files
import (
"errors"
"io"
"os"
"path/filepath"
)
// ReaderFile is a implementation of File created from an `io.Reader`.
// ReaderFiles are never directories, and can be read from and closed.
type ReaderFile struct {
filename string
fullpath string
abspath string
reader io.ReadCloser
stat os.FileInfo
}
func NewReaderFile(filename, path string, reader io.ReadCloser, stat os.FileInfo) *ReaderFile {
return &ReaderFile{filename, path, path, reader, stat}
}
func NewReaderPathFile(filename, path string, reader io.ReadCloser, stat os.FileInfo) (*ReaderFile, error) {
abspath, err := filepath.Abs(path)
if err != nil {
return nil, err
}
return &ReaderFile{filename, path, abspath, reader, stat}, nil
}
func (f *ReaderFile) IsDirectory() bool {
return false
}
func (f *ReaderFile) NextFile() (File, error) {
return nil, ErrNotDirectory
}
func (f *ReaderFile) FileName() string {
return f.filename
}
func (f *ReaderFile) FullPath() string {
return f.fullpath
}
func (f *ReaderFile) AbsPath() string {
return f.abspath
}
func (f *ReaderFile) Read(p []byte) (int, error) {
return f.reader.Read(p)
}
func (f *ReaderFile) Close() error {
return f.reader.Close()
}
func (f *ReaderFile) Stat() os.FileInfo {
return f.stat
}
func (f *ReaderFile) Size() (int64, error) {
if f.stat == nil {
return 0, errors.New("File size unknown")
}
return f.stat.Size(), nil
}
package files
import (
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
)
// serialFile implements File, and reads from a path on the OS filesystem.
// No more than one file will be opened at a time (directories will advance
// to the next file when NextFile() is called).
type serialFile struct {
name string
path string
files []os.FileInfo
stat os.FileInfo
current *File
handleHiddenFiles bool
}
func NewSerialFile(name, path string, hidden bool, stat os.FileInfo) (File, error) {
switch mode := stat.Mode(); {
case mode.IsRegular():
file, err := os.Open(path)
if err != nil {
return nil, err
}
return NewReaderPathFile(name, path, file, stat)
case mode.IsDir():
// for directories, stat all of the contents first, so we know what files to
// open when NextFile() is called
contents, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
}
return &serialFile{name, path, contents, stat, nil, hidden}, nil
case mode&os.ModeSymlink != 0:
target, err := os.Readlink(path)
if err != nil {
return nil, err
}
return NewLinkFile(name, path, target, stat), nil
default:
return nil, fmt.Errorf("Unrecognized file type for %s: %s", name, mode.String())
}
}
func (f *serialFile) IsDirectory() bool {
// non-directories get created as a ReaderFile, so serialFiles should only
// represent directories
return true
}
func (f *serialFile) NextFile() (File, error) {
// if a file was opened previously, close it
err := f.Close()
if err != nil {
return nil, err
}
// if there aren't any files left in the root directory, we're done
if len(f.files) == 0 {
return nil, io.EOF
}
stat := f.files[0]
f.files = f.files[1:]
for !f.handleHiddenFiles && strings.HasPrefix(stat.Name(), ".") {
if len(f.files) == 0 {
return nil, io.EOF
}
stat = f.files[0]
f.files = f.files[1:]
}
// open the next file
fileName := filepath.ToSlash(filepath.Join(f.name, stat.Name()))
filePath := filepath.ToSlash(filepath.Join(f.path, stat.Name()))
// recursively call the constructor on the next file
// if it's a regular file, we will open it as a ReaderFile
// if it's a directory, files in it will be opened serially
sf, err := NewSerialFile(fileName, filePath, f.handleHiddenFiles, stat)
if err != nil {
return nil, err
}
f.current = &sf
return sf, nil
}
func (f *serialFile) FileName() string {
return f.name
}
func (f *serialFile) FullPath() string {
return f.path
}
func (f *serialFile) Read(p []byte) (int, error) {
return 0, io.EOF
}
func (f *serialFile) Close() error {
// close the current file if there is one
if f.current != nil {
err := (*f.current).Close()
// ignore EINVAL error, the file might have already been closed
if err != nil && err != syscall.EINVAL {
return err
}
}
return nil
}
func (f *serialFile) Stat() os.FileInfo {
return f.stat
}
func (f *serialFile) Size() (int64, error) {
if !f.stat.IsDir() {
return f.stat.Size(), nil
}
var du int64
err := filepath.Walk(f.FullPath(), func(p string, fi os.FileInfo, err error) error {
if err != nil {
return err
}
if fi != nil && fi.Mode()&(os.ModeSymlink|os.ModeNamedPipe) == 0 {
du += fi.Size()
}
return nil
})
return du, err
}
package files
import (
"errors"
"io"
)
// SliceFile implements File, and provides simple directory handling.
// It contains children files, and is created from a `[]File`.
// SliceFiles are always directories, and can't be read from or closed.
type SliceFile struct {
filename string
path string
files []File
n int
}
func NewSliceFile(filename, path string, files []File) *SliceFile {
return &SliceFile{filename, path, files, 0}
}
func (f *SliceFile) IsDirectory() bool {
return true
}
func (f *SliceFile) NextFile() (File, error) {
if f.n >= len(f.files) {
return nil, io.EOF
}
file := f.files[f.n]
f.n++
return file, nil
}
func (f *SliceFile) FileName() string {
return f.filename
}
func (f *SliceFile) FullPath() string {
return f.path
}
func (f *SliceFile) Read(p []byte) (int, error) {
return 0, io.EOF
}
func (f *SliceFile) Close() error {
return ErrNotReader
}
func (f *SliceFile) Peek(n int) File {
return f.files[n]
}
func (f *SliceFile) Length() int {
return len(f.files)
}
func (f *SliceFile) Size() (int64, error) {
var size int64
for _, file := range f.files {
sizeFile, ok := file.(SizeFile)
if !ok {
return 0, errors.New("Could not get size of child file")
}
s, err := sizeFile.Size()
if err != nil {
return 0, err
}
size += s
}
return size, nil
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment