393 lines
10 KiB
Go
393 lines
10 KiB
Go
package appearances
|
|
|
|
import (
|
|
"fmt"
|
|
"sync"
|
|
)
|
|
|
|
// Manager provides high-level management of the appearance system
|
|
type Manager struct {
|
|
appearances *Appearances
|
|
database Database
|
|
logger Logger
|
|
mutex sync.RWMutex
|
|
|
|
// Statistics
|
|
totalLookups int64
|
|
successfulLookups int64
|
|
failedLookups int64
|
|
cacheHits int64
|
|
cacheMisses int64
|
|
}
|
|
|
|
// NewManager creates a new appearance manager
|
|
func NewManager(database Database, logger Logger) *Manager {
|
|
return &Manager{
|
|
appearances: NewAppearances(),
|
|
database: database,
|
|
logger: logger,
|
|
}
|
|
}
|
|
|
|
// Initialize loads appearances from database
|
|
func (m *Manager) Initialize() error {
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Initializing appearance manager...")
|
|
}
|
|
|
|
if m.database == nil {
|
|
if m.logger != nil {
|
|
m.logger.LogWarning("No database provided, starting with empty appearance list")
|
|
}
|
|
return nil
|
|
}
|
|
|
|
appearances, err := m.database.LoadAllAppearances()
|
|
if err != nil {
|
|
return fmt.Errorf("failed to load appearances from database: %w", err)
|
|
}
|
|
|
|
for _, appearance := range appearances {
|
|
if err := m.appearances.InsertAppearance(appearance); err != nil {
|
|
if m.logger != nil {
|
|
m.logger.LogError("Failed to insert appearance %d: %v", appearance.GetID(), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Loaded %d appearances from database", len(appearances))
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetAppearances returns the appearances collection
|
|
func (m *Manager) GetAppearances() *Appearances {
|
|
return m.appearances
|
|
}
|
|
|
|
// FindAppearanceByID finds an appearance by ID with statistics tracking
|
|
func (m *Manager) FindAppearanceByID(id int32) *Appearance {
|
|
m.mutex.Lock()
|
|
m.totalLookups++
|
|
m.mutex.Unlock()
|
|
|
|
appearance := m.appearances.FindAppearanceByID(id)
|
|
|
|
m.mutex.Lock()
|
|
if appearance != nil {
|
|
m.successfulLookups++
|
|
m.cacheHits++
|
|
} else {
|
|
m.failedLookups++
|
|
m.cacheMisses++
|
|
}
|
|
m.mutex.Unlock()
|
|
|
|
if m.logger != nil && appearance == nil {
|
|
m.logger.LogDebug("Appearance lookup failed for ID: %d", id)
|
|
}
|
|
|
|
return appearance
|
|
}
|
|
|
|
// AddAppearance adds a new appearance
|
|
func (m *Manager) AddAppearance(appearance *Appearance) error {
|
|
if appearance == nil {
|
|
return fmt.Errorf("appearance cannot be nil")
|
|
}
|
|
|
|
// Validate the appearance
|
|
if len(appearance.GetName()) == 0 {
|
|
return fmt.Errorf("appearance name cannot be empty")
|
|
}
|
|
|
|
if appearance.GetID() <= 0 {
|
|
return fmt.Errorf("appearance ID must be positive")
|
|
}
|
|
|
|
// Check if appearance already exists
|
|
if m.appearances.HasAppearance(appearance.GetID()) {
|
|
return fmt.Errorf("appearance with ID %d already exists", appearance.GetID())
|
|
}
|
|
|
|
// Add to collection
|
|
if err := m.appearances.InsertAppearance(appearance); err != nil {
|
|
return fmt.Errorf("failed to insert appearance: %w", err)
|
|
}
|
|
|
|
// Save to database if available
|
|
if m.database != nil {
|
|
if err := m.database.SaveAppearance(appearance); err != nil {
|
|
// Remove from collection if database save failed
|
|
m.appearances.RemoveAppearance(appearance.GetID())
|
|
return fmt.Errorf("failed to save appearance to database: %w", err)
|
|
}
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Added appearance %d: %s (min client: %d)",
|
|
appearance.GetID(), appearance.GetName(), appearance.GetMinClientVersion())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UpdateAppearance updates an existing appearance
|
|
func (m *Manager) UpdateAppearance(appearance *Appearance) error {
|
|
if appearance == nil {
|
|
return fmt.Errorf("appearance cannot be nil")
|
|
}
|
|
|
|
// Check if appearance exists
|
|
if !m.appearances.HasAppearance(appearance.GetID()) {
|
|
return fmt.Errorf("appearance with ID %d does not exist", appearance.GetID())
|
|
}
|
|
|
|
// Update in collection
|
|
if err := m.appearances.UpdateAppearance(appearance); err != nil {
|
|
return fmt.Errorf("failed to update appearance: %w", err)
|
|
}
|
|
|
|
// Save to database if available
|
|
if m.database != nil {
|
|
if err := m.database.SaveAppearance(appearance); err != nil {
|
|
return fmt.Errorf("failed to save appearance to database: %w", err)
|
|
}
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Updated appearance %d: %s", appearance.GetID(), appearance.GetName())
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// RemoveAppearance removes an appearance
|
|
func (m *Manager) RemoveAppearance(id int32) error {
|
|
// Check if appearance exists
|
|
if !m.appearances.HasAppearance(id) {
|
|
return fmt.Errorf("appearance with ID %d does not exist", id)
|
|
}
|
|
|
|
// Remove from database first if available
|
|
if m.database != nil {
|
|
if err := m.database.DeleteAppearance(id); err != nil {
|
|
return fmt.Errorf("failed to delete appearance from database: %w", err)
|
|
}
|
|
}
|
|
|
|
// Remove from collection
|
|
if !m.appearances.RemoveAppearance(id) {
|
|
return fmt.Errorf("failed to remove appearance from collection")
|
|
}
|
|
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Removed appearance %d", id)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// GetCompatibleAppearances returns appearances compatible with client version
|
|
func (m *Manager) GetCompatibleAppearances(clientVersion int16) []*Appearance {
|
|
return m.appearances.GetCompatibleAppearances(clientVersion)
|
|
}
|
|
|
|
// SearchAppearancesByName searches for appearances by name substring
|
|
func (m *Manager) SearchAppearancesByName(nameSubstring string) []*Appearance {
|
|
return m.appearances.FindAppearancesByName(nameSubstring)
|
|
}
|
|
|
|
// GetStatistics returns appearance system statistics
|
|
func (m *Manager) GetStatistics() map[string]interface{} {
|
|
m.mutex.RLock()
|
|
defer m.mutex.RUnlock()
|
|
|
|
// Get basic appearance statistics
|
|
stats := m.appearances.GetStatistics()
|
|
|
|
// Add manager statistics
|
|
stats["total_lookups"] = m.totalLookups
|
|
stats["successful_lookups"] = m.successfulLookups
|
|
stats["failed_lookups"] = m.failedLookups
|
|
stats["cache_hits"] = m.cacheHits
|
|
stats["cache_misses"] = m.cacheMisses
|
|
|
|
if m.totalLookups > 0 {
|
|
stats["success_rate"] = float64(m.successfulLookups) / float64(m.totalLookups) * 100
|
|
stats["cache_hit_rate"] = float64(m.cacheHits) / float64(m.totalLookups) * 100
|
|
}
|
|
|
|
return stats
|
|
}
|
|
|
|
// ResetStatistics resets all statistics
|
|
func (m *Manager) ResetStatistics() {
|
|
m.mutex.Lock()
|
|
defer m.mutex.Unlock()
|
|
|
|
m.totalLookups = 0
|
|
m.successfulLookups = 0
|
|
m.failedLookups = 0
|
|
m.cacheHits = 0
|
|
m.cacheMisses = 0
|
|
}
|
|
|
|
// ValidateAllAppearances validates all appearances in the system
|
|
func (m *Manager) ValidateAllAppearances() []string {
|
|
return m.appearances.ValidateAppearances()
|
|
}
|
|
|
|
// ReloadFromDatabase reloads all appearances from database
|
|
func (m *Manager) ReloadFromDatabase() error {
|
|
if m.database == nil {
|
|
return fmt.Errorf("no database available")
|
|
}
|
|
|
|
// Clear current appearances
|
|
m.appearances.ClearAppearances()
|
|
|
|
// Reload from database
|
|
return m.Initialize()
|
|
}
|
|
|
|
// GetAppearanceCount returns the total number of appearances
|
|
func (m *Manager) GetAppearanceCount() int {
|
|
return m.appearances.GetAppearanceCount()
|
|
}
|
|
|
|
// ProcessCommand handles appearance-related commands
|
|
func (m *Manager) ProcessCommand(command string, args []string) (string, error) {
|
|
switch command {
|
|
case "stats":
|
|
return m.handleStatsCommand(args)
|
|
case "validate":
|
|
return m.handleValidateCommand(args)
|
|
case "search":
|
|
return m.handleSearchCommand(args)
|
|
case "info":
|
|
return m.handleInfoCommand(args)
|
|
case "reload":
|
|
return m.handleReloadCommand(args)
|
|
default:
|
|
return "", fmt.Errorf("unknown appearance command: %s", command)
|
|
}
|
|
}
|
|
|
|
// handleStatsCommand shows appearance system statistics
|
|
func (m *Manager) handleStatsCommand(args []string) (string, error) {
|
|
stats := m.GetStatistics()
|
|
|
|
result := "Appearance System Statistics:\n"
|
|
result += fmt.Sprintf("Total Appearances: %d\n", stats["total_appearances"])
|
|
result += fmt.Sprintf("Total Lookups: %d\n", stats["total_lookups"])
|
|
result += fmt.Sprintf("Successful Lookups: %d\n", stats["successful_lookups"])
|
|
result += fmt.Sprintf("Failed Lookups: %d\n", stats["failed_lookups"])
|
|
|
|
if successRate, exists := stats["success_rate"]; exists {
|
|
result += fmt.Sprintf("Success Rate: %.1f%%\n", successRate)
|
|
}
|
|
|
|
if cacheHitRate, exists := stats["cache_hit_rate"]; exists {
|
|
result += fmt.Sprintf("Cache Hit Rate: %.1f%%\n", cacheHitRate)
|
|
}
|
|
|
|
if minID, exists := stats["min_id"]; exists {
|
|
result += fmt.Sprintf("ID Range: %d - %d\n", minID, stats["max_id"])
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleValidateCommand validates all appearances
|
|
func (m *Manager) handleValidateCommand(args []string) (string, error) {
|
|
issues := m.ValidateAllAppearances()
|
|
|
|
if len(issues) == 0 {
|
|
return "All appearances are valid.", nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Found %d issues with appearances:\n", len(issues))
|
|
for i, issue := range issues {
|
|
if i >= 10 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf("%d. %s\n", i+1, issue)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleSearchCommand searches for appearances by name
|
|
func (m *Manager) handleSearchCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("search term required")
|
|
}
|
|
|
|
searchTerm := args[0]
|
|
results := m.SearchAppearancesByName(searchTerm)
|
|
|
|
if len(results) == 0 {
|
|
return fmt.Sprintf("No appearances found matching '%s'.", searchTerm), nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Found %d appearances matching '%s':\n", len(results), searchTerm)
|
|
for i, appearance := range results {
|
|
if i >= 20 { // Limit output
|
|
result += "... (and more)\n"
|
|
break
|
|
}
|
|
result += fmt.Sprintf(" %d: %s (min client: %d)\n",
|
|
appearance.GetID(), appearance.GetName(), appearance.GetMinClientVersion())
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleInfoCommand shows information about a specific appearance
|
|
func (m *Manager) handleInfoCommand(args []string) (string, error) {
|
|
if len(args) == 0 {
|
|
return "", fmt.Errorf("appearance ID required")
|
|
}
|
|
|
|
var appearanceID int32
|
|
if _, err := fmt.Sscanf(args[0], "%d", &appearanceID); err != nil {
|
|
return "", fmt.Errorf("invalid appearance ID: %s", args[0])
|
|
}
|
|
|
|
appearance := m.FindAppearanceByID(appearanceID)
|
|
if appearance == nil {
|
|
return fmt.Sprintf("Appearance %d not found.", appearanceID), nil
|
|
}
|
|
|
|
result := fmt.Sprintf("Appearance Information:\n")
|
|
result += fmt.Sprintf("ID: %d\n", appearance.GetID())
|
|
result += fmt.Sprintf("Name: %s\n", appearance.GetName())
|
|
result += fmt.Sprintf("Min Client Version: %d\n", appearance.GetMinClientVersion())
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// handleReloadCommand reloads appearances from database
|
|
func (m *Manager) handleReloadCommand(args []string) (string, error) {
|
|
if err := m.ReloadFromDatabase(); err != nil {
|
|
return "", fmt.Errorf("failed to reload appearances: %w", err)
|
|
}
|
|
|
|
count := m.GetAppearanceCount()
|
|
return fmt.Sprintf("Successfully reloaded %d appearances from database.", count), nil
|
|
}
|
|
|
|
// Shutdown gracefully shuts down the manager
|
|
func (m *Manager) Shutdown() {
|
|
if m.logger != nil {
|
|
m.logger.LogInfo("Shutting down appearance manager...")
|
|
}
|
|
|
|
// Clear appearances
|
|
m.appearances.ClearAppearances()
|
|
}
|