Files
voidraft/internal/services/config_notification_service.go
2025-07-17 00:12:00 +08:00

478 lines
13 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"reflect"
"sync"
"time"
"voidraft/internal/models"
"github.com/knadh/koanf/v2"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// ConfigChangeType 配置变更类型
type ConfigChangeType string
const (
// ConfigChangeTypeHotkey 热键配置变更
ConfigChangeTypeHotkey ConfigChangeType = "hotkey"
// ConfigChangeTypeDataPath 数据路径配置变更
ConfigChangeTypeDataPath ConfigChangeType = "datapath"
// ConfigChangeTypeBackup 备份配置变更
ConfigChangeTypeBackup ConfigChangeType = "backup"
)
// ConfigChangeCallback 配置变更回调函数类型
type ConfigChangeCallback func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error
// ConfigListener 配置监听器
type ConfigListener struct {
ID string // 监听器唯一ID
Name string // 监听器名称
ChangeType ConfigChangeType // 监听的配置变更类型
Callback ConfigChangeCallback // 回调函数(现在包含新旧配置)
DebounceDelay time.Duration // 防抖延迟时间
GetConfigFunc func(*koanf.Koanf) *models.AppConfig // 获取相关配置的函数
// 内部状态
mu sync.RWMutex // 监听器状态锁
timer *time.Timer // 防抖定时器
lastConfigHash string // 上次配置的哈希值,用于变更检测
lastConfig *models.AppConfig // 上次的配置副本
ctx context.Context
cancel context.CancelFunc
}
// ConfigNotificationService 配置通知服务
type ConfigNotificationService struct {
listeners map[ConfigChangeType][]*ConfigListener // 支持多监听器的map
mu sync.RWMutex // 监听器map的读写锁
logger *log.Service // 日志服务
koanf *koanf.Koanf // koanf实例
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
}
// NewConfigNotificationService 创建配置通知服务
func NewConfigNotificationService(k *koanf.Koanf, logger *log.Service) *ConfigNotificationService {
ctx, cancel := context.WithCancel(context.Background())
return &ConfigNotificationService{
listeners: make(map[ConfigChangeType][]*ConfigListener),
logger: logger,
koanf: k,
ctx: ctx,
cancel: cancel,
}
}
// RegisterListener 注册配置监听器
func (cns *ConfigNotificationService) RegisterListener(listener *ConfigListener) error {
// 生成唯一ID如果没有提供
if listener.ID == "" {
listener.ID = fmt.Sprintf("%s_%d", listener.Name, time.Now().UnixNano())
}
// 初始化新监听器
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.mu.Lock()
cns.listeners[listener.ChangeType] = append(cns.listeners[listener.ChangeType], listener)
cns.mu.Unlock()
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.koanf); config != nil {
listener.mu.Lock()
listener.lastConfig = deepCopyConfigReflect(config)
listener.lastConfigHash = computeConfigHash(config)
listener.mu.Unlock()
}
return nil
}
// UnregisterListener 注销指定ID的配置监听器
func (cns *ConfigNotificationService) UnregisterListener(changeType ConfigChangeType, listenerID string) {
cns.mu.Lock()
defer cns.mu.Unlock()
listeners := cns.listeners[changeType]
for i, listener := range listeners {
if listener.ID == listenerID {
// 取消监听器
listener.cancel()
// 从切片中移除
cns.listeners[changeType] = append(listeners[:i], listeners[i+1:]...)
break
}
}
// 如果该类型没有监听器了,删除整个条目
if len(cns.listeners[changeType]) == 0 {
delete(cns.listeners, changeType)
}
}
// UnregisterAllListeners 注销指定类型的所有监听器
func (cns *ConfigNotificationService) UnregisterAllListeners(changeType ConfigChangeType) {
cns.mu.Lock()
defer cns.mu.Unlock()
if listeners, exists := cns.listeners[changeType]; exists {
for _, listener := range listeners {
listener.cancel()
}
delete(cns.listeners, changeType)
}
}
// CheckConfigChanges 检查配置变更并通知相关监听器
func (cns *ConfigNotificationService) CheckConfigChanges() {
cns.mu.RLock()
allListeners := make(map[ConfigChangeType][]*ConfigListener)
for changeType, listeners := range cns.listeners {
// 创建监听器切片的副本以避免并发访问问题
listenersCopy := make([]*ConfigListener, len(listeners))
copy(listenersCopy, listeners)
allListeners[changeType] = listenersCopy
}
cns.mu.RUnlock()
// 检查所有监听器
for _, listeners := range allListeners {
for _, listener := range listeners {
cns.checkAndNotify(listener)
}
}
}
// checkAndNotify 检查配置变更并通知
func (cns *ConfigNotificationService) checkAndNotify(listener *ConfigListener) {
if listener.GetConfigFunc == nil {
return
}
currentConfig := listener.GetConfigFunc(cns.koanf)
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 = deepCopyConfigReflect(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)
}
// deepCopyConfigReflect 使用反射实现高效深拷贝
func deepCopyConfigReflect(src *models.AppConfig) *models.AppConfig {
if src == nil {
return nil
}
// 使用反射进行深拷贝
srcValue := reflect.ValueOf(src).Elem()
dstValue := reflect.New(srcValue.Type()).Elem()
deepCopyValue(srcValue, dstValue)
return dstValue.Addr().Interface().(*models.AppConfig)
}
// deepCopyValue 递归深拷贝reflect.Value
func deepCopyValue(src, dst reflect.Value) {
switch src.Kind() {
case reflect.Ptr:
if src.IsNil() {
return
}
dst.Set(reflect.New(src.Elem().Type()))
deepCopyValue(src.Elem(), dst.Elem())
case reflect.Struct:
for i := 0; i < src.NumField(); i++ {
if dst.Field(i).CanSet() {
deepCopyValue(src.Field(i), dst.Field(i))
}
}
case reflect.Slice:
if src.IsNil() {
return
}
dst.Set(reflect.MakeSlice(src.Type(), src.Len(), src.Cap()))
for i := 0; i < src.Len(); i++ {
deepCopyValue(src.Index(i), dst.Index(i))
}
case reflect.Map:
if src.IsNil() {
return
}
dst.Set(reflect.MakeMap(src.Type()))
for _, key := range src.MapKeys() {
srcValue := src.MapIndex(key)
dstValue := reflect.New(srcValue.Type()).Elem()
deepCopyValue(srcValue, dstValue)
dst.SetMapIndex(key, dstValue)
}
case reflect.Interface:
if src.IsNil() {
return
}
srcValue := src.Elem()
dstValue := reflect.New(srcValue.Type()).Elem()
deepCopyValue(srcValue, dstValue)
dst.Set(dstValue)
case reflect.Array:
for i := 0; i < src.Len(); i++ {
deepCopyValue(src.Index(i), dst.Index(i))
}
default:
// 对于基本类型和string直接赋值
if dst.CanSet() {
dst.Set(src)
}
}
}
// deepCopyConfig 保留原有的JSON深拷贝方法作为备用
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 := deepCopyConfigReflect(oldConfig)
newConfigCopy := deepCopyConfigReflect(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.mu.Lock()
for changeType, listeners := range cns.listeners {
for _, listener := range listeners {
listener.cancel()
}
delete(cns.listeners, changeType)
}
cns.mu.Unlock()
cns.wg.Wait() // 等待所有协程完成
}
// GetListeners 获取指定类型的所有监听器
func (cns *ConfigNotificationService) GetListeners(changeType ConfigChangeType) []*ConfigListener {
cns.mu.RLock()
defer cns.mu.RUnlock()
listeners := cns.listeners[changeType]
result := make([]*ConfigListener, len(listeners))
copy(result, listeners)
return result
}
// CreateHotkeyListener 创建热键配置监听器
func CreateHotkeyListener(name string, callback func(enable bool, hotkey *models.HotkeyCombo) error) *ConfigListener {
return &ConfigListener{
Name: name,
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(k *koanf.Koanf) *models.AppConfig {
var config models.AppConfig
if err := k.Unmarshal("", &config); err != nil {
return nil
}
return &config
},
}
}
// CreateDataPathListener 创建数据路径配置监听器
func CreateDataPathListener(name string, callback func() error) *ConfigListener {
return &ConfigListener{
Name: name,
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()
}
return nil
},
DebounceDelay: 100 * time.Millisecond,
GetConfigFunc: func(k *koanf.Koanf) *models.AppConfig {
var config models.AppConfig
if err := k.Unmarshal("", &config); err != nil {
return nil
}
return &config
},
}
}
// CreateBackupConfigListener 创建备份配置监听器
func CreateBackupConfigListener(name string, callback func(config *models.GitBackupConfig) error) *ConfigListener {
return &ConfigListener{
Name: name,
ChangeType: ConfigChangeTypeBackup,
Callback: func(changeType ConfigChangeType, oldConfig, newConfig *models.AppConfig) error {
if newConfig == nil {
defaultConfig := models.NewDefaultAppConfig()
return callback(&defaultConfig.Backup)
}
return callback(&newConfig.Backup)
},
DebounceDelay: 200 * time.Millisecond,
GetConfigFunc: func(k *koanf.Koanf) *models.AppConfig {
var config models.AppConfig
if err := k.Unmarshal("", &config); err != nil {
return nil
}
return &config
},
}
}
// ServiceShutdown 关闭服务
func (cns *ConfigNotificationService) ServiceShutdown() error {
cns.Cleanup()
return nil
}