main
1package memfs
2
3import (
4 "errors"
5 "fmt"
6 "io"
7 "os"
8 "path/filepath"
9 "strings"
10 "sync"
11)
12
13type storage struct {
14 files map[string]*file
15 children map[string]map[string]*file
16}
17
18func newStorage() *storage {
19 return &storage{
20 files: make(map[string]*file, 0),
21 children: make(map[string]map[string]*file, 0),
22 }
23}
24
25func (s *storage) Has(path string) bool {
26 path = clean(path)
27
28 _, ok := s.files[path]
29 return ok
30}
31
32func (s *storage) New(path string, mode os.FileMode, flag int) (*file, error) {
33 path = clean(path)
34 if s.Has(path) {
35 if !s.MustGet(path).mode.IsDir() {
36 return nil, fmt.Errorf("file already exists %q", path)
37 }
38
39 return nil, nil
40 }
41
42 name := filepath.Base(path)
43
44 f := &file{
45 name: name,
46 content: &content{name: name},
47 mode: mode,
48 flag: flag,
49 }
50
51 s.files[path] = f
52 s.createParent(path, mode, f)
53 return f, nil
54}
55
56func (s *storage) createParent(path string, mode os.FileMode, f *file) error {
57 base := filepath.Dir(path)
58 base = clean(base)
59 if f.Name() == string(separator) {
60 return nil
61 }
62
63 if _, err := s.New(base, mode.Perm()|os.ModeDir, 0); err != nil {
64 return err
65 }
66
67 if _, ok := s.children[base]; !ok {
68 s.children[base] = make(map[string]*file, 0)
69 }
70
71 s.children[base][f.Name()] = f
72 return nil
73}
74
75func (s *storage) Children(path string) []*file {
76 path = clean(path)
77
78 l := make([]*file, 0)
79 for _, f := range s.children[path] {
80 l = append(l, f)
81 }
82
83 return l
84}
85
86func (s *storage) MustGet(path string) *file {
87 f, ok := s.Get(path)
88 if !ok {
89 panic(fmt.Errorf("couldn't find %q", path))
90 }
91
92 return f
93}
94
95func (s *storage) Get(path string) (*file, bool) {
96 path = clean(path)
97 if !s.Has(path) {
98 return nil, false
99 }
100
101 file, ok := s.files[path]
102 return file, ok
103}
104
105func (s *storage) Rename(from, to string) error {
106 from = clean(from)
107 to = clean(to)
108
109 if !s.Has(from) {
110 return os.ErrNotExist
111 }
112
113 move := [][2]string{{from, to}}
114
115 for pathFrom := range s.files {
116 if pathFrom == from || !strings.HasPrefix(pathFrom, from) {
117 continue
118 }
119
120 rel, _ := filepath.Rel(from, pathFrom)
121 pathTo := filepath.Join(to, rel)
122
123 move = append(move, [2]string{pathFrom, pathTo})
124 }
125
126 for _, ops := range move {
127 from := ops[0]
128 to := ops[1]
129
130 if err := s.move(from, to); err != nil {
131 return err
132 }
133 }
134
135 return nil
136}
137
138func (s *storage) move(from, to string) error {
139 s.files[to] = s.files[from]
140 s.files[to].name = filepath.Base(to)
141 s.children[to] = s.children[from]
142
143 defer func() {
144 delete(s.children, from)
145 delete(s.files, from)
146 delete(s.children[filepath.Dir(from)], filepath.Base(from))
147 }()
148
149 return s.createParent(to, 0644, s.files[to])
150}
151
152func (s *storage) Remove(path string) error {
153 path = clean(path)
154
155 f, has := s.Get(path)
156 if !has {
157 return os.ErrNotExist
158 }
159
160 if f.mode.IsDir() && len(s.children[path]) != 0 {
161 return fmt.Errorf("dir: %s contains files", path)
162 }
163
164 base, file := filepath.Split(path)
165 base = filepath.Clean(base)
166
167 delete(s.children[base], file)
168 delete(s.files, path)
169 return nil
170}
171
172func clean(path string) string {
173 return filepath.Clean(filepath.FromSlash(path))
174}
175
176type content struct {
177 name string
178 bytes []byte
179
180 m sync.RWMutex
181}
182
183func (c *content) WriteAt(p []byte, off int64) (int, error) {
184 if off < 0 {
185 return 0, &os.PathError{
186 Op: "writeat",
187 Path: c.name,
188 Err: errors.New("negative offset"),
189 }
190 }
191
192 c.m.Lock()
193 prev := len(c.bytes)
194
195 diff := int(off) - prev
196 if diff > 0 {
197 c.bytes = append(c.bytes, make([]byte, diff)...)
198 }
199
200 c.bytes = append(c.bytes[:off], p...)
201 if len(c.bytes) < prev {
202 c.bytes = c.bytes[:prev]
203 }
204 c.m.Unlock()
205
206 return len(p), nil
207}
208
209func (c *content) ReadAt(b []byte, off int64) (n int, err error) {
210 if off < 0 {
211 return 0, &os.PathError{
212 Op: "readat",
213 Path: c.name,
214 Err: errors.New("negative offset"),
215 }
216 }
217
218 c.m.RLock()
219 size := int64(len(c.bytes))
220 if off >= size {
221 c.m.RUnlock()
222 return 0, io.EOF
223 }
224
225 l := int64(len(b))
226 if off+l > size {
227 l = size - off
228 }
229
230 btr := c.bytes[off : off+l]
231 n = copy(b, btr)
232
233 if len(btr) < len(b) {
234 err = io.EOF
235 }
236 c.m.RUnlock()
237
238 return
239}