🏡 index : quine-global/quintodrome.git

package plugins

import (
	"regexp"
	"slices"

	"github.com/navidrome/navidrome/core/agents"
)

// Capability represents a plugin capability type.
// Capabilities are detected by checking which functions a plugin exports.
type Capability string

// capabilityFunctions maps each capability to its required/optional functions.
// A plugin has a capability if it exports at least one of these functions.
var capabilityFunctions = map[Capability][]string{}

// registerCapability registers a capability with its associated functions.
func registerCapability(cap Capability, functions ...string) {
	capabilityFunctions[cap] = functions
}

// functionExistsChecker is an interface for checking if a function exists in a plugin.
// This allows for testing without a real plugin instance.
type functionExistsChecker interface {
	FunctionExists(name string) bool
}

// detectCapabilities detects which capabilities a plugin has by checking
// which functions it exports.
func detectCapabilities(plugin functionExistsChecker) []Capability {
	var capabilities []Capability

	for cap, functions := range capabilityFunctions {
		if slices.ContainsFunc(functions, plugin.FunctionExists) {
			capabilities = append(capabilities, cap) // Found at least one function, plugin has this capability
		}
	}

	return capabilities
}

// hasCapability checks if the given capabilities slice contains a specific capability.
func hasCapability(capabilities []Capability, cap Capability) bool {
	return slices.Contains(capabilities, cap)
}

// retryLaterRe matches one capability's `<capability>(retry_later[:seconds])` token, which is
// all a plugin fault carries back across the WASM boundary. The capability is part of the
// pattern, so another capability's token in the same message cannot mask this one. The leading
// \b keeps a superstring like `useragent(retry_later)` from matching `agent`.
var (
	agentRetryLaterRe     = retryLaterRe("agent")
	scrobblerRetryLaterRe = retryLaterRe("scrobbler")
)

func retryLaterRe(capability string) *regexp.Regexp {
	return regexp.MustCompile(`\b` + capability + `\(retry_later(?::(\d+))?\)`)
}

// parseRetryLater reports whether msg carries the capability's retry_later token, with its delay.
func parseRetryLater(re *regexp.Regexp, msg string) (*agents.RetryLaterError, bool) {
	m := re.FindStringSubmatch(msg)
	if m == nil {
		return nil, false
	}
	return &agents.RetryLaterError{RetryIn: agents.ParseRetryIn(m[1])}, true
}