321 lines
8.7 KiB
Go
321 lines
8.7 KiB
Go
package services
|
|
|
|
import (
|
|
"context"
|
|
"crypto/sha256"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
"voidraft/internal/models"
|
|
|
|
"github.com/spf13/viper"
|
|
"github.com/wailsapp/wails/v3/pkg/services/log"
|
|
)
|
|
|
|
// ConfigChangeType 配置变更类型
|
|
type ConfigChangeType string
|
|
|
|
const (
|
|
// ConfigChangeTypeHotkey 热键配置变更
|
|
ConfigChangeTypeHotkey ConfigChangeType = "hotkey"
|
|
// ConfigChangeTypeDataPath 数据路径配置变更
|
|
ConfigChangeTypeDataPath ConfigChangeType = "datapath"
|
|
)
|
|
|
|
// ConfigChangeCallback 配置变更回调函数类型
|
|
type ConfigChangeCallback func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error
|
|
|
|
// ConfigListener 配置监听器
|
|
type ConfigListener struct {
|
|
Name string // 监听器名称
|
|
ChangeType ConfigChangeType // 监听的配置变更类型
|
|
Callback ConfigChangeCallback // 回调函数(现在包含新旧配置)
|
|
DebounceDelay time.Duration // 防抖延迟时间
|
|
GetConfigFunc func(*viper.Viper) *models.AppConfig // 获取相关配置的函数
|
|
|
|
// 内部状态
|
|
mu sync.RWMutex // 监听器状态锁
|
|
timer *time.Timer // 防抖定时器
|
|
lastConfigHash string // 上次配置的哈希值,用于变更检测
|
|
lastConfig *models.AppConfig // 上次的配置副本
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
}
|
|
|
|
// ConfigNotificationService 配置通知服务
|
|
type ConfigNotificationService struct {
|
|
listeners sync.Map // 使用sync.Map替代普通map+锁
|
|
logger *log.LoggerService // 日志服务
|
|
viper *viper.Viper // Viper实例
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// NewConfigNotificationService 创建配置通知服务
|
|
func NewConfigNotificationService(viper *viper.Viper, logger *log.LoggerService) *ConfigNotificationService {
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
return &ConfigNotificationService{
|
|
logger: logger,
|
|
viper: viper,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
}
|
|
}
|
|
|
|
// RegisterListener 注册配置监听器
|
|
func (cns *ConfigNotificationService) RegisterListener(listener *ConfigListener) error {
|
|
// 清理已存在的监听器
|
|
if existingValue, loaded := cns.listeners.LoadAndDelete(listener.ChangeType); loaded {
|
|
if existing, ok := existingValue.(interface{ cancel() }); ok {
|
|
existing.cancel()
|
|
}
|
|
}
|
|
|
|
// 初始化新监听器
|
|
listener.ctx, listener.cancel = context.WithCancel(cns.ctx)
|
|
if err := cns.initializeListenerState(listener); err != nil {
|
|
listener.cancel()
|
|
return fmt.Errorf("failed to initialize listener state: %w", err)
|
|
}
|
|
|
|
cns.listeners.Store(listener.ChangeType, listener)
|
|
return nil
|
|
}
|
|
|
|
// initializeListenerState 初始化监听器状态
|
|
func (cns *ConfigNotificationService) initializeListenerState(listener *ConfigListener) error {
|
|
if listener.GetConfigFunc == nil {
|
|
return fmt.Errorf("GetConfigFunc is required")
|
|
}
|
|
|
|
if config := listener.GetConfigFunc(cns.viper); config != nil {
|
|
listener.mu.Lock()
|
|
listener.lastConfig = deepCopyConfig(config)
|
|
listener.lastConfigHash = computeConfigHash(config)
|
|
listener.mu.Unlock()
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// UnregisterListener 注销配置监听器
|
|
func (cns *ConfigNotificationService) UnregisterListener(changeType ConfigChangeType) {
|
|
if value, loaded := cns.listeners.LoadAndDelete(changeType); loaded {
|
|
if listener, ok := value.(*ConfigListener); ok {
|
|
listener.cancel()
|
|
}
|
|
}
|
|
}
|
|
|
|
// CheckConfigChanges 检查配置变更并通知相关监听器
|
|
func (cns *ConfigNotificationService) CheckConfigChanges() {
|
|
cns.listeners.Range(func(key, value interface{}) bool {
|
|
if listener, ok := value.(*ConfigListener); ok {
|
|
cns.checkAndNotify(listener)
|
|
}
|
|
return true
|
|
})
|
|
}
|
|
|
|
// checkAndNotify 检查配置变更并通知
|
|
func (cns *ConfigNotificationService) checkAndNotify(listener *ConfigListener) {
|
|
if listener.GetConfigFunc == nil {
|
|
return
|
|
}
|
|
|
|
currentConfig := listener.GetConfigFunc(cns.viper)
|
|
|
|
listener.mu.RLock()
|
|
lastHash := listener.lastConfigHash
|
|
lastConfig := listener.lastConfig
|
|
listener.mu.RUnlock()
|
|
|
|
var hasChanges bool
|
|
var currentHash string
|
|
|
|
if currentConfig != nil {
|
|
currentHash = computeConfigHash(currentConfig)
|
|
hasChanges = currentHash != lastHash
|
|
} else {
|
|
hasChanges = lastConfig != nil
|
|
}
|
|
|
|
if hasChanges {
|
|
listener.mu.Lock()
|
|
listener.lastConfig = deepCopyConfig(currentConfig)
|
|
listener.lastConfigHash = currentHash
|
|
listener.mu.Unlock()
|
|
|
|
cns.debounceNotify(listener, lastConfig, currentConfig)
|
|
}
|
|
}
|
|
|
|
// computeConfigHash 计算配置的哈希值
|
|
func computeConfigHash(config *models.AppConfig) string {
|
|
if config == nil {
|
|
return ""
|
|
}
|
|
|
|
jsonBytes, err := json.Marshal(config)
|
|
if err != nil {
|
|
return fmt.Sprintf("%p", config)
|
|
}
|
|
|
|
hash := sha256.Sum256(jsonBytes)
|
|
return fmt.Sprintf("%x", hash)
|
|
}
|
|
|
|
// deepCopyConfig 深拷贝配置对象
|
|
func deepCopyConfig(src *models.AppConfig) *models.AppConfig {
|
|
if src == nil {
|
|
return nil
|
|
}
|
|
|
|
jsonBytes, err := json.Marshal(src)
|
|
if err != nil {
|
|
return src
|
|
}
|
|
|
|
var dst models.AppConfig
|
|
if err := json.Unmarshal(jsonBytes, &dst); err != nil {
|
|
return src
|
|
}
|
|
|
|
return &dst
|
|
}
|
|
|
|
// debounceNotify 防抖通知
|
|
func (cns *ConfigNotificationService) debounceNotify(listener *ConfigListener, oldConfig, newConfig *models.AppConfig) {
|
|
listener.mu.Lock()
|
|
defer listener.mu.Unlock()
|
|
|
|
// 取消之前的定时器
|
|
if listener.timer != nil {
|
|
listener.timer.Stop()
|
|
}
|
|
|
|
// 创建配置副本,避免在闭包中持有原始引用
|
|
oldConfigCopy := deepCopyConfig(oldConfig)
|
|
newConfigCopy := deepCopyConfig(newConfig)
|
|
|
|
changeType := listener.ChangeType
|
|
|
|
listener.timer = time.AfterFunc(listener.DebounceDelay, func() {
|
|
cns.executeCallback(listener.ctx, changeType, listener.Callback, oldConfigCopy, newConfigCopy)
|
|
})
|
|
}
|
|
|
|
// executeCallback 执行回调函数
|
|
func (cns *ConfigNotificationService) executeCallback(
|
|
ctx context.Context,
|
|
changeType ConfigChangeType,
|
|
callback ConfigChangeCallback,
|
|
oldConfig, newConfig *models.AppConfig,
|
|
) {
|
|
cns.wg.Add(1)
|
|
go func() {
|
|
defer cns.wg.Done()
|
|
defer func() {
|
|
if r := recover(); r != nil {
|
|
cns.logger.Error("config callback panic", "error", r)
|
|
}
|
|
}()
|
|
|
|
callbackCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
done := make(chan error, 1)
|
|
go func() {
|
|
done <- callback(changeType, oldConfig, newConfig)
|
|
}()
|
|
|
|
select {
|
|
case <-callbackCtx.Done():
|
|
cns.logger.Error("config callback timeout")
|
|
case err := <-done:
|
|
if err != nil {
|
|
cns.logger.Error("config callback error", "error", err)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// Cleanup 清理所有监听器
|
|
func (cns *ConfigNotificationService) Cleanup() {
|
|
cns.cancel() // 取消所有context
|
|
|
|
cns.listeners.Range(func(key, value interface{}) bool {
|
|
cns.listeners.Delete(key)
|
|
return true
|
|
})
|
|
|
|
cns.wg.Wait() // 等待所有协程完成
|
|
}
|
|
|
|
// CreateHotkeyListener 创建热键配置监听器
|
|
func CreateHotkeyListener(callback func(enable bool, hotkey *models.HotkeyCombo) error) *ConfigListener {
|
|
return &ConfigListener{
|
|
Name: "HotkeyListener",
|
|
ChangeType: ConfigChangeTypeHotkey,
|
|
Callback: func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error {
|
|
if newConfig != nil {
|
|
return callback(newConfig.General.EnableGlobalHotkey, &newConfig.General.GlobalHotkey)
|
|
}
|
|
// 使用默认配置
|
|
defaultConfig := models.NewDefaultAppConfig()
|
|
return callback(defaultConfig.General.EnableGlobalHotkey, &defaultConfig.General.GlobalHotkey)
|
|
},
|
|
DebounceDelay: 200 * time.Millisecond,
|
|
GetConfigFunc: func(v *viper.Viper) *models.AppConfig {
|
|
var config models.AppConfig
|
|
if err := v.Unmarshal(&config); err != nil {
|
|
return nil
|
|
}
|
|
return &config
|
|
},
|
|
}
|
|
}
|
|
|
|
// CreateDataPathListener 创建数据路径配置监听器
|
|
func CreateDataPathListener(callback func(oldPath, newPath string) error) *ConfigListener {
|
|
return &ConfigListener{
|
|
Name: "DataPathListener",
|
|
ChangeType: ConfigChangeTypeDataPath,
|
|
Callback: func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error {
|
|
var oldPath, newPath string
|
|
|
|
if oldConfig != nil {
|
|
oldPath = oldConfig.General.DataPath
|
|
}
|
|
|
|
if newConfig != nil {
|
|
newPath = newConfig.General.DataPath
|
|
} else {
|
|
defaultConfig := models.NewDefaultAppConfig()
|
|
newPath = defaultConfig.General.DataPath
|
|
}
|
|
|
|
if oldPath != newPath {
|
|
return callback(oldPath, newPath)
|
|
}
|
|
return nil
|
|
},
|
|
DebounceDelay: 100 * time.Millisecond,
|
|
GetConfigFunc: func(v *viper.Viper) *models.AppConfig {
|
|
var config models.AppConfig
|
|
if err := v.Unmarshal(&config); err != nil {
|
|
return nil
|
|
}
|
|
return &config
|
|
},
|
|
}
|
|
}
|
|
|
|
// ServiceShutdown 关闭服务
|
|
func (cns *ConfigNotificationService) ServiceShutdown() error {
|
|
cns.Cleanup()
|
|
return nil
|
|
}
|