WIP on DataStore refactor
This commit is contained in:
parent
aa0280d004
commit
5acc586800
|
@ -0,0 +1,83 @@
|
||||||
|
// Package filestore provide a storage backend based on the local file system.
|
||||||
|
//
|
||||||
|
// FileStore is a storage backend used as a handler.DataStore in handler.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]` files without an extension 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.
|
||||||
|
//
|
||||||
|
// In addition, it provides an exclusive upload locking mechanism using lock files
|
||||||
|
// which are stored on disk. Each of them stores the PID of the process which
|
||||||
|
// acquired the lock. This allows locks to be automatically freed when a process
|
||||||
|
// is unable to release it on its own because the process is not alive anymore.
|
||||||
|
// For more information, consult the documentation for handler.LockerDataStore
|
||||||
|
// interface, which is implemented by FileStore
|
||||||
|
package filestore
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/tus/tusd/pkg/handler"
|
||||||
|
|
||||||
|
"gopkg.in/Acconut/lockfile.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var defaultFilePerm = os.FileMode(0664)
|
||||||
|
|
||||||
|
// See the handler.DataStore interface for documentation about the different
|
||||||
|
// methods.
|
||||||
|
type FileLocker struct {
|
||||||
|
// Relative or absolute path to store files in. FileStore does not check
|
||||||
|
// whether the path exists, use os.MkdirAll in this case on your own.
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// In addition, a locking mechanism is provided.
|
||||||
|
func New(path string) FileLocker {
|
||||||
|
return FileLocker{path}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (locker FileLocker) NewLock(id string) (handler.UploadLock, error) {
|
||||||
|
path, err := filepath.Abs(filepath.Join(locker.Path, id+".lock"))
|
||||||
|
if err != nil {
|
||||||
|
return lockfile.Lockfile(""), err
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use Lockfile directly instead of lockfile.New to bypass the unnecessary
|
||||||
|
// check whether the provided path is absolute since we just resolved it
|
||||||
|
// on our own.
|
||||||
|
return fileUploadLock{
|
||||||
|
file: lockfile.Lockfile(path),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type fileUploadLock struct {
|
||||||
|
file lockfile.Lockfile
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock fileUploadLock) Lock() error {
|
||||||
|
err = lock.file.TryLock()
|
||||||
|
if err == lockfile.ErrBusy {
|
||||||
|
return handler.ErrFileLocked
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (lock fileUploadLock) Unlock() error {
|
||||||
|
err = lock.file.Unlock()
|
||||||
|
|
||||||
|
// A "no such file or directory" will be returned if no lockfile was found.
|
||||||
|
// Since this means that the file has never been locked, we drop the error
|
||||||
|
// and continue as if nothing happened.
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
|
@ -8,12 +8,8 @@ type StoreComposer struct {
|
||||||
|
|
||||||
UsesTerminater bool
|
UsesTerminater bool
|
||||||
Terminater TerminaterDataStore
|
Terminater TerminaterDataStore
|
||||||
UsesFinisher bool
|
|
||||||
Finisher FinisherDataStore
|
|
||||||
UsesLocker bool
|
UsesLocker bool
|
||||||
Locker LockerDataStore
|
Locker LockerDataStore
|
||||||
UsesGetReader bool
|
|
||||||
GetReader GetReaderDataStore
|
|
||||||
UsesConcater bool
|
UsesConcater bool
|
||||||
Concater ConcaterDataStore
|
Concater ConcaterDataStore
|
||||||
UsesLengthDeferrer bool
|
UsesLengthDeferrer bool
|
||||||
|
@ -42,24 +38,12 @@ func (store *StoreComposer) Capabilities() string {
|
||||||
} else {
|
} else {
|
||||||
str += "✗"
|
str += "✗"
|
||||||
}
|
}
|
||||||
str += ` Finisher: `
|
|
||||||
if store.UsesFinisher {
|
|
||||||
str += "✓"
|
|
||||||
} else {
|
|
||||||
str += "✗"
|
|
||||||
}
|
|
||||||
str += ` Locker: `
|
str += ` Locker: `
|
||||||
if store.UsesLocker {
|
if store.UsesLocker {
|
||||||
str += "✓"
|
str += "✓"
|
||||||
} else {
|
} else {
|
||||||
str += "✗"
|
str += "✗"
|
||||||
}
|
}
|
||||||
str += ` GetReader: `
|
|
||||||
if store.UsesGetReader {
|
|
||||||
str += "✓"
|
|
||||||
} else {
|
|
||||||
str += "✗"
|
|
||||||
}
|
|
||||||
str += ` Concater: `
|
str += ` Concater: `
|
||||||
if store.UsesConcater {
|
if store.UsesConcater {
|
||||||
str += "✓"
|
str += "✓"
|
||||||
|
@ -86,18 +70,12 @@ func (store *StoreComposer) UseTerminater(ext TerminaterDataStore) {
|
||||||
store.UsesTerminater = ext != nil
|
store.UsesTerminater = ext != nil
|
||||||
store.Terminater = ext
|
store.Terminater = ext
|
||||||
}
|
}
|
||||||
func (store *StoreComposer) UseFinisher(ext FinisherDataStore) {
|
|
||||||
store.UsesFinisher = ext != nil
|
|
||||||
store.Finisher = ext
|
|
||||||
}
|
|
||||||
func (store *StoreComposer) UseLocker(ext LockerDataStore) {
|
func (store *StoreComposer) UseLocker(ext LockerDataStore) {
|
||||||
store.UsesLocker = ext != nil
|
store.UsesLocker = ext != nil
|
||||||
store.Locker = ext
|
store.Locker = ext
|
||||||
}
|
}
|
||||||
func (store *StoreComposer) UseGetReader(ext GetReaderDataStore) {
|
|
||||||
store.UsesGetReader = ext != nil
|
|
||||||
store.GetReader = ext
|
|
||||||
}
|
|
||||||
func (store *StoreComposer) UseConcater(ext ConcaterDataStore) {
|
func (store *StoreComposer) UseConcater(ext ConcaterDataStore) {
|
||||||
store.UsesConcater = ext != nil
|
store.UsesConcater = ext != nil
|
||||||
store.Concater = ext
|
store.Concater = ext
|
||||||
|
|
|
@ -47,12 +47,7 @@ func (f FileInfo) StopUpload() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type DataStore interface {
|
type Upload interface {
|
||||||
// Create a new upload using the size as the file's length. The method must
|
|
||||||
// return an unique id which is used to identify the upload. If no backend
|
|
||||||
// (e.g. Riak) specifes the id you may want to use the uid package to
|
|
||||||
// generate one. The properties Size and MetaData will be filled.
|
|
||||||
NewUpload(info FileInfo) (id string, err error)
|
|
||||||
// Write the chunk read from src into the file specified by the id at the
|
// Write the chunk read from src into the file specified by the id at the
|
||||||
// given offset. The handler will take care of validating the offset and
|
// given offset. The handler will take care of validating the offset and
|
||||||
// limiting the size of the src to not overflow the file's size. It may
|
// limiting the size of the src to not overflow the file's size. It may
|
||||||
|
@ -60,31 +55,50 @@ type DataStore interface {
|
||||||
// It will also lock resources while they are written to ensure only one
|
// It will also lock resources while they are written to ensure only one
|
||||||
// write happens per time.
|
// write happens per time.
|
||||||
// The function call must return the number of bytes written.
|
// The function call must return the number of bytes written.
|
||||||
WriteChunk(id string, offset int64, src io.Reader) (int64, error)
|
WriteChunk(offset int64, src io.Reader) (int64, error)
|
||||||
// Read the fileinformation used to validate the offset and respond to HEAD
|
// Read the fileinformation used to validate the offset and respond to HEAD
|
||||||
// requests. It may return an os.ErrNotExist which will be interpreted as a
|
// requests. It may return an os.ErrNotExist which will be interpreted as a
|
||||||
// 404 Not Found.
|
// 404 Not Found.
|
||||||
GetInfo(id string) (FileInfo, error)
|
GetInfo() (FileInfo, error)
|
||||||
|
// GetReader returns a reader which allows iterating of the content of an
|
||||||
|
// upload specified by its ID. It should attempt to provide a reader even if
|
||||||
|
// the upload has not been finished yet but it's not required.
|
||||||
|
// If the returned reader also implements the io.Closer interface, the
|
||||||
|
// Close() method will be invoked once everything has been read.
|
||||||
|
// If the given upload could not be found, the error tusd.ErrNotFound should
|
||||||
|
// be returned.
|
||||||
|
GetReader() (io.Reader, error)
|
||||||
|
// FinisherDataStore is the interface which can be implemented by DataStores
|
||||||
|
// which need to do additional operations once an entire upload has been
|
||||||
|
// completed. These tasks may include but are not limited to freeing unused
|
||||||
|
// resources or notifying other services. For example, S3Store uses this
|
||||||
|
// interface for removing a temporary object.
|
||||||
|
// FinishUpload executes additional operations for the finished upload which
|
||||||
|
// is specified by its ID.
|
||||||
|
FinishUpload() error
|
||||||
|
}
|
||||||
|
|
||||||
|
type DataStore interface {
|
||||||
|
// Create a new upload using the size as the file's length. The method must
|
||||||
|
// return an unique id which is used to identify the upload. If no backend
|
||||||
|
// (e.g. Riak) specifes the id you may want to use the uid package to
|
||||||
|
// generate one. The properties Size and MetaData will be filled.
|
||||||
|
NewUpload(info FileInfo) (upload Upload, err error)
|
||||||
|
|
||||||
|
GetUpload(id string) (upload Upload, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TerminatableUpload interface {
|
||||||
|
// Terminate an upload so any further requests to the resource, both reading
|
||||||
|
// and writing, must return os.ErrNotExist or similar.
|
||||||
|
Terminate() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// TerminaterDataStore is the interface which must be implemented by DataStores
|
// TerminaterDataStore is the interface which must be implemented by DataStores
|
||||||
// if they want to receive DELETE requests using the Handler. If this interface
|
// if they want to receive DELETE requests using the Handler. If this interface
|
||||||
// is not implemented, no request handler for this method is attached.
|
// is not implemented, no request handler for this method is attached.
|
||||||
type TerminaterDataStore interface {
|
type TerminaterDataStore interface {
|
||||||
// Terminate an upload so any further requests to the resource, both reading
|
AsTerminatableUpload(upload Upload) TerminatableUpload
|
||||||
// and writing, must return os.ErrNotExist or similar.
|
|
||||||
Terminate(id string) error
|
|
||||||
}
|
|
||||||
|
|
||||||
// FinisherDataStore is the interface which can be implemented by DataStores
|
|
||||||
// which need to do additional operations once an entire upload has been
|
|
||||||
// completed. These tasks may include but are not limited to freeing unused
|
|
||||||
// resources or notifying other services. For example, S3Store uses this
|
|
||||||
// interface for removing a temporary object.
|
|
||||||
type FinisherDataStore interface {
|
|
||||||
// FinishUpload executes additional operations for the finished upload which
|
|
||||||
// is specified by its ID.
|
|
||||||
FinishUpload(id string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LockerDataStore is the interface required for custom lock persisting mechanisms.
|
// LockerDataStore is the interface required for custom lock persisting mechanisms.
|
||||||
|
@ -106,22 +120,6 @@ type LockerDataStore interface {
|
||||||
UnlockUpload(id string) error
|
UnlockUpload(id string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReaderDataStore is the interface which must be implemented if handler should
|
|
||||||
// expose and support the GET route. It will allow clients to download the
|
|
||||||
// content of an upload regardless whether it's finished or not.
|
|
||||||
// Please, be aware that this feature is not part of the official tus
|
|
||||||
// specification. Instead it's a custom mechanism by tusd.
|
|
||||||
type GetReaderDataStore interface {
|
|
||||||
// GetReader returns a reader which allows iterating of the content of an
|
|
||||||
// upload specified by its ID. It should attempt to provide a reader even if
|
|
||||||
// the upload has not been finished yet but it's not required.
|
|
||||||
// If the returned reader also implements the io.Closer interface, the
|
|
||||||
// Close() method will be invoked once everything has been read.
|
|
||||||
// If the given upload could not be found, the error tusd.ErrNotFound should
|
|
||||||
// be returned.
|
|
||||||
GetReader(id string) (io.Reader, error)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ConcaterDataStore is the interface required to be implemented if the
|
// ConcaterDataStore is the interface required to be implemented if the
|
||||||
// Concatenation extension should be enabled. Only in this case, the handler
|
// Concatenation extension should be enabled. Only in this case, the handler
|
||||||
// will parse and respect the Upload-Concat header.
|
// will parse and respect the Upload-Concat header.
|
||||||
|
@ -140,5 +138,9 @@ type ConcaterDataStore interface {
|
||||||
// client to upload files when their total size is not yet known. Instead, the
|
// client to upload files when their total size is not yet known. Instead, the
|
||||||
// client must send the total size as soon as it becomes known.
|
// client must send the total size as soon as it becomes known.
|
||||||
type LengthDeferrerDataStore interface {
|
type LengthDeferrerDataStore interface {
|
||||||
DeclareLength(id string, length int64) error
|
AsLengthDeclarableUpload(upload Upload) LengthDeclarableUpload
|
||||||
|
}
|
||||||
|
|
||||||
|
type LengthDeclarableUpload interface {
|
||||||
|
DeclareLength(length int64) error
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package handler_test
|
package handler_test
|
||||||
|
|
||||||
|
/*
|
||||||
import (
|
import (
|
||||||
"github.com/tus/tusd/pkg/filestore"
|
"github.com/tus/tusd/pkg/filestore"
|
||||||
"github.com/tus/tusd/pkg/handler"
|
"github.com/tus/tusd/pkg/handler"
|
||||||
|
@ -21,3 +22,4 @@ func ExampleNewStoreComposer() {
|
||||||
|
|
||||||
_, _ = handler.NewHandler(config)
|
_, _ = handler.NewHandler(config)
|
||||||
}
|
}
|
||||||
|
*/
|
|
@ -1,7 +1,6 @@
|
||||||
package handler
|
package handler
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"io"
|
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
@ -9,15 +8,11 @@ import (
|
||||||
|
|
||||||
type zeroStore struct{}
|
type zeroStore struct{}
|
||||||
|
|
||||||
func (store zeroStore) NewUpload(info FileInfo) (string, error) {
|
func (store zeroStore) NewUpload(info FileInfo) (Upload, error) {
|
||||||
return "", nil
|
return nil, nil
|
||||||
}
|
}
|
||||||
func (store zeroStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
func (store zeroStore) GetUpload(id string) (Upload, error) {
|
||||||
return 0, nil
|
return nil, nil
|
||||||
}
|
|
||||||
|
|
||||||
func (store zeroStore) GetInfo(id string) (FileInfo, error) {
|
|
||||||
return FileInfo{}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestConfig(t *testing.T) {
|
func TestConfig(t *testing.T) {
|
|
@ -0,0 +1,93 @@
|
||||||
|
package handler_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/golang/mock/gomock"
|
||||||
|
. "github.com/tus/tusd/pkg/handler"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTerminate(t *testing.T) {
|
||||||
|
SubTest(t, "ExtensionDiscovery", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
||||||
|
composer = NewStoreComposer()
|
||||||
|
composer.UseCore(store)
|
||||||
|
composer.UseTerminater(store)
|
||||||
|
|
||||||
|
handler, _ := NewHandler(Config{
|
||||||
|
StoreComposer: composer,
|
||||||
|
})
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Method: "OPTIONS",
|
||||||
|
Code: http.StatusOK,
|
||||||
|
ResHeader: map[string]string{
|
||||||
|
"Tus-Extension": "creation,creation-with-upload,termination",
|
||||||
|
},
|
||||||
|
}).Run(handler, t)
|
||||||
|
})
|
||||||
|
|
||||||
|
SubTest(t, "Termination", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
||||||
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
locker := NewMockLocker(ctrl)
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
locker.EXPECT().LockUpload("foo"),
|
||||||
|
store.EXPECT().GetInfo("foo").Return(FileInfo{
|
||||||
|
ID: "foo",
|
||||||
|
Size: 10,
|
||||||
|
}, nil),
|
||||||
|
store.EXPECT().Terminate("foo").Return(nil),
|
||||||
|
locker.EXPECT().UnlockUpload("foo"),
|
||||||
|
)
|
||||||
|
|
||||||
|
composer = NewStoreComposer()
|
||||||
|
composer.UseCore(store)
|
||||||
|
composer.UseTerminater(store)
|
||||||
|
composer.UseLocker(locker)
|
||||||
|
|
||||||
|
handler, _ := NewHandler(Config{
|
||||||
|
StoreComposer: composer,
|
||||||
|
NotifyTerminatedUploads: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
c := make(chan FileInfo, 1)
|
||||||
|
handler.TerminatedUploads = c
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Method: "DELETE",
|
||||||
|
URL: "foo",
|
||||||
|
ReqHeader: map[string]string{
|
||||||
|
"Tus-Resumable": "1.0.0",
|
||||||
|
},
|
||||||
|
Code: http.StatusNoContent,
|
||||||
|
}).Run(handler, t)
|
||||||
|
|
||||||
|
info := <-c
|
||||||
|
|
||||||
|
a := assert.New(t)
|
||||||
|
a.Equal("foo", info.ID)
|
||||||
|
a.Equal(int64(10), info.Size)
|
||||||
|
})
|
||||||
|
|
||||||
|
SubTest(t, "NotProvided", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
||||||
|
composer = NewStoreComposer()
|
||||||
|
composer.UseCore(store)
|
||||||
|
|
||||||
|
handler, _ := NewUnroutedHandler(Config{
|
||||||
|
StoreComposer: composer,
|
||||||
|
})
|
||||||
|
|
||||||
|
(&httpTest{
|
||||||
|
Method: "DELETE",
|
||||||
|
URL: "foo",
|
||||||
|
ReqHeader: map[string]string{
|
||||||
|
"Tus-Resumable": "1.0.0",
|
||||||
|
},
|
||||||
|
Code: http.StatusNotImplemented,
|
||||||
|
}).Run(http.HandlerFunc(handler.DelFile), t)
|
||||||
|
})
|
||||||
|
}
|
|
@ -28,10 +28,12 @@ func TestGet(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
locker := NewMockLocker(ctrl)
|
locker := NewMockLocker(ctrl)
|
||||||
|
upload := NewMockFullUpload(ctrl)
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
locker.EXPECT().LockUpload("yes"),
|
locker.EXPECT().LockUpload("yes"),
|
||||||
store.EXPECT().GetInfo("yes").Return(FileInfo{
|
store.EXPECT().GetUpload("yes").Return(upload, nil),
|
||||||
|
upload.EXPECT().GetInfo().Return(FileInfo{
|
||||||
Offset: 5,
|
Offset: 5,
|
||||||
Size: 20,
|
Size: 20,
|
||||||
MetaData: map[string]string{
|
MetaData: map[string]string{
|
||||||
|
@ -39,13 +41,12 @@ func TestGet(t *testing.T) {
|
||||||
"filetype": "image/jpeg",
|
"filetype": "image/jpeg",
|
||||||
},
|
},
|
||||||
}, nil),
|
}, nil),
|
||||||
store.EXPECT().GetReader("yes").Return(reader, nil),
|
upload.EXPECT().GetReader().Return(reader, nil),
|
||||||
locker.EXPECT().UnlockUpload("yes"),
|
locker.EXPECT().UnlockUpload("yes"),
|
||||||
)
|
)
|
||||||
|
|
||||||
composer = NewStoreComposer()
|
composer = NewStoreComposer()
|
||||||
composer.UseCore(store)
|
composer.UseCore(store)
|
||||||
composer.UseGetReader(store)
|
|
||||||
composer.UseLocker(locker)
|
composer.UseLocker(locker)
|
||||||
|
|
||||||
handler, _ := NewHandler(Config{
|
handler, _ := NewHandler(Config{
|
||||||
|
@ -70,9 +71,16 @@ func TestGet(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
SubTest(t, "EmptyDownload", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
SubTest(t, "EmptyDownload", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
||||||
store.EXPECT().GetInfo("yes").Return(FileInfo{
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
upload := NewMockFullUpload(ctrl)
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
store.EXPECT().GetUpload("yes").Return(upload, nil),
|
||||||
|
upload.EXPECT().GetInfo().Return(FileInfo{
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
}, nil)
|
}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
handler, _ := NewHandler(Config{
|
handler, _ := NewHandler(Config{
|
||||||
StoreComposer: composer,
|
StoreComposer: composer,
|
||||||
|
@ -90,28 +98,20 @@ func TestGet(t *testing.T) {
|
||||||
}).Run(handler, t)
|
}).Run(handler, t)
|
||||||
})
|
})
|
||||||
|
|
||||||
SubTest(t, "NotProvided", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
|
||||||
composer = NewStoreComposer()
|
|
||||||
composer.UseCore(store)
|
|
||||||
|
|
||||||
handler, _ := NewUnroutedHandler(Config{
|
|
||||||
StoreComposer: composer,
|
|
||||||
})
|
|
||||||
|
|
||||||
(&httpTest{
|
|
||||||
Method: "GET",
|
|
||||||
URL: "foo",
|
|
||||||
Code: http.StatusNotImplemented,
|
|
||||||
}).Run(http.HandlerFunc(handler.GetFile), t)
|
|
||||||
})
|
|
||||||
|
|
||||||
SubTest(t, "InvalidFileType", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
SubTest(t, "InvalidFileType", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
||||||
store.EXPECT().GetInfo("yes").Return(FileInfo{
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
upload := NewMockFullUpload(ctrl)
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
store.EXPECT().GetUpload("yes").Return(upload, nil),
|
||||||
|
upload.EXPECT().GetInfo().Return(FileInfo{
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
MetaData: map[string]string{
|
MetaData: map[string]string{
|
||||||
"filetype": "non-a-valid-mime-type",
|
"filetype": "non-a-valid-mime-type",
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
handler, _ := NewHandler(Config{
|
handler, _ := NewHandler(Config{
|
||||||
StoreComposer: composer,
|
StoreComposer: composer,
|
||||||
|
@ -131,13 +131,20 @@ func TestGet(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
SubTest(t, "NotWhitelistedFileType", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
SubTest(t, "NotWhitelistedFileType", func(t *testing.T, store *MockFullDataStore, composer *StoreComposer) {
|
||||||
store.EXPECT().GetInfo("yes").Return(FileInfo{
|
ctrl := gomock.NewController(t)
|
||||||
|
defer ctrl.Finish()
|
||||||
|
upload := NewMockFullUpload(ctrl)
|
||||||
|
|
||||||
|
gomock.InOrder(
|
||||||
|
store.EXPECT().GetUpload("yes").Return(upload, nil),
|
||||||
|
upload.EXPECT().GetInfo().Return(FileInfo{
|
||||||
Offset: 0,
|
Offset: 0,
|
||||||
MetaData: map[string]string{
|
MetaData: map[string]string{
|
||||||
"filetype": "text/html",
|
"filetype": "text/html",
|
||||||
"filename": "invoice.html",
|
"filename": "invoice.html",
|
||||||
},
|
},
|
||||||
}, nil)
|
}, nil),
|
||||||
|
)
|
||||||
|
|
||||||
handler, _ := NewHandler(Config{
|
handler, _ := NewHandler(Config{
|
||||||
StoreComposer: composer,
|
StoreComposer: composer,
|
||||||
|
|
|
@ -40,16 +40,12 @@ func NewHandler(config Config) (*Handler, error) {
|
||||||
mux.Post("", http.HandlerFunc(handler.PostFile))
|
mux.Post("", http.HandlerFunc(handler.PostFile))
|
||||||
mux.Head(":id", http.HandlerFunc(handler.HeadFile))
|
mux.Head(":id", http.HandlerFunc(handler.HeadFile))
|
||||||
mux.Add("PATCH", ":id", http.HandlerFunc(handler.PatchFile))
|
mux.Add("PATCH", ":id", http.HandlerFunc(handler.PatchFile))
|
||||||
|
mux.Get(":id", http.HandlerFunc(handler.GetFile))
|
||||||
|
|
||||||
// Only attach the DELETE handler if the Terminate() method is provided
|
// Only attach the DELETE handler if the Terminate() method is provided
|
||||||
if config.StoreComposer.UsesTerminater {
|
if config.StoreComposer.UsesTerminater {
|
||||||
mux.Del(":id", http.HandlerFunc(handler.DelFile))
|
mux.Del(":id", http.HandlerFunc(handler.DelFile))
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET handler requires the GetReader() method
|
|
||||||
if config.StoreComposer.UsesGetReader {
|
|
||||||
mux.Get(":id", http.HandlerFunc(handler.GetFile))
|
|
||||||
}
|
|
||||||
|
|
||||||
return routedHandler, nil
|
return routedHandler, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,156 +1,268 @@
|
||||||
// Automatically generated by MockGen. DO NOT EDIT!
|
// Code generated by MockGen. DO NOT EDIT.
|
||||||
// Source: utils_test.go
|
// Source: utils_test.go
|
||||||
|
|
||||||
|
// Package handler_test is a generated GoMock package.
|
||||||
package handler_test
|
package handler_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
gomock "github.com/golang/mock/gomock"
|
gomock "github.com/golang/mock/gomock"
|
||||||
handler "github.com/tus/tusd/pkg/handler"
|
handler "github.com/tus/tusd/pkg/handler"
|
||||||
io "io"
|
io "io"
|
||||||
|
reflect "reflect"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Mock of FullDataStore interface
|
// MockFullDataStore is a mock of FullDataStore interface
|
||||||
type MockFullDataStore struct {
|
type MockFullDataStore struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *_MockFullDataStoreRecorder
|
recorder *MockFullDataStoreMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recorder for MockFullDataStore (not exported)
|
// MockFullDataStoreMockRecorder is the mock recorder for MockFullDataStore
|
||||||
type _MockFullDataStoreRecorder struct {
|
type MockFullDataStoreMockRecorder struct {
|
||||||
mock *MockFullDataStore
|
mock *MockFullDataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMockFullDataStore creates a new mock instance
|
||||||
func NewMockFullDataStore(ctrl *gomock.Controller) *MockFullDataStore {
|
func NewMockFullDataStore(ctrl *gomock.Controller) *MockFullDataStore {
|
||||||
mock := &MockFullDataStore{ctrl: ctrl}
|
mock := &MockFullDataStore{ctrl: ctrl}
|
||||||
mock.recorder = &_MockFullDataStoreRecorder{mock}
|
mock.recorder = &MockFullDataStoreMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockFullDataStore) EXPECT() *_MockFullDataStoreRecorder {
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
return _m.recorder
|
func (m *MockFullDataStore) EXPECT() *MockFullDataStoreMockRecorder {
|
||||||
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockFullDataStore) NewUpload(info handler.FileInfo) (string, error) {
|
// NewUpload mocks base method
|
||||||
ret := _m.ctrl.Call(_m, "NewUpload", info)
|
func (m *MockFullDataStore) NewUpload(info handler.FileInfo) (handler.Upload, error) {
|
||||||
ret0, _ := ret[0].(string)
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "NewUpload", info)
|
||||||
|
ret0, _ := ret[0].(handler.Upload)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockFullDataStoreRecorder) NewUpload(arg0 interface{}) *gomock.Call {
|
// NewUpload indicates an expected call of NewUpload
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "NewUpload", arg0)
|
func (mr *MockFullDataStoreMockRecorder) NewUpload(info interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "NewUpload", reflect.TypeOf((*MockFullDataStore)(nil).NewUpload), info)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockFullDataStore) WriteChunk(id string, offset int64, src io.Reader) (int64, error) {
|
// GetUpload mocks base method
|
||||||
ret := _m.ctrl.Call(_m, "WriteChunk", id, offset, src)
|
func (m *MockFullDataStore) GetUpload(id string) (handler.Upload, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetUpload", id)
|
||||||
|
ret0, _ := ret[0].(handler.Upload)
|
||||||
|
ret1, _ := ret[1].(error)
|
||||||
|
return ret0, ret1
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUpload indicates an expected call of GetUpload
|
||||||
|
func (mr *MockFullDataStoreMockRecorder) GetUpload(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUpload", reflect.TypeOf((*MockFullDataStore)(nil).GetUpload), id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsTerminatableUpload mocks base method
|
||||||
|
func (m *MockFullDataStore) AsTerminatableUpload(upload handler.Upload) handler.TerminatableUpload {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "AsTerminatableUpload", upload)
|
||||||
|
ret0, _ := ret[0].(handler.TerminatableUpload)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsTerminatableUpload indicates an expected call of AsTerminatableUpload
|
||||||
|
func (mr *MockFullDataStoreMockRecorder) AsTerminatableUpload(upload interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AsTerminatableUpload", reflect.TypeOf((*MockFullDataStore)(nil).AsTerminatableUpload), upload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConcatUploads mocks base method
|
||||||
|
func (m *MockFullDataStore) ConcatUploads(destination string, partialUploads []string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "ConcatUploads", destination, partialUploads)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConcatUploads indicates an expected call of ConcatUploads
|
||||||
|
func (mr *MockFullDataStoreMockRecorder) ConcatUploads(destination, partialUploads interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConcatUploads", reflect.TypeOf((*MockFullDataStore)(nil).ConcatUploads), destination, partialUploads)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsLengthDeclarableUpload mocks base method
|
||||||
|
func (m *MockFullDataStore) AsLengthDeclarableUpload(upload handler.Upload) handler.LengthDeclarableUpload {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "AsLengthDeclarableUpload", upload)
|
||||||
|
ret0, _ := ret[0].(handler.LengthDeclarableUpload)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// AsLengthDeclarableUpload indicates an expected call of AsLengthDeclarableUpload
|
||||||
|
func (mr *MockFullDataStoreMockRecorder) AsLengthDeclarableUpload(upload interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AsLengthDeclarableUpload", reflect.TypeOf((*MockFullDataStore)(nil).AsLengthDeclarableUpload), upload)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFullUpload is a mock of FullUpload interface
|
||||||
|
type MockFullUpload struct {
|
||||||
|
ctrl *gomock.Controller
|
||||||
|
recorder *MockFullUploadMockRecorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockFullUploadMockRecorder is the mock recorder for MockFullUpload
|
||||||
|
type MockFullUploadMockRecorder struct {
|
||||||
|
mock *MockFullUpload
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewMockFullUpload creates a new mock instance
|
||||||
|
func NewMockFullUpload(ctrl *gomock.Controller) *MockFullUpload {
|
||||||
|
mock := &MockFullUpload{ctrl: ctrl}
|
||||||
|
mock.recorder = &MockFullUploadMockRecorder{mock}
|
||||||
|
return mock
|
||||||
|
}
|
||||||
|
|
||||||
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
|
func (m *MockFullUpload) EXPECT() *MockFullUploadMockRecorder {
|
||||||
|
return m.recorder
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteChunk mocks base method
|
||||||
|
func (m *MockFullUpload) WriteChunk(offset int64, src io.Reader) (int64, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "WriteChunk", offset, src)
|
||||||
ret0, _ := ret[0].(int64)
|
ret0, _ := ret[0].(int64)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockFullDataStoreRecorder) WriteChunk(arg0, arg1, arg2 interface{}) *gomock.Call {
|
// WriteChunk indicates an expected call of WriteChunk
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "WriteChunk", arg0, arg1, arg2)
|
func (mr *MockFullUploadMockRecorder) WriteChunk(offset, src interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "WriteChunk", reflect.TypeOf((*MockFullUpload)(nil).WriteChunk), offset, src)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockFullDataStore) GetInfo(id string) (handler.FileInfo, error) {
|
// GetInfo mocks base method
|
||||||
ret := _m.ctrl.Call(_m, "GetInfo", id)
|
func (m *MockFullUpload) GetInfo() (handler.FileInfo, error) {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "GetInfo")
|
||||||
ret0, _ := ret[0].(handler.FileInfo)
|
ret0, _ := ret[0].(handler.FileInfo)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockFullDataStoreRecorder) GetInfo(arg0 interface{}) *gomock.Call {
|
// GetInfo indicates an expected call of GetInfo
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "GetInfo", arg0)
|
func (mr *MockFullUploadMockRecorder) GetInfo() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetInfo", reflect.TypeOf((*MockFullUpload)(nil).GetInfo))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockFullDataStore) Terminate(id string) error {
|
// GetReader mocks base method
|
||||||
ret := _m.ctrl.Call(_m, "Terminate", id)
|
func (m *MockFullUpload) GetReader() (io.Reader, error) {
|
||||||
ret0, _ := ret[0].(error)
|
m.ctrl.T.Helper()
|
||||||
return ret0
|
ret := m.ctrl.Call(m, "GetReader")
|
||||||
}
|
|
||||||
|
|
||||||
func (_mr *_MockFullDataStoreRecorder) Terminate(arg0 interface{}) *gomock.Call {
|
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "Terminate", arg0)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockFullDataStore) ConcatUploads(destination string, partialUploads []string) error {
|
|
||||||
ret := _m.ctrl.Call(_m, "ConcatUploads", destination, partialUploads)
|
|
||||||
ret0, _ := ret[0].(error)
|
|
||||||
return ret0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_mr *_MockFullDataStoreRecorder) ConcatUploads(arg0, arg1 interface{}) *gomock.Call {
|
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "ConcatUploads", arg0, arg1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (_m *MockFullDataStore) GetReader(id string) (io.Reader, error) {
|
|
||||||
ret := _m.ctrl.Call(_m, "GetReader", id)
|
|
||||||
ret0, _ := ret[0].(io.Reader)
|
ret0, _ := ret[0].(io.Reader)
|
||||||
ret1, _ := ret[1].(error)
|
ret1, _ := ret[1].(error)
|
||||||
return ret0, ret1
|
return ret0, ret1
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockFullDataStoreRecorder) GetReader(arg0 interface{}) *gomock.Call {
|
// GetReader indicates an expected call of GetReader
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "GetReader", arg0)
|
func (mr *MockFullUploadMockRecorder) GetReader() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetReader", reflect.TypeOf((*MockFullUpload)(nil).GetReader))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockFullDataStore) FinishUpload(id string) error {
|
// FinishUpload mocks base method
|
||||||
ret := _m.ctrl.Call(_m, "FinishUpload", id)
|
func (m *MockFullUpload) FinishUpload() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "FinishUpload")
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockFullDataStoreRecorder) FinishUpload(arg0 interface{}) *gomock.Call {
|
// FinishUpload indicates an expected call of FinishUpload
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "FinishUpload", arg0)
|
func (mr *MockFullUploadMockRecorder) FinishUpload() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FinishUpload", reflect.TypeOf((*MockFullUpload)(nil).FinishUpload))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockFullDataStore) DeclareLength(id string, length int64) error {
|
// Terminate mocks base method
|
||||||
ret := _m.ctrl.Call(_m, "DeclareLength", id, length)
|
func (m *MockFullUpload) Terminate() error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "Terminate")
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockFullDataStoreRecorder) DeclareLength(arg0, arg1 interface{}) *gomock.Call {
|
// Terminate indicates an expected call of Terminate
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "DeclareLength", arg0, arg1)
|
func (mr *MockFullUploadMockRecorder) Terminate() *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Terminate", reflect.TypeOf((*MockFullUpload)(nil).Terminate))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock of Locker interface
|
// DeclareLength mocks base method
|
||||||
|
func (m *MockFullUpload) DeclareLength(length int64) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "DeclareLength", length)
|
||||||
|
ret0, _ := ret[0].(error)
|
||||||
|
return ret0
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeclareLength indicates an expected call of DeclareLength
|
||||||
|
func (mr *MockFullUploadMockRecorder) DeclareLength(length interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeclareLength", reflect.TypeOf((*MockFullUpload)(nil).DeclareLength), length)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MockLocker is a mock of Locker interface
|
||||||
type MockLocker struct {
|
type MockLocker struct {
|
||||||
ctrl *gomock.Controller
|
ctrl *gomock.Controller
|
||||||
recorder *_MockLockerRecorder
|
recorder *MockLockerMockRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
// Recorder for MockLocker (not exported)
|
// MockLockerMockRecorder is the mock recorder for MockLocker
|
||||||
type _MockLockerRecorder struct {
|
type MockLockerMockRecorder struct {
|
||||||
mock *MockLocker
|
mock *MockLocker
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewMockLocker creates a new mock instance
|
||||||
func NewMockLocker(ctrl *gomock.Controller) *MockLocker {
|
func NewMockLocker(ctrl *gomock.Controller) *MockLocker {
|
||||||
mock := &MockLocker{ctrl: ctrl}
|
mock := &MockLocker{ctrl: ctrl}
|
||||||
mock.recorder = &_MockLockerRecorder{mock}
|
mock.recorder = &MockLockerMockRecorder{mock}
|
||||||
return mock
|
return mock
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockLocker) EXPECT() *_MockLockerRecorder {
|
// EXPECT returns an object that allows the caller to indicate expected use
|
||||||
return _m.recorder
|
func (m *MockLocker) EXPECT() *MockLockerMockRecorder {
|
||||||
|
return m.recorder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockLocker) LockUpload(id string) error {
|
// LockUpload mocks base method
|
||||||
ret := _m.ctrl.Call(_m, "LockUpload", id)
|
func (m *MockLocker) LockUpload(id string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "LockUpload", id)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockLockerRecorder) LockUpload(arg0 interface{}) *gomock.Call {
|
// LockUpload indicates an expected call of LockUpload
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "LockUpload", arg0)
|
func (mr *MockLockerMockRecorder) LockUpload(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LockUpload", reflect.TypeOf((*MockLocker)(nil).LockUpload), id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_m *MockLocker) UnlockUpload(id string) error {
|
// UnlockUpload mocks base method
|
||||||
ret := _m.ctrl.Call(_m, "UnlockUpload", id)
|
func (m *MockLocker) UnlockUpload(id string) error {
|
||||||
|
m.ctrl.T.Helper()
|
||||||
|
ret := m.ctrl.Call(m, "UnlockUpload", id)
|
||||||
ret0, _ := ret[0].(error)
|
ret0, _ := ret[0].(error)
|
||||||
return ret0
|
return ret0
|
||||||
}
|
}
|
||||||
|
|
||||||
func (_mr *_MockLockerRecorder) UnlockUpload(arg0 interface{}) *gomock.Call {
|
// UnlockUpload indicates an expected call of UnlockUpload
|
||||||
return _mr.mock.ctrl.RecordCall(_mr.mock, "UnlockUpload", arg0)
|
func (mr *MockLockerMockRecorder) UnlockUpload(id interface{}) *gomock.Call {
|
||||||
|
mr.mock.ctrl.T.Helper()
|
||||||
|
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UnlockUpload", reflect.TypeOf((*MockLocker)(nil).UnlockUpload), id)
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,8 +19,6 @@ func SubTest(t *testing.T, name string, runTest func(*testing.T, *MockFullDataSt
|
||||||
composer := handler.NewStoreComposer()
|
composer := handler.NewStoreComposer()
|
||||||
composer.UseCore(store)
|
composer.UseCore(store)
|
||||||
composer.UseTerminater(store)
|
composer.UseTerminater(store)
|
||||||
composer.UseFinisher(store)
|
|
||||||
composer.UseGetReader(store)
|
|
||||||
composer.UseConcater(store)
|
composer.UseConcater(store)
|
||||||
composer.UseLengthDeferrer(store)
|
composer.UseLengthDeferrer(store)
|
||||||
|
|
||||||
|
|
|
@ -33,14 +33,17 @@ func TestTerminate(t *testing.T) {
|
||||||
ctrl := gomock.NewController(t)
|
ctrl := gomock.NewController(t)
|
||||||
defer ctrl.Finish()
|
defer ctrl.Finish()
|
||||||
locker := NewMockLocker(ctrl)
|
locker := NewMockLocker(ctrl)
|
||||||
|
upload := NewMockFullUpload(ctrl)
|
||||||
|
|
||||||
gomock.InOrder(
|
gomock.InOrder(
|
||||||
locker.EXPECT().LockUpload("foo"),
|
locker.EXPECT().LockUpload("foo"),
|
||||||
store.EXPECT().GetInfo("foo").Return(FileInfo{
|
store.EXPECT().GetUpload("foo").Return(upload, nil),
|
||||||
|
upload.EXPECT().GetInfo().Return(FileInfo{
|
||||||
ID: "foo",
|
ID: "foo",
|
||||||
Size: 10,
|
Size: 10,
|
||||||
}, nil),
|
}, nil),
|
||||||
store.EXPECT().Terminate("foo").Return(nil),
|
store.EXPECT().AsTerminatableUpload(upload).Return(upload),
|
||||||
|
upload.EXPECT().Terminate().Return(nil),
|
||||||
locker.EXPECT().UnlockUpload("foo"),
|
locker.EXPECT().UnlockUpload("foo"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -297,14 +297,19 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
||||||
PartialUploads: partialUploads,
|
PartialUploads: partialUploads,
|
||||||
}
|
}
|
||||||
|
|
||||||
id, err := handler.composer.Core.NewUpload(info)
|
upload, err := handler.composer.Core.NewUpload(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Should we use GetInfo here?
|
info, err = upload.GetInfo()
|
||||||
info.ID = id
|
if err != nil {
|
||||||
|
handler.sendError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
id := info.ID
|
||||||
|
|
||||||
// Add the Location header directly after creating the new resource to even
|
// Add the Location header directly after creating the new resource to even
|
||||||
// include it in cases of failure when an error is returned
|
// include it in cases of failure when an error is returned
|
||||||
|
@ -341,7 +346,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
||||||
defer locker.UnlockUpload(id)
|
defer locker.UnlockUpload(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handler.writeChunk(id, info, w, r); err != nil {
|
if err := handler.writeChunk(upload, info, w, r); err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -349,7 +354,7 @@ func (handler *UnroutedHandler) PostFile(w http.ResponseWriter, r *http.Request)
|
||||||
// Directly finish the upload if the upload is empty (i.e. has a size of 0).
|
// Directly finish the upload if the upload is empty (i.e. has a size of 0).
|
||||||
// This statement is in an else-if block to avoid causing duplicate calls
|
// This statement is in an else-if block to avoid causing duplicate calls
|
||||||
// to finishUploadIfComplete if an upload is empty and contains a chunk.
|
// to finishUploadIfComplete if an upload is empty and contains a chunk.
|
||||||
handler.finishUploadIfComplete(info)
|
handler.finishUploadIfComplete(upload, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler.sendResp(w, r, http.StatusCreated)
|
handler.sendResp(w, r, http.StatusCreated)
|
||||||
|
@ -374,7 +379,13 @@ func (handler *UnroutedHandler) HeadFile(w http.ResponseWriter, r *http.Request)
|
||||||
defer locker.UnlockUpload(id)
|
defer locker.UnlockUpload(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := handler.composer.Core.GetInfo(id)
|
upload, err := handler.composer.Core.GetUpload(id)
|
||||||
|
if err != nil {
|
||||||
|
handler.sendError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := upload.GetInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -444,7 +455,13 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
|
||||||
defer locker.UnlockUpload(id)
|
defer locker.UnlockUpload(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := handler.composer.Core.GetInfo(id)
|
upload, err := handler.composer.Core.GetUpload(id)
|
||||||
|
if err != nil {
|
||||||
|
handler.sendError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := upload.GetInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -482,7 +499,9 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
|
||||||
handler.sendError(w, r, ErrInvalidUploadLength)
|
handler.sendError(w, r, ErrInvalidUploadLength)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := handler.composer.LengthDeferrer.DeclareLength(id, uploadLength); err != nil {
|
|
||||||
|
lengthDeclarableUpload := handler.composer.LengthDeferrer.AsLengthDeclarableUpload(upload)
|
||||||
|
if err := lengthDeclarableUpload.DeclareLength(uploadLength); err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -491,7 +510,7 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
|
||||||
info.SizeIsDeferred = false
|
info.SizeIsDeferred = false
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handler.writeChunk(id, info, w, r); err != nil {
|
if err := handler.writeChunk(upload, info, w, r); err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -502,10 +521,11 @@ func (handler *UnroutedHandler) PatchFile(w http.ResponseWriter, r *http.Request
|
||||||
// writeChunk reads the body from the requests r and appends it to the upload
|
// writeChunk reads the body from the requests r and appends it to the upload
|
||||||
// with the corresponding id. Afterwards, it will set the necessary response
|
// with the corresponding id. Afterwards, it will set the necessary response
|
||||||
// headers but will not send the response.
|
// headers but will not send the response.
|
||||||
func (handler *UnroutedHandler) writeChunk(id string, info FileInfo, w http.ResponseWriter, r *http.Request) error {
|
func (handler *UnroutedHandler) writeChunk(upload Upload, info FileInfo, w http.ResponseWriter, r *http.Request) error {
|
||||||
// Get Content-Length if possible
|
// Get Content-Length if possible
|
||||||
length := r.ContentLength
|
length := r.ContentLength
|
||||||
offset := info.Offset
|
offset := info.Offset
|
||||||
|
id := info.ID
|
||||||
|
|
||||||
// Test if this upload fits into the file's size
|
// Test if this upload fits into the file's size
|
||||||
if !info.SizeIsDeferred && offset+length > info.Size {
|
if !info.SizeIsDeferred && offset+length > info.Size {
|
||||||
|
@ -562,9 +582,9 @@ func (handler *UnroutedHandler) writeChunk(id string, info FileInfo, w http.Resp
|
||||||
}
|
}
|
||||||
|
|
||||||
var err error
|
var err error
|
||||||
bytesWritten, err = handler.composer.Core.WriteChunk(id, offset, reader)
|
bytesWritten, err = upload.WriteChunk(offset, reader)
|
||||||
if terminateUpload && handler.composer.UsesTerminater {
|
if terminateUpload && handler.composer.UsesTerminater {
|
||||||
if terminateErr := handler.terminateUpload(id, info); terminateErr != nil {
|
if terminateErr := handler.terminateUpload(upload, info); terminateErr != nil {
|
||||||
// We only log this error and not show it to the user since this
|
// We only log this error and not show it to the user since this
|
||||||
// termination error is not relevant to the uploading client
|
// termination error is not relevant to the uploading client
|
||||||
handler.log("UploadStopTerminateError", "id", id, "error", terminateErr.Error())
|
handler.log("UploadStopTerminateError", "id", id, "error", terminateErr.Error())
|
||||||
|
@ -591,21 +611,19 @@ func (handler *UnroutedHandler) writeChunk(id string, info FileInfo, w http.Resp
|
||||||
handler.Metrics.incBytesReceived(uint64(bytesWritten))
|
handler.Metrics.incBytesReceived(uint64(bytesWritten))
|
||||||
info.Offset = newOffset
|
info.Offset = newOffset
|
||||||
|
|
||||||
return handler.finishUploadIfComplete(info)
|
return handler.finishUploadIfComplete(upload, info)
|
||||||
}
|
}
|
||||||
|
|
||||||
// finishUploadIfComplete checks whether an upload is completed (i.e. upload offset
|
// finishUploadIfComplete checks whether an upload is completed (i.e. upload offset
|
||||||
// matches upload size) and if so, it will call the data store's FinishUpload
|
// matches upload size) and if so, it will call the data store's FinishUpload
|
||||||
// function and send the necessary message on the CompleteUpload channel.
|
// function and send the necessary message on the CompleteUpload channel.
|
||||||
func (handler *UnroutedHandler) finishUploadIfComplete(info FileInfo) error {
|
func (handler *UnroutedHandler) finishUploadIfComplete(upload Upload, info FileInfo) error {
|
||||||
// If the upload is completed, ...
|
// If the upload is completed, ...
|
||||||
if !info.SizeIsDeferred && info.Offset == info.Size {
|
if !info.SizeIsDeferred && info.Offset == info.Size {
|
||||||
// ... allow custom mechanism to finish and cleanup the upload
|
// ... allow custom mechanism to finish and cleanup the upload
|
||||||
if handler.composer.UsesFinisher {
|
if err := upload.FinishUpload(); err != nil {
|
||||||
if err := handler.composer.Finisher.FinishUpload(info.ID); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// ... send the info out to the channel
|
// ... send the info out to the channel
|
||||||
if handler.config.NotifyCompleteUploads {
|
if handler.config.NotifyCompleteUploads {
|
||||||
|
@ -621,11 +639,6 @@ func (handler *UnroutedHandler) finishUploadIfComplete(info FileInfo) error {
|
||||||
// GetFile handles requests to download a file using a GET request. This is not
|
// GetFile handles requests to download a file using a GET request. This is not
|
||||||
// part of the specification.
|
// part of the specification.
|
||||||
func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) {
|
func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request) {
|
||||||
if !handler.composer.UsesGetReader {
|
|
||||||
handler.sendError(w, r, ErrNotImplemented)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
id, err := extractIDFromPath(r.URL.Path)
|
id, err := extractIDFromPath(r.URL.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
|
@ -642,7 +655,13 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request)
|
||||||
defer locker.UnlockUpload(id)
|
defer locker.UnlockUpload(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
info, err := handler.composer.Core.GetInfo(id)
|
upload, err := handler.composer.Core.GetUpload(id)
|
||||||
|
if err != nil {
|
||||||
|
handler.sendError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := upload.GetInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -661,7 +680,7 @@ func (handler *UnroutedHandler) GetFile(w http.ResponseWriter, r *http.Request)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
src, err := handler.composer.GetReader.GetReader(id)
|
src, err := upload.GetReader()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -761,16 +780,22 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
|
||||||
defer locker.UnlockUpload(id)
|
defer locker.UnlockUpload(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
upload, err := handler.composer.Core.GetUpload(id)
|
||||||
|
if err != nil {
|
||||||
|
handler.sendError(w, r, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var info FileInfo
|
var info FileInfo
|
||||||
if handler.config.NotifyTerminatedUploads {
|
if handler.config.NotifyTerminatedUploads {
|
||||||
info, err = handler.composer.Core.GetInfo(id)
|
info, err = upload.GetInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
err = handler.terminateUpload(id, info)
|
err = handler.terminateUpload(upload, info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
handler.sendError(w, r, err)
|
handler.sendError(w, r, err)
|
||||||
return
|
return
|
||||||
|
@ -784,8 +809,10 @@ func (handler *UnroutedHandler) DelFile(w http.ResponseWriter, r *http.Request)
|
||||||
// and updates the statistics.
|
// and updates the statistics.
|
||||||
// Note the the info argument is only needed if the terminated uploads
|
// Note the the info argument is only needed if the terminated uploads
|
||||||
// notifications are enabled.
|
// notifications are enabled.
|
||||||
func (handler *UnroutedHandler) terminateUpload(id string, info FileInfo) error {
|
func (handler *UnroutedHandler) terminateUpload(upload Upload, info FileInfo) error {
|
||||||
err := handler.composer.Terminater.Terminate(id)
|
terminatableUpload := handler.composer.Terminater.AsTerminatableUpload(upload)
|
||||||
|
|
||||||
|
err := terminatableUpload.Terminate()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -951,7 +978,12 @@ func getHostAndProtocol(r *http.Request, allowForwarded bool) (host, proto strin
|
||||||
// of a final resource.
|
// of a final resource.
|
||||||
func (handler *UnroutedHandler) sizeOfUploads(ids []string) (size int64, err error) {
|
func (handler *UnroutedHandler) sizeOfUploads(ids []string) (size int64, err error) {
|
||||||
for _, id := range ids {
|
for _, id := range ids {
|
||||||
info, err := handler.composer.Core.GetInfo(id)
|
upload, err := handler.composer.Core.GetUpload(id)
|
||||||
|
if err != nil {
|
||||||
|
return size, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info, err := upload.GetInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return size, err
|
return size, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/tus/tusd/pkg/handler"
|
"github.com/tus/tusd/pkg/handler"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:generate mockgen -package handler_test -source utils_test.go -aux_files tusd=datastore.go -destination=handler_mock_test.go
|
//go:generate mockgen -package handler_test -source utils_test.go -aux_files handler=datastore.go -destination=handler_mock_test.go
|
||||||
|
|
||||||
// FullDataStore is an interface combining most interfaces for data stores.
|
// FullDataStore is an interface combining most interfaces for data stores.
|
||||||
// This is used by mockgen(1) to generate a mocked data store used for testing
|
// This is used by mockgen(1) to generate a mocked data store used for testing
|
||||||
|
@ -25,11 +25,15 @@ type FullDataStore interface {
|
||||||
handler.DataStore
|
handler.DataStore
|
||||||
handler.TerminaterDataStore
|
handler.TerminaterDataStore
|
||||||
handler.ConcaterDataStore
|
handler.ConcaterDataStore
|
||||||
handler.GetReaderDataStore
|
|
||||||
handler.FinisherDataStore
|
|
||||||
handler.LengthDeferrerDataStore
|
handler.LengthDeferrerDataStore
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type FullUpload interface {
|
||||||
|
handler.Upload
|
||||||
|
handler.TerminatableUpload
|
||||||
|
handler.LengthDeclarableUpload
|
||||||
|
}
|
||||||
|
|
||||||
type Locker interface {
|
type Locker interface {
|
||||||
handler.LockerDataStore
|
handler.LockerDataStore
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue