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"
11 "io/fs"
12 "os"
13
14 "github.com/go-git/go-billy/v6"
15 "gitroot.dev/server/logger"
16)
17
18type StandardFs struct {
19 ctx context.Context
20 logger *logger.Logger
21 orig billy.Filesystem
22}
23
24type mineFile struct {
25 logger *logger.Logger
26 fs *StandardFs
27 name string
28 orig billy.File
29 currentReadDir int
30}
31
32func ToFs(ctx context.Context, filesystem billy.Filesystem) *StandardFs {
33 return &StandardFs{
34 ctx: ctx,
35 logger: logger.NewLoggerCtx(logger.FS_PLUGIN, ctx).NewSubLogger("standard"),
36 orig: filesystem,
37 }
38}
39
40func (m *StandardFs) ReadFile(name string) ([]byte, error) {
41 m.logger.Debug("read file", logger.NewLoggerPair("name", name))
42 file, err := m.orig.Open(name)
43 if err != nil {
44 return nil, err
45 }
46 defer file.Close()
47 return io.ReadAll(file)
48}
49
50func (m *StandardFs) ReadDir(name string) ([]fs.DirEntry, error) {
51 m.logger.Debug("ReadDir", logger.NewLoggerPair("filepath", name))
52 entries, err := m.orig.ReadDir(name)
53 if err != nil {
54 return nil, err
55 }
56 return entries, nil
57}
58
59func (m *StandardFs) Stat(name string) (fs.FileInfo, error) {
60 m.logger.Debug("Stat")
61 return m.orig.Stat(name)
62}
63
64func (m *StandardFs) Sub(dir string) (fs.FS, error) {
65 m.logger.Debug("Sub")
66 f, err := m.orig.Chroot(dir)
67 return ToFs(m.ctx, f), err
68}
69
70func (m *StandardFs) Open(name string) (fs.File, error) {
71 m.logger.Debug("open file", logger.NewLoggerPair("name", name))
72 info, err := m.orig.Stat(name)
73 if err != nil {
74 if !os.IsNotExist(err) {
75 m.logger.Error("open file stat", err, logger.NewLoggerPair("name", name))
76 }
77 return nil, err
78 }
79 if info.IsDir() {
80 return &mineFile{
81 logger: m.logger.NewSubLogger("file").With("file", name),
82 fs: m,
83 name: name,
84 currentReadDir: 0,
85 }, nil
86 }
87 file, err := m.orig.Open(name)
88 if err != nil {
89 m.logger.Error("open file err", err, logger.NewLoggerPair("name", name))
90 return nil, err
91 }
92 return &mineFile{
93 logger: m.logger.NewSubLogger("file").With("file", name),
94 fs: m,
95 name: name,
96 orig: file,
97 currentReadDir: 0,
98 }, nil
99}
100
101func (m *mineFile) Close() error {
102 m.logger.Debug("close")
103 if m.orig != nil {
104 m.orig.Close()
105 }
106 return nil
107}
108
109func (m *mineFile) Read(p []byte) (int, error) {
110 m.logger.Debug("Read")
111 if m.orig == nil {
112 return 0, errors.New("can't read a dir")
113 }
114 return m.orig.Read(p)
115}
116
117func (m *mineFile) Stat() (fs.FileInfo, error) {
118 m.logger.Debug("Stat")
119 return m.fs.orig.Stat(m.name)
120}
121
122func (m *mineFile) ReadDir(n int) ([]fs.DirEntry, error) {
123 m.logger.Debug("ReadDir")
124 infos, err := m.fs.orig.ReadDir(m.name)
125 if err != nil {
126 return nil, err
127 }
128
129 if n <= 0 {
130 m.currentReadDir = 0
131 return infos, nil
132 }
133
134 if m.currentReadDir >= len(infos) {
135 m.currentReadDir = 0
136 return nil, io.EOF
137 }
138
139 from := m.currentReadDir
140 to := from + n
141 if to > len(infos) {
142 to = len(infos)
143 }
144
145 result := infos[from:to]
146 m.currentReadDir = to
147
148 if len(result) < n {
149 m.currentReadDir = 0
150 }
151 return result, nil
152}
153
154func (m *mineFile) Seek(offset int64, whence int) (int64, error) {
155 if m.orig == nil {
156 return 0, errors.New("can't seek a dir")
157 }
158 return m.orig.Seek(offset, whence)
159}