66107df1af
This change brings the storagedriver API in line with the Go standard library's use of int64 for offsets. The main benefit is simplicity in interfacing with the io library reducing the number of type conversions in simple code.
182 lines
4.3 KiB
Go
182 lines
4.3 KiB
Go
package inmemory
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
|
|
"github.com/docker/docker-registry/storagedriver"
|
|
"github.com/docker/docker-registry/storagedriver/factory"
|
|
)
|
|
|
|
const driverName = "inmemory"
|
|
|
|
func init() {
|
|
factory.Register(driverName, &inMemoryDriverFactory{})
|
|
}
|
|
|
|
// inMemoryDriverFacotry implements the factory.StorageDriverFactory interface.
|
|
type inMemoryDriverFactory struct{}
|
|
|
|
func (factory *inMemoryDriverFactory) Create(parameters map[string]string) (storagedriver.StorageDriver, error) {
|
|
return New(), nil
|
|
}
|
|
|
|
// Driver is a storagedriver.StorageDriver implementation backed by a local map.
|
|
// Intended solely for example and testing purposes.
|
|
type Driver struct {
|
|
storage map[string][]byte
|
|
mutex sync.RWMutex
|
|
}
|
|
|
|
// New constructs a new Driver.
|
|
func New() *Driver {
|
|
return &Driver{storage: make(map[string][]byte)}
|
|
}
|
|
|
|
// Implement the storagedriver.StorageDriver interface.
|
|
|
|
// GetContent retrieves the content stored at "path" as a []byte.
|
|
func (d *Driver) GetContent(path string) ([]byte, error) {
|
|
d.mutex.RLock()
|
|
defer d.mutex.RUnlock()
|
|
contents, ok := d.storage[path]
|
|
if !ok {
|
|
return nil, storagedriver.PathNotFoundError{Path: path}
|
|
}
|
|
return contents, nil
|
|
}
|
|
|
|
// PutContent stores the []byte content at a location designated by "path".
|
|
func (d *Driver) PutContent(path string, contents []byte) error {
|
|
d.mutex.Lock()
|
|
defer d.mutex.Unlock()
|
|
d.storage[path] = contents
|
|
return nil
|
|
}
|
|
|
|
// ReadStream retrieves an io.ReadCloser for the content stored at "path" with a
|
|
// given byte offset.
|
|
func (d *Driver) ReadStream(path string, offset int64) (io.ReadCloser, error) {
|
|
d.mutex.RLock()
|
|
defer d.mutex.RUnlock()
|
|
contents, err := d.GetContent(path)
|
|
if err != nil {
|
|
return nil, err
|
|
} else if len(contents) <= int(offset) {
|
|
return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset}
|
|
}
|
|
|
|
src := contents[offset:]
|
|
buf := make([]byte, len(src))
|
|
copy(buf, src)
|
|
return ioutil.NopCloser(bytes.NewReader(buf)), nil
|
|
}
|
|
|
|
// WriteStream stores the contents of the provided io.ReadCloser at a location
|
|
// designated by the given path.
|
|
func (d *Driver) WriteStream(path string, offset, size int64, reader io.ReadCloser) error {
|
|
defer reader.Close()
|
|
d.mutex.RLock()
|
|
defer d.mutex.RUnlock()
|
|
|
|
resumableOffset, err := d.CurrentSize(path)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if offset > int64(resumableOffset) {
|
|
return storagedriver.InvalidOffsetError{Path: path, Offset: offset}
|
|
}
|
|
|
|
contents, err := ioutil.ReadAll(reader)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if offset > 0 {
|
|
contents = append(d.storage[path][0:offset], contents...)
|
|
}
|
|
|
|
d.storage[path] = contents
|
|
return nil
|
|
}
|
|
|
|
// CurrentSize retrieves the curernt size in bytes of the object at the given
|
|
// path.
|
|
func (d *Driver) CurrentSize(path string) (uint64, error) {
|
|
d.mutex.RLock()
|
|
defer d.mutex.RUnlock()
|
|
contents, ok := d.storage[path]
|
|
if !ok {
|
|
return 0, nil
|
|
}
|
|
return uint64(len(contents)), nil
|
|
}
|
|
|
|
// List returns a list of the objects that are direct descendants of the given
|
|
// path.
|
|
func (d *Driver) List(path string) ([]string, error) {
|
|
if path[len(path)-1] != '/' {
|
|
path += "/"
|
|
}
|
|
subPathMatcher, err := regexp.Compile(fmt.Sprintf("^%s[^/]+", path))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
d.mutex.RLock()
|
|
defer d.mutex.RUnlock()
|
|
// we use map to collect unique keys
|
|
keySet := make(map[string]struct{})
|
|
for k := range d.storage {
|
|
if key := subPathMatcher.FindString(k); key != "" {
|
|
keySet[key] = struct{}{}
|
|
}
|
|
}
|
|
|
|
keys := make([]string, 0, len(keySet))
|
|
for k := range keySet {
|
|
keys = append(keys, k)
|
|
}
|
|
return keys, nil
|
|
}
|
|
|
|
// Move moves an object stored at sourcePath to destPath, removing the original
|
|
// object.
|
|
func (d *Driver) Move(sourcePath string, destPath string) error {
|
|
d.mutex.Lock()
|
|
defer d.mutex.Unlock()
|
|
contents, ok := d.storage[sourcePath]
|
|
if !ok {
|
|
return storagedriver.PathNotFoundError{Path: sourcePath}
|
|
}
|
|
d.storage[destPath] = contents
|
|
delete(d.storage, sourcePath)
|
|
return nil
|
|
}
|
|
|
|
// Delete recursively deletes all objects stored at "path" and its subpaths.
|
|
func (d *Driver) Delete(path string) error {
|
|
d.mutex.Lock()
|
|
defer d.mutex.Unlock()
|
|
var subPaths []string
|
|
for k := range d.storage {
|
|
if strings.HasPrefix(k, path) {
|
|
subPaths = append(subPaths, k)
|
|
}
|
|
}
|
|
|
|
if len(subPaths) == 0 {
|
|
return storagedriver.PathNotFoundError{Path: path}
|
|
}
|
|
|
|
for _, subPath := range subPaths {
|
|
delete(d.storage, subPath)
|
|
}
|
|
return nil
|
|
}
|