Move terminating method in own interface

In addition, the DELETE handler is only provided if the TerminaterDataStore
interface is implemented.
This commit is contained in:
Marius 2015-12-27 00:44:02 +01:00
parent f96e2614fc
commit 4af7434c10
9 changed files with 69 additions and 22 deletions

View File

@ -43,7 +43,7 @@ func main() {
stderr.Fatalf("Unable to ensure directory exists: %s", err)
}
var store tusd.DataStore
var store tusd.TerminaterDataStore
store = filestore.New(dir)
if storeSize > 0 {

View File

@ -51,6 +51,14 @@ type DataStore interface {
// If the returned reader also implements the io.Closer interface, the
// Close() method will be invoked once everything has been read.
GetReader(id string) (io.Reader, error)
}
// TerminaterDataStore is the interface which must be implemented by DataStores
// if they want to receive DELETE requests using the Handler. If this interface
// is not implemented, no request handler for this method is attached.
type TerminaterDataStore interface {
DataStore
// Terminate an upload so any further requests to the resource, both reading
// and writing, must return os.ErrNotExist or similar.
Terminate(id string) error

View File

@ -39,9 +39,13 @@ func NewHandler(config Config) (*Handler, error) {
mux.Post("", http.HandlerFunc(handler.PostFile))
mux.Head(":id", http.HandlerFunc(handler.HeadFile))
mux.Get(":id", http.HandlerFunc(handler.GetFile))
mux.Del(":id", http.HandlerFunc(handler.DelFile))
mux.Add("PATCH", ":id", http.HandlerFunc(handler.PatchFile))
// Only attach the DELETE handler if the Terminate() method is provided
if _, ok := config.DataStore.(TerminaterDataStore); ok {
mux.Del(":id", http.HandlerFunc(handler.DelFile))
}
return routedHandler, nil
}

View File

@ -28,10 +28,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) {
return nil, ErrNotImplemented
}
func (store zeroStore) Terminate(id string) error {
return ErrNotImplemented
}
type httpTest struct {
Name string

View File

@ -22,7 +22,7 @@ import (
type LimitedStore struct {
StoreSize int64
tusd.DataStore
tusd.TerminaterDataStore
uploads map[string]int64
usedSize int64
@ -42,11 +42,13 @@ func (p pairlist) Len() int { return len(p) }
func (p pairlist) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
func (p pairlist) Less(i, j int) bool { return p[i].value < p[j].value }
// Create a new limited store with the given size as the maximum storage size
func New(storeSize int64, dataStore tusd.DataStore) *LimitedStore {
// New creates a new limited store with the given size as the maximum storage
// size. The wrapped data store needs to implement the TerminaterDataStore
// interface, in order to provide the required Terminate method.
func New(storeSize int64, dataStore tusd.TerminaterDataStore) *LimitedStore {
return &LimitedStore{
StoreSize: storeSize,
DataStore: dataStore,
TerminaterDataStore: dataStore,
uploads: make(map[string]int64),
mutex: new(sync.Mutex),
}
@ -60,7 +62,7 @@ func (store *LimitedStore) NewUpload(info tusd.FileInfo) (string, error) {
return "", err
}
id, err := store.DataStore.NewUpload(info)
id, err := store.TerminaterDataStore.NewUpload(info)
if err != nil {
return "", err
}
@ -79,7 +81,7 @@ func (store *LimitedStore) Terminate(id string) error {
}
func (store *LimitedStore) terminate(id string) error {
err := store.DataStore.Terminate(id)
err := store.TerminaterDataStore.Terminate(id)
if err != nil {
return err
}

View File

@ -24,10 +24,6 @@ func (store zeroStore) GetReader(id string) (io.Reader, error) {
return nil, tusd.ErrNotImplemented
}
func (store zeroStore) Terminate(id string) error {
return tusd.ErrNotImplemented
}
func TestMemoryLocker(t *testing.T) {
var locker tusd.LockerDataStore
locker = NewMemoryLocker(&zeroStore{})

View File

@ -17,7 +17,7 @@ func TestOptions(t *testing.T) {
Method: "OPTIONS",
Code: http.StatusNoContent,
ResHeader: map[string]string{
"Tus-Extension": "creation,concatenation,termination",
"Tus-Extension": "creation,concatenation",
"Tus-Version": "1.0.0",
"Tus-Resumable": "1.0.0",
"Tus-Max-Size": "400",

View File

@ -26,6 +26,16 @@ func TestTerminate(t *testing.T) {
},
})
(&httpTest{
Name: "Successful OPTIONS request",
Method: "OPTIONS",
URL: "",
ResHeader: map[string]string{
"Tus-Extension": "creation,concatenation,termination",
},
Code: http.StatusNoContent,
}).Run(handler, t)
(&httpTest{
Name: "Successful request",
Method: "DELETE",
@ -36,3 +46,19 @@ func TestTerminate(t *testing.T) {
Code: http.StatusNoContent,
}).Run(handler, t)
}
func TestTerminateNotImplemented(t *testing.T) {
handler, _ := NewHandler(Config{
DataStore: zeroStore{},
})
(&httpTest{
Name: "TerminaterDataStore not implemented",
Method: "DELETE",
URL: "foo",
ReqHeader: map[string]string{
"Tus-Resumable": "1.0.0",
},
Code: http.StatusMethodNotAllowed,
}).Run(handler, t)
}

View File

@ -76,6 +76,7 @@ type UnroutedHandler struct {
isBasePathAbs bool
basePath string
logger *log.Logger
extensions string
// For each finished upload the corresponding info object will be sent using
// this unbuffered channel. The NotifyCompleteUploads property in the Config
@ -108,6 +109,12 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
base = "/" + base
}
// Only promote extesions using the Tus-Extension header which are implemented
extensions := "creation,concatenation"
if _, ok := config.DataStore.(TerminaterDataStore); ok {
extensions += ",termination"
}
handler := &UnroutedHandler{
config: config,
dataStore: config.DataStore,
@ -115,6 +122,7 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
isBasePathAbs: uri.IsAbs(),
CompleteUploads: make(chan FileInfo),
logger: logger,
extensions: extensions,
}
return handler, nil
@ -167,7 +175,7 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
}
header.Set("Tus-Version", "1.0.0")
header.Set("Tus-Extension", "creation,concatenation,termination")
header.Set("Tus-Extension", handler.extensions)
w.WriteHeader(http.StatusNoContent)
return
@ -429,6 +437,13 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request)
// DelFile terminates an upload permanently.
func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request) {
// Abort the request handling if the required interface is not implemented
tstore, ok := handler.config.DataStore.(TerminaterDataStore)
if !ok {
handler.sendError(w, r, ErrNotImplemented)
return
}
id, err := extractIDFromPath(r.URL.Path)
if err != nil {
handler.sendError(w, r, err)
@ -444,7 +459,7 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
defer locker.UnlockUpload(id)
}
err = handler.dataStore.Terminate(id)
err = tstore.Terminate(id)
if err != nil {
handler.sendError(w, r, err)
return