1// Copyright 2019 The Hugo Authors. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6// http://www.apache.org/licenses/LICENSE-2.0
7//
8// Unless required by applicable law or agreed to in writing, software
9// distributed under the License is distributed on an "AS IS" BASIS,
10// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11// See the License for the specific language governing permissions and
12// limitations under the License.
13
14package hugofs
15
16import (
17	"os"
18	"syscall"
19	"time"
20
21	"github.com/pkg/errors"
22
23	"github.com/spf13/afero"
24)
25
26var (
27	_ afero.Fs      = (*SliceFs)(nil)
28	_ afero.Lstater = (*SliceFs)(nil)
29	_ afero.File    = (*sliceDir)(nil)
30)
31
32func NewSliceFs(dirs ...FileMetaInfo) (afero.Fs, error) {
33	if len(dirs) == 0 {
34		return NoOpFs, nil
35	}
36
37	for _, dir := range dirs {
38		if !dir.IsDir() {
39			return nil, errors.New("this fs supports directories only")
40		}
41	}
42
43	fs := &SliceFs{
44		dirs: dirs,
45	}
46
47	return fs, nil
48}
49
50// SliceFs is an ordered composite filesystem.
51type SliceFs struct {
52	dirs []FileMetaInfo
53}
54
55func (fs *SliceFs) Chmod(n string, m os.FileMode) error {
56	return syscall.EPERM
57}
58
59func (fs *SliceFs) Chtimes(n string, a, m time.Time) error {
60	return syscall.EPERM
61}
62
63func (fs *SliceFs) Chown(n string, uid, gid int) error {
64	return syscall.EPERM
65}
66
67func (fs *SliceFs) LstatIfPossible(name string) (os.FileInfo, bool, error) {
68	fi, _, err := fs.pickFirst(name)
69	if err != nil {
70		return nil, false, err
71	}
72
73	if fi.IsDir() {
74		return decorateFileInfo(fi, fs, fs.getOpener(name), "", "", nil), false, nil
75	}
76
77	return nil, false, errors.Errorf("lstat: files not supported: %q", name)
78}
79
80func (fs *SliceFs) Mkdir(n string, p os.FileMode) error {
81	return syscall.EPERM
82}
83
84func (fs *SliceFs) MkdirAll(n string, p os.FileMode) error {
85	return syscall.EPERM
86}
87
88func (fs *SliceFs) Name() string {
89	return "SliceFs"
90}
91
92func (fs *SliceFs) Open(name string) (afero.File, error) {
93	fi, idx, err := fs.pickFirst(name)
94	if err != nil {
95		return nil, err
96	}
97
98	if !fi.IsDir() {
99		panic("currently only dirs in here")
100	}
101
102	return &sliceDir{
103		lfs:     fs,
104		idx:     idx,
105		dirname: name,
106	}, nil
107}
108
109func (fs *SliceFs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) {
110	panic("not implemented")
111}
112
113func (fs *SliceFs) ReadDir(name string) ([]os.FileInfo, error) {
114	panic("not implemented")
115}
116
117func (fs *SliceFs) Remove(n string) error {
118	return syscall.EPERM
119}
120
121func (fs *SliceFs) RemoveAll(p string) error {
122	return syscall.EPERM
123}
124
125func (fs *SliceFs) Rename(o, n string) error {
126	return syscall.EPERM
127}
128
129func (fs *SliceFs) Stat(name string) (os.FileInfo, error) {
130	fi, _, err := fs.LstatIfPossible(name)
131	return fi, err
132}
133
134func (fs *SliceFs) Create(n string) (afero.File, error) {
135	return nil, syscall.EPERM
136}
137
138func (fs *SliceFs) getOpener(name string) func() (afero.File, error) {
139	return func() (afero.File, error) {
140		return fs.Open(name)
141	}
142}
143
144func (fs *SliceFs) pickFirst(name string) (os.FileInfo, int, error) {
145	for i, mfs := range fs.dirs {
146		meta := mfs.Meta()
147		fs := meta.Fs
148		fi, _, err := lstatIfPossible(fs, name)
149		if err == nil {
150			// Gotta match!
151			return fi, i, nil
152		}
153
154		if !os.IsNotExist(err) {
155			// Real error
156			return nil, -1, err
157		}
158	}
159
160	// Not found
161	return nil, -1, os.ErrNotExist
162}
163
164func (fs *SliceFs) readDirs(name string, startIdx, count int) ([]os.FileInfo, error) {
165	collect := func(lfs *FileMeta) ([]os.FileInfo, error) {
166		d, err := lfs.Fs.Open(name)
167		if err != nil {
168			if !os.IsNotExist(err) {
169				return nil, err
170			}
171			return nil, nil
172		} else {
173			defer d.Close()
174			dirs, err := d.Readdir(-1)
175			if err != nil {
176				return nil, err
177			}
178			return dirs, nil
179		}
180	}
181
182	var dirs []os.FileInfo
183
184	for i := startIdx; i < len(fs.dirs); i++ {
185		mfs := fs.dirs[i]
186
187		fis, err := collect(mfs.Meta())
188		if err != nil {
189			return nil, err
190		}
191
192		dirs = append(dirs, fis...)
193
194	}
195
196	seen := make(map[string]bool)
197	var duplicates []int
198	for i, fi := range dirs {
199		if !fi.IsDir() {
200			continue
201		}
202
203		if seen[fi.Name()] {
204			duplicates = append(duplicates, i)
205		} else {
206			// Make sure it's opened by this filesystem.
207			dirs[i] = decorateFileInfo(fi, fs, fs.getOpener(fi.(FileMetaInfo).Meta().Filename), "", "", nil)
208			seen[fi.Name()] = true
209		}
210	}
211
212	// Remove duplicate directories, keep first.
213	if len(duplicates) > 0 {
214		for i := len(duplicates) - 1; i >= 0; i-- {
215			idx := duplicates[i]
216			dirs = append(dirs[:idx], dirs[idx+1:]...)
217		}
218	}
219
220	if count > 0 && len(dirs) >= count {
221		return dirs[:count], nil
222	}
223
224	return dirs, nil
225}
226
227type sliceDir struct {
228	lfs     *SliceFs
229	idx     int
230	dirname string
231}
232
233func (f *sliceDir) Close() error {
234	return nil
235}
236
237func (f *sliceDir) Name() string {
238	return f.dirname
239}
240
241func (f *sliceDir) Read(p []byte) (n int, err error) {
242	panic("not implemented")
243}
244
245func (f *sliceDir) ReadAt(p []byte, off int64) (n int, err error) {
246	panic("not implemented")
247}
248
249func (f *sliceDir) Readdir(count int) ([]os.FileInfo, error) {
250	return f.lfs.readDirs(f.dirname, f.idx, count)
251}
252
253func (f *sliceDir) Readdirnames(count int) ([]string, error) {
254	dirsi, err := f.Readdir(count)
255	if err != nil {
256		return nil, err
257	}
258
259	dirs := make([]string, len(dirsi))
260	for i, d := range dirsi {
261		dirs[i] = d.Name()
262	}
263	return dirs, nil
264}
265
266func (f *sliceDir) Seek(offset int64, whence int) (int64, error) {
267	panic("not implemented")
268}
269
270func (f *sliceDir) Stat() (os.FileInfo, error) {
271	panic("not implemented")
272}
273
274func (f *sliceDir) Sync() error {
275	panic("not implemented")
276}
277
278func (f *sliceDir) Truncate(size int64) error {
279	panic("not implemented")
280}
281
282func (f *sliceDir) Write(p []byte) (n int, err error) {
283	panic("not implemented")
284}
285
286func (f *sliceDir) WriteAt(p []byte, off int64) (n int, err error) {
287	panic("not implemented")
288}
289
290func (f *sliceDir) WriteString(s string) (ret int, err error) {
291	panic("not implemented")
292}
293