2015-12-18 22:24:12 +00:00
|
|
|
// Package limitedstore provides a storage with a limited space.
|
|
|
|
//
|
|
|
|
// This goal is achieved by using a simple wrapper around existing
|
2015-04-11 11:40:39 +00:00
|
|
|
// datastores (tusd.DataStore) while limiting the used storage size.
|
|
|
|
// It will start terminating existing uploads if not enough space is left in
|
|
|
|
// order to create a new upload.
|
2015-11-26 11:43:04 +00:00
|
|
|
// The order in which the uploads will be terminated is defined by their size,
|
|
|
|
// whereas the biggest ones are deleted first.
|
2015-04-11 11:40:39 +00:00
|
|
|
// This package's functionality is very limited and naive. It will terminate
|
2015-11-26 11:43:04 +00:00
|
|
|
// uploads whether they are finished yet or not. Only one datastore is allowed to
|
2015-04-11 11:40:39 +00:00
|
|
|
// access the underlying storage else the limited store will not function
|
|
|
|
// properly. Two tusd.FileStore instances using the same directory, for example.
|
2015-11-26 11:43:04 +00:00
|
|
|
// In addition the limited store will keep a list of the uploads' IDs in memory
|
2015-04-11 11:40:39 +00:00
|
|
|
// which may create a growing memory leak.
|
|
|
|
package limitedstore
|
|
|
|
|
|
|
|
import (
|
|
|
|
"github.com/tus/tusd"
|
2015-11-26 11:43:31 +00:00
|
|
|
"sort"
|
2015-04-11 11:40:39 +00:00
|
|
|
"sync"
|
|
|
|
)
|
|
|
|
|
|
|
|
type LimitedStore struct {
|
2016-02-21 22:25:35 +00:00
|
|
|
tusd.DataStore
|
|
|
|
terminater tusd.TerminaterDataStore
|
|
|
|
|
2015-04-11 11:40:39 +00:00
|
|
|
StoreSize int64
|
|
|
|
|
|
|
|
uploads map[string]int64
|
|
|
|
usedSize int64
|
|
|
|
|
|
|
|
mutex *sync.Mutex
|
|
|
|
}
|
|
|
|
|
2015-11-25 02:39:58 +00:00
|
|
|
// pair structure to perform map-sorting
|
|
|
|
type pair struct {
|
2015-11-26 11:43:31 +00:00
|
|
|
key string
|
|
|
|
value int64
|
2015-11-21 03:32:48 +00:00
|
|
|
}
|
|
|
|
|
2015-11-25 02:39:58 +00:00
|
|
|
type pairlist []pair
|
2015-11-21 03:32:48 +00:00
|
|
|
|
2015-11-25 02:39:58 +00:00
|
|
|
func (p pairlist) Len() int { return len(p) }
|
2015-11-26 11:43:31 +00:00
|
|
|
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 }
|
2015-11-21 03:32:48 +00:00
|
|
|
|
2015-12-26 23:44:02 +00:00
|
|
|
// 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.
|
2016-02-21 22:25:35 +00:00
|
|
|
func New(storeSize int64, dataStore tusd.DataStore, terminater tusd.TerminaterDataStore) *LimitedStore {
|
2015-04-11 11:40:39 +00:00
|
|
|
return &LimitedStore{
|
2016-02-21 22:25:35 +00:00
|
|
|
StoreSize: storeSize,
|
|
|
|
DataStore: dataStore,
|
|
|
|
terminater: terminater,
|
|
|
|
uploads: make(map[string]int64),
|
|
|
|
mutex: new(sync.Mutex),
|
2015-04-11 11:40:39 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-21 22:25:35 +00:00
|
|
|
func (store *LimitedStore) UseIn(composer *tusd.StoreComposer) {
|
|
|
|
composer.UseCore(store)
|
|
|
|
composer.UseTerminater(store)
|
|
|
|
}
|
|
|
|
|
2015-04-11 11:40:39 +00:00
|
|
|
func (store *LimitedStore) NewUpload(info tusd.FileInfo) (string, error) {
|
|
|
|
store.mutex.Lock()
|
|
|
|
defer store.mutex.Unlock()
|
|
|
|
|
|
|
|
if err := store.ensureSpace(info.Size); err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
2016-02-21 22:25:35 +00:00
|
|
|
id, err := store.DataStore.NewUpload(info)
|
2015-04-11 11:40:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return "", err
|
|
|
|
}
|
|
|
|
|
|
|
|
store.usedSize += info.Size
|
|
|
|
store.uploads[id] = info.Size
|
|
|
|
|
|
|
|
return id, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store *LimitedStore) Terminate(id string) error {
|
|
|
|
store.mutex.Lock()
|
|
|
|
defer store.mutex.Unlock()
|
|
|
|
|
|
|
|
return store.terminate(id)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (store *LimitedStore) terminate(id string) error {
|
2016-02-21 22:25:35 +00:00
|
|
|
err := store.terminater.Terminate(id)
|
2015-04-11 11:40:39 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
size := store.uploads[id]
|
|
|
|
delete(store.uploads, id)
|
|
|
|
store.usedSize -= size
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure enough space is available to store an upload of the specified size.
|
|
|
|
// It will terminate uploads until enough space is freed.
|
|
|
|
func (store *LimitedStore) ensureSpace(size int64) error {
|
|
|
|
if (store.usedSize + size) <= store.StoreSize {
|
|
|
|
// Enough space is available to store the new upload
|
|
|
|
return nil
|
|
|
|
}
|
2015-11-26 11:43:31 +00:00
|
|
|
|
|
|
|
sortedUploads := make(pairlist, len(store.uploads))
|
|
|
|
i := 0
|
|
|
|
for u, h := range store.uploads {
|
|
|
|
sortedUploads[i] = pair{u, h}
|
|
|
|
i++
|
|
|
|
}
|
|
|
|
sort.Sort(sort.Reverse(sortedUploads))
|
|
|
|
|
|
|
|
// Forward traversal through the uploads in terms of size, biggest upload first
|
|
|
|
for _, k := range sortedUploads {
|
|
|
|
id := k.key
|
|
|
|
|
2015-04-11 11:40:39 +00:00
|
|
|
if err := store.terminate(id); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
if (store.usedSize + size) <= store.StoreSize {
|
|
|
|
// Enough space has been freed to store the new upload
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|