refactor: Add MinerFactory to centralize miner instantiation
- Create pkg/mining/miner_factory.go with factory pattern - Support for miner type aliases (e.g., "ttminer" -> "tt-miner") - Add global convenience functions: CreateMiner, IsMinerSupported, etc. - Replace 5 duplicate switch statements in manager.go and service.go - Makes adding new miner types simpler (single registration point) - Full test coverage in miner_factory_test.go 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
2a30744a08
commit
50cde8bdf3
4 changed files with 298 additions and 39 deletions
|
|
@ -239,14 +239,9 @@ func (m *Manager) StartMiner(ctx context.Context, minerType string, config *Conf
|
|||
config = &Config{}
|
||||
}
|
||||
|
||||
var miner Miner
|
||||
switch strings.ToLower(minerType) {
|
||||
case "xmrig":
|
||||
miner = NewXMRigMiner()
|
||||
case "tt-miner", "ttminer":
|
||||
miner = NewTTMiner()
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported miner type: %s", minerType)
|
||||
miner, err := CreateMiner(minerType)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
instanceName := miner.GetName()
|
||||
|
|
@ -358,14 +353,9 @@ func (m *Manager) UninstallMiner(ctx context.Context, minerType string) error {
|
|||
}
|
||||
}
|
||||
|
||||
var miner Miner
|
||||
switch strings.ToLower(minerType) {
|
||||
case "xmrig":
|
||||
miner = NewXMRigMiner()
|
||||
case "tt-miner", "ttminer":
|
||||
miner = NewTTMiner()
|
||||
default:
|
||||
return fmt.Errorf("unsupported miner type: %s", minerType)
|
||||
miner, err := CreateMiner(minerType)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := miner.Uninstall(); err != nil {
|
||||
|
|
|
|||
129
pkg/mining/miner_factory.go
Normal file
129
pkg/mining/miner_factory.go
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
package mining
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// MinerConstructor is a function that creates a new miner instance
|
||||
type MinerConstructor func() Miner
|
||||
|
||||
// MinerFactory handles miner instantiation and registration
|
||||
type MinerFactory struct {
|
||||
mu sync.RWMutex
|
||||
constructors map[string]MinerConstructor
|
||||
aliases map[string]string // maps aliases to canonical names
|
||||
}
|
||||
|
||||
// globalFactory is the default factory instance
|
||||
var globalFactory = NewMinerFactory()
|
||||
|
||||
// NewMinerFactory creates a new MinerFactory with default miners registered
|
||||
func NewMinerFactory() *MinerFactory {
|
||||
f := &MinerFactory{
|
||||
constructors: make(map[string]MinerConstructor),
|
||||
aliases: make(map[string]string),
|
||||
}
|
||||
f.registerDefaults()
|
||||
return f
|
||||
}
|
||||
|
||||
// registerDefaults registers all built-in miners
|
||||
func (f *MinerFactory) registerDefaults() {
|
||||
// XMRig miner (CPU/GPU RandomX, Cryptonight, etc.)
|
||||
f.Register("xmrig", func() Miner { return NewXMRigMiner() })
|
||||
|
||||
// TT-Miner (GPU Kawpow, etc.)
|
||||
f.Register("tt-miner", func() Miner { return NewTTMiner() })
|
||||
f.RegisterAlias("ttminer", "tt-miner")
|
||||
}
|
||||
|
||||
// Register adds a miner constructor to the factory
|
||||
func (f *MinerFactory) Register(name string, constructor MinerConstructor) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.constructors[strings.ToLower(name)] = constructor
|
||||
}
|
||||
|
||||
// RegisterAlias adds an alias for an existing miner type
|
||||
func (f *MinerFactory) RegisterAlias(alias, canonicalName string) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
f.aliases[strings.ToLower(alias)] = strings.ToLower(canonicalName)
|
||||
}
|
||||
|
||||
// Create instantiates a miner of the specified type
|
||||
func (f *MinerFactory) Create(minerType string) (Miner, error) {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
|
||||
name := strings.ToLower(minerType)
|
||||
|
||||
// Check for alias first
|
||||
if canonical, ok := f.aliases[name]; ok {
|
||||
name = canonical
|
||||
}
|
||||
|
||||
constructor, ok := f.constructors[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported miner type: %s", minerType)
|
||||
}
|
||||
|
||||
return constructor(), nil
|
||||
}
|
||||
|
||||
// IsSupported checks if a miner type is registered
|
||||
func (f *MinerFactory) IsSupported(minerType string) bool {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
|
||||
name := strings.ToLower(minerType)
|
||||
|
||||
// Check alias
|
||||
if canonical, ok := f.aliases[name]; ok {
|
||||
name = canonical
|
||||
}
|
||||
|
||||
_, ok := f.constructors[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// ListTypes returns all registered miner type names (excluding aliases)
|
||||
func (f *MinerFactory) ListTypes() []string {
|
||||
f.mu.RLock()
|
||||
defer f.mu.RUnlock()
|
||||
|
||||
types := make([]string, 0, len(f.constructors))
|
||||
for name := range f.constructors {
|
||||
types = append(types, name)
|
||||
}
|
||||
return types
|
||||
}
|
||||
|
||||
// --- Global factory functions for convenience ---
|
||||
|
||||
// CreateMiner creates a miner using the global factory
|
||||
func CreateMiner(minerType string) (Miner, error) {
|
||||
return globalFactory.Create(minerType)
|
||||
}
|
||||
|
||||
// IsMinerSupported checks if a miner type is supported using the global factory
|
||||
func IsMinerSupported(minerType string) bool {
|
||||
return globalFactory.IsSupported(minerType)
|
||||
}
|
||||
|
||||
// ListMinerTypes returns all registered miner types from the global factory
|
||||
func ListMinerTypes() []string {
|
||||
return globalFactory.ListTypes()
|
||||
}
|
||||
|
||||
// RegisterMinerType adds a miner constructor to the global factory
|
||||
func RegisterMinerType(name string, constructor MinerConstructor) {
|
||||
globalFactory.Register(name, constructor)
|
||||
}
|
||||
|
||||
// RegisterMinerAlias adds an alias to the global factory
|
||||
func RegisterMinerAlias(alias, canonicalName string) {
|
||||
globalFactory.RegisterAlias(alias, canonicalName)
|
||||
}
|
||||
155
pkg/mining/miner_factory_test.go
Normal file
155
pkg/mining/miner_factory_test.go
Normal file
|
|
@ -0,0 +1,155 @@
|
|||
package mining
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMinerFactory_Create(t *testing.T) {
|
||||
factory := NewMinerFactory()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
minerType string
|
||||
wantErr bool
|
||||
}{
|
||||
{"xmrig lowercase", "xmrig", false},
|
||||
{"xmrig uppercase", "XMRIG", false},
|
||||
{"xmrig mixed case", "XmRig", false},
|
||||
{"tt-miner", "tt-miner", false},
|
||||
{"ttminer alias", "ttminer", false},
|
||||
{"unknown type", "unknown", true},
|
||||
{"empty type", "", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
miner, err := factory.Create(tt.minerType)
|
||||
if tt.wantErr {
|
||||
if err == nil {
|
||||
t.Errorf("Create(%q) expected error, got nil", tt.minerType)
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("Create(%q) unexpected error: %v", tt.minerType, err)
|
||||
}
|
||||
if miner == nil {
|
||||
t.Errorf("Create(%q) returned nil miner", tt.minerType)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinerFactory_IsSupported(t *testing.T) {
|
||||
factory := NewMinerFactory()
|
||||
|
||||
tests := []struct {
|
||||
minerType string
|
||||
want bool
|
||||
}{
|
||||
{"xmrig", true},
|
||||
{"tt-miner", true},
|
||||
{"ttminer", true}, // alias
|
||||
{"unknown", false},
|
||||
{"", false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.minerType, func(t *testing.T) {
|
||||
if got := factory.IsSupported(tt.minerType); got != tt.want {
|
||||
t.Errorf("IsSupported(%q) = %v, want %v", tt.minerType, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinerFactory_ListTypes(t *testing.T) {
|
||||
factory := NewMinerFactory()
|
||||
|
||||
types := factory.ListTypes()
|
||||
if len(types) < 2 {
|
||||
t.Errorf("ListTypes() returned %d types, expected at least 2", len(types))
|
||||
}
|
||||
|
||||
// Check that expected types are present
|
||||
typeMap := make(map[string]bool)
|
||||
for _, typ := range types {
|
||||
typeMap[typ] = true
|
||||
}
|
||||
|
||||
expectedTypes := []string{"xmrig", "tt-miner"}
|
||||
for _, expected := range expectedTypes {
|
||||
if !typeMap[expected] {
|
||||
t.Errorf("ListTypes() missing expected type %q", expected)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinerFactory_Register(t *testing.T) {
|
||||
factory := NewMinerFactory()
|
||||
|
||||
// Register a custom miner type
|
||||
called := false
|
||||
factory.Register("custom-miner", func() Miner {
|
||||
called = true
|
||||
return NewXMRigMiner() // Return something valid for testing
|
||||
})
|
||||
|
||||
if !factory.IsSupported("custom-miner") {
|
||||
t.Error("custom-miner should be supported after registration")
|
||||
}
|
||||
|
||||
_, err := factory.Create("custom-miner")
|
||||
if err != nil {
|
||||
t.Errorf("Create custom-miner failed: %v", err)
|
||||
}
|
||||
if !called {
|
||||
t.Error("custom constructor was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestMinerFactory_RegisterAlias(t *testing.T) {
|
||||
factory := NewMinerFactory()
|
||||
|
||||
// Register an alias for xmrig
|
||||
factory.RegisterAlias("x", "xmrig")
|
||||
|
||||
if !factory.IsSupported("x") {
|
||||
t.Error("alias 'x' should be supported")
|
||||
}
|
||||
|
||||
miner, err := factory.Create("x")
|
||||
if err != nil {
|
||||
t.Errorf("Create with alias failed: %v", err)
|
||||
}
|
||||
if miner == nil {
|
||||
t.Error("Create with alias returned nil miner")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobalFactory_CreateMiner(t *testing.T) {
|
||||
// Test global convenience functions
|
||||
miner, err := CreateMiner("xmrig")
|
||||
if err != nil {
|
||||
t.Errorf("CreateMiner failed: %v", err)
|
||||
}
|
||||
if miner == nil {
|
||||
t.Error("CreateMiner returned nil")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobalFactory_IsMinerSupported(t *testing.T) {
|
||||
if !IsMinerSupported("xmrig") {
|
||||
t.Error("xmrig should be supported")
|
||||
}
|
||||
if IsMinerSupported("nosuchminer") {
|
||||
t.Error("nosuchminer should not be supported")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGlobalFactory_ListMinerTypes(t *testing.T) {
|
||||
types := ListMinerTypes()
|
||||
if len(types) < 2 {
|
||||
t.Errorf("ListMinerTypes() returned %d types, expected at least 2", len(types))
|
||||
}
|
||||
}
|
||||
|
|
@ -420,14 +420,9 @@ func (s *Service) updateInstallationCache() (*SystemInfo, error) {
|
|||
}
|
||||
|
||||
for _, availableMiner := range s.Manager.ListAvailableMiners() {
|
||||
var miner Miner
|
||||
switch availableMiner.Name {
|
||||
case "xmrig":
|
||||
miner = NewXMRigMiner()
|
||||
case "tt-miner":
|
||||
miner = NewTTMiner()
|
||||
default:
|
||||
continue
|
||||
miner, err := CreateMiner(availableMiner.Name)
|
||||
if err != nil {
|
||||
continue // Skip unsupported miner types
|
||||
}
|
||||
details, err := miner.CheckInstallation()
|
||||
if err != nil {
|
||||
|
|
@ -483,14 +478,9 @@ func (s *Service) handleDoctor(c *gin.Context) {
|
|||
func (s *Service) handleUpdateCheck(c *gin.Context) {
|
||||
updates := make(map[string]string)
|
||||
for _, availableMiner := range s.Manager.ListAvailableMiners() {
|
||||
var miner Miner
|
||||
switch availableMiner.Name {
|
||||
case "xmrig":
|
||||
miner = NewXMRigMiner()
|
||||
case "tt-miner":
|
||||
miner = NewTTMiner()
|
||||
default:
|
||||
continue
|
||||
miner, err := CreateMiner(availableMiner.Name)
|
||||
if err != nil {
|
||||
continue // Skip unsupported miner types
|
||||
}
|
||||
|
||||
details, err := miner.CheckInstallation()
|
||||
|
|
@ -580,13 +570,8 @@ func (s *Service) handleListAvailableMiners(c *gin.Context) {
|
|||
// @Router /miners/{miner_type}/install [post]
|
||||
func (s *Service) handleInstallMiner(c *gin.Context) {
|
||||
minerType := c.Param("miner_name")
|
||||
var miner Miner
|
||||
switch minerType {
|
||||
case "xmrig":
|
||||
miner = NewXMRigMiner()
|
||||
case "tt-miner":
|
||||
miner = NewTTMiner()
|
||||
default:
|
||||
miner, err := CreateMiner(minerType)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "unknown miner type"})
|
||||
return
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue