2015-12-18 22:24:12 +00:00
|
|
|
// Package filestore provide a storage backend based on the local file system.
|
|
|
|
//
|
2019-06-11 16:23:20 +00:00
|
|
|
// FileStore is a storage backend used as a handler.DataStore in handler.NewHandler.
|
2015-02-01 15:17:56 +00:00
|
|
|
// It stores the uploads in a directory specified in two different files: The
|
|
|
|
// `[id].info` files are used to store the fileinfo in JSON format. The
|
2019-08-19 11:03:42 +00:00
|
|
|
// `[id]` files without an extension contain the raw binary data uploaded.
|
2015-02-01 15:17:56 +00:00
|
|
|
// No cleanup is performed so you may want to run a cronjob to ensure your disk
|
|
|
|
// is not filled up with old and finished uploads.
|
2015-02-01 13:57:57 +00:00
|
|
|
package filestore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"encoding/json"
|
2017-01-26 20:46:06 +00:00
|
|
|
"fmt"
|
2015-02-01 13:57:57 +00:00
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
2015-12-26 20:23:09 +00:00
|
|
|
"path/filepath"
|
2015-02-01 13:57:57 +00:00
|
|
|
|
2019-06-11 16:23:20 +00:00
|
|
|
"github.com/tus/tusd/internal/uid"
|
|
|
|
"github.com/tus/tusd/pkg/handler"
|
2015-02-01 13:57:57 +00:00
|
|
|
)
|
|
|
|
|
2016-07-25 18:59:01 +00:00
|
|
|
var defaultFilePerm = os.FileMode(0664)
|
2015-02-01 13:57:57 +00:00
|
|
|
|
2019-06-11 16:23:20 +00:00
|
|
|
// See the handler.DataStore interface for documentation about the different
|
2015-02-01 15:17:56 +00:00
|
|
|
// methods.
|
2015-02-01 13:57:57 +00:00
|
|
|
type FileStore struct {
|
2015-02-01 15:17:56 +00:00
|
|
|
// Relative or absolute path to store files in. FileStore does not check
|
2015-12-18 23:21:34 +00:00
|
|
|
// whether the path exists, use os.MkdirAll in this case on your own.
|
2015-02-01 13:57:57 +00:00
|
|
|
Path string
|
|
|
|
}
|
|
|
|
|
2015-12-18 23:21:34 +00:00
|
|
|
// New creates a new file based storage backend. The directory specified will
|
|
|
|
// be used as the only storage entry. This method does not check
|
|
|
|
// whether the path exists, use os.MkdirAll to ensure.
|
2015-12-26 20:23:09 +00:00
|
|
|
// In addition, a locking mechanism is provided.
|
|
|
|
func New(path string) FileStore {
|
|
|
|
return FileStore{path}
|
2015-12-18 23:21:34 +00:00
|
|
|
}
|
|
|
|
|
2016-03-11 19:17:43 +00:00
|
|
|
// UseIn sets this store as the core data store in the passed composer and adds
|
|
|
|
// all possible extension to it.
|
2019-06-11 16:23:20 +00:00
|
|
|
func (store FileStore) UseIn(composer *handler.StoreComposer) {
|
2016-02-21 22:25:35 +00:00
|
|
|
composer.UseCore(store)
|
|
|
|
composer.UseTerminater(store)
|
2017-11-21 12:00:43 +00:00
|
|
|
composer.UseConcater(store)
|
2018-05-13 14:28:02 +00:00
|
|
|
composer.UseLengthDeferrer(store)
|
2016-02-21 22:25:35 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
func (store FileStore) NewUpload(info handler.FileInfo) (handler.Upload, error) {
|
|
|
|
id := uid.Uid()
|
2019-08-19 07:29:56 +00:00
|
|
|
binPath := store.binPath(id)
|
2015-03-23 16:58:13 +00:00
|
|
|
info.ID = id
|
2019-08-19 07:29:56 +00:00
|
|
|
info.Storage = map[string]string{
|
|
|
|
"Type": "filestore",
|
|
|
|
"Path": binPath,
|
|
|
|
}
|
2015-02-01 13:57:57 +00:00
|
|
|
|
2019-08-19 11:03:42 +00:00
|
|
|
// Create binary file with no content
|
2019-08-19 07:29:56 +00:00
|
|
|
file, err := os.OpenFile(binPath, os.O_CREATE|os.O_WRONLY, defaultFilePerm)
|
2015-02-01 13:57:57 +00:00
|
|
|
if err != nil {
|
2017-01-26 20:46:06 +00:00
|
|
|
if os.IsNotExist(err) {
|
|
|
|
err = fmt.Errorf("upload directory does not exist: %s", store.Path)
|
|
|
|
}
|
2019-08-26 09:41:52 +00:00
|
|
|
return nil, err
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
upload := &fileUpload{
|
|
|
|
info: info,
|
|
|
|
infoPath: store.infoPath(id),
|
|
|
|
binPath: store.binPath(id),
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
// writeInfo creates the file by itself if necessary
|
|
|
|
err = upload.writeInfo()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2019-05-15 22:03:14 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
return upload, nil
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
func (store FileStore) GetUpload(id string) (handler.Upload, error) {
|
2019-06-11 16:23:20 +00:00
|
|
|
info := handler.FileInfo{}
|
2015-02-01 13:57:57 +00:00
|
|
|
data, err := ioutil.ReadFile(store.infoPath(id))
|
|
|
|
if err != nil {
|
2019-08-26 09:41:52 +00:00
|
|
|
return nil, err
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
2016-02-21 22:38:38 +00:00
|
|
|
if err := json.Unmarshal(data, &info); err != nil {
|
2019-08-26 09:41:52 +00:00
|
|
|
return nil, err
|
2016-02-21 22:38:38 +00:00
|
|
|
}
|
|
|
|
|
2019-08-19 07:29:56 +00:00
|
|
|
binPath := store.binPath(id)
|
2019-08-26 09:41:52 +00:00
|
|
|
infoPath := store.infoPath(id)
|
2019-08-19 07:29:56 +00:00
|
|
|
stat, err := os.Stat(binPath)
|
2016-02-21 22:38:38 +00:00
|
|
|
if err != nil {
|
2019-08-26 09:41:52 +00:00
|
|
|
return nil, err
|
2016-02-21 22:38:38 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
info.Offset = stat.Size()
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
return &fileUpload{
|
|
|
|
info: info,
|
|
|
|
binPath: binPath,
|
|
|
|
infoPath: infoPath,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store FileStore) AsTerminatableUpload(upload handler.Upload) handler.TerminatableUpload {
|
|
|
|
return upload.(*fileUpload)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store FileStore) AsLengthDeclarableUpload(upload handler.Upload) handler.LengthDeclarableUpload {
|
|
|
|
return upload.(*fileUpload)
|
|
|
|
}
|
|
|
|
|
|
|
|
// binPath returns the path to the file storing the binary data.
|
|
|
|
func (store FileStore) binPath(id string) string {
|
|
|
|
return filepath.Join(store.Path, id)
|
|
|
|
}
|
|
|
|
|
|
|
|
// infoPath returns the path to the .info file storing the file's info.
|
|
|
|
func (store FileStore) infoPath(id string) string {
|
|
|
|
return filepath.Join(store.Path, id+".info")
|
|
|
|
}
|
|
|
|
|
|
|
|
type fileUpload struct {
|
|
|
|
// info stores the current information about the upload
|
|
|
|
info handler.FileInfo
|
|
|
|
// infoPath is the path to the .info file
|
|
|
|
infoPath string
|
|
|
|
// binPath is the path to the binary file (which has no extension)
|
|
|
|
binPath string
|
|
|
|
}
|
|
|
|
|
|
|
|
func (upload *fileUpload) GetInfo() (handler.FileInfo, error) {
|
|
|
|
return upload.info, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (upload *fileUpload) WriteChunk(offset int64, src io.Reader) (int64, error) {
|
|
|
|
file, err := os.OpenFile(upload.binPath, os.O_WRONLY|os.O_APPEND, defaultFilePerm)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
n, err := io.Copy(file, src)
|
|
|
|
|
|
|
|
// If the HTTP PATCH request gets interrupted in the middle (e.g. because
|
|
|
|
// the user wants to pause the upload), Go's net/http returns an io.ErrUnexpectedEOF.
|
|
|
|
// However, for FileStore it's not important whether the stream has ended
|
|
|
|
// on purpose or accidentally.
|
|
|
|
if err == io.ErrUnexpectedEOF {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
|
|
|
|
upload.info.Offset += n
|
|
|
|
|
|
|
|
return n, err
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
func (upload *fileUpload) GetReader() (io.Reader, error) {
|
|
|
|
return os.Open(upload.binPath)
|
2015-02-06 21:05:33 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
func (upload *fileUpload) Terminate() error {
|
|
|
|
if err := os.Remove(upload.infoPath); err != nil {
|
2015-02-28 13:47:39 +00:00
|
|
|
return err
|
|
|
|
}
|
2019-08-26 09:41:52 +00:00
|
|
|
if err := os.Remove(upload.binPath); err != nil {
|
2015-02-28 13:47:39 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-01-20 14:33:17 +00:00
|
|
|
func (store FileStore) ConcatUploads(dest string, uploads []string) (err error) {
|
|
|
|
file, err := os.OpenFile(store.binPath(dest), os.O_WRONLY|os.O_APPEND, defaultFilePerm)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
for _, id := range uploads {
|
2019-08-26 09:41:52 +00:00
|
|
|
src, err := os.Open(store.binPath(id))
|
2016-01-20 14:33:17 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2016-02-21 22:38:38 +00:00
|
|
|
if _, err := io.Copy(file, src); err != nil {
|
2016-01-20 14:33:17 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
func (upload *fileUpload) DeclareLength(length int64) error {
|
|
|
|
upload.info.Size = length
|
|
|
|
upload.info.SizeIsDeferred = false
|
|
|
|
return upload.writeInfo()
|
2018-05-13 14:28:02 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
// writeInfo updates the entire information. Everything will be overwritten.
|
|
|
|
func (upload *fileUpload) writeInfo() error {
|
|
|
|
data, err := json.Marshal(upload.info)
|
2015-12-26 20:23:09 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2019-08-26 09:41:52 +00:00
|
|
|
return ioutil.WriteFile(upload.infoPath, data, defaultFilePerm)
|
2015-12-26 20:23:09 +00:00
|
|
|
}
|
|
|
|
|
2019-08-26 09:41:52 +00:00
|
|
|
func (upload *fileUpload) FinishUpload() error {
|
|
|
|
return nil
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|