1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-09-10 22:49:13 +08:00
Files
kubo/commands/file.go
2014-11-18 02:13:56 -08:00

177 lines
3.9 KiB
Go

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()
}