Commit 521e206b authored by Jeromy's avatar Jeromy

Flatten multipart file transfers

License: MIT
Signed-off-by: default avatarJeromy <jeromyj@gmail.com>
parent d064dedd
...@@ -44,7 +44,17 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c ...@@ -44,7 +44,17 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c
} }
} }
stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive, root) // if '--hidden' is provided, enumerate hidden paths
hiddenOpt := req.Option("hidden")
hidden := false
if hiddenOpt != nil {
hidden, _, err = hiddenOpt.Bool()
if err != nil {
return req, nil, nil, u.ErrCast()
}
}
stringArgs, fileArgs, err := parseArgs(stringVals, stdin, cmd.Arguments, recursive, hidden, root)
if err != nil { if err != nil {
return req, cmd, path, err return req, cmd, path, err
} }
...@@ -223,7 +233,7 @@ func parseOpts(args []string, root *cmds.Command) ( ...@@ -223,7 +233,7 @@ func parseOpts(args []string, root *cmds.Command) (
return return
} }
func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool, root *cmds.Command) ([]string, []files.File, error) { func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive, hidden bool, root *cmds.Command) ([]string, []files.File, error) {
// ignore stdin on Windows // ignore stdin on Windows
if runtime.GOOS == "windows" { if runtime.GOOS == "windows" {
stdin = nil stdin = nil
...@@ -308,7 +318,7 @@ func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursi ...@@ -308,7 +318,7 @@ func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursi
// treat stringArg values as file paths // treat stringArg values as file paths
fpath := inputs[0] fpath := inputs[0]
inputs = inputs[1:] inputs = inputs[1:]
file, err := appendFile(fpath, argDef, recursive) file, err := appendFile(fpath, argDef, recursive, hidden)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
...@@ -389,7 +399,7 @@ func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, err ...@@ -389,7 +399,7 @@ func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, err
const notRecursiveFmtStr = "'%s' is a directory, use the '-%s' flag to specify directories" const notRecursiveFmtStr = "'%s' is a directory, use the '-%s' flag to specify directories"
const dirNotSupportedFmtStr = "Invalid path '%s', argument '%s' does not support directories" const dirNotSupportedFmtStr = "Invalid path '%s', argument '%s' does not support directories"
func appendFile(fpath string, argDef *cmds.Argument, recursive bool) (files.File, error) { func appendFile(fpath string, argDef *cmds.Argument, recursive, hidden bool) (files.File, error) {
fpath = filepath.ToSlash(filepath.Clean(fpath)) fpath = filepath.ToSlash(filepath.Clean(fpath))
if fpath == "." { if fpath == "." {
...@@ -414,7 +424,7 @@ func appendFile(fpath string, argDef *cmds.Argument, recursive bool) (files.File ...@@ -414,7 +424,7 @@ func appendFile(fpath string, argDef *cmds.Argument, recursive bool) (files.File
} }
} }
return files.NewSerialFile(path.Base(fpath), fpath, stat) return files.NewSerialFile(path.Base(fpath), fpath, hidden, stat)
} }
// isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`), // isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`),
......
package files package files
import ( import (
"io"
"io/ioutil" "io/ioutil"
"mime" "mime"
"mime/multipart" "mime/multipart"
"net/http"
"net/url" "net/url"
) )
...@@ -12,7 +12,8 @@ const ( ...@@ -12,7 +12,8 @@ const (
multipartFormdataType = "multipart/form-data" multipartFormdataType = "multipart/form-data"
multipartMixedType = "multipart/mixed" multipartMixedType = "multipart/mixed"
applicationSymlink = "application/symlink" applicationDirectory = "application/x-directory"
applicationSymlink = "application/symlink"
contentTypeHeader = "Content-Type" contentTypeHeader = "Content-Type"
) )
...@@ -45,40 +46,33 @@ func NewFileFromPart(part *multipart.Part) (File, error) { ...@@ -45,40 +46,33 @@ func NewFileFromPart(part *multipart.Part) (File, error) {
}, nil }, nil
} }
var params map[string]string
var err error var err error
f.Mediatype, params, err = mime.ParseMediaType(contentType) f.Mediatype, _, err = mime.ParseMediaType(contentType)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if f.IsDirectory() {
boundary, found := params["boundary"]
if !found {
return nil, http.ErrMissingBoundary
}
f.Reader = multipart.NewReader(part, boundary)
}
return f, nil return f, nil
} }
func (f *MultipartFile) IsDirectory() bool { func (f *MultipartFile) IsDirectory() bool {
return f.Mediatype == multipartFormdataType || f.Mediatype == multipartMixedType return f.Mediatype == multipartFormdataType || f.Mediatype == applicationDirectory
} }
func (f *MultipartFile) NextFile() (File, error) { func (f *MultipartFile) NextFile() (File, error) {
if !f.IsDirectory() { if !f.IsDirectory() {
return nil, ErrNotDirectory return nil, ErrNotDirectory
} }
if f.Reader != nil {
part, err := f.Reader.NextPart()
if err != nil {
return nil, err
}
part, err := f.Reader.NextPart() return NewFileFromPart(part)
if err != nil {
return nil, err
} }
return NewFileFromPart(part) return nil, io.EOF
} }
func (f *MultipartFile) FileName() string { func (f *MultipartFile) FileName() string {
......
...@@ -6,6 +6,7 @@ import ( ...@@ -6,6 +6,7 @@ import (
"io/ioutil" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"strings"
"syscall" "syscall"
) )
...@@ -18,9 +19,10 @@ type serialFile struct { ...@@ -18,9 +19,10 @@ type serialFile struct {
files []os.FileInfo files []os.FileInfo
stat os.FileInfo stat os.FileInfo
current *File current *File
hidden bool
} }
func NewSerialFile(name, path string, stat os.FileInfo) (File, error) { func NewSerialFile(name, path string, hidden bool, stat os.FileInfo) (File, error) {
switch mode := stat.Mode(); { switch mode := stat.Mode(); {
case mode.IsRegular(): case mode.IsRegular():
file, err := os.Open(path) file, err := os.Open(path)
...@@ -35,7 +37,7 @@ func NewSerialFile(name, path string, stat os.FileInfo) (File, error) { ...@@ -35,7 +37,7 @@ func NewSerialFile(name, path string, stat os.FileInfo) (File, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &serialFile{name, path, contents, stat, nil}, nil return &serialFile{name, path, contents, stat, nil, hidden}, nil
case mode&os.ModeSymlink != 0: case mode&os.ModeSymlink != 0:
target, err := os.Readlink(path) target, err := os.Readlink(path)
if err != nil { if err != nil {
...@@ -68,6 +70,15 @@ func (f *serialFile) NextFile() (File, error) { ...@@ -68,6 +70,15 @@ func (f *serialFile) NextFile() (File, error) {
stat := f.files[0] stat := f.files[0]
f.files = f.files[1:] f.files = f.files[1:]
for !f.hidden && 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 // open the next file
fileName := filepath.ToSlash(filepath.Join(f.name, stat.Name())) fileName := filepath.ToSlash(filepath.Join(f.name, stat.Name()))
filePath := filepath.ToSlash(filepath.Join(f.path, stat.Name())) filePath := filepath.ToSlash(filepath.Join(f.path, stat.Name()))
...@@ -75,7 +86,7 @@ func (f *serialFile) NextFile() (File, error) { ...@@ -75,7 +86,7 @@ func (f *serialFile) NextFile() (File, error) {
// recursively call the constructor on the next file // 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 regular file, we will open it as a ReaderFile
// if it's a directory, files in it will be opened serially // if it's a directory, files in it will be opened serially
sf, err := NewSerialFile(fileName, filePath, stat) sf, err := NewSerialFile(fileName, filePath, f.hidden, stat)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -94,7 +105,7 @@ func (f *serialFile) FullPath() string { ...@@ -94,7 +105,7 @@ func (f *serialFile) FullPath() string {
} }
func (f *serialFile) Read(p []byte) (int, error) { func (f *serialFile) Read(p []byte) (int, error) {
return 0, ErrNotReader return 0, io.EOF
} }
func (f *serialFile) Close() error { func (f *serialFile) Close() error {
......
...@@ -41,7 +41,7 @@ func (f *SliceFile) FullPath() string { ...@@ -41,7 +41,7 @@ func (f *SliceFile) FullPath() string {
} }
func (f *SliceFile) Read(p []byte) (int, error) { func (f *SliceFile) Read(p []byte) (int, error) {
return 0, ErrNotReader return 0, io.EOF
} }
func (f *SliceFile) Close() error { func (f *SliceFile) Close() error {
......
...@@ -13,7 +13,6 @@ import ( ...@@ -13,7 +13,6 @@ import (
"strings" "strings"
cmds "github.com/ipfs/go-ipfs/commands" cmds "github.com/ipfs/go-ipfs/commands"
path "github.com/ipfs/go-ipfs/path"
config "github.com/ipfs/go-ipfs/repo/config" config "github.com/ipfs/go-ipfs/repo/config"
context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context" context "github.com/ipfs/go-ipfs/Godeps/_workspace/src/golang.org/x/net/context"
...@@ -86,8 +85,8 @@ func (c *client) Send(req cmds.Request) (cmds.Response, error) { ...@@ -86,8 +85,8 @@ func (c *client) Send(req cmds.Request) (cmds.Response, error) {
reader = fileReader reader = fileReader
} }
pth := path.Join(req.Path()) path := strings.Join(req.Path(), "/")
url := fmt.Sprintf(ApiUrlFormat, c.serverAddress, ApiPath, pth, query) url := fmt.Sprintf(ApiUrlFormat, c.serverAddress, ApiPath, path, query)
httpReq, err := http.NewRequest("POST", url, reader) httpReq, err := http.NewRequest("POST", url, reader)
if err != nil { if err != nil {
......
...@@ -17,7 +17,7 @@ import ( ...@@ -17,7 +17,7 @@ import (
type MultiFileReader struct { type MultiFileReader struct {
io.Reader io.Reader
files files.File files []files.File
currentFile io.Reader currentFile io.Reader
buf bytes.Buffer buf bytes.Buffer
mpWriter *multipart.Writer mpWriter *multipart.Writer
...@@ -34,7 +34,7 @@ type MultiFileReader struct { ...@@ -34,7 +34,7 @@ type MultiFileReader struct {
// if `form` is false, the Content-Type will be 'multipart/mixed'. // if `form` is false, the Content-Type will be 'multipart/mixed'.
func NewMultiFileReader(file files.File, form bool) *MultiFileReader { func NewMultiFileReader(file files.File, form bool) *MultiFileReader {
mfr := &MultiFileReader{ mfr := &MultiFileReader{
files: file, files: []files.File{file},
form: form, form: form,
mutex: &sync.Mutex{}, mutex: &sync.Mutex{},
} }
...@@ -54,34 +54,41 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) { ...@@ -54,34 +54,41 @@ func (mfr *MultiFileReader) Read(buf []byte) (written int, err error) {
// if the current file isn't set, advance to the next file // if the current file isn't set, advance to the next file
if mfr.currentFile == nil { if mfr.currentFile == nil {
file, err := mfr.files.NextFile() var file files.File
if err == io.EOF { for file == nil {
mfr.mpWriter.Close() if len(mfr.files) == 0 {
mfr.closed = true mfr.mpWriter.Close()
} else if err != nil { mfr.closed = true
return 0, err return mfr.buf.Read(buf)
}
nextfile, err := mfr.files[len(mfr.files)-1].NextFile()
if err == io.EOF {
mfr.files = mfr.files[:len(mfr.files)-1]
continue
} else if err != nil {
return 0, err
}
file = nextfile
} }
// handle starting a new file part // handle starting a new file part
if !mfr.closed { if !mfr.closed {
var contentType string var contentType string
if s, ok := file.(*files.Symlink); ok { if _, ok := file.(*files.Symlink); ok {
mfr.currentFile = s
contentType = "application/symlink" contentType = "application/symlink"
} else if file.IsDirectory() { } else if file.IsDirectory() {
// if file is a directory, create a multifilereader from it mfr.files = append(mfr.files, file)
// (using 'multipart/mixed') contentType = "application/x-directory"
nmfr := NewMultiFileReader(file, false)
mfr.currentFile = nmfr
contentType = fmt.Sprintf("multipart/mixed; boundary=%s", nmfr.Boundary())
} else { } else {
// otherwise, use the file as a reader to read its contents // otherwise, use the file as a reader to read its contents
mfr.currentFile = file
contentType = "application/octet-stream" contentType = "application/octet-stream"
} }
mfr.currentFile = file
// write the boundary and headers // write the boundary and headers
header := make(textproto.MIMEHeader) header := make(textproto.MIMEHeader)
filename := url.QueryEscape(file.FileName()) filename := url.QueryEscape(file.FileName())
......
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