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: MIT
  4
  5package gitroot
  6
  7// #include <stdlib.h>
  8import "C"
  9
 10import (
 11	"encoding/json"
 12	"errors"
 13	"runtime"
 14
 15	"github.com/tidwall/gjson"
 16	"gitroot.dev/libs/golang/plugin/model"
 17)
 18
 19type PluginFactory = func(server model.Server) model.Plugin
 20
 21type pServer struct {
 22	worktree         *model.GrFs
 23	webcontent       *model.GrFs
 24	cache            *model.GrFs
 25	plugin           model.Plugin
 26	pluginOpts       model.PluginOption
 27	run              []model.PluginRun
 28	atLeastOneChange bool
 29	exportedFuncs    map[string]func(args map[string]string) (map[string]string, error)
 30}
 31
 32var server = &pServer{
 33	atLeastOneChange: false,
 34	pluginOpts:       model.PluginOption{},
 35	exportedFuncs:    map[string]func(args map[string]string) (map[string]string, error){},
 36}
 37
 38func Register(run []model.PluginRun, p PluginFactory) {
 39	server.run = run
 40	server.plugin = p(server)
 41}
 42
 43func (s *pServer) PluginOption(opts ...model.PluginOptionWith) {
 44	for _, opt := range opts {
 45		server.pluginOpts = opt(server.pluginOpts)
 46	}
 47}
 48
 49func (s *pServer) ForgeConf() (model.ForgeConf, error) {
 50	forgeConf := model.ForgeConf{}
 51	ptrSize := _forgeConf()
 52	if ptrSize == 0 {
 53		return forgeConf, errors.New("can't make diff")
 54	}
 55	ptrDiff := uint32(ptrSize >> 32)
 56	sizeDiff := uint32(ptrSize)
 57	forgeConfJson := gjson.Parse(ptrToString(ptrDiff, sizeDiff))
 58	return model.ForgeConf{
 59		Domain:             forgeConfJson.Get("domain").String(),
 60		ExternalSshAddr:    forgeConfJson.Get("externalSshAddr").String(),
 61		ExternalHttpAddr:   forgeConfJson.Get("externalHttpAddr").String(),
 62		RootRepositoryName: forgeConfJson.Get("rootRepositoryName").String(),
 63	}, nil
 64}
 65
 66func (s *pServer) Worktree() *model.GrFs {
 67	if s.worktree == nil {
 68		s.worktree = model.NewGrFs(model.WORKTREE, s)
 69	}
 70	return s.worktree
 71}
 72
 73func (s *pServer) Webcontent() *model.GrFs {
 74	if s.webcontent == nil {
 75		s.webcontent = model.NewGrFs(model.WEBCONTENT, s)
 76	}
 77	return s.webcontent
 78}
 79
 80func (s *pServer) Cache() *model.GrFs {
 81	if s.cache == nil {
 82		s.cache = model.NewGrFs(model.CACHE, s)
 83	}
 84	return s.cache
 85}
 86
 87// CopyFile implements [model.ServerNeeded].
 88func (s *pServer) CopyFile(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error {
 89	s.atLeastOneChange = true
 90	ptr, size := stringToPtr(string(fromFs))
 91	ptr2, size2 := stringToPtr(fromPath)
 92	ptr3, size3 := stringToPtr(string(toFs))
 93	ptr4, size4 := stringToPtr(toPath)
 94	resPtrSize := _copyFile(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
 95	runtime.KeepAlive(fromFs)
 96	runtime.KeepAlive(fromPath)
 97	runtime.KeepAlive(toFs)
 98	runtime.KeepAlive(toPath)
 99	return ptrSizeToError(resPtrSize)
100}
101
102// DeleteFile implements [model.ServerNeeded].
103func (s *pServer) DeleteFile(fromFs model.FsBase, filename string) error {
104	s.atLeastOneChange = true
105	ptr, size := stringToPtr(string(fromFs))
106	ptr2, size2 := stringToPtr(filename)
107	resPtrSize := _deleteFile(ptr, size, ptr2, size2)
108	runtime.KeepAlive(fromFs)
109	runtime.KeepAlive(filename)
110	return ptrSizeToError(resPtrSize)
111}
112
113// MoveFile implements [model.ServerNeeded].
114func (s *pServer) MoveFile(fromFs model.FsBase, fromPath string, toFs model.FsBase, toPath string) error {
115	s.atLeastOneChange = true
116	ptr, size := stringToPtr(string(fromFs))
117	ptr2, size2 := stringToPtr(fromPath)
118	ptr3, size3 := stringToPtr(string(toFs))
119	ptr4, size4 := stringToPtr(toPath)
120	resPtrSize := _moveFile(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
121	runtime.KeepAlive(fromFs)
122	runtime.KeepAlive(fromPath)
123	runtime.KeepAlive(toFs)
124	runtime.KeepAlive(toPath)
125	return ptrSizeToError(resPtrSize)
126}
127
128// ReplaceContent implements [model.ServerNeeded].
129func (s *pServer) ReplaceContent(fromFs model.FsBase, filepath string, oldContent string, content string) error {
130	s.atLeastOneChange = true
131	ptr, size := stringToPtr(string(fromFs))
132	ptr2, size2 := stringToPtr(filepath)
133	ptr3, size3 := stringToPtr(oldContent)
134	ptr4, size4 := stringToPtr(content)
135	resPtrSize := _replaceContent(ptr, size, ptr2, size2, ptr3, size3, ptr4, size4)
136	runtime.KeepAlive(fromFs)
137	runtime.KeepAlive(filepath)
138	runtime.KeepAlive(oldContent)
139	runtime.KeepAlive(content)
140	return ptrSizeToError(resPtrSize)
141}
142
143// WriteContent implements [model.ServerNeeded].
144func (s *pServer) WriteContent(fromFs model.FsBase, filepath string, content string) error {
145	s.atLeastOneChange = true
146	ptr, size := stringToPtr(string(fromFs))
147	ptr2, size2 := stringToPtr(filepath)
148	ptr3, size3 := stringToPtr(content)
149	resPtrSize := _writeContent(ptr, size, ptr2, size2, ptr3, size3)
150	runtime.KeepAlive(fromFs)
151	runtime.KeepAlive(filepath)
152	runtime.KeepAlive(content)
153	return ptrSizeToError(resPtrSize)
154}
155
156func (s *pServer) CommitAllIfNeeded(message string) {
157	if s.atLeastOneChange {
158		ptr, size := stringToPtr(message)
159		_commitAll(ptr, size)
160		runtime.KeepAlive(message)
161	}
162}
163
164func (s *pServer) DiffWithParent(hash string, oldFilepath string, newFilePath string) (string, error) {
165	ptrHash, sizeHash := stringToPtr(hash)
166	ptrOldFile, sizeOldFile := stringToPtr(oldFilepath)
167	ptrNewFile, sizeNewFile := stringToPtr(newFilePath)
168	ptrSizeDiff := _diffWithParent(ptrHash, sizeHash, ptrOldFile, sizeOldFile, ptrNewFile, sizeNewFile)
169	if ptrSizeDiff == 0 {
170		return "", errors.New("can't make diff")
171	}
172	ptrDiff := uint32(ptrSizeDiff >> 32)
173	sizeDiff := uint32(ptrSizeDiff)
174	return ptrToString(ptrDiff, sizeDiff), nil
175}
176
177func (s *pServer) Log(message string) {
178	ptr, size := stringToPtr(message)
179	_log(ptr, size)
180	runtime.KeepAlive(message)
181}
182
183func (s *pServer) LogError(message string, err error) {
184	ptr, size := stringToPtr(message)
185	errPtr, errSize := stringToPtr(err.Error())
186	_logError(ptr, size, errPtr, errSize)
187	runtime.KeepAlive(message)
188	runtime.KeepAlive(err)
189}
190
191func (s *pServer) Merge(from string, to string) {
192	fromPtr, fromSize := stringToPtr(from)
193	toPtr, toSize := stringToPtr(to)
194	_merge(fromPtr, fromSize, toPtr, toSize)
195	runtime.KeepAlive(from)
196	runtime.KeepAlive(to)
197}
198
199func (s *pServer) Commits(from string, to string) ([]model.Commit, error) {
200	fromPtr, fromSize := stringToPtr(from)
201	toPtr, toSize := stringToPtr(to)
202	ptrSizeDiff := _commits(fromPtr, fromSize, toPtr, toSize)
203	runtime.KeepAlive(from)
204	runtime.KeepAlive(to)
205	if ptrSizeDiff == 0 {
206		return nil, errors.New("can't get commits")
207	}
208	ptrDiff := uint32(ptrSizeDiff >> 32)
209	sizeDiff := uint32(ptrSizeDiff)
210	return model.CommitsFromString(ptrToString(ptrDiff, sizeDiff)), nil
211}
212
213func (s *pServer) Exec(exec model.Exec) (model.ExecStatus, error) {
214	execMarshalled, err := json.Marshal(exec)
215	if err != nil {
216		return model.ExecStatus{}, err
217	}
218	ptr, size := stringToPtr(string(execMarshalled))
219	ptrSize := _exec(ptr, size)
220	if ptrSize == 0 {
221		return model.ExecStatus{}, errors.New("can't exec")
222	}
223	ptrLog := uint32(ptrSize >> 32)
224	sizeLog := uint32(ptrSize)
225	return model.ExecStatusFromString(ptrToString(ptrLog, sizeLog)), nil
226}
227
228func (s *pServer) ReportToPlugin(report model.Report) {
229	if s.pluginOpts.Reporter != nil {
230		if err := s.pluginOpts.Reporter(report); err != nil {
231			s.LogError("plugin reporter err", err)
232		}
233	}
234}
235
236func (s *pServer) Report(level model.ReportLevel, content []string) error {
237	reportMarshalled, err := json.Marshal(model.ReportToGitroot{Level: level, Content: content})
238	if err != nil {
239		return err
240	}
241	ptr, size := stringToPtr(string(reportMarshalled))
242	_report(ptr, size)
243	return nil
244}
245
246func (s *pServer) ExportFunc(name string, callback func(args map[string]string) (map[string]string, error)) {
247	s.exportedFuncs[name] = callback
248}
249
250func (s *pServer) CanCallFunc(plugin string, name string, args map[string]string) bool {
251	call := model.Call{
252		Plugin: plugin,
253		Name:   name,
254		Args:   args,
255	}
256	callJson, err := json.Marshal(call)
257	if err != nil {
258		s.LogError("can't serialize canCall", err)
259		return false
260	}
261	ptr, size := stringToPtr(string(callJson))
262	ptrSize := _canCall(ptr, size)
263	if ptrSize == 0 {
264		return false
265	}
266	return true
267}
268
269func (s *pServer) CallFunc(plugin string, name string, args map[string]string) (map[string]string, error) {
270	call := model.Call{
271		Plugin: plugin,
272		Name:   name,
273		Args:   args,
274	}
275	callJson, err := json.Marshal(call)
276	if err != nil {
277		return nil, err
278	}
279	ptr, size := stringToPtr(string(callJson))
280	ptrSize := _call(ptr, size)
281	if ptrSize == 0 {
282		return nil, errors.New("can't call")
283	}
284	ptrRes := uint32(ptrSize >> 32)
285	sizeRes := uint32(ptrSize)
286	resCall := model.CallResFromString(ptrToString(ptrRes, sizeRes))
287	if resCall.Err != "" {
288		return nil, errors.New(resCall.Err)
289	}
290	return resCall.Res, nil
291}
292
293func (s *pServer) HttpClient(header map[string][]string) model.HttpClient {
294	return model.NewHttpClient(s, header)
295}
296
297func (s *pServer) DoRequest(request model.HttpRequest) (model.HttpResponse, error) {
298	requestJson, err := json.Marshal(request)
299	if err != nil {
300		return model.HttpResponse{}, err
301	}
302	ptr, size := stringToPtr(string(requestJson))
303	ptrSize := _httpClient(ptr, size)
304	if ptrSize == 0 {
305		return model.HttpResponse{}, errors.New("can't http")
306	}
307	ptrRes := uint32(ptrSize >> 32)
308	sizeRes := uint32(ptrSize)
309	res := model.HttpResponseFromString(ptrToString(ptrRes, sizeRes))
310	return res, nil
311}