pool: Reddo component-like fields

This commit is contained in:
Tom Wiesing 2022-12-22 13:49:05 +01:00
parent 99983ee6db
commit 337a5fbeba
No known key found for this signature in database
48 changed files with 291 additions and 163 deletions

View file

@ -37,33 +37,32 @@ var errServerListen = exit.Error{
func (s server) Run(context wisski_distillery.Context) error { func (s server) Run(context wisski_distillery.Context) error {
dis := context.Environment dis := context.Environment
// if the caller requested a trigger, just trigger the cron tasks
if s.Trigger { if s.Trigger {
context.Println("Triggering Cron Tasks")
return dis.Control().Trigger(context.Context, context.Environment.Environment) return dis.Control().Trigger(context.Context, context.Environment.Environment)
} }
// start the cron tasks
{ {
// create a channel for notifications // create a channel for notifications
notify, cancel := dis.Cron().Listen(context.Context) notify, cancel := dis.Cron().Listen(context.Context)
defer cancel() defer cancel()
// start the cron tasks // start the cron tasks
context.Printf("Starting cron tasks %s\n", s.Bind)
done := dis.Cron().Start(context.Context, notify) done := dis.Cron().Start(context.Context, notify)
defer func() { defer func() {
<-done <-done
}() }()
} }
// and start the server
handler, err := dis.Control().Server(context.Context, context.Stderr) handler, err := dis.Control().Server(context.Context, context.Stderr)
if err != nil { if err != nil {
return err return err
} }
context.Printf("Listening on %s\n", s.Bind) zerolog.Ctx(context.Context).Info().Str("bind", s.Bind).Msg("listening")
// make a new listener // create a new listener
listener, err := dis.Still.Environment.Listen("tcp", s.Bind) listener, err := dis.Still.Environment.Listen("tcp", s.Bind)
if err != nil { if err != nil {
return errServerListen.Wrap(err) return errServerListen.Wrap(err)

View file

@ -15,9 +15,10 @@ import (
// Control represents the running control server. // Control represents the running control server.
type Control struct { type Control struct {
component.Base component.Base
Dependencies struct {
Servables []component.Servable Servables []component.Servable
Cronables []component.Cronable Cronables []component.Cronable
}
} }
var ( var (

View file

@ -16,8 +16,9 @@ import (
type Cron struct { type Cron struct {
component.Base component.Base
Dependencies struct {
Tasks []component.Cronable Tasks []component.Cronable
}
} }
// Listen returns a channel that listens for triggers in the current process. // Listen returns a channel that listens for triggers in the current process.
@ -51,11 +52,11 @@ func (control *Cron) Listen(ctx context.Context) (<-chan struct{}, func()) {
// Once should not be called concurrently with Cron. // Once should not be called concurrently with Cron.
func (control *Cron) Once(ctx context.Context) { func (control *Cron) Once(ctx context.Context) {
var wg sync.WaitGroup var wg sync.WaitGroup
wg.Add(len(control.Tasks)) wg.Add(len(control.Dependencies.Tasks))
zerolog.Ctx(ctx).Info().Time("time", time.Now()).Msg("Starting Cron") zerolog.Ctx(ctx).Info().Time("time", time.Now()).Msg("Starting Cron")
for _, task := range control.Tasks { for _, task := range control.Dependencies.Tasks {
go func(task component.Cronable) { go func(task component.Cronable) {
defer wg.Done() defer wg.Done()

View file

@ -8,7 +8,9 @@ import (
type UpdateInstanceList struct { type UpdateInstanceList struct {
component.Base component.Base
Dependencies struct {
Home *Home Home *Home
}
} }
var ( var (
@ -20,18 +22,20 @@ func (*UpdateInstanceList) TaskName() string {
} }
func (ul *UpdateInstanceList) Cron(ctx context.Context) error { func (ul *UpdateInstanceList) Cron(ctx context.Context) error {
names, err := ul.Home.instanceMap(ctx) names, err := ul.Dependencies.Home.instanceMap(ctx)
if err != nil { if err != nil {
return err return err
} }
ul.Home.instanceNames.Set(names) ul.Dependencies.Home.instanceNames.Set(names)
return nil return nil
} }
type UpdateRedirect struct { type UpdateRedirect struct {
component.Base component.Base
Dependencies struct {
Home *Home Home *Home
}
} }
var ( var (
@ -43,18 +47,20 @@ func (ur *UpdateRedirect) TaskName() string {
} }
func (ur *UpdateRedirect) Cron(ctx context.Context) error { func (ur *UpdateRedirect) Cron(ctx context.Context) error {
redirect, err := ur.Home.loadRedirect(ctx) redirect, err := ur.Dependencies.Home.loadRedirect(ctx)
if err != nil { if err != nil {
return err return err
} }
ur.Home.redirect.Set(&redirect) ur.Dependencies.Home.redirect.Set(&redirect)
return nil return nil
} }
type UpdateHome struct { type UpdateHome struct {
component.Base component.Base
Dependencies struct {
Home *Home Home *Home
}
} }
var ( var (
@ -66,11 +72,11 @@ func (ur *UpdateHome) TaskName() string {
} }
func (ur *UpdateHome) Cron(ctx context.Context) error { func (ur *UpdateHome) Cron(ctx context.Context) error {
bytes, err := ur.Home.homeRender(ctx) bytes, err := ur.Dependencies.Home.homeRender(ctx)
if err != nil { if err != nil {
return err return err
} }
ur.Home.homeBytes.Set(bytes) ur.Dependencies.Home.homeBytes.Set(bytes)
return nil return nil
} }

View file

@ -12,8 +12,9 @@ import (
type Home struct { type Home struct {
component.Base component.Base
Dependencies struct {
Instances *instances.Instances Instances *instances.Instances
}
redirect lazy.Lazy[*Redirect] redirect lazy.Lazy[*Redirect]
instanceNames lazy.Lazy[map[string]struct{}] instanceNames lazy.Lazy[map[string]struct{}]

View file

@ -13,7 +13,7 @@ import (
) )
func (home *Home) instanceMap(ctx context.Context) (map[string]struct{}, error) { func (home *Home) instanceMap(ctx context.Context) (map[string]struct{}, error) {
wissKIs, err := home.Instances.All(ctx) wissKIs, err := home.Dependencies.Instances.All(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -37,7 +37,7 @@ func (home *Home) homeRender(ctx context.Context) ([]byte, error) {
context.SelfRedirect = home.Config.SelfRedirect.String() context.SelfRedirect = home.Config.SelfRedirect.String()
// find all the WissKIs // find all the WissKIs
wissKIs, err := home.Instances.All(ctx) wissKIs, err := home.Dependencies.Instances.All(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -52,7 +52,7 @@ func (info *Info) ingredients(r *http.Request) (cp ingredientsContext, err error
cp.Time = time.Now().UTC() cp.Time = time.Now().UTC()
// find the instance itself! // find the instance itself!
instance, err := info.Instances.WissKI(r.Context(), mux.Vars(r)["slug"]) instance, err := info.Dependencies.Instances.WissKI(r.Context(), mux.Vars(r)["slug"])
if err == instances.ErrWissKINotFound { if err == instances.ErrWissKINotFound {
return cp, httpx.ErrNotFound return cp, httpx.ErrNotFound
} }

View file

@ -37,7 +37,7 @@ func (info *Info) Status(ctx context.Context, QuickInformation bool) (target sta
group.Go(func() error { group.Go(func() error {
// list all the instances // list all the instances
all, err := info.Instances.All(ctx) all, err := info.Dependencies.Instances.All(ctx)
if err != nil { if err != nil {
return err return err
} }
@ -63,7 +63,7 @@ func (info *Info) Status(ctx context.Context, QuickInformation bool) (target sta
flags := component.FetcherFlags{ flags := component.FetcherFlags{
Context: ctx, Context: ctx,
} }
for _, o := range info.Fetchers { for _, o := range info.Dependencies.Fetchers {
o := o o := o
group.Go(func() error { group.Go(func() error {
return o.Fetch(flags, &target) return o.Fetch(flags, &target)

View file

@ -16,13 +16,15 @@ import (
type Info struct { type Info struct {
component.Base component.Base
Dependencies struct {
Analytics *lazy.PoolAnalytics
Fetchers []component.DistilleryFetcher Fetchers []component.DistilleryFetcher
Exporter *exporter.Exporter Exporter *exporter.Exporter
Instances *instances.Instances Instances *instances.Instances
SnapshotsLog *logger.Logger SnapshotsLog *logger.Logger
}
Analytics *lazy.PoolAnalytics
} }
var ( var (
@ -33,7 +35,9 @@ var (
func (*Info) Routes() []string { return []string{"/dis/"} } func (*Info) Routes() []string { return []string{"/dis/"} }
func (info *Info) Handler(ctx context.Context, route string) (handler http.Handler, err error) { func (info *Info) Handler(ctx context.Context, route string) (handler http.Handler, err error) {
router := httprouter.New() router := httprouter.New()
{ {
socket := &httpx.WebSocket{ socket := &httpx.WebSocket{
Context: ctx, Context: ctx,
@ -47,41 +51,41 @@ func (info *Info) Handler(ctx context.Context, route string) (handler http.Handl
// handle everything // handle everything
router.HandlerFunc(http.MethodGet, route, func(w http.ResponseWriter, r *http.Request) { router.HandlerFunc(http.MethodGet, route, func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "index", http.StatusTemporaryRedirect) http.Redirect(w, r, route+"index", http.StatusTemporaryRedirect)
}) })
// add a handler for the index page // add a handler for the index page
router.Handler(http.MethodGet, "index", httpx.HTMLHandler[indexContext]{ router.Handler(http.MethodGet, route+"index", httpx.HTMLHandler[indexContext]{
Handler: info.index, Handler: info.index,
Template: indexTemplate, Template: indexTemplate,
}) })
// add a handler for the component page // add a handler for the component page
router.Handler(http.MethodGet, "components", httpx.HTMLHandler[componentContext]{ router.Handler(http.MethodGet, route+"components", httpx.HTMLHandler[componentContext]{
Handler: info.components, Handler: info.components,
Template: componentsTemplate, Template: componentsTemplate,
}) })
// add a handler for the component page // add a handler for the component page
router.Handler(http.MethodGet, "ingredients/{slug}", httpx.HTMLHandler[ingredientsContext]{ router.Handler(http.MethodGet, route+"ingredients/:slug", httpx.HTMLHandler[ingredientsContext]{
Handler: info.ingredients, Handler: info.ingredients,
Template: ingredientsTemplate, Template: ingredientsTemplate,
}) })
// add a handler for the instance page // add a handler for the instance page
router.Handler(http.MethodGet, "instance/{slug}", httpx.HTMLHandler[instanceContext]{ router.Handler(http.MethodGet, route+"instance/:slug", httpx.HTMLHandler[instanceContext]{
Handler: info.instance, Handler: info.instance,
Template: instanceTemplate, Template: instanceTemplate,
}) })
router.Handler(http.MethodPost, "api/login", httpx.RedirectHandler(func(r *http.Request) (string, int, error) { router.Handler(http.MethodPost, route+"api/login", httpx.RedirectHandler(func(r *http.Request) (string, int, error) {
// parse the form // parse the form
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
return "", 0, err return "", 0, err
} }
// get the instance // get the instance
instance, err := info.Instances.WissKI(r.Context(), r.PostFormValue("slug")) instance, err := info.Dependencies.Instances.WissKI(r.Context(), r.PostFormValue("slug"))
if err != nil { if err != nil {
return "", 0, httpx.ErrNotFound return "", 0, httpx.ErrNotFound
} }

View file

@ -29,7 +29,7 @@ type instanceContext struct {
func (info *Info) instance(r *http.Request) (is instanceContext, err error) { func (info *Info) instance(r *http.Request) (is instanceContext, err error) {
// find the instance itself! // find the instance itself!
instance, err := info.Instances.WissKI(r.Context(), mux.Vars(r)["slug"]) instance, err := info.Dependencies.Instances.WissKI(r.Context(), mux.Vars(r)["slug"])
if err == instances.ErrWissKINotFound { if err == instances.ErrWissKINotFound {
return is, httpx.ErrNotFound return is, httpx.ErrNotFound
} }

View file

@ -23,7 +23,7 @@ type InstanceAction struct {
var socketInstanceActions = map[string]InstanceAction{ var socketInstanceActions = map[string]InstanceAction{
"snapshot": { "snapshot": {
HandleInteractive: func(ctx context.Context, info *Info, instance *wisski.WissKI, out io.Writer, params ...string) error { HandleInteractive: func(ctx context.Context, info *Info, instance *wisski.WissKI, out io.Writer, params ...string) error {
return info.Exporter.MakeExport( return info.Dependencies.Exporter.MakeExport(
ctx, ctx,
out, out,
exporter.ExportTask{ exporter.ExportTask{
@ -78,7 +78,7 @@ func (info *Info) handleInstanceAction(conn httpx.WebSocketConnection, action In
} }
// resolve the instance // resolve the instance
instance, err := info.Instances.WissKI(conn.Context(), string(slug.Bytes)) instance, err := info.Dependencies.Instances.WissKI(conn.Context(), string(slug.Bytes))
if err != nil { if err != nil {
<-conn.WriteText("Instance not found") <-conn.WriteText("Instance not found")
return return

View file

@ -17,7 +17,7 @@ func (control *Control) Server(ctx context.Context, progress io.Writer) (*http.S
mux := http.NewServeMux() mux := http.NewServeMux()
// add all the servable routes! // add all the servable routes!
for _, s := range control.Servables { for _, s := range control.Dependencies.Servables {
for _, route := range s.Routes() { for _, route := range s.Routes() {
zerolog.Ctx(ctx).Info().Str("component", s.Name()).Str("route", route).Msg("mounting route") zerolog.Ctx(ctx).Info().Str("component", s.Name()).Str("route", route).Msg("mounting route")
handler, err := s.Handler(ctx, route) handler, err := s.Handler(ctx, route)

View file

@ -36,6 +36,17 @@
</td> </td>
</tr> </tr>
{{ end }} {{ end }}
{{ range $name, $comp := .DCFields }}
<tr>
<td>Component Pointer</td>
<td>
<code>Dependencies/{{ $name }}</code>
</td>
<td>
<code><a href="#{{ $comp }}">{{ $comp }}</a></code>
</td>
</tr>
{{ end }}
{{ range $name, $iface := .IFields }} {{ range $name, $iface := .IFields }}
<tr> <tr>
<td>Interface Slice</td> <td>Interface Slice</td>
@ -47,6 +58,17 @@
</td> </td>
</tr> </tr>
{{ end }} {{ end }}
{{ range $name, $iface := .DIFields }}
<tr>
<td>Interface Slice</td>
<td>
<code>Dependencies/{{ $name }}</code>
</td>
<td>
<code><a href="#{{ $iface }}">[]{{ $iface }}</a></code>
</td>
</tr>
{{ end }}
{{ range $name, $sig := $comp.Methods }} {{ range $name, $sig := $comp.Methods }}
<tr> <tr>
<td> <td>

View file

@ -75,7 +75,7 @@ func (backup *Backup) run(ctx context.Context, progress io.Writer, exporter *Exp
defer done() defer done()
// create a new status display // create a new status display
backups := exporter.Backupable backups := exporter.Dependencies.Backupable
backup.ComponentErrors = make(map[string]error, len(backups)) backup.ComponentErrors = make(map[string]error, len(backups))
// Component backup tasks // Component backup tasks
@ -125,7 +125,7 @@ func (backup *Backup) run(ctx context.Context, progress io.Writer, exporter *Exp
} }
// list all instances // list all instances
wissKIs, err := exporter.Instances.All(ctx) wissKIs, err := exporter.Dependencies.Instances.All(ctx)
if err != nil { if err != nil {
backup.InstanceListErr = err backup.InstanceListErr = err
return nil return nil

View file

@ -18,13 +18,14 @@ import (
// Exporter manages snapshots and backups // Exporter manages snapshots and backups
type Exporter struct { type Exporter struct {
component.Base component.Base
Dependencies struct {
SQL *sql.SQL SQL *sql.SQL
Instances *instances.Instances Instances *instances.Instances
ExporterLogger *logger.Logger ExporterLogger *logger.Logger
Snapshotable []component.Snapshotable Snapshotable []component.Snapshotable
Backupable []component.Backupable Backupable []component.Backupable
}
} }
// Path returns the path that contains all snapshot related data. // Path returns the path that contains all snapshot related data.

View file

@ -11,7 +11,9 @@ import (
type Pathbuilders struct { type Pathbuilders struct {
component.Base component.Base
Dependencies struct {
Instances *instances.Instances Instances *instances.Instances
}
} }
var ( var (
@ -24,7 +26,7 @@ func (Pathbuilders) SnapshotName() string { return "pathbuilders" }
func (pbs *Pathbuilders) Snapshot(wisski models.Instance, scontext component.StagingContext) error { func (pbs *Pathbuilders) Snapshot(wisski models.Instance, scontext component.StagingContext) error {
return scontext.AddDirectory(".", func(ctx context.Context) error { return scontext.AddDirectory(".", func(ctx context.Context) error {
builders, err := pbs.Instances.Instance(ctx, wisski).Pathbuilder().GetAll(ctx, nil) builders, err := pbs.Dependencies.Instances.Instance(ctx, wisski).Pathbuilder().GetAll(ctx, nil)
if err != nil { if err != nil {
return err return err
} }

View file

@ -131,7 +131,7 @@ func (exporter *Exporter) MakeExport(ctx context.Context, progress io.Writer, ta
// write out the log entry // write out the log entry
entry.Path = stagingDir entry.Path = stagingDir
entry.Packed = false entry.Packed = false
exporter.ExporterLogger.Add(ctx, entry) exporter.Dependencies.ExporterLogger.Add(ctx, entry)
logging.ProgressF(progress, ctx, "Wrote %s\n", stagingDir) logging.ProgressF(progress, ctx, "Wrote %s\n", stagingDir)
return nil return nil
@ -159,7 +159,7 @@ func (exporter *Exporter) MakeExport(ctx context.Context, progress io.Writer, ta
logging.LogMessage(progress, ctx, "Writing Log Entry") logging.LogMessage(progress, ctx, "Writing Log Entry")
entry.Path = archivePath entry.Path = archivePath
entry.Packed = true entry.Packed = true
exporter.ExporterLogger.Add(ctx, entry) exporter.Dependencies.ExporterLogger.Add(ctx, entry)
// and we're done! // and we're done!
return nil return nil

View file

@ -14,8 +14,9 @@ import (
// Logger is responsible for logging backups and snapshots // Logger is responsible for logging backups and snapshots
type Logger struct { type Logger struct {
component.Base component.Base
Dependencies struct {
SQL *sql.SQL SQL *sql.SQL
}
} }
// For retrieves (and prunes) the ExportLog. // For retrieves (and prunes) the ExportLog.
@ -35,7 +36,7 @@ func (log *Logger) For(ctx context.Context, slug string) (exports []models.Expor
// Log retrieves (and prunes) all entries in the snapshot log. // Log retrieves (and prunes) all entries in the snapshot log.
func (log *Logger) Log(ctx context.Context) ([]models.Export, error) { func (log *Logger) Log(ctx context.Context) ([]models.Export, error) {
// query the table! // query the table!
table, err := log.SQL.QueryTable(ctx, false, models.ExportTable) table, err := log.Dependencies.SQL.QueryTable(ctx, false, models.ExportTable)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -67,7 +68,7 @@ func (log *Logger) Log(ctx context.Context) ([]models.Export, error) {
// AddToExportLog adds the provided export to the log. // AddToExportLog adds the provided export to the log.
func (log *Logger) Add(ctx context.Context, export models.Export) error { func (log *Logger) Add(ctx context.Context, export models.Export) error {
// find the table // find the table
table, err := log.SQL.QueryTable(ctx, false, models.ExportTable) table, err := log.Dependencies.SQL.QueryTable(ctx, false, models.ExportTable)
if err != nil { if err != nil {
return err return err
} }

View file

@ -52,6 +52,6 @@ func (exporter *Exporter) PruneExports(ctx context.Context, progress io.Writer)
} }
// prune the snapshot log! // prune the snapshot log!
_, err = exporter.ExporterLogger.Log(ctx) _, err = exporter.Dependencies.ExporterLogger.Log(ctx)
return err return err
} }

View file

@ -107,7 +107,7 @@ func (snapshot *Snapshot) makeParts(ctx context.Context, progress io.Writer, sna
defer st.Stop() defer st.Stop()
// get all the components // get all the components
comps := collection.FilterClone(snapshots.Snapshotable, func(sc component.Snapshotable) bool { comps := collection.FilterClone(snapshots.Dependencies.Snapshotable, func(sc component.Snapshotable) bool {
return sc.SnapshotNeedsRunning() == needsRunning return sc.SnapshotNeedsRunning() == needsRunning
}) })

View file

@ -19,9 +19,10 @@ import (
// Instances manages multiple WissKI Instances. // Instances manages multiple WissKI Instances.
type Instances struct { type Instances struct {
component.Base component.Base
Dependencies struct {
Malt *malt.Malt Malt *malt.Malt
SQL *sql.SQL SQL *sql.SQL
}
} }
func (instances *Instances) Path() string { func (instances *Instances) Path() string {
@ -38,13 +39,13 @@ var errSQL = exit.Error{
// use uses the non-nil wisski instance with this instances // use uses the non-nil wisski instance with this instances
func (instances *Instances) use(wisski *wisski.WissKI) { func (instances *Instances) use(wisski *wisski.WissKI) {
wisski.Liquid.Malt = instances.Malt wisski.Liquid.Malt = instances.Dependencies.Malt
} }
// WissKI returns the WissKI with the provided slug, if it exists. // WissKI returns the WissKI with the provided slug, if it exists.
// It the WissKI does not exist, returns ErrWissKINotFound. // It the WissKI does not exist, returns ErrWissKINotFound.
func (instances *Instances) WissKI(ctx context.Context, slug string) (wissKI *wisski.WissKI, err error) { func (instances *Instances) WissKI(ctx context.Context, slug string) (wissKI *wisski.WissKI, err error) {
sql := instances.SQL sql := instances.Dependencies.SQL
if err := sql.WaitQueryTable(ctx); err != nil { if err := sql.WaitQueryTable(ctx); err != nil {
return nil, err return nil, err
} }
@ -84,7 +85,7 @@ func (instances *Instances) Instance(ctx context.Context, instance models.Instan
// Has checks if a WissKI with the provided slug exists inside the database. // Has checks if a WissKI with the provided slug exists inside the database.
// It does not perform any checks on the WissKI itself. // It does not perform any checks on the WissKI itself.
func (instances *Instances) Has(ctx context.Context, slug string) (ok bool, err error) { func (instances *Instances) Has(ctx context.Context, slug string) (ok bool, err error) {
sql := instances.SQL sql := instances.Dependencies.SQL
if err := sql.WaitQueryTable(ctx); err != nil { if err := sql.WaitQueryTable(ctx); err != nil {
return false, err return false, err
} }
@ -128,7 +129,7 @@ func (instances *Instances) Load(ctx context.Context, slugs ...string) ([]*wissk
// find finds instances based on the provided query // find finds instances based on the provided query
func (instances *Instances) find(ctx context.Context, order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) { func (instances *Instances) find(ctx context.Context, order bool, query func(table *gorm.DB) *gorm.DB) (results []*wisski.WissKI, err error) {
sql := instances.SQL sql := instances.Dependencies.SQL
if err := sql.WaitQueryTable(ctx); err != nil { if err := sql.WaitQueryTable(ctx); err != nil {
return nil, err return nil, err
} }

View file

@ -12,8 +12,8 @@ import (
type Malt struct { type Malt struct {
component.Base component.Base
TS *triplestore.Triplestore TS *triplestore.Triplestore `auto:"true"`
SQL *sql.SQL SQL *sql.SQL `auto:"true"`
Meta *meta.Meta Meta *meta.Meta `auto:"true"`
ExporterLog *logger.Logger ExporterLog *logger.Logger `auto:"true"`
} }

View file

@ -10,8 +10,9 @@ import (
// Component meta is responsible for managing metadata per WissKI Instance // Component meta is responsible for managing metadata per WissKI Instance
type Meta struct { type Meta struct {
component.Base component.Base
Dependencies struct {
SQL *sql.SQL SQL *sql.SQL
}
sl sync.Mutex sl sync.Mutex
sc map[string]*Storage sc map[string]*Storage
@ -40,7 +41,7 @@ func (meta *Meta) Storage(slug string) *Storage {
// create a new storage // create a new storage
meta.sc[slug] = &Storage{ meta.sc[slug] = &Storage{
Slug: slug, Slug: slug,
sql: meta.SQL, sql: meta.Dependencies.SQL,
} }
return meta.sc[slug] return meta.sc[slug]
} }

View file

@ -21,7 +21,7 @@ func (resolver *Resolver) Cron(ctx context.Context) error {
// AllPrefixes returns a list of all prefixes from the server. // AllPrefixes returns a list of all prefixes from the server.
// Prefixes may be cached on the server // Prefixes may be cached on the server
func (resolver *Resolver) AllPrefixes(ctx context.Context) (map[string]string, error) { func (resolver *Resolver) AllPrefixes(ctx context.Context) (map[string]string, error) {
instances, err := resolver.Instances.All(ctx) instances, err := resolver.Dependencies.Instances.All(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -17,8 +17,9 @@ import (
type Resolver struct { type Resolver struct {
component.Base component.Base
Dependencies struct {
Instances *instances.Instances Instances *instances.Instances
}
prefixes lazy.Lazy[map[string]string] // cached prefixes (from the server) prefixes lazy.Lazy[map[string]string] // cached prefixes (from the server)
RefreshInterval time.Duration RefreshInterval time.Duration

View file

@ -65,7 +65,7 @@ func (ssh2 *SSH2) handleAuth(ctx ssh.Context, key ssh.PublicKey) bool {
// grab permissions for each instance // grab permissions for each instance
{ {
instances, err := ssh2.Instances.All(ctx) instances, err := ssh2.Dependencies.Instances.All(ctx)
if err != nil { if err != nil {
return false return false
} }

View file

@ -11,7 +11,9 @@ import (
type SSH2 struct { type SSH2 struct {
component.Base component.Base
Dependencies struct {
Instances *instances.Instances Instances *instances.Instances
}
} }
var ( var (

View file

@ -11,9 +11,10 @@ import (
// Barrel provides access to the underlying Barrel // Barrel provides access to the underlying Barrel
type Barrel struct { type Barrel struct {
ingredient.Base ingredient.Base
Dependencies struct {
Locker *locker.Locker Locker *locker.Locker
MStore *mstore.MStore MStore *mstore.MStore
}
} }
func (barrel *Barrel) DataPath() string { func (barrel *Barrel) DataPath() string {

View file

@ -17,10 +17,10 @@ import (
// //
// It also logs the current time into the metadata belonging to this instance. // It also logs the current time into the metadata belonging to this instance.
func (barrel *Barrel) Build(ctx context.Context, progress io.Writer, start bool) error { func (barrel *Barrel) Build(ctx context.Context, progress io.Writer, start bool) error {
if !barrel.Locker.TryLock(ctx) { if !barrel.Dependencies.Locker.TryLock(ctx) {
return locker.Locked return locker.Locked
} }
defer barrel.Locker.Unlock(ctx) defer barrel.Dependencies.Locker.Unlock(ctx)
stack := barrel.Stack() stack := barrel.Stack()
@ -48,7 +48,7 @@ func (barrel *Barrel) Build(ctx context.Context, progress io.Writer, start bool)
var lastRebuild = mstore.For[int64]("lastRebuild") var lastRebuild = mstore.For[int64]("lastRebuild")
func (barrel Barrel) LastRebuild(ctx context.Context) (t time.Time, err error) { func (barrel Barrel) LastRebuild(ctx context.Context) (t time.Time, err error) {
epoch, err := lastRebuild.Get(ctx, barrel.MStore) epoch, err := lastRebuild.Get(ctx, barrel.Dependencies.MStore)
if err == meta.ErrMetadatumNotSet { if err == meta.ErrMetadatumNotSet {
return t, nil return t, nil
} }
@ -61,16 +61,17 @@ func (barrel Barrel) LastRebuild(ctx context.Context) (t time.Time, err error) {
} }
func (barrel *Barrel) setLastRebuild(ctx context.Context) error { func (barrel *Barrel) setLastRebuild(ctx context.Context) error {
return lastRebuild.Set(ctx, barrel.MStore, time.Now().Unix()) return lastRebuild.Set(ctx, barrel.Dependencies.MStore, time.Now().Unix())
} }
type LastRebuildFetcher struct { type LastRebuildFetcher struct {
ingredient.Base ingredient.Base
Dependencies struct {
Barrel *Barrel Barrel *Barrel
}
} }
func (lbr *LastRebuildFetcher) Fetch(ctx context.Context, flags ingredient.FetcherFlags, info *status.WissKI) (err error) { func (lbr *LastRebuildFetcher) Fetch(ctx context.Context, flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
info.LastRebuild, _ = lbr.Barrel.LastRebuild(ctx) info.LastRebuild, _ = lbr.Dependencies.Barrel.LastRebuild(ctx)
return return
} }

View file

@ -20,7 +20,7 @@ var errCronFailed = exit.Error{
} }
func (drush *Drush) Cron(ctx context.Context, progress io.Writer) error { func (drush *Drush) Cron(ctx context.Context, progress io.Writer) error {
code := drush.Barrel.Shell(ctx, stream.NonInteractive(progress), "/runtime/cron.sh")() code := drush.Dependencies.Barrel.Shell(ctx, stream.NonInteractive(progress), "/runtime/cron.sh")()
if code != 0 { if code != 0 {
// keep going, because we want to run as many crons as possible // keep going, because we want to run as many crons as possible
logging.ProgressF(progress, ctx, "%v", errCronFailed.WithMessageF(drush.Slug, code)) logging.ProgressF(progress, ctx, "%v", errCronFailed.WithMessageF(drush.Slug, code))
@ -31,7 +31,7 @@ func (drush *Drush) Cron(ctx context.Context, progress io.Writer) error {
func (drush *Drush) LastCron(ctx context.Context, server *phpx.Server) (t time.Time, err error) { func (drush *Drush) LastCron(ctx context.Context, server *phpx.Server) (t time.Time, err error) {
var timestamp int64 var timestamp int64
err = drush.PHP.EvalCode(ctx, server, &timestamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `) err = drush.Dependencies.PHP.EvalCode(ctx, server, &timestamp, `$val = \Drupal::state()->get('system.cron_last'); return $val; `)
if err != nil { if err != nil {
return return
} }
@ -40,8 +40,9 @@ func (drush *Drush) LastCron(ctx context.Context, server *phpx.Server) (t time.T
type LastCronFetcher struct { type LastCronFetcher struct {
ingredient.Base ingredient.Base
Dependencies struct {
Drush *Drush Drush *Drush
}
} }
var ( var (
@ -53,6 +54,6 @@ func (lbr *LastCronFetcher) Fetch(flags ingredient.FetcherFlags, info *status.Wi
return return
} }
info.LastRebuild, _ = lbr.Drush.LastCron(flags.Context, flags.Server) info.LastRebuild, _ = lbr.Dependencies.Drush.LastCron(flags.Context, flags.Server)
return return
} }

View file

@ -10,8 +10,9 @@ import (
// Drush implements commands related to drush // Drush implements commands related to drush
type Drush struct { type Drush struct {
ingredient.Base ingredient.Base
Dependencies struct {
Barrel *barrel.Barrel Barrel *barrel.Barrel
MStore *mstore.MStore MStore *mstore.MStore
PHP *php.PHP PHP *php.PHP
}
} }

View file

@ -20,7 +20,7 @@ var errBlindUpdateFailed = exit.Error{
// Update performs a blind drush update // Update performs a blind drush update
func (drush *Drush) Update(ctx context.Context, progress io.Writer) error { func (drush *Drush) Update(ctx context.Context, progress io.Writer) error {
code := drush.Barrel.Shell(ctx, stream.NonInteractive(progress), "/runtime/blind_update.sh")() code := drush.Dependencies.Barrel.Shell(ctx, stream.NonInteractive(progress), "/runtime/blind_update.sh")()
if code != 0 { if code != 0 {
return errBlindUpdateFailed.WithMessageF(drush.Slug, code) return errBlindUpdateFailed.WithMessageF(drush.Slug, code)
} }
@ -31,7 +31,7 @@ func (drush *Drush) Update(ctx context.Context, progress io.Writer) error {
const lastUpdate = mstore.For[int64]("lastUpdate") const lastUpdate = mstore.For[int64]("lastUpdate")
func (drush *Drush) LastUpdate(ctx context.Context) (t time.Time, err error) { func (drush *Drush) LastUpdate(ctx context.Context) (t time.Time, err error) {
epoch, err := lastUpdate.Get(ctx, drush.MStore) epoch, err := lastUpdate.Get(ctx, drush.Dependencies.MStore)
if err == meta.ErrMetadatumNotSet { if err == meta.ErrMetadatumNotSet {
return t, nil return t, nil
} }
@ -44,13 +44,14 @@ func (drush *Drush) LastUpdate(ctx context.Context) (t time.Time, err error) {
} }
func (drush *Drush) setLastUpdate(ctx context.Context) error { func (drush *Drush) setLastUpdate(ctx context.Context) error {
return lastUpdate.Set(ctx, drush.MStore, time.Now().Unix()) return lastUpdate.Set(ctx, drush.Dependencies.MStore, time.Now().Unix())
} }
type LastUpdateFetcher struct { type LastUpdateFetcher struct {
ingredient.Base ingredient.Base
Dependencies struct {
Drush *Drush Drush *Drush
}
} }
var ( var (
@ -58,6 +59,6 @@ var (
) )
func (lbr *LastUpdateFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { func (lbr *LastUpdateFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
info.LastUpdate, err = lbr.Drush.LastUpdate(flags.Context) info.LastUpdate, err = lbr.Dependencies.Drush.LastUpdate(flags.Context)
return return
} }

View file

@ -17,14 +17,16 @@ import (
// Instead, this should code directly defined in go. // Instead, this should code directly defined in go.
type Provisioner struct { type Provisioner struct {
ingredient.Base ingredient.Base
Dependencies struct {
Barrel *barrel.Barrel Barrel *barrel.Barrel
}
} }
// Provision provisions an instance, assuming that the required databases already exist. // Provision provisions an instance, assuming that the required databases already exist.
func (provision *Provisioner) Provision(ctx context.Context, progress io.Writer) error { func (provision *Provisioner) Provision(ctx context.Context, progress io.Writer) error {
// build the container // build the container
if err := provision.Barrel.Build(ctx, progress, false); err != nil { if err := provision.Dependencies.Barrel.Build(ctx, progress, false); err != nil {
return err return err
} }
@ -55,7 +57,7 @@ func (provision *Provisioner) Provision(ctx context.Context, progress io.Writer)
// TODO: Move the provision script into the control plane! // TODO: Move the provision script into the control plane!
provisionScript := "sudo PATH=$PATH -u www-data /bin/bash /provision_container.sh " + strings.Join(provisionParams, " ") provisionScript := "sudo PATH=$PATH -u www-data /bin/bash /provision_container.sh " + strings.Join(provisionParams, " ")
code, err := provision.Barrel.Stack().Run(ctx, stream.NonInteractive(progress), true, "barrel", "/bin/bash", "-c", provisionScript) code, err := provision.Dependencies.Barrel.Stack().Run(ctx, stream.NonInteractive(progress), true, "barrel", "/bin/bash", "-c", provisionScript)
if err != nil { if err != nil {
return err return err
} }

View file

@ -19,8 +19,9 @@ func (barrel *Barrel) Running(ctx context.Context, progress io.Writer) (bool, er
type RunningFetcher struct { type RunningFetcher struct {
ingredient.Base ingredient.Base
Dependencies struct {
Barrel *Barrel Barrel *Barrel
}
} }
var ( var (
@ -28,6 +29,6 @@ var (
) )
func (rf *RunningFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { func (rf *RunningFetcher) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {
info.Running, err = rf.Barrel.Running(flags.Context, io.Discard) info.Running, err = rf.Dependencies.Barrel.Running(flags.Context, io.Discard)
return return
} }

View file

@ -14,7 +14,9 @@ import (
type SSH struct { type SSH struct {
ingredient.Base ingredient.Base
Dependencies struct {
Barrel *barrel.Barrel Barrel *barrel.Barrel
}
} }
var ( var (
@ -22,7 +24,7 @@ var (
) )
func (ssh *SSH) Keys() ([]ssh.PublicKey, error) { func (ssh *SSH) Keys() ([]ssh.PublicKey, error) {
file, err := ssh.Environment.Open(ssh.Barrel.AuthorizedKeysPath()) file, err := ssh.Environment.Open(ssh.Dependencies.Barrel.AuthorizedKeysPath())
if environment.IsNotExist(err) { if environment.IsNotExist(err) {
return nil, nil return nil, nil
} }

View file

@ -13,9 +13,10 @@ import (
type Info struct { type Info struct {
ingredient.Base ingredient.Base
Dependencies struct {
PHP *php.PHP PHP *php.PHP
Fetchers []ingredient.WissKIFetcher Fetchers []ingredient.WissKIFetcher
}
Analytics *lazy.PoolAnalytics Analytics *lazy.PoolAnalytics
} }
@ -35,7 +36,7 @@ func (wisski *Info) Information(ctx context.Context, quick bool) (info status.Wi
// potentially setup a new server // potentially setup a new server
if !flags.Quick { if !flags.Quick {
flags.Server = wisski.PHP.NewServer() flags.Server = wisski.Dependencies.PHP.NewServer()
if err == nil { if err == nil {
defer flags.Server.Close() defer flags.Server.Close()
} }
@ -43,7 +44,7 @@ func (wisski *Info) Information(ctx context.Context, quick bool) (info status.Wi
// run all the fetchers! // run all the fetchers!
var group errgroup.Group var group errgroup.Group
for _, fetcher := range wisski.Fetchers { for _, fetcher := range wisski.Dependencies.Fetchers {
fetcher := fetcher fetcher := fetcher
group.Go(func() error { group.Go(func() error {
return fetcher.Fetch(flags, &info) return fetcher.Fetch(flags, &info)

View file

@ -7,8 +7,6 @@ import (
type SnapshotsFetcher struct { type SnapshotsFetcher struct {
ingredient.Base ingredient.Base
Info *Info
} }
var ( var (

View file

@ -13,8 +13,9 @@ import (
type Pathbuilder struct { type Pathbuilder struct {
ingredient.Base ingredient.Base
Dependencies struct {
PHP *php.PHP PHP *php.PHP
}
} }
var ( var (
@ -28,7 +29,7 @@ var pathbuilderPHP string
// //
// server is the server to fetch the pathbuilders from, any may be nil. // server is the server to fetch the pathbuilders from, any may be nil.
func (pathbuilder *Pathbuilder) All(ctx context.Context, server *phpx.Server) (ids []string, err error) { func (pathbuilder *Pathbuilder) All(ctx context.Context, server *phpx.Server) (ids []string, err error) {
err = pathbuilder.PHP.ExecScript(ctx, server, &ids, pathbuilderPHP, "all_list") err = pathbuilder.Dependencies.PHP.ExecScript(ctx, server, &ids, pathbuilderPHP, "all_list")
slices.Sort(ids) slices.Sort(ids)
return return
} }
@ -38,7 +39,7 @@ func (pathbuilder *Pathbuilder) All(ctx context.Context, server *phpx.Server) (i
// //
// server is the server to fetch the pathbuilders from, any may be nil. // server is the server to fetch the pathbuilders from, any may be nil.
func (pathbuilder *Pathbuilder) Get(ctx context.Context, server *phpx.Server, id string) (xml string, err error) { func (pathbuilder *Pathbuilder) Get(ctx context.Context, server *phpx.Server, id string) (xml string, err error) {
err = pathbuilder.PHP.ExecScript(ctx, server, &xml, pathbuilderPHP, "one_xml", id) err = pathbuilder.Dependencies.PHP.ExecScript(ctx, server, &xml, pathbuilderPHP, "one_xml", id)
return return
} }
@ -46,7 +47,7 @@ func (pathbuilder *Pathbuilder) Get(ctx context.Context, server *phpx.Server, id
// //
// server is the server to fetch the pathbuilders from, any may be nil. // server is the server to fetch the pathbuilders from, any may be nil.
func (pathbuilder *Pathbuilder) GetAll(ctx context.Context, server *phpx.Server) (pathbuilders map[string]string, err error) { func (pathbuilder *Pathbuilder) GetAll(ctx context.Context, server *phpx.Server) (pathbuilders map[string]string, err error) {
err = pathbuilder.PHP.ExecScript(ctx, server, &pathbuilders, pathbuilderPHP, "all_xml") err = pathbuilder.Dependencies.PHP.ExecScript(ctx, server, &pathbuilders, pathbuilderPHP, "all_xml")
return return
} }

View file

@ -20,9 +20,10 @@ import (
// Prefixes implements reading and writing prefix // Prefixes implements reading and writing prefix
type Prefixes struct { type Prefixes struct {
ingredient.Base ingredient.Base
Dependencies struct {
PHP *php.PHP PHP *php.PHP
MStore *mstore.MStore MStore *mstore.MStore
}
} }
var ( var (
@ -58,7 +59,7 @@ func (prefixes *Prefixes) All(ctx context.Context, server *phpx.Server) ([]strin
func (wisski *Prefixes) database(ctx context.Context, server *phpx.Server) (prefixes []string, err error) { func (wisski *Prefixes) database(ctx context.Context, server *phpx.Server) (prefixes []string, err error) {
// get all the ugly prefixes // get all the ugly prefixes
err = wisski.PHP.ExecScript(ctx, server, &prefixes, listURIPrefixesPHP, "list_prefixes") err = wisski.Dependencies.PHP.ExecScript(ctx, server, &prefixes, listURIPrefixesPHP, "list_prefixes")
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -149,7 +150,7 @@ var prefix = mstore.For[string]("prefix")
// Prefixes returns the cached prefixes from the given instance // Prefixes returns the cached prefixes from the given instance
func (wisski *Prefixes) AllCached(ctx context.Context) (results []string, err error) { func (wisski *Prefixes) AllCached(ctx context.Context) (results []string, err error) {
return prefix.GetAll(ctx, wisski.MStore) return prefix.GetAll(ctx, wisski.Dependencies.MStore)
} }
// Update updates the cached prefixes of this instance // Update updates the cached prefixes of this instance
@ -158,7 +159,7 @@ func (wisski *Prefixes) Update(ctx context.Context) error {
if err != nil { if err != nil {
return err return err
} }
return prefix.SetAll(ctx, wisski.MStore, prefixes...) return prefix.SetAll(ctx, wisski.Dependencies.MStore, prefixes...)
} }
func (prefixes *Prefixes) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) { func (prefixes *Prefixes) Fetch(flags ingredient.FetcherFlags, info *status.WissKI) (err error) {

View file

@ -11,18 +11,19 @@ import (
type Settings struct { type Settings struct {
ingredient.Base ingredient.Base
Dependencies struct {
PHP *php.PHP PHP *php.PHP
}
} }
//go:embed settings.php //go:embed settings.php
var settingsPHP string var settingsPHP string
func (settings *Settings) Get(ctx context.Context, server *phpx.Server, key string) (value any, err error) { func (settings *Settings) Get(ctx context.Context, server *phpx.Server, key string) (value any, err error) {
err = settings.PHP.ExecScript(ctx, server, &value, settingsPHP, "get_setting", key) err = settings.Dependencies.PHP.ExecScript(ctx, server, &value, settingsPHP, "get_setting", key)
return return
} }
func (settings *Settings) Set(ctx context.Context, server *phpx.Server, key string, value any) error { func (settings *Settings) Set(ctx context.Context, server *phpx.Server, key string, value any) error {
return settings.PHP.ExecScript(ctx, server, nil, settingsPHP, "set_setting", key, value) return settings.Dependencies.PHP.ExecScript(ctx, server, nil, settingsPHP, "set_setting", key, value)
} }

View file

@ -12,8 +12,9 @@ import (
type Stats struct { type Stats struct {
ingredient.Base ingredient.Base
Dependencies struct {
PHP *php.PHP PHP *php.PHP
}
} }
var ( var (
@ -25,7 +26,7 @@ var statsPHP string
// Get fetches all statistics from the server // Get fetches all statistics from the server
func (stats *Stats) Get(ctx context.Context, server *phpx.Server) (data status.Statistics, err error) { func (stats *Stats) Get(ctx context.Context, server *phpx.Server) (data status.Statistics, err error) {
err = stats.PHP.ExecScript(ctx, server, &data, statsPHP, "export_statistics") err = stats.Dependencies.PHP.ExecScript(ctx, server, &data, statsPHP, "export_statistics")
return return
} }

View file

@ -11,8 +11,9 @@ import (
type PHP struct { type PHP struct {
ingredient.Base ingredient.Base
Dependencies struct {
Barrel *barrel.Barrel Barrel *barrel.Barrel
}
} }
// ExecScript executes the PHP code as a script on the given server. // ExecScript executes the PHP code as a script on the given server.

View file

@ -21,6 +21,6 @@ func (php *PHP) NewServer() *phpx.Server {
} }
func (php *PHP) spawn(ctx context.Context, str stream.IOStream, code string) error { func (php *PHP) spawn(ctx context.Context, str stream.IOStream, code string) error {
php.Barrel.Shell(ctx, str, "-c", shellescape.QuoteCommand([]string{"drush", "php:eval", code}))() php.Dependencies.Barrel.Shell(ctx, str, "-c", shellescape.QuoteCommand([]string{"drush", "php:eval", code}))()
return nil return nil
} }

View file

@ -16,10 +16,10 @@ import (
var errGetValidator = errors.New("GetPasswordValidator: Unknown Error") var errGetValidator = errors.New("GetPasswordValidator: Unknown Error")
func (u *Users) GetPasswordValidator(ctx context.Context, username string) (pv PasswordValidator, err error) { func (u *Users) GetPasswordValidator(ctx context.Context, username string) (pv PasswordValidator, err error) {
server := u.PHP.NewServer() server := u.Dependencies.PHP.NewServer()
var hash string var hash string
err = u.PHP.ExecScript(ctx, server, &hash, usersPHP, "get_password_hash", username) err = u.Dependencies.PHP.ExecScript(ctx, server, &hash, usersPHP, "get_password_hash", username)
if err != nil { if err != nil {
server.Close() server.Close()
return pv, err return pv, err

View file

@ -14,8 +14,9 @@ import (
type Users struct { type Users struct {
ingredient.Base ingredient.Base
Dependencies struct {
PHP *php.PHP PHP *php.PHP
}
} }
var ( var (
@ -27,7 +28,7 @@ var usersPHP string
// All returns all known usernames // All returns all known usernames
func (u *Users) All(ctx context.Context, server *phpx.Server) (users []status.User, err error) { func (u *Users) All(ctx context.Context, server *phpx.Server) (users []status.User, err error) {
err = u.PHP.ExecScript(ctx, server, &users, usersPHP, "list_users") err = u.Dependencies.PHP.ExecScript(ctx, server, &users, usersPHP, "list_users")
return return
} }
@ -38,7 +39,7 @@ func (u *Users) Login(ctx context.Context, server *phpx.Server, username string)
// generate a (relative) link // generate a (relative) link
var path string var path string
err = u.PHP.ExecScript(ctx, server, &path, usersPHP, "get_login_link", username) err = u.Dependencies.PHP.ExecScript(ctx, server, &path, usersPHP, "get_login_link", username)
// if something went wrong, return // if something went wrong, return
if err != nil { if err != nil {
@ -64,7 +65,7 @@ var errSetPassword = errors.New("SetPassword: Unknown Error")
// SetPassword sets the password for a given user // SetPassword sets the password for a given user
func (u *Users) SetPassword(ctx context.Context, server *phpx.Server, username, password string) error { func (u *Users) SetPassword(ctx context.Context, server *phpx.Server, username, password string) error {
var ok bool var ok bool
err := u.PHP.ExecScript(ctx, server, &ok, usersPHP, "set_user_password", username, password) err := u.Dependencies.PHP.ExecScript(ctx, server, &ok, usersPHP, "set_user_password", username, password)
if err != nil { if err != nil {
return err return err
} }

View file

@ -105,6 +105,11 @@ func (di *delayedInit[Component]) Run(all []Component) {
// - A pointer to a struct type that implements component // - A pointer to a struct type that implements component
// - A slice type of an interface type that implements component // - A slice type of an interface type that implements component
// //
// Such component-like fields are only initialized if one of the following conditions are met:
//
// - The field has a tag 'auto' with the value `true`
// - The field lives inside a struct field named `Dependencies`
//
// These fields are initialized in an undefined order during initialization. // These fields are initialized in an undefined order during initialization.
// The init function may not rely on these existing. // The init function may not rely on these existing.
// Furthermore, the init function may not cause other components to be initialized. // Furthermore, the init function may not cause other components to be initialized.

View file

@ -19,6 +19,9 @@ type PoolAnalyticsComponent struct {
CFields map[string]string // fields with type C for which C implements component CFields map[string]string // fields with type C for which C implements component
IFields map[string]string // fields []I where I is an interface that implements component IFields map[string]string // fields []I where I is an interface that implements component
DCFields map[string]string // fields of the auto field with type C for which C implements component
DIFields map[string]string // fields of the auto field []I where I is an interface that implements component
Methods map[string]string // Method signatures of type Methods map[string]string // Method signatures of type
} }
type PoolAnalyticsGroup struct { type PoolAnalyticsGroup struct {
@ -61,11 +64,18 @@ func (context *PoolContext[Component]) anal(anal *PoolAnalytics, groups []reflec
anal.Components[meta.Name].CFields = collection.MapValues(meta.CFields, func(key string, tp reflect.Type) string { anal.Components[meta.Name].CFields = collection.MapValues(meta.CFields, func(key string, tp reflect.Type) string {
return nameOf(tp.Elem()) return nameOf(tp.Elem())
}) })
anal.Components[meta.Name].DCFields = collection.MapValues(meta.DCFields, func(key string, tp reflect.Type) string {
return nameOf(tp.Elem())
})
anal.Components[meta.Name].IFields = collection.MapValues(meta.IFields, func(key string, iface reflect.Type) string { anal.Components[meta.Name].IFields = collection.MapValues(meta.IFields, func(key string, iface reflect.Type) string {
ifaces = append(ifaces, iface) ifaces = append(ifaces, iface)
return nameOf(iface) return nameOf(iface)
}) })
anal.Components[meta.Name].DIFields = collection.MapValues(meta.DIFields, func(key string, iface reflect.Type) string {
ifaces = append(ifaces, iface)
return nameOf(iface)
})
} }
// and analyze all ifaces // and analyze all ifaces

View file

@ -32,11 +32,14 @@ type meta[Component any] struct {
CFields map[string]reflect.Type // fields with type C for which C implements component CFields map[string]reflect.Type // fields with type C for which C implements component
IFields map[string]reflect.Type // fields []I where I is an interface that implements component IFields map[string]reflect.Type // fields []I where I is an interface that implements component
DCFields map[string]reflect.Type // fields with type C for which C inside auto field which implement component
DIFields map[string]reflect.Type // fields []I where I is an interface inside auto field that implements component
} }
// init initializes this meta // init initializes this meta
func (m *meta[Component]) init(tp reflect.Type) { func (m *meta[Component]) init(tp reflect.Type) {
var componentType = reflectx.TypeOf[Component]() var component = reflectx.TypeOf[Component]()
if tp.Kind() != reflect.Pointer && tp.Elem().Kind() != reflect.Struct { if tp.Kind() != reflect.Pointer && tp.Elem().Kind() != reflect.Struct {
panic("GetMeta: Type (" + tp.String() + ") must be backed by a pointer to slice") panic("GetMeta: Type (" + tp.String() + ") must be backed by a pointer to slice")
@ -47,25 +50,59 @@ func (m *meta[Component]) init(tp reflect.Type) {
m.CFields = make(map[string]reflect.Type) m.CFields = make(map[string]reflect.Type)
m.IFields = make(map[string]reflect.Type) m.IFields = make(map[string]reflect.Type)
scanForFields(component, m.Name, m.Elem, false, m.CFields, m.IFields)
// fill the above variables, with a mapping of field name to struct // check if we have a dependencies field of struct type
count := m.Elem.NumField() dependenciesField, ok := m.Elem.FieldByName(dependencies)
if !ok {
return
}
if dependenciesField.Type.Kind() != reflect.Struct {
panic("GetMeta: " + dependencies + " field (" + m.Name + ") is not a struct")
}
// and initialize the type map of the given map
m.DCFields = make(map[string]reflect.Type)
m.DIFields = make(map[string]reflect.Type)
scanForFields(component, m.Name, dependenciesField.Type, true, m.DCFields, m.DIFields)
}
// scanForFields scans the structtype for fields of component-like fields.
// they are then writen to the cFields and iFields maps.
// inDependenciesStruct indicates if we are inside a dependency struct
func scanForFields(component reflect.Type, elem string, structType reflect.Type, inDependenciesStruct bool, cFields map[string]reflect.Type, iFields map[string]reflect.Type) {
count := structType.NumField()
for i := 0; i < count; i++ { for i := 0; i < count; i++ {
field := m.Elem.Field(i) field := structType.Field(i)
if !inDependenciesStruct && field.Tag.Get("auto") != "true" {
continue
}
if inDependenciesStruct && field.Tag != "" {
panic("GetMeta: " + dependencies + " field (" + elem + ") contains field (" + field.Name + ") with tag")
}
name := field.Name
tp := field.Type tp := field.Type
name := field.Name
switch { switch {
// field is a pointer to struct that implements a component case implementsComponent(component, tp):
case tp.Implements(componentType) && tp.Kind() == reflect.Pointer && tp.Elem().Kind() == reflect.Struct: cFields[name] = tp
m.CFields[name] = tp case implementsSlice(component, tp):
iFields[name] = tp.Elem()
case inDependenciesStruct:
panic("GetMeta: " + dependencies + " field (" + elem + ") contains non-auto fields")
}
}
}
// field is []I, where I is an interface that implements component func implementsComponent(component reflect.Type, tp reflect.Type) bool {
case tp.Kind() == reflect.Slice && tp.Elem().Kind() == reflect.Interface && tp.Elem().Implements(componentType): return tp.Implements(component) && tp.Kind() == reflect.Pointer && tp.Elem().Kind() == reflect.Struct
m.IFields[name] = tp.Elem() }
}
} func implementsSlice(component reflect.Type, tp reflect.Type) bool {
return tp.Kind() == reflect.Slice && tp.Elem().Kind() == reflect.Interface && tp.Elem().Implements(component)
} }
func nameOf(tp reflect.Type) string { func nameOf(tp reflect.Type) string {
@ -79,12 +116,16 @@ func (m meta[Component]) New() Component {
// NeedsInitComponent // NeedsInitComponent
func (m meta[Component]) NeedsInitComponent() bool { func (m meta[Component]) NeedsInitComponent() bool {
return len(m.CFields) > 0 || len(m.IFields) > 0 return len(m.CFields) > 0 || len(m.IFields) > 0 || len(m.DCFields) > 0 || len(m.DIFields) > 0
} }
// name of the dependencies field
const dependencies = "Dependencies"
// InitComponent sets up the fields of the given instance of a component. // InitComponent sets up the fields of the given instance of a component.
func (m meta[Component]) InitComponent(instance reflect.Value, all []Component) { func (m meta[Component]) InitComponent(instance reflect.Value, all []Component) {
elem := instance.Elem() elem := instance.Elem()
dependenciesElem := elem.FieldByName(dependencies)
// assign the component fields // assign the component fields
for field, eType := range m.CFields { for field, eType := range m.CFields {
@ -95,14 +136,28 @@ func (m meta[Component]) InitComponent(instance reflect.Value, all []Component)
field := elem.FieldByName(field) field := elem.FieldByName(field)
field.Set(reflect.ValueOf(c)) field.Set(reflect.ValueOf(c))
} }
for field, eType := range m.DCFields {
c := collection.First(all, func(c Component) bool {
return reflect.TypeOf(c).AssignableTo(eType)
})
// assign the multi subtypes field := dependenciesElem.FieldByName(field)
field.Set(reflect.ValueOf(c))
}
// assign the interface subtypes
registryR := reflect.ValueOf(all) registryR := reflect.ValueOf(all)
for field, eType := range m.IFields { for field, eType := range m.IFields {
cs := filterSubtype(registryR, eType) cs := filterSubtype(registryR, eType)
field := elem.FieldByName(field) field := elem.FieldByName(field)
field.Set(cs) field.Set(cs)
} }
for field, eType := range m.DIFields {
cs := filterSubtype(registryR, eType)
field := dependenciesElem.FieldByName(field)
field.Set(cs)
}
} }
// filterSubtype filters the slice of type []S into a slice of type []iface. // filterSubtype filters the slice of type []S into a slice of type []iface.