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 plugin
6
7import (
8 "encoding/json"
9 "fmt"
10 "io"
11 "os"
12 "path/filepath"
13 "reflect"
14 "slices"
15 "strings"
16
17 "github.com/goccy/go-yaml"
18 "github.com/samber/oops"
19 "gitroot.dev/libs/golang/glob"
20 pluginLib "gitroot.dev/libs/golang/plugin/model"
21 "gitroot.dev/server/user"
22)
23
24type Plugin struct {
25 Url string `yaml:",omitempty"`
26 Crc string `yaml:",omitempty"`
27 Name string
28 Version string
29 Active bool
30 content []byte
31 commiter *user.Commiter
32 Run []PluginRun
33}
34
35type PluginRun struct {
36 pluginLib.PluginRun `yaml:",inline"`
37 glob *glob.Glob
38 write PluginWrite
39}
40
41type PluginWrite struct { //TODO not implemented
42 git []PluginWriteRight
43 web []PluginWriteRight
44}
45
46type PluginWriteRight struct {
47 pluginLib.PluginWriteRight
48 glob *glob.Glob
49}
50
51func pluginLibToPlugin(run []pluginLib.PluginRun) ([]PluginRun, error) {
52 p := make([]PluginRun, len(run))
53 for i, pl := range run {
54 g, err := glob.NewGlob(pl.Path)
55 if err != nil {
56 return nil, oops.Wrapf(err, "can't glob %s", pl.Path)
57 }
58 pluginWrite := PluginWrite{
59 git: make([]PluginWriteRight, len(pl.Write.Git)),
60 web: make([]PluginWriteRight, len(pl.Write.Web)),
61 }
62 for j, pw := range pl.Write.Git {
63 g, err := glob.NewGlob(pl.Path)
64 if err != nil {
65 return nil, oops.Wrapf(err, "can't glob %s", pl.Path)
66 }
67 pluginWrite.git[j] = PluginWriteRight{PluginWriteRight: pw, glob: g}
68 }
69 for j, pw := range pl.Write.Web {
70 g, err := glob.NewGlob(pl.Path)
71 if err != nil {
72 return nil, oops.Wrapf(err, "can't glob %s", pl.Path)
73 }
74 pluginWrite.web[j] = PluginWriteRight{PluginWriteRight: pw, glob: g}
75 }
76 p[i] = PluginRun{PluginRun: pl, glob: g, write: pluginWrite}
77 }
78 return p, nil
79}
80
81func ParsePlugins(fileContent []byte, withRun bool) ([]Plugin, error) {
82 pluginsRaw := make([]Plugin, 0)
83 if err := yaml.Unmarshal(fileContent, &pluginsRaw); err != nil {
84 return nil, oops.Wrapf(err, "can't parse yaml config")
85 }
86
87 plugins := make([]Plugin, len(pluginsRaw))
88 for i, plugin := range pluginsRaw {
89 plugins[i] = plugin.DetermineNameAndVersionFromUrl()
90 }
91 if withRun {
92 for i, plugin := range plugins {
93 for j, pluginRun := range plugin.Run {
94 g, err := glob.NewGlob(pluginRun.Path)
95 if err != nil {
96 return nil, oops.Wrapf(err, "can't glob %s", pluginRun.Path)
97 }
98 plugins[i].Run[j].glob = g
99 for h, pluginWrite := range pluginRun.Write.Git {
100 g, err = glob.NewGlob(pluginWrite.Path)
101 if err != nil {
102 return nil, oops.Wrapf(err, "can't glob git %s", pluginRun.Path)
103 }
104 if plugins[i].Run[j].write.git == nil {
105 plugins[i].Run[j].write.git = make([]PluginWriteRight, len(pluginRun.Write.Git))
106 }
107 plugins[i].Run[j].write.git[h] = PluginWriteRight{PluginWriteRight: pluginWrite, glob: g}
108 }
109 for h, pluginWrite := range pluginRun.Write.Web {
110 g, err = glob.NewGlob(pluginWrite.Path)
111 if err != nil {
112 return nil, oops.Wrapf(err, "can't glob git %s", pluginRun.Path)
113 }
114 if plugins[i].Run[j].write.web == nil {
115 plugins[i].Run[j].write.web = make([]PluginWriteRight, len(pluginRun.Write.Web))
116 }
117 plugins[i].Run[j].write.web[h] = PluginWriteRight{PluginWriteRight: pluginWrite, glob: g}
118 }
119 }
120 }
121 }
122 return plugins, nil
123}
124
125func (p Plugin) SetActive(active bool) Plugin {
126 return Plugin{
127 Url: p.Url,
128 Crc: p.Crc,
129 Name: p.Name,
130 Version: p.Version,
131 Active: active,
132 content: p.content,
133 commiter: p.commiter,
134 Run: p.Run,
135 }
136}
137
138func (p Plugin) SetRun(run []PluginRun) Plugin {
139 return Plugin{
140 Url: p.Url,
141 Crc: p.Crc,
142 Name: p.Name,
143 Version: p.Version,
144 Active: p.Active,
145 content: p.content,
146 commiter: p.commiter,
147 Run: run,
148 }
149}
150
151func (p Plugin) UpdateVersion(version string) Plugin {
152 return Plugin{
153 Url: p.Url,
154 Crc: p.Crc,
155 Name: p.Name,
156 Version: version,
157 Active: p.Active,
158 content: p.content,
159 commiter: p.commiter,
160 Run: p.Run,
161 }
162}
163
164func (p Plugin) OverrideWith(other Plugin) Plugin {
165 copy := Plugin{
166 Url: p.Url,
167 Crc: p.Crc,
168 Name: p.Name,
169 Version: p.Version,
170 Active: p.Active,
171 content: p.content,
172 commiter: p.commiter,
173 Run: p.Run,
174 }
175 if copy.Url == "" {
176 copy.Url = other.Url
177 }
178 if copy.Crc == "" {
179 copy.Crc = other.Crc
180 }
181 if copy.Name == "" {
182 copy.Name = other.Name
183 }
184 if copy.Version == "" {
185 copy.Version = other.Version
186 }
187 if !copy.Active {
188 copy.Active = other.Active
189 }
190 if len(copy.Run) == 0 {
191 copy.Run = other.Run
192 }
193 if len(copy.content) == 0 {
194 copy.content = other.content
195 }
196 if copy.commiter == nil || copy.commiter.Signer == nil {
197 copy.commiter = other.commiter
198 }
199 return copy
200}
201
202func (p Plugin) Commiter() *user.Commiter {
203 return p.commiter
204}
205
206func (p Plugin) DetermineNameAndVersionFromUrl() Plugin {
207 if p.Url != "" { //if child repo no url
208 _, versions := filepath.Split(p.Url)
209 nameVersion := strings.TrimSuffix(versions, ".wasm")
210 nameVersions := strings.Split(nameVersion, "-")
211 return p.OverrideWith(Plugin{Name: nameVersions[0], Version: nameVersions[1]})
212 }
213 return p
214}
215
216func (p Plugin) uuid() string {
217 return fmt.Sprintf("%s-%s", p.Name, p.Version)
218}
219
220func (pr PluginRun) Marshal() ([]byte, error) {
221 return json.Marshal(pr.Configuration)
222}
223
224func (m *Manager) PathWasm(p Plugin) string {
225 return filepath.Join(m.conf.GetDirPathDataPlugin(p.Name), fmt.Sprintf("%s-%s.wasm", p.Name, p.Version))
226}
227
228func (m *Manager) Install(p Plugin) error {
229 r, err := os.Open(p.Url)
230 if err != nil {
231 return err
232 }
233 defer r.Close()
234 w, err := os.Create(m.PathWasm(p))
235 if err != nil {
236 return err
237 }
238 defer func() {
239 if c := w.Close(); err == nil {
240 err = c
241 }
242 }()
243 _, err = io.Copy(w, r)
244 return err
245}
246
247func (m *Manager) Equal(aPlugin, bPlugin Plugin) bool {
248 return aPlugin.Name == bPlugin.Name &&
249 aPlugin.Active == bPlugin.Active &&
250 m.equalPluginRun(aPlugin.Run, bPlugin.Run)
251}
252
253func (m *Manager) equalPluginRun(aPlugin, bPlugin []PluginRun) bool {
254 if len(aPlugin) == len(bPlugin) {
255 for i, w := range aPlugin {
256 isEqual := w.Path == bPlugin[i].Path &&
257 slices.Equal(w.Branch, bPlugin[i].Branch) &&
258 slices.Equal(w.When, bPlugin[i].When) &&
259 m.equalPluginWrite(w.Write, bPlugin[i].Write) &&
260 reflect.DeepEqual(w.Configuration, bPlugin[i].Configuration)
261 if !isEqual {
262 return false
263 }
264 }
265 return true
266 }
267 return false
268}
269
270func (m *Manager) equalPluginWrite(aPlugin, bPlugin pluginLib.PluginWrite) bool {
271 if len(aPlugin.Git) == len(bPlugin.Git) {
272 for i, w := range aPlugin.Git {
273 if w.Path != bPlugin.Git[i].Path || slices.Equal(w.Can, bPlugin.Git[i].Can) {
274 return false
275 }
276 }
277 return true
278 }
279 return false
280}