Files
dynagate/config.go
T

219 lines
4.5 KiB
Go
Raw Normal View History

2026-07-09 11:43:10 +03:00
// Dynagate config manager
package main
import (
"context"
"encoding/csv"
"log"
"os"
"strings"
"sync"
"time"
)
type ModelConfig struct {
Model string
Key string
Endpoint string
Extra string
}
type ConfigManager struct {
filePath string
mu sync.RWMutex
configs []ModelConfig
uniqueModels []string
lastModTime time.Time
lastSize int64
}
func NewConfigManager(filePath string) *ConfigManager {
cm := &ConfigManager{
filePath: filePath,
}
if err := cm.Load(); err != nil {
log.Printf("Initial configuration load failed or file not found (will retry on change): %v", err)
}
return cm
}
func (cm *ConfigManager) Load() error {
file, err := os.Open(cm.filePath)
if err != nil {
return err
}
defer file.Close()
stat, err := file.Stat()
if err != nil {
return err
}
reader := csv.NewReader(file)
reader.FieldsPerRecord = -1
rows, err := reader.ReadAll()
if err != nil {
return err
}
var configs []ModelConfig
if len(rows) == 0 {
cm.update(configs, stat.ModTime(), stat.Size())
return nil
}
// Detect header row
hasHeader := false
firstRow := rows[0]
for _, col := range firstRow {
colLower := strings.ToLower(strings.TrimSpace(col))
if colLower == "model" || colLower == "key" || colLower == "endpoint" {
hasHeader = true
break
}
}
var startIdx int
modelIdx, keyIdx, endpointIdx, extraIdx := 0, 1, 2, -1
if hasHeader {
startIdx = 1
modelIdx, keyIdx, endpointIdx, extraIdx = -1, -1, -1, -1
for i, col := range firstRow {
colLower := strings.ToLower(strings.TrimSpace(col))
switch colLower {
case "model":
modelIdx = i
case "key":
keyIdx = i
case "endpoint":
endpointIdx = i
case "extra":
extraIdx = i
}
}
// Fallbacks if columns are not named exactly
if modelIdx == -1 {
modelIdx = 0
}
if keyIdx == -1 {
keyIdx = 1
}
if endpointIdx == -1 {
endpointIdx = 2
}
} else {
startIdx = 0
}
for i := startIdx; i < len(rows); i++ {
row := rows[i]
maxIdx := modelIdx
if keyIdx > maxIdx {
maxIdx = keyIdx
}
if endpointIdx > maxIdx {
maxIdx = endpointIdx
}
if len(row) <= maxIdx {
continue
}
m := strings.TrimSpace(row[modelIdx])
k := strings.TrimSpace(row[keyIdx])
ep := strings.TrimSpace(row[endpointIdx])
if m == "" || k == "" || ep == "" {
continue
}
extra := ""
if hasHeader {
if extraIdx != -1 && len(row) > extraIdx {
extra = strings.TrimSpace(row[extraIdx])
}
} else {
if len(row) > 3 {
extra = strings.TrimSpace(row[3])
}
}
configs = append(configs, ModelConfig{
Model: m,
Key: k,
Endpoint: ep,
Extra: extra,
})
}
cm.update(configs, stat.ModTime(), stat.Size())
log.Printf("Loaded %d model configurations from %s", len(configs), cm.filePath)
return nil
}
func (cm *ConfigManager) update(configs []ModelConfig, modTime time.Time, size int64) {
cm.mu.Lock()
defer cm.mu.Unlock()
cm.configs = configs
cm.lastModTime = modTime
cm.lastSize = size
var uniqueModels []string
seen := make(map[string]bool)
for _, cfg := range configs {
if !seen[cfg.Model] {
seen[cfg.Model] = true
uniqueModels = append(uniqueModels, cfg.Model)
}
}
cm.uniqueModels = uniqueModels
}
func (cm *ConfigManager) GetConfigs() []ModelConfig {
cm.mu.RLock()
defer cm.mu.RUnlock()
res := make([]ModelConfig, len(cm.configs))
copy(res, cm.configs)
return res
}
func (cm *ConfigManager) GetUniqueModels() []string {
cm.mu.RLock()
defer cm.mu.RUnlock()
res := make([]string, len(cm.uniqueModels))
copy(res, cm.uniqueModels)
return res
}
func (cm *ConfigManager) Watch(ctx context.Context) {
ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
stat, err := os.Stat(cm.filePath)
if err != nil {
continue
}
cm.mu.RLock()
lastMod := cm.lastModTime
lastSz := cm.lastSize
cm.mu.RUnlock()
if stat.ModTime().After(lastMod) || stat.Size() != lastSz {
log.Printf("Detected changes in %s, reloading...", cm.filePath)
if err := cm.Load(); err != nil {
log.Printf("Error reloading configuration: %v", err)
} else {
log.Printf("Configuration reloaded successfully")
}
}
}
}
}