// SPDX-FileCopyrightText: 2025 Romain Maneschi // // SPDX-License-Identifier: EUPL-1.2 package fs import ( "context" "errors" "io" "io/fs" "os" "github.com/go-git/go-billy/v6" "gitroot.dev/server/logger" ) type StandardFs struct { ctx context.Context logger *logger.Logger orig billy.Filesystem } type mineFile struct { logger *logger.Logger fs *StandardFs name string orig billy.File currentReadDir int } func ToFs(ctx context.Context, filesystem billy.Filesystem) *StandardFs { return &StandardFs{ ctx: ctx, logger: logger.NewLoggerCtx(logger.FS_PLUGIN, ctx).NewSubLogger("standard"), orig: filesystem, } } func (m *StandardFs) ReadFile(name string) ([]byte, error) { m.logger.Debug("read file", logger.NewLoggerPair("name", name)) file, err := m.orig.Open(name) if err != nil { return nil, err } defer file.Close() return io.ReadAll(file) } func (m *StandardFs) ReadDir(name string) ([]fs.DirEntry, error) { m.logger.Debug("ReadDir", logger.NewLoggerPair("filepath", name)) entries, err := m.orig.ReadDir(name) if err != nil { return nil, err } return entries, nil } func (m *StandardFs) Stat(name string) (fs.FileInfo, error) { m.logger.Debug("Stat") return m.orig.Stat(name) } func (m *StandardFs) Sub(dir string) (fs.FS, error) { m.logger.Debug("Sub") f, err := m.orig.Chroot(dir) return ToFs(m.ctx, f), err } func (m *StandardFs) Open(name string) (fs.File, error) { m.logger.Debug("open file", logger.NewLoggerPair("name", name)) info, err := m.orig.Stat(name) if err != nil { if !os.IsNotExist(err) { m.logger.Error("open file stat", err, logger.NewLoggerPair("name", name)) } return nil, err } if info.IsDir() { return &mineFile{ logger: m.logger.NewSubLogger("file").With("file", name), fs: m, name: name, currentReadDir: 0, }, nil } file, err := m.orig.Open(name) if err != nil { m.logger.Error("open file err", err, logger.NewLoggerPair("name", name)) return nil, err } return &mineFile{ logger: m.logger.NewSubLogger("file").With("file", name), fs: m, name: name, orig: file, currentReadDir: 0, }, nil } func (m *mineFile) Close() error { m.logger.Debug("close") if m.orig == nil { return errors.New("can't close a dir") } return m.orig.Close() } func (m *mineFile) Read(p []byte) (int, error) { m.logger.Debug("Read") if m.orig == nil { return 0, errors.New("can't read a dir") } return m.orig.Read(p) } func (m *mineFile) Stat() (fs.FileInfo, error) { m.logger.Debug("Stat") return m.fs.orig.Stat(m.name) } func (m *mineFile) ReadDir(n int) ([]fs.DirEntry, error) { m.logger.Debug("ReadDir") infos, err := m.fs.orig.ReadDir(m.name) if err != nil { return nil, err } if n > 0 { from := m.currentReadDir to := m.currentReadDir + n if to > len(infos) { to = len(infos) - 1 } if to < 0 { return []fs.DirEntry{}, io.EOF } infos = infos[from:to] m.currentReadDir = to } else { m.currentReadDir = 0 } return infos, nil } func (m *mineFile) Seek(offset int64, whence int) (int64, error) { if m.orig == nil { return 0, errors.New("can't seek a dir") } return m.orig.Seek(offset, whence) }