GitRoot

craft your forge, build your project, grow your community freely
  1// SPDX-FileCopyrightText: 2025 Romain Maneschi <romain@gitroot.dev>
  2//
  3// SPDX-License-Identifier: EUPL-1.2
  4
  5package fs
  6
  7import (
  8	"context"
  9	"errors"
 10	"io/fs"
 11	"os"
 12	"strings"
 13	"time"
 14
 15	"gitroot.dev/server/logger"
 16)
 17
 18var ErrFsNotFound = errors.New("fs not found")
 19
 20type multipleFs struct {
 21	logger *logger.Logger
 22	subFs  map[string]fs.FS
 23}
 24
 25func NewMultiple(ctx context.Context, subFs map[string]fs.FS) *multipleFs {
 26	return &multipleFs{
 27		logger: logger.NewLoggerCtx(logger.FS_PLUGIN, ctx).NewSubLogger("multiple"),
 28		subFs:  subFs,
 29	}
 30}
 31
 32func (m *multipleFs) UpdateSubFs(path string, subFs fs.FS) {
 33	m.subFs[path] = subFs
 34}
 35
 36func (m *multipleFs) splitFsFile(name string) (fs.FS, string, error) {
 37	parts := strings.SplitN(name, string(os.PathSeparator), 2)
 38	subFs, ok := m.subFs[parts[0]]
 39	if !ok || len(parts) < 2 {
 40		return nil, "", ErrFsNotFound
 41	}
 42	return subFs, parts[1], nil
 43}
 44
 45func (m *multipleFs) ReadFile(name string) ([]byte, error) {
 46	m.logger.Debug("read file", logger.NewLoggerPair("name", name))
 47	subFs, filepath, err := m.splitFsFile(name)
 48	if err != nil {
 49		return nil, err
 50	}
 51	return fs.ReadFile(subFs, filepath)
 52}
 53
 54func (m *multipleFs) ReadDir(name string) ([]fs.DirEntry, error) {
 55	m.logger.Debug("ReadDir", logger.NewLoggerPair("filepath", name))
 56	subFs, filepath, err := m.splitFsFile(name)
 57	if err != nil {
 58		return nil, err
 59	}
 60	return fs.ReadDir(subFs, filepath)
 61}
 62
 63func (m *multipleFs) Stat(name string) (fs.FileInfo, error) {
 64	m.logger.Debug("Stat")
 65	subFs, filepath, err := m.splitFsFile(name)
 66	if err != nil {
 67		return nil, err
 68	}
 69	return fs.Stat(subFs, filepath)
 70}
 71
 72func (m *multipleFs) Sub(dir string) (fs.FS, error) {
 73	m.logger.Debug("Sub")
 74	subFs, filepath, err := m.splitFsFile(dir)
 75	if err != nil {
 76		return nil, err
 77	}
 78	return fs.Sub(subFs, filepath)
 79}
 80
 81func (m *multipleFs) Open(name string) (fs.File, error) {
 82	m.logger.Debug("open file", logger.NewLoggerPair("name", name))
 83	if name == "." {
 84		return &fakeFile{
 85			name:  "/",
 86			subFs: m.subFs,
 87		}, nil
 88	}
 89	subFs, filepath, err := m.splitFsFile(name)
 90	if err != nil {
 91		return nil, err
 92	}
 93	return subFs.Open(filepath)
 94}
 95
 96type fakeFile struct {
 97	name  string
 98	subFs map[string]fs.FS
 99}
100type fakeFileInfo struct {
101	name string
102}
103
104func (m *fakeFile) Close() error {
105	return nil
106}
107
108func (m *fakeFile) Read(p []byte) (int, error) {
109	return 0, errors.New("can't read a dir")
110}
111
112func (m *fakeFile) Stat() (fs.FileInfo, error) {
113	return &fakeFileInfo{}, nil
114}
115
116func (m *fakeFile) ReadDir(n int) ([]fs.DirEntry, error) {
117	subDirs := []fs.FileInfo{}
118	for key := range m.subFs {
119		subDirs = append(subDirs, &fakeFileInfo{name: key})
120	}
121	return toDirEntry(subDirs), nil
122}
123
124func (f *fakeFileInfo) Name() string {
125	return f.name
126}
127func (f *fakeFileInfo) Size() int64 {
128	return 0
129}
130func (f *fakeFileInfo) Mode() fs.FileMode {
131	return fs.ModeDir
132}
133func (f *fakeFileInfo) ModTime() time.Time {
134	return time.Now()
135}
136func (f *fakeFileInfo) IsDir() bool {
137	return true
138}
139func (f *fakeFileInfo) Sys() any {
140	return nil
141}