118 lines
3 KiB
Go
118 lines
3 KiB
Go
|
|
package plugin
|
||
|
|
|
||
|
|
import (
|
||
|
|
"encoding/json"
|
||
|
|
"path/filepath"
|
||
|
|
"sort"
|
||
|
|
|
||
|
|
core "github.com/host-uk/core/pkg/framework/core"
|
||
|
|
"github.com/host-uk/core/pkg/io"
|
||
|
|
)
|
||
|
|
|
||
|
|
const registryFilename = "registry.json"
|
||
|
|
|
||
|
|
// Registry manages installed plugins.
|
||
|
|
// Plugin metadata is stored in a registry.json file under the base path.
|
||
|
|
type Registry struct {
|
||
|
|
medium io.Medium
|
||
|
|
basePath string // e.g., ~/.core/plugins/
|
||
|
|
plugins map[string]*PluginConfig
|
||
|
|
}
|
||
|
|
|
||
|
|
// NewRegistry creates a new plugin registry.
|
||
|
|
func NewRegistry(m io.Medium, basePath string) *Registry {
|
||
|
|
return &Registry{
|
||
|
|
medium: m,
|
||
|
|
basePath: basePath,
|
||
|
|
plugins: make(map[string]*PluginConfig),
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// List returns all installed plugins sorted by name.
|
||
|
|
func (r *Registry) List() []*PluginConfig {
|
||
|
|
result := make([]*PluginConfig, 0, len(r.plugins))
|
||
|
|
for _, cfg := range r.plugins {
|
||
|
|
result = append(result, cfg)
|
||
|
|
}
|
||
|
|
sort.Slice(result, func(i, j int) bool {
|
||
|
|
return result[i].Name < result[j].Name
|
||
|
|
})
|
||
|
|
return result
|
||
|
|
}
|
||
|
|
|
||
|
|
// Get returns a plugin by name.
|
||
|
|
// The second return value indicates whether the plugin was found.
|
||
|
|
func (r *Registry) Get(name string) (*PluginConfig, bool) {
|
||
|
|
cfg, ok := r.plugins[name]
|
||
|
|
return cfg, ok
|
||
|
|
}
|
||
|
|
|
||
|
|
// Add registers a plugin in the registry.
|
||
|
|
func (r *Registry) Add(cfg *PluginConfig) error {
|
||
|
|
if cfg.Name == "" {
|
||
|
|
return core.E("plugin.Registry.Add", "plugin name is required", nil)
|
||
|
|
}
|
||
|
|
r.plugins[cfg.Name] = cfg
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Remove unregisters a plugin from the registry.
|
||
|
|
func (r *Registry) Remove(name string) error {
|
||
|
|
if _, ok := r.plugins[name]; !ok {
|
||
|
|
return core.E("plugin.Registry.Remove", "plugin not found: "+name, nil)
|
||
|
|
}
|
||
|
|
delete(r.plugins, name)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// registryPath returns the full path to the registry file.
|
||
|
|
func (r *Registry) registryPath() string {
|
||
|
|
return filepath.Join(r.basePath, registryFilename)
|
||
|
|
}
|
||
|
|
|
||
|
|
// Load reads the plugin registry from disk.
|
||
|
|
// If the registry file does not exist, the registry starts empty.
|
||
|
|
func (r *Registry) Load() error {
|
||
|
|
path := r.registryPath()
|
||
|
|
|
||
|
|
if !r.medium.IsFile(path) {
|
||
|
|
// No registry file yet; start with empty registry
|
||
|
|
r.plugins = make(map[string]*PluginConfig)
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
content, err := r.medium.Read(path)
|
||
|
|
if err != nil {
|
||
|
|
return core.E("plugin.Registry.Load", "failed to read registry", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
var plugins map[string]*PluginConfig
|
||
|
|
if err := json.Unmarshal([]byte(content), &plugins); err != nil {
|
||
|
|
return core.E("plugin.Registry.Load", "failed to parse registry", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if plugins == nil {
|
||
|
|
plugins = make(map[string]*PluginConfig)
|
||
|
|
}
|
||
|
|
r.plugins = plugins
|
||
|
|
return nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// Save writes the plugin registry to disk.
|
||
|
|
func (r *Registry) Save() error {
|
||
|
|
if err := r.medium.EnsureDir(r.basePath); err != nil {
|
||
|
|
return core.E("plugin.Registry.Save", "failed to create plugin directory", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
data, err := json.MarshalIndent(r.plugins, "", " ")
|
||
|
|
if err != nil {
|
||
|
|
return core.E("plugin.Registry.Save", "failed to marshal registry", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
if err := r.medium.Write(r.registryPath(), string(data)); err != nil {
|
||
|
|
return core.E("plugin.Registry.Save", "failed to write registry", err)
|
||
|
|
}
|
||
|
|
|
||
|
|
return nil
|
||
|
|
}
|