Files
silo-server/internal/collections/templates/registry.go
T

184 lines
5.2 KiB
Go

package templates
import "sync"
// Registry holds the curated catalog of collection templates. Built-in entries
// are registered via init(); custom installations could add their own through
// Register().
type Registry struct {
mu sync.RWMutex
// templates preserves insertion order for stable presentation.
templates []Template
// byID enables O(1) lookups for template instantiation.
byID map[string]int
// bundles preserves insertion order for stable presentation.
bundles []Bundle
// byBundleID enables O(1) bundle lookup.
byBundleID map[string]int
// categoryOrder is the display order for category groups in Catalog().
categoryOrder []Category
}
// NewRegistry returns an empty registry with the default category ordering.
func NewRegistry() *Registry {
return &Registry{
byID: make(map[string]int),
byBundleID: make(map[string]int),
categoryOrder: []Category{
CategoryTrending,
CategoryPopular,
CategoryStreaming,
CategoryTopRated,
CategoryInTheaters,
CategoryUpcoming,
CategoryAiring,
CategoryEditorial,
CategoryCustom,
},
}
}
// Register adds a template. Panics on duplicate ID — duplicates are a
// programming error, not a runtime concern.
func (r *Registry) Register(t Template) {
if err := validate(t); err != nil {
panic("templates: invalid template " + t.ID + ": " + err.Error())
}
r.mu.Lock()
defer r.mu.Unlock()
if _, exists := r.byID[t.ID]; exists {
panic("templates: duplicate template ID " + t.ID)
}
r.byID[t.ID] = len(r.templates)
r.templates = append(r.templates, t)
}
// RegisterBundle adds a template bundle. Panics on duplicate IDs or invalid
// references because bundles are curated startup data.
func (r *Registry) RegisterBundle(b Bundle) {
r.mu.Lock()
defer r.mu.Unlock()
if err := r.validateBundleLocked(b); err != nil {
panic("templates: invalid bundle " + b.ID + ": " + err.Error())
}
if _, exists := r.byBundleID[b.ID]; exists {
panic("templates: duplicate bundle ID " + b.ID)
}
r.byBundleID[b.ID] = len(r.bundles)
r.bundles = append(r.bundles, b)
}
// Get returns the template with the given ID.
func (r *Registry) Get(id string) (Template, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
idx, ok := r.byID[id]
if !ok {
return Template{}, false
}
return r.templates[idx], true
}
// GetBundle returns the bundle with the given ID.
func (r *Registry) GetBundle(id string) (Bundle, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
idx, ok := r.byBundleID[id]
if !ok {
return Bundle{}, false
}
return r.bundles[idx], true
}
// List returns every registered template in registration order.
func (r *Registry) List() []Template {
r.mu.RLock()
defer r.mu.RUnlock()
out := make([]Template, len(r.templates))
copy(out, r.templates)
return out
}
// ListBundles returns every registered bundle in registration order.
func (r *Registry) ListBundles() []Bundle {
r.mu.RLock()
defer r.mu.RUnlock()
out := make([]Bundle, len(r.bundles))
copy(out, r.bundles)
return out
}
// BundleCatalog returns all registered bundles in stable display order.
func (r *Registry) BundleCatalog() BundleCatalog {
return BundleCatalog{Bundles: r.ListBundles()}
}
// Catalog returns templates grouped by category, in display order. Empty
// categories are omitted. Categories not in the configured order appear last
// in the order they were first registered.
func (r *Registry) Catalog() Catalog {
r.mu.RLock()
defer r.mu.RUnlock()
groups := make(map[Category][]Template)
seenCategories := make([]Category, 0)
for _, t := range r.templates {
if _, ok := groups[t.Category]; !ok {
seenCategories = append(seenCategories, t.Category)
}
groups[t.Category] = append(groups[t.Category], t)
}
// Build the final ordering: configured order first, then any extras.
ordered := make([]Category, 0, len(groups))
included := make(map[Category]bool)
for _, c := range r.categoryOrder {
if _, ok := groups[c]; ok {
ordered = append(ordered, c)
included[c] = true
}
}
for _, c := range seenCategories {
if !included[c] {
ordered = append(ordered, c)
}
}
out := Catalog{Categories: make([]CategoryGroup, 0, len(ordered))}
for _, c := range ordered {
out.Categories = append(out.Categories, CategoryGroup{
Category: c,
Label: CategoryLabel(c),
Templates: groups[c],
})
}
return out
}
// Default is the package-level registry seeded with built-in templates.
var Default = NewRegistry()
// Register is shorthand for Default.Register.
func Register(t Template) { Default.Register(t) }
// RegisterBundle is shorthand for Default.RegisterBundle.
func RegisterBundle(b Bundle) { Default.RegisterBundle(b) }
// Get is shorthand for Default.Get.
func Get(id string) (Template, bool) { return Default.Get(id) }
// GetBundle is shorthand for Default.GetBundle.
func GetBundle(id string) (Bundle, bool) { return Default.GetBundle(id) }
// List is shorthand for Default.List.
func List() []Template { return Default.List() }
// ListBundles is shorthand for Default.ListBundles.
func ListBundles() []Bundle { return Default.ListBundles() }
// CatalogDefault is shorthand for Default.Catalog.
func CatalogDefault() Catalog { return Default.Catalog() }
// BundleCatalogDefault is shorthand for Default.BundleCatalog.
func BundleCatalogDefault() BundleCatalog { return Default.BundleCatalog() }