Extract concatenation into own interface
This commit is contained in:
parent
bfde73ff89
commit
b6a28421af
|
@ -1,11 +1,8 @@
|
|||
package tusd_test
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
. "github.com/tus/tusd"
|
||||
|
@ -38,6 +35,10 @@ func (s concatPartialStore) GetInfo(id string) (FileInfo, error) {
|
|||
}, nil
|
||||
}
|
||||
|
||||
func (s concatPartialStore) ConcatUploads(id string, uploads []string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConcatPartial(t *testing.T) {
|
||||
handler, _ := NewHandler(Config{
|
||||
MaxSize: 400,
|
||||
|
@ -47,6 +48,16 @@ func TestConcatPartial(t *testing.T) {
|
|||
},
|
||||
})
|
||||
|
||||
(&httpTest{
|
||||
Name: "Successful OPTIONS request",
|
||||
Method: "OPTIONS",
|
||||
URL: "",
|
||||
ResHeader: map[string]string{
|
||||
"Tus-Extension": "creation,concatenation",
|
||||
},
|
||||
Code: http.StatusNoContent,
|
||||
}).Run(handler, t)
|
||||
|
||||
(&httpTest{
|
||||
Name: "Successful POST request",
|
||||
Method: "POST",
|
||||
|
@ -122,33 +133,15 @@ func (s concatFinalStore) GetInfo(id string) (FileInfo, error) {
|
|||
return FileInfo{}, ErrNotFound
|
||||
}
|
||||
|
||||
func (s concatFinalStore) GetReader(id string) (io.Reader, error) {
|
||||
if id == "a" {
|
||||
return strings.NewReader("hello"), nil
|
||||
}
|
||||
|
||||
if id == "b" {
|
||||
return strings.NewReader("world"), nil
|
||||
}
|
||||
|
||||
return nil, ErrNotFound
|
||||
}
|
||||
|
||||
func (s concatFinalStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
||||
func (s concatFinalStore) ConcatUploads(id string, uploads []string) error {
|
||||
if id != "foo" {
|
||||
s.t.Error("unexpected file id")
|
||||
s.t.Error("expected final file id to be foo")
|
||||
}
|
||||
|
||||
if offset != 0 {
|
||||
s.t.Error("expected offset to be 0")
|
||||
if !reflect.DeepEqual(uploads, []string{"a", "b"}) {
|
||||
s.t.Errorf("expected Concatenating uploads to be a and b")
|
||||
}
|
||||
|
||||
b, _ := ioutil.ReadAll(src)
|
||||
if string(b) != "helloworld" {
|
||||
s.t.Error("unexpected content")
|
||||
}
|
||||
|
||||
return 10, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestConcatFinal(t *testing.T) {
|
||||
|
|
15
datastore.go
15
datastore.go
|
@ -107,3 +107,18 @@ type GetReaderDataStore interface {
|
|||
// be returned.
|
||||
GetReader(id string) (io.Reader, error)
|
||||
}
|
||||
|
||||
// ConcaterDataStore is the interface required to be implemented if the
|
||||
// Concatenation extension should be enabled. Only in this case, the handler
|
||||
// will parse and respect the Upload-Concat header.
|
||||
type ConcaterDataStore interface {
|
||||
DataStore
|
||||
|
||||
// ConcatUploads concatenations the content from the provided partial uploads
|
||||
// and write the result in the destination upload which is specified by its
|
||||
// ID. The caller (usually the handler) must and will ensure that this
|
||||
// destination upload has been created before with enough space to hold all
|
||||
// partial uploads. The order, in which the partial uploads are supplied,
|
||||
// must be respected during concatenation.
|
||||
ConcatUploads(destination string, partialUploads []string) error
|
||||
}
|
||||
|
|
|
@ -102,6 +102,34 @@ func (store FileStore) Terminate(id string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
var bytesRead int64
|
||||
defer func() {
|
||||
err = store.setOffset(dest, bytesRead)
|
||||
}()
|
||||
|
||||
for _, id := range uploads {
|
||||
src, err := store.GetReader(id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
n, err := io.Copy(file, src)
|
||||
bytesRead += n
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (store FileStore) LockUpload(id string) error {
|
||||
lock, err := store.newLock(id)
|
||||
if err != nil {
|
||||
|
|
|
@ -15,6 +15,7 @@ var _ tusd.DataStore = FileStore{}
|
|||
var _ tusd.GetReaderDataStore = FileStore{}
|
||||
var _ tusd.TerminaterDataStore = FileStore{}
|
||||
var _ tusd.LockerDataStore = FileStore{}
|
||||
var _ tusd.ConcaterDataStore = FileStore{}
|
||||
|
||||
func TestFilestore(t *testing.T) {
|
||||
tmp, err := ioutil.TempDir("", "tusd-filestore-")
|
||||
|
|
|
@ -17,7 +17,7 @@ func TestOptions(t *testing.T) {
|
|||
Method: "OPTIONS",
|
||||
Code: http.StatusNoContent,
|
||||
ResHeader: map[string]string{
|
||||
"Tus-Extension": "creation,concatenation",
|
||||
"Tus-Extension": "creation",
|
||||
"Tus-Version": "1.0.0",
|
||||
"Tus-Resumable": "1.0.0",
|
||||
"Tus-Max-Size": "400",
|
||||
|
|
|
@ -31,7 +31,7 @@ func TestTerminate(t *testing.T) {
|
|||
Method: "OPTIONS",
|
||||
URL: "",
|
||||
ResHeader: map[string]string{
|
||||
"Tus-Extension": "creation,concatenation,termination",
|
||||
"Tus-Extension": "creation,termination",
|
||||
},
|
||||
Code: http.StatusNoContent,
|
||||
}).Run(handler, t)
|
||||
|
|
|
@ -110,10 +110,13 @@ func NewUnroutedHandler(config Config) (*UnroutedHandler, error) {
|
|||
}
|
||||
|
||||
// Only promote extesions using the Tus-Extension header which are implemented
|
||||
extensions := "creation,concatenation"
|
||||
extensions := "creation"
|
||||
if _, ok := config.DataStore.(TerminaterDataStore); ok {
|
||||
extensions += ",termination"
|
||||
}
|
||||
if _, ok := config.DataStore.(ConcaterDataStore); ok {
|
||||
extensions += ",concatenation"
|
||||
}
|
||||
|
||||
handler := &UnroutedHandler{
|
||||
config: config,
|
||||
|
@ -197,8 +200,16 @@ func (handler *UnroutedHandler) Middleware(h http.Handler) http.Handler {
|
|||
// PostFile creates a new file upload using the datastore after validating the
|
||||
// length and parsing the metadata.
|
||||
func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request) {
|
||||
// Only use the proper Upload-Concat header if the concatenation extension
|
||||
// is even supported by the data store.
|
||||
var concatHeader string
|
||||
concatStore, ok := handler.dataStore.(ConcaterDataStore)
|
||||
if ok {
|
||||
concatHeader = r.Header.Get("Upload-Concat")
|
||||
}
|
||||
|
||||
// Parse Upload-Concat header
|
||||
isPartial, isFinal, partialUploads, err := parseConcat(r.Header.Get("Upload-Concat"))
|
||||
isPartial, isFinal, partialUploads, err := parseConcat(concatHeader)
|
||||
if err != nil {
|
||||
handler.sendError(w, r, err)
|
||||
return
|
||||
|
@ -246,7 +257,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
|||
}
|
||||
|
||||
if isFinal {
|
||||
if err := handler.fillFinalUpload(id, partialUploads); err != nil {
|
||||
if err := concatStore.ConcatUploads(id, partialUploads); err != nil {
|
||||
handler.sendError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
@ -548,30 +559,6 @@ func (handler *UnroutedHandler) sizeOfUploads(ids []string) (size int64, err err
|
|||
return
|
||||
}
|
||||
|
||||
// Fill an empty upload with the content of the uploads by their ids. The data
|
||||
// will be written in the order as they appear in the slice
|
||||
func (handler *UnroutedHandler) fillFinalUpload(id string, uploads []string) error {
|
||||
dataStore, ok := handler.dataStore.(GetReaderDataStore)
|
||||
if !ok {
|
||||
return ErrNotImplemented
|
||||
}
|
||||
|
||||
readers := make([]io.Reader, len(uploads))
|
||||
|
||||
for index, uploadID := range uploads {
|
||||
reader, err := dataStore.GetReader(uploadID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
readers[index] = reader
|
||||
}
|
||||
|
||||
reader := io.MultiReader(readers...)
|
||||
_, err := handler.dataStore.WriteChunk(id, 0, reader)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Parse the Upload-Metadata header as defined in the File Creation extension.
|
||||
// e.g. Upload-Metadata: name bHVucmpzLnBuZw==,type aW1hZ2UvcG5n
|
||||
func parseMeta(header string) map[string]string {
|
||||
|
|
Loading…
Reference in New Issue