diff --git a/commands/cli/parse.go b/commands/cli/parse.go index ee6b1656d..d0f84de02 100644 --- a/commands/cli/parse.go +++ b/commands/cli/parse.go @@ -11,6 +11,7 @@ import ( "strings" cmds "github.com/jbenet/go-ipfs/commands" + cmdsFiles "github.com/jbenet/go-ipfs/commands/files" u "github.com/jbenet/go-ipfs/util" ) @@ -65,7 +66,7 @@ func Parse(input []string, stdin *os.File, root *cmds.Command) (cmds.Request, *c } req.SetArguments(stringArgs) - file := &cmds.SliceFile{"", fileArgs} + file := &cmdsFiles.SliceFile{"", fileArgs} req.SetFiles(file) err = cmd.CheckArguments(req) @@ -139,7 +140,7 @@ func parseOptions(input []string) (map[string]interface{}, []string, error) { return opts, args, nil } -func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []cmds.File, error) { +func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursive bool) ([]string, []cmdsFiles.File, error) { // ignore stdin on Windows if runtime.GOOS == "windows" { stdin = nil @@ -176,7 +177,7 @@ func parseArgs(inputs []string, stdin *os.File, argDefs []cmds.Argument, recursi } stringArgs := make([]string, 0, numInputs) - fileArgs := make([]cmds.File, 0, numInputs) + fileArgs := make([]cmdsFiles.File, 0, numInputs) argDefIndex := 0 // the index of the current argument definition for i := 0; i < numInputs; i++ { @@ -263,7 +264,7 @@ func appendStdinAsString(args []string, stdin *os.File) ([]string, *os.File, err return append(args, buf.String()), nil, nil } -func appendFile(args []cmds.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]cmds.File, []string, error) { +func appendFile(args []cmdsFiles.File, inputs []string, argDef *cmds.Argument, recursive bool) ([]cmdsFiles.File, []string, error) { path := inputs[0] file, err := os.Open(path) @@ -297,13 +298,13 @@ func appendFile(args []cmds.File, inputs []string, argDef *cmds.Argument, recurs return append(args, arg), inputs[1:], nil } -func appendStdinAsFile(args []cmds.File, stdin *os.File) ([]cmds.File, *os.File) { - arg := &cmds.ReaderFile{"", stdin} +func appendStdinAsFile(args []cmdsFiles.File, stdin *os.File) ([]cmdsFiles.File, *os.File) { + arg := &cmdsFiles.ReaderFile{"", stdin} return append(args, arg), nil } -// recursively get file or directory contents as a cmds.File -func openPath(file *os.File, path string) (cmds.File, error) { +// recursively get file or directory contents as a cmdsFiles.File +func openPath(file *os.File, path string) (cmdsFiles.File, error) { stat, err := file.Stat() if err != nil { return nil, err @@ -311,7 +312,7 @@ func openPath(file *os.File, path string) (cmds.File, error) { // for non-directories, return a ReaderFile if !stat.IsDir() { - return &cmds.ReaderFile{path, file}, nil + return &cmdsFiles.ReaderFile{path, file}, nil } // for directories, recursively iterate though children then return as a SliceFile @@ -323,7 +324,7 @@ func openPath(file *os.File, path string) (cmds.File, error) { // make sure contents are sorted so -- repeatably -- we get the same inputs. sort.Sort(sortFIByName(contents)) - files := make([]cmds.File, 0, len(contents)) + files := make([]cmdsFiles.File, 0, len(contents)) for _, child := range contents { childPath := fp.Join(path, child.Name()) childFile, err := os.Open(childPath) @@ -339,7 +340,7 @@ func openPath(file *os.File, path string) (cmds.File, error) { files = append(files, f) } - return &cmds.SliceFile{path, files}, nil + return &cmdsFiles.SliceFile{path, files}, nil } // isTerminal returns true if stdin is a Stdin pipe (e.g. `cat file | ipfs`), diff --git a/commands/file.go b/commands/file.go deleted file mode 100644 index 466b1d842..000000000 --- a/commands/file.go +++ /dev/null @@ -1,176 +0,0 @@ -package commands - -import ( - "errors" - "io" - "mime" - "mime/multipart" - "net/http" -) - -const ( - multipartFormdataType = "multipart/form-data" - multipartMixedType = "multipart/mixed" - - contentTypeHeader = "Content-Type" -) - -var ( - ErrNotDirectory = errors.New("Couln'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 full filename path associated with this file - FileName() 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) -} - -// 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) - - var params map[string]string - var err error - f.Mediatype, params, err = mime.ParseMediaType(contentType) - if err != nil { - 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 -} - -func (f *MultipartFile) IsDirectory() bool { - return f.Mediatype == multipartFormdataType || f.Mediatype == multipartMixedType -} - -func (f *MultipartFile) NextFile() (File, error) { - if !f.IsDirectory() { - return nil, ErrNotDirectory - } - - part, err := f.Reader.NextPart() - if err != nil { - return nil, err - } - - return NewFileFromPart(part) -} - -func (f *MultipartFile) FileName() string { - return f.Part.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() -} - -// 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 - Files []File -} - -func (f *SliceFile) IsDirectory() bool { - return true -} - -func (f *SliceFile) NextFile() (File, error) { - if len(f.Files) == 0 { - return nil, io.EOF - } - file := f.Files[0] - f.Files = f.Files[1:] - return file, nil -} - -func (f *SliceFile) FileName() string { - return f.Filename -} - -func (f *SliceFile) Read(p []byte) (int, error) { - return 0, ErrNotReader -} - -func (f *SliceFile) Close() error { - return ErrNotReader -} - -// 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 - Reader io.Reader -} - -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) Read(p []byte) (int, error) { - return f.Reader.Read(p) -} - -func (f *ReaderFile) Close() error { - closer, ok := f.Reader.(io.Closer) - if !ok { - return nil - } - return closer.Close() -} diff --git a/commands/files/file.go b/commands/files/file.go new file mode 100644 index 000000000..9e9b043a1 --- /dev/null +++ b/commands/files/file.go @@ -0,0 +1,31 @@ +package files + +import ( + "errors" + "io" +) + +var ( + ErrNotDirectory = errors.New("Couln'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 full filename path associated with this file + FileName() 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) +} diff --git a/commands/file_test.go b/commands/files/file_test.go similarity index 99% rename from commands/file_test.go rename to commands/files/file_test.go index 19c8f587b..1ade078b4 100644 --- a/commands/file_test.go +++ b/commands/files/file_test.go @@ -1,4 +1,4 @@ -package commands +package files import ( "io" diff --git a/commands/files/multipartfile.go b/commands/files/multipartfile.go new file mode 100644 index 000000000..4594b859b --- /dev/null +++ b/commands/files/multipartfile.go @@ -0,0 +1,85 @@ +package files + +import ( + "mime" + "mime/multipart" + "net/http" +) + +const ( + multipartFormdataType = "multipart/form-data" + multipartMixedType = "multipart/mixed" + + 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) + + var params map[string]string + var err error + f.Mediatype, params, err = mime.ParseMediaType(contentType) + if err != nil { + 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 +} + +func (f *MultipartFile) IsDirectory() bool { + return f.Mediatype == multipartFormdataType || f.Mediatype == multipartMixedType +} + +func (f *MultipartFile) NextFile() (File, error) { + if !f.IsDirectory() { + return nil, ErrNotDirectory + } + + part, err := f.Reader.NextPart() + if err != nil { + return nil, err + } + + return NewFileFromPart(part) +} + +func (f *MultipartFile) FileName() string { + return f.Part.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() +} diff --git a/commands/files/readerfile.go b/commands/files/readerfile.go new file mode 100644 index 000000000..bb9d338b6 --- /dev/null +++ b/commands/files/readerfile.go @@ -0,0 +1,34 @@ +package files + +import "io" + +// 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 + Reader io.Reader +} + +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) Read(p []byte) (int, error) { + return f.Reader.Read(p) +} + +func (f *ReaderFile) Close() error { + closer, ok := f.Reader.(io.Closer) + if !ok { + return nil + } + return closer.Close() +} diff --git a/commands/files/slicefile.go b/commands/files/slicefile.go new file mode 100644 index 000000000..e1035f2ce --- /dev/null +++ b/commands/files/slicefile.go @@ -0,0 +1,36 @@ +package files + +import "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 + Files []File +} + +func (f *SliceFile) IsDirectory() bool { + return true +} + +func (f *SliceFile) NextFile() (File, error) { + if len(f.Files) == 0 { + return nil, io.EOF + } + file := f.Files[0] + f.Files = f.Files[1:] + return file, nil +} + +func (f *SliceFile) FileName() string { + return f.Filename +} + +func (f *SliceFile) Read(p []byte) (int, error) { + return 0, ErrNotReader +} + +func (f *SliceFile) Close() error { + return ErrNotReader +} diff --git a/commands/http/multifilereader.go b/commands/http/multifilereader.go index 00e6f18c1..37c26dca3 100644 --- a/commands/http/multifilereader.go +++ b/commands/http/multifilereader.go @@ -8,7 +8,7 @@ import ( "net/textproto" "sync" - cmds "github.com/jbenet/go-ipfs/commands" + files "github.com/jbenet/go-ipfs/commands/files" ) // MultiFileReader reads from a `commands.File` (which can be a directory of files @@ -16,7 +16,7 @@ import ( type MultiFileReader struct { io.Reader - files cmds.File + files files.File currentFile io.Reader buf bytes.Buffer mpWriter *multipart.Writer @@ -31,7 +31,7 @@ type MultiFileReader struct { // NewMultiFileReader constructs a MultiFileReader. `file` can be any `commands.File`. // 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 cmds.File, form bool) *MultiFileReader { +func NewMultiFileReader(file files.File, form bool) *MultiFileReader { mfr := &MultiFileReader{ files: file, form: form, diff --git a/commands/http/parse.go b/commands/http/parse.go index 49c3f052c..b74b1594e 100644 --- a/commands/http/parse.go +++ b/commands/http/parse.go @@ -8,6 +8,7 @@ import ( "strings" cmds "github.com/jbenet/go-ipfs/commands" + files "github.com/jbenet/go-ipfs/commands/files" ) // Parse parses the data in a http.Request and returns a command Request object @@ -94,9 +95,9 @@ func Parse(r *http.Request, root *cmds.Command) (cmds.Request, error) { contentType := r.Header.Get(contentTypeHeader) mediatype, _, _ := mime.ParseMediaType(contentType) - var f *cmds.MultipartFile + var f *files.MultipartFile if mediatype == "multipart/form-data" { - f = &cmds.MultipartFile{Mediatype: mediatype} + f = &files.MultipartFile{Mediatype: mediatype} f.Reader, err = r.MultipartReader() if err != nil { return nil, err diff --git a/commands/request.go b/commands/request.go index 8e23e30eb..ee3747442 100644 --- a/commands/request.go +++ b/commands/request.go @@ -8,6 +8,7 @@ import ( context "github.com/jbenet/go-ipfs/Godeps/_workspace/src/code.google.com/p/go.net/context" + "github.com/jbenet/go-ipfs/commands/files" "github.com/jbenet/go-ipfs/core" "github.com/jbenet/go-ipfs/repo/config" u "github.com/jbenet/go-ipfs/util" @@ -71,8 +72,8 @@ type Request interface { SetOptions(opts map[string]interface{}) error Arguments() []string SetArguments([]string) - Files() File - SetFiles(File) + Files() files.File + SetFiles(files.File) Context() *Context SetContext(Context) Command() *Command @@ -84,7 +85,7 @@ type request struct { path []string options optMap arguments []string - files File + files files.File cmd *Command ctx Context optionDefs map[string]Option @@ -159,11 +160,11 @@ func (r *request) SetArguments(args []string) { r.arguments = args } -func (r *request) Files() File { +func (r *request) Files() files.File { return r.files } -func (r *request) SetFiles(f File) { +func (r *request) SetFiles(f files.File) { r.files = f } @@ -259,7 +260,7 @@ func NewEmptyRequest() (Request, error) { // NewRequest returns a request initialized with given arguments // An non-nil error will be returned if the provided option values are invalid -func NewRequest(path []string, opts optMap, args []string, file File, cmd *Command, optDefs map[string]Option) (Request, error) { +func NewRequest(path []string, opts optMap, args []string, file files.File, cmd *Command, optDefs map[string]Option) (Request, error) { if path == nil { path = make([]string, 0) } diff --git a/core/commands/add.go b/core/commands/add.go index 50cf293bb..5da995f46 100644 --- a/core/commands/add.go +++ b/core/commands/add.go @@ -8,6 +8,7 @@ import ( "path" cmds "github.com/jbenet/go-ipfs/commands" + files "github.com/jbenet/go-ipfs/commands/files" core "github.com/jbenet/go-ipfs/core" importer "github.com/jbenet/go-ipfs/importer" "github.com/jbenet/go-ipfs/importer/chunk" @@ -138,7 +139,7 @@ func addNode(n *core.IpfsNode, node *dag.Node) error { return nil } -func addFile(n *core.IpfsNode, file cmds.File, out chan interface{}) (*dag.Node, error) { +func addFile(n *core.IpfsNode, file files.File, out chan interface{}) (*dag.Node, error) { if file.IsDirectory() { return addDir(n, file, out) } @@ -155,7 +156,7 @@ func addFile(n *core.IpfsNode, file cmds.File, out chan interface{}) (*dag.Node, return dns[len(dns)-1], nil // last dag node is the file. } -func addDir(n *core.IpfsNode, dir cmds.File, out chan interface{}) (*dag.Node, error) { +func addDir(n *core.IpfsNode, dir files.File, out chan interface{}) (*dag.Node, error) { log.Infof("adding directory: %s", dir.FileName()) tree := &dag.Node{Data: ft.FolderPBData()}