2015-02-01 15:17:56 +00:00
|
|
|
// FileStore is a storage backend used as a tusd.DataStore in tusd.NewHandler.
|
|
|
|
// 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
|
|
|
|
// `[id].bin` files contain the raw binary data uploaded.
|
|
|
|
// 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 (
|
2015-10-01 17:45:39 +00:00
|
|
|
"bytes"
|
2015-02-01 13:57:57 +00:00
|
|
|
"encoding/json"
|
|
|
|
"io"
|
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
|
|
|
|
"github.com/tus/tusd"
|
|
|
|
"github.com/tus/tusd/uid"
|
|
|
|
)
|
|
|
|
|
2015-02-06 21:07:53 +00:00
|
|
|
var defaultFilePerm = os.FileMode(0775)
|
2015-02-01 13:57:57 +00:00
|
|
|
|
2015-02-01 15:17:56 +00:00
|
|
|
// See the tusd.DataStore interface for documentation about the different
|
|
|
|
// 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
|
|
|
|
// whether the path exists, you os.MkdirAll in this case on your own.
|
2015-10-01 17:40:56 +00:00
|
|
|
Path string
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 17:40:56 +00:00
|
|
|
// NewFileStore creates a new FileStore instance.
|
|
|
|
func NewFileStore(path string) (store *FileStore) {
|
2015-10-01 17:45:39 +00:00
|
|
|
store = &FileStore{
|
|
|
|
Path: path,
|
|
|
|
}
|
|
|
|
return
|
2015-10-01 17:40:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (store *FileStore) NewUpload(info tusd.FileInfo) (id string, err error) {
|
2015-02-01 13:57:57 +00:00
|
|
|
id = uid.Uid()
|
2015-03-23 16:58:13 +00:00
|
|
|
info.ID = id
|
2015-02-01 13:57:57 +00:00
|
|
|
|
|
|
|
// Create .bin file with no content
|
|
|
|
file, err := os.OpenFile(store.binPath(id), os.O_CREATE|os.O_WRONLY, defaultFilePerm)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
// writeInfo creates the file by itself if necessary
|
|
|
|
err = store.writeInfo(id, info)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-10-01 17:40:56 +00:00
|
|
|
func (store *FileStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
2015-10-01 17:45:39 +00:00
|
|
|
file, err := os.OpenFile(store.binPath(id), os.O_WRONLY|os.O_APPEND, defaultFilePerm)
|
2015-02-01 13:57:57 +00:00
|
|
|
if err != nil {
|
2015-03-23 18:02:12 +00:00
|
|
|
return 0, err
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
defer file.Close()
|
|
|
|
|
|
|
|
n, err := io.Copy(file, src)
|
|
|
|
if n > 0 {
|
|
|
|
if err := store.setOffset(id, offset+n); err != nil {
|
2015-03-23 18:02:12 +00:00
|
|
|
return 0, err
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
}
|
2015-03-23 18:02:12 +00:00
|
|
|
return n, err
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 17:40:56 +00:00
|
|
|
func (store *FileStore) GetInfo(id string) (info tusd.FileInfo, err error) {
|
2015-10-01 17:45:39 +00:00
|
|
|
data, err := ioutil.ReadFile(store.infoPath(id))
|
2015-02-01 13:57:57 +00:00
|
|
|
if err != nil {
|
|
|
|
return info, err
|
|
|
|
}
|
|
|
|
err = json.Unmarshal(data, &info)
|
2015-10-01 17:40:56 +00:00
|
|
|
return
|
2015-02-01 13:57:57 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 17:40:56 +00:00
|
|
|
func (store *FileStore) GetReader(id string) (io.Reader, error) {
|
2015-10-21 18:28:22 +00:00
|
|
|
hasLock, err := store.LockFile(id)
|
|
|
|
if err != nil {
|
|
|
|
return bytes.NewReader(make([]byte, 0)), err
|
|
|
|
}
|
|
|
|
if !hasLock {
|
2015-10-01 17:45:39 +00:00
|
|
|
return bytes.NewReader(make([]byte, 0)), tusd.ErrFileLocked
|
2015-10-01 17:40:56 +00:00
|
|
|
}
|
2015-10-21 18:28:22 +00:00
|
|
|
defer store.UnlockFile(id)
|
2015-10-01 17:45:39 +00:00
|
|
|
return os.Open(store.binPath(id))
|
2015-02-06 21:05:33 +00:00
|
|
|
}
|
|
|
|
|
2015-10-01 17:40:56 +00:00
|
|
|
func (store *FileStore) Terminate(id string) error {
|
2015-02-28 13:47:39 +00:00
|
|
|
if err := os.Remove(store.infoPath(id)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
if err := os.Remove(store.binPath(id)); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2015-10-21 18:35:06 +00:00
|
|
|
func (store *FileStore) LockFile(id string) (hasLock bool, err error) {
|
2015-10-21 18:28:22 +00:00
|
|
|
info, err := store.GetInfo(id)
|
|
|
|
if err != nil {
|
|
|
|
hasLock = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if info.Locked {
|
|
|
|
// Cannot acquire lock if something else has the lock.
|
|
|
|
hasLock = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
info.Locked = true
|
2015-10-21 18:35:06 +00:00
|
|
|
err = store.writeInfo(id, info)
|
2015-10-21 18:28:22 +00:00
|
|
|
if err != nil {
|
|
|
|
hasLock = false
|
|
|
|
return
|
|
|
|
}
|
|
|
|
hasLock = true
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store *FileStore) UnlockFile(id string) (err error) {
|
|
|
|
info, err := store.GetInfo(id)
|
|
|
|
if err != nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
info.Locked = false
|
2015-10-21 18:35:06 +00:00
|
|
|
err = store.writeInfo(id, info)
|
2015-10-21 18:28:22 +00:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2015-02-01 15:17:56 +00:00
|
|
|
// Return the path to the .bin storing the binary data
|
2015-10-01 17:40:56 +00:00
|
|
|
func (store *FileStore) binPath(id string) string {
|
2015-02-01 13:57:57 +00:00
|
|
|
return store.Path + "/" + id + ".bin"
|
|
|
|
}
|
|
|
|
|
2015-02-01 15:17:56 +00:00
|
|
|
// Return the path to the .info file storing the file's info
|
2015-10-01 17:40:56 +00:00
|
|
|
func (store *FileStore) infoPath(id string) string {
|
2015-02-01 13:57:57 +00:00
|
|
|
return store.Path + "/" + id + ".info"
|
|
|
|
}
|
|
|
|
|
2015-02-01 15:17:56 +00:00
|
|
|
// Update the entire information. Everything will be overwritten.
|
2015-10-01 17:40:56 +00:00
|
|
|
func (store *FileStore) writeInfo(id string, info tusd.FileInfo) error {
|
2015-02-01 13:57:57 +00:00
|
|
|
data, err := json.Marshal(info)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return ioutil.WriteFile(store.infoPath(id), data, defaultFilePerm)
|
|
|
|
}
|
|
|
|
|
2015-02-01 15:17:56 +00:00
|
|
|
// Update the .info file using the new upload.
|
2015-10-01 17:40:56 +00:00
|
|
|
func (store *FileStore) setOffset(id string, offset int64) error {
|
2015-02-01 13:57:57 +00:00
|
|
|
info, err := store.GetInfo(id)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// never decrement the offset
|
|
|
|
if info.Offset >= offset {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
info.Offset = offset
|
|
|
|
return store.writeInfo(id, info)
|
|
|
|
}
|