Refactoring file locking out of tusd.Handler

* Updated filestore.FileStore implementation to use simple locking scheme. Refactored to make interface use pointers. Removed locks from tusd.Handler
* Refactored sample code to use pointer implementation of FileStore.
* Moved ErrFileLocked variable to datastore.go
This commit is contained in:
Adam Kaplan 2015-10-01 13:40:56 -04:00
parent 3b4353578d
commit 766dabc238
5 changed files with 64 additions and 65 deletions

View File

@ -39,9 +39,7 @@ func main() {
}
var store tusd.DataStore
store = filestore.FileStore{
Path: dir,
}
store = filestore.NewFileStore(dir)
if storeSize > 0 {
store = limitedstore.New(storeSize, store)

View File

@ -2,8 +2,15 @@ package tusd
import (
"io"
"errors"
)
// Error indicating that the data store has locked the file for further edits.
// This error is not a part of the official tus specification. Implementers of
// tusd.DataStore have the option to return this error to signal a file is
// locked for writing, and cannot be written to by another HTTP request.
var ErrFileLocked = errors.New("file currently locked")
type MetaData map[string]string
type FileInfo struct {

View File

@ -7,6 +7,7 @@
package filestore
import (
"bytes"
"encoding/json"
"io"
"io/ioutil"
@ -23,10 +24,20 @@ var defaultFilePerm = os.FileMode(0775)
type FileStore struct {
// 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.
Path string
Path string
locks map[string]bool
}
func (store FileStore) NewUpload(info tusd.FileInfo) (id string, err error) {
// NewFileStore creates a new FileStore instance.
func NewFileStore(path string) (store *FileStore) {
store = &FileStore{
Path: path,
locks: make(map[string]bool),
}
return
}
func (store *FileStore) NewUpload(info tusd.FileInfo) (id string, err error) {
id = uid.Uid()
info.ID = id
@ -42,8 +53,13 @@ func (store FileStore) NewUpload(info tusd.FileInfo) (id string, err error) {
return
}
func (store FileStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
file, err := os.OpenFile(store.binPath(id), os.O_WRONLY|os.O_APPEND, defaultFilePerm)
func (store *FileStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
if !store.getLock(id) {
return 0, tusd.ErrFileLocked
}
defer store.clearLock(id)
file, err := os.OpenFile(store.binPath(id), os.O_WRONLY|os.O_APPEND, defaultFilePerm)
if err != nil {
return 0, err
}
@ -58,21 +74,28 @@ func (store FileStore) WriteChunk(id string, offset int64, src io.Reader) (int64
return n, err
}
func (store FileStore) GetInfo(id string) (tusd.FileInfo, error) {
info := tusd.FileInfo{}
data, err := ioutil.ReadFile(store.infoPath(id))
func (store *FileStore) GetInfo(id string) (info tusd.FileInfo, err error) {
data, err := ioutil.ReadFile(store.infoPath(id))
if err != nil {
return info, err
}
err = json.Unmarshal(data, &info)
return info, err
return
}
func (store FileStore) GetReader(id string) (io.Reader, error) {
return os.Open(store.binPath(id))
func (store *FileStore) GetReader(id string) (io.Reader, error) {
if !store.getLock(id) {
return bytes.NewReader(make([]byte, 0)), tusd.ErrFileLocked
}
defer store.clearLock(id)
return os.Open(store.binPath(id))
}
func (store FileStore) Terminate(id string) error {
func (store *FileStore) Terminate(id string) error {
if !store.getLock(id) {
return tusd.ErrFileLocked
}
defer store.clearLock(id)
if err := os.Remove(store.infoPath(id)); err != nil {
return err
}
@ -83,17 +106,17 @@ func (store FileStore) Terminate(id string) error {
}
// Return the path to the .bin storing the binary data
func (store FileStore) binPath(id string) string {
func (store *FileStore) binPath(id string) string {
return store.Path + "/" + id + ".bin"
}
// Return the path to the .info file storing the file's info
func (store FileStore) infoPath(id string) string {
func (store *FileStore) infoPath(id string) string {
return store.Path + "/" + id + ".info"
}
// Update the entire information. Everything will be overwritten.
func (store FileStore) writeInfo(id string, info tusd.FileInfo) error {
func (store *FileStore) writeInfo(id string, info tusd.FileInfo) error {
data, err := json.Marshal(info)
if err != nil {
return err
@ -102,7 +125,7 @@ func (store FileStore) writeInfo(id string, info tusd.FileInfo) error {
}
// Update the .info file using the new upload.
func (store FileStore) setOffset(id string, offset int64) error {
func (store *FileStore) setOffset(id string, offset int64) error {
info, err := store.GetInfo(id)
if err != nil {
return err
@ -116,3 +139,19 @@ func (store FileStore) setOffset(id string, offset int64) error {
info.Offset = offset
return store.writeInfo(id, info)
}
// getLock obtains a lock on reading/writing data for the given file ID.
func (store *FileStore) getLock(id string) (hasLock bool) {
if _, locked := store.locks[id]; locked {
hasLock = false
return
}
store.locks[id] = true
hasLock = true
return
}
// clearLock removes the lock for the given file ID.
func (store *FileStore) clearLock(id string) {
delete(store.locks, id)
}

View File

@ -11,7 +11,7 @@ import (
)
// Test interface implementation of Filestore
var _ tusd.DataStore = FileStore{}
var _ tusd.DataStore = NewFileStore("")
func TestFilestore(t *testing.T) {
tmp, err := ioutil.TempDir("", "tusd-filestore-")
@ -19,7 +19,7 @@ func TestFilestore(t *testing.T) {
t.Fatal(err)
}
store := FileStore{tmp}
store := NewFileStore(tmp)
// Create new upload
id, err := store.NewUpload(tusd.FileInfo{

View File

@ -25,7 +25,6 @@ var (
ErrInvalidUploadLength = errors.New("missing or invalid Upload-Length header")
ErrInvalidOffset = errors.New("missing or invalid Upload-Offset header")
ErrNotFound = errors.New("upload not found")
ErrFileLocked = errors.New("file currently locked")
ErrIllegalOffset = errors.New("illegal offset")
ErrSizeExceeded = errors.New("resource's size exceeded")
ErrNotImplemented = errors.New("feature not implemented")
@ -72,7 +71,6 @@ type Handler struct {
isBasePathAbs bool
basePath string
routeHandler http.Handler
locks map[string]bool
// For each finished upload the corresponding info object will be sent using
// this unbuffered channel. The NotifyCompleteUploads property in the Config
@ -106,7 +104,6 @@ func NewHandler(config Config) (*Handler, error) {
basePath: base,
isBasePathAbs: uri.IsAbs(),
routeHandler: mux,
locks: make(map[string]bool),
CompleteUploads: make(chan FileInfo),
}
@ -276,20 +273,6 @@ func (handler *Handler) headFile(w http.ResponseWriter, r *http.Request) {
func (handler *Handler) patchFile(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get(":id")
// Ensure file is not locked
if _, ok := handler.locks[id]; ok {
handler.sendError(w, ErrFileLocked)
return
}
// Lock file for further writes (heads are allowed)
handler.locks[id] = true
// File will be unlocked regardless of an error or success
defer func() {
delete(handler.locks, id)
}()
info, err := handler.dataStore.GetInfo(id)
if err != nil {
handler.sendError(w, err)
@ -354,20 +337,6 @@ func (handler *Handler) patchFile(w http.ResponseWriter, r *http.Request) {
func (handler *Handler) getFile(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get(":id")
// Ensure file is not locked
if _, ok := handler.locks[id]; ok {
handler.sendError(w, ErrFileLocked)
return
}
// Lock file for further writes (heads are allowed)
handler.locks[id] = true
// File will be unlocked regardless of an error or success
defer func() {
delete(handler.locks, id)
}()
info, err := handler.dataStore.GetInfo(id)
if err != nil {
handler.sendError(w, err)
@ -401,20 +370,6 @@ func (handler *Handler) getFile(w http.ResponseWriter, r *http.Request) {
func (handler *Handler) delFile(w http.ResponseWriter, r *http.Request) {
id := r.URL.Query().Get(":id")
// Ensure file is not locked
if _, ok := handler.locks[id]; ok {
handler.sendError(w, ErrFileLocked)
return
}
// Lock file for further writes (heads are allowed)
handler.locks[id] = true
// File will be unlocked regardless of an error or success
defer func() {
delete(handler.locks, id)
}()
err := handler.dataStore.Terminate(id)
if err != nil {
handler.sendError(w, err)