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}