1package inmemory
2
3import (
4	"context"
5	"fmt"
6	"io"
7	"io/ioutil"
8	"sync"
9	"time"
10
11	storagedriver "github.com/docker/distribution/registry/storage/driver"
12	"github.com/docker/distribution/registry/storage/driver/base"
13	"github.com/docker/distribution/registry/storage/driver/factory"
14)
15
16const driverName = "inmemory"
17
18func init() {
19	factory.Register(driverName, &inMemoryDriverFactory{})
20}
21
22// inMemoryDriverFacotry implements the factory.StorageDriverFactory interface.
23type inMemoryDriverFactory struct{}
24
25func (factory *inMemoryDriverFactory) Create(parameters map[string]interface{}) (storagedriver.StorageDriver, error) {
26	return New(), nil
27}
28
29type driver struct {
30	root  *dir
31	mutex sync.RWMutex
32}
33
34// baseEmbed allows us to hide the Base embed.
35type baseEmbed struct {
36	base.Base
37}
38
39// Driver is a storagedriver.StorageDriver implementation backed by a local map.
40// Intended solely for example and testing purposes.
41type Driver struct {
42	baseEmbed // embedded, hidden base driver.
43}
44
45var _ storagedriver.StorageDriver = &Driver{}
46
47// New constructs a new Driver.
48func New() *Driver {
49	return &Driver{
50		baseEmbed: baseEmbed{
51			Base: base.Base{
52				StorageDriver: &driver{
53					root: &dir{
54						common: common{
55							p:   "/",
56							mod: time.Now(),
57						},
58					},
59				},
60			},
61		},
62	}
63}
64
65// Implement the storagedriver.StorageDriver interface.
66
67func (d *driver) Name() string {
68	return driverName
69}
70
71// GetContent retrieves the content stored at "path" as a []byte.
72func (d *driver) GetContent(ctx context.Context, path string) ([]byte, error) {
73	d.mutex.RLock()
74	defer d.mutex.RUnlock()
75
76	rc, err := d.reader(ctx, path, 0)
77	if err != nil {
78		return nil, err
79	}
80	defer rc.Close()
81
82	return ioutil.ReadAll(rc)
83}
84
85// PutContent stores the []byte content at a location designated by "path".
86func (d *driver) PutContent(ctx context.Context, p string, contents []byte) error {
87	d.mutex.Lock()
88	defer d.mutex.Unlock()
89
90	normalized := normalize(p)
91
92	f, err := d.root.mkfile(normalized)
93	if err != nil {
94		// TODO(stevvooe): Again, we need to clarify when this is not a
95		// directory in StorageDriver API.
96		return fmt.Errorf("not a file")
97	}
98
99	f.truncate()
100	f.WriteAt(contents, 0)
101
102	return nil
103}
104
105// Reader retrieves an io.ReadCloser for the content stored at "path" with a
106// given byte offset.
107func (d *driver) Reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
108	d.mutex.RLock()
109	defer d.mutex.RUnlock()
110
111	return d.reader(ctx, path, offset)
112}
113
114func (d *driver) reader(ctx context.Context, path string, offset int64) (io.ReadCloser, error) {
115	if offset < 0 {
116		return nil, storagedriver.InvalidOffsetError{Path: path, Offset: offset}
117	}
118
119	normalized := normalize(path)
120	found := d.root.find(normalized)
121
122	if found.path() != normalized {
123		return nil, storagedriver.PathNotFoundError{Path: path}
124	}
125
126	if found.isdir() {
127		return nil, fmt.Errorf("%q is a directory", path)
128	}
129
130	return ioutil.NopCloser(found.(*file).sectionReader(offset)), nil
131}
132
133// Writer returns a FileWriter which will store the content written to it
134// at the location designated by "path" after the call to Commit.
135func (d *driver) Writer(ctx context.Context, path string, append bool) (storagedriver.FileWriter, error) {
136	d.mutex.Lock()
137	defer d.mutex.Unlock()
138
139	normalized := normalize(path)
140
141	f, err := d.root.mkfile(normalized)
142	if err != nil {
143		return nil, fmt.Errorf("not a file")
144	}
145
146	if !append {
147		f.truncate()
148	}
149
150	return d.newWriter(f), nil
151}
152
153// Stat returns info about the provided path.
154func (d *driver) Stat(ctx context.Context, path string) (storagedriver.FileInfo, error) {
155	d.mutex.RLock()
156	defer d.mutex.RUnlock()
157
158	normalized := normalize(path)
159	found := d.root.find(normalized)
160
161	if found.path() != normalized {
162		return nil, storagedriver.PathNotFoundError{Path: path}
163	}
164
165	fi := storagedriver.FileInfoFields{
166		Path:    path,
167		IsDir:   found.isdir(),
168		ModTime: found.modtime(),
169	}
170
171	if !fi.IsDir {
172		fi.Size = int64(len(found.(*file).data))
173	}
174
175	return storagedriver.FileInfoInternal{FileInfoFields: fi}, nil
176}
177
178// List returns a list of the objects that are direct descendants of the given
179// path.
180func (d *driver) List(ctx context.Context, path string) ([]string, error) {
181	d.mutex.RLock()
182	defer d.mutex.RUnlock()
183
184	normalized := normalize(path)
185
186	found := d.root.find(normalized)
187
188	if !found.isdir() {
189		return nil, fmt.Errorf("not a directory") // TODO(stevvooe): Need error type for this...
190	}
191
192	entries, err := found.(*dir).list(normalized)
193
194	if err != nil {
195		switch err {
196		case errNotExists:
197			return nil, storagedriver.PathNotFoundError{Path: path}
198		case errIsNotDir:
199			return nil, fmt.Errorf("not a directory")
200		default:
201			return nil, err
202		}
203	}
204
205	return entries, nil
206}
207
208// Move moves an object stored at sourcePath to destPath, removing the original
209// object.
210func (d *driver) Move(ctx context.Context, sourcePath string, destPath string) error {
211	d.mutex.Lock()
212	defer d.mutex.Unlock()
213
214	normalizedSrc, normalizedDst := normalize(sourcePath), normalize(destPath)
215
216	err := d.root.move(normalizedSrc, normalizedDst)
217	switch err {
218	case errNotExists:
219		return storagedriver.PathNotFoundError{Path: destPath}
220	default:
221		return err
222	}
223}
224
225// Delete recursively deletes all objects stored at "path" and its subpaths.
226func (d *driver) Delete(ctx context.Context, path string) error {
227	d.mutex.Lock()
228	defer d.mutex.Unlock()
229
230	normalized := normalize(path)
231
232	err := d.root.delete(normalized)
233	switch err {
234	case errNotExists:
235		return storagedriver.PathNotFoundError{Path: path}
236	default:
237		return err
238	}
239}
240
241// URLFor returns a URL which may be used to retrieve the content stored at the given path.
242// May return an UnsupportedMethodErr in certain StorageDriver implementations.
243func (d *driver) URLFor(ctx context.Context, path string, options map[string]interface{}) (string, error) {
244	return "", storagedriver.ErrUnsupportedMethod{}
245}
246
247// Walk traverses a filesystem defined within driver, starting
248// from the given path, calling f on each file
249func (d *driver) Walk(ctx context.Context, path string, f storagedriver.WalkFn) error {
250	return storagedriver.WalkFallback(ctx, d, path, f)
251}
252
253type writer struct {
254	d         *driver
255	f         *file
256	closed    bool
257	committed bool
258	cancelled bool
259}
260
261func (d *driver) newWriter(f *file) storagedriver.FileWriter {
262	return &writer{
263		d: d,
264		f: f,
265	}
266}
267
268func (w *writer) Write(p []byte) (int, error) {
269	if w.closed {
270		return 0, fmt.Errorf("already closed")
271	} else if w.committed {
272		return 0, fmt.Errorf("already committed")
273	} else if w.cancelled {
274		return 0, fmt.Errorf("already cancelled")
275	}
276
277	w.d.mutex.Lock()
278	defer w.d.mutex.Unlock()
279
280	return w.f.WriteAt(p, int64(len(w.f.data)))
281}
282
283func (w *writer) Size() int64 {
284	w.d.mutex.RLock()
285	defer w.d.mutex.RUnlock()
286
287	return int64(len(w.f.data))
288}
289
290func (w *writer) Close() error {
291	if w.closed {
292		return fmt.Errorf("already closed")
293	}
294	w.closed = true
295	return nil
296}
297
298func (w *writer) Cancel() error {
299	if w.closed {
300		return fmt.Errorf("already closed")
301	} else if w.committed {
302		return fmt.Errorf("already committed")
303	}
304	w.cancelled = true
305
306	w.d.mutex.Lock()
307	defer w.d.mutex.Unlock()
308
309	return w.d.root.delete(w.f.path())
310}
311
312func (w *writer) Commit() error {
313	if w.closed {
314		return fmt.Errorf("already closed")
315	} else if w.committed {
316		return fmt.Errorf("already committed")
317	} else if w.cancelled {
318		return fmt.Errorf("already cancelled")
319	}
320	w.committed = true
321	return nil
322}
323