✨ Added configuration change notification service
This commit is contained in:
@@ -28,8 +28,12 @@ const (
|
||||
|
||||
// GeneralConfig 通用设置配置
|
||||
type GeneralConfig struct {
|
||||
AlwaysOnTop bool `json:"alwaysOnTop" yaml:"always_on_top" mapstructure:"always_on_top"` // 窗口是否置顶
|
||||
DataPath string `json:"dataPath" yaml:"data_path" mapstructure:"data_path"` // 数据存储路径
|
||||
AlwaysOnTop bool `json:"alwaysOnTop" yaml:"always_on_top" mapstructure:"always_on_top"` // 窗口是否置顶
|
||||
DefaultDataPath string `json:"defaultDataPath" yaml:"default_data_path" mapstructure:"default_data_path"` // 默认数据存储路径
|
||||
|
||||
// 数据存储路径配置
|
||||
UseCustomDataPath bool `json:"useCustomDataPath" yaml:"use_custom_data_path" mapstructure:"use_custom_data_path"` // 是否使用自定义数据路径
|
||||
CustomDataPath string `json:"customDataPath" yaml:"custom_data_path" mapstructure:"custom_data_path"` // 自定义数据存储路径
|
||||
|
||||
// 全局热键设置
|
||||
EnableGlobalHotkey bool `json:"enableGlobalHotkey" yaml:"enable_global_hotkey" mapstructure:"enable_global_hotkey"` // 是否启用全局热键
|
||||
@@ -109,7 +113,9 @@ func NewDefaultAppConfig() *AppConfig {
|
||||
return &AppConfig{
|
||||
General: GeneralConfig{
|
||||
AlwaysOnTop: false,
|
||||
DataPath: dataDir,
|
||||
DefaultDataPath: dataDir,
|
||||
UseCustomDataPath: false,
|
||||
CustomDataPath: "",
|
||||
EnableGlobalHotkey: false,
|
||||
GlobalHotkey: HotkeyCombo{
|
||||
Ctrl: false,
|
||||
|
456
internal/services/config_notification_service.go
Normal file
456
internal/services/config_notification_service.go
Normal file
@@ -0,0 +1,456 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"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"
|
||||
)
|
||||
|
||||
// ConfigChangeCallback 配置变更回调函数类型
|
||||
type ConfigChangeCallback func(changeType ConfigChangeType, oldConfig, newConfig interface{}) error
|
||||
|
||||
// ConfigListener 配置监听器
|
||||
type ConfigListener struct {
|
||||
Name string // 监听器名称
|
||||
ChangeType ConfigChangeType // 监听的配置变更类型
|
||||
Callback ConfigChangeCallback // 回调函数(现在包含新旧配置)
|
||||
DebounceDelay time.Duration // 防抖延迟时间
|
||||
GetConfigFunc func(*viper.Viper) interface{} // 获取相关配置的函数
|
||||
|
||||
// 内部状态
|
||||
mu sync.RWMutex // 监听器状态锁
|
||||
timer *time.Timer // 防抖定时器
|
||||
lastConfigHash string // 上次配置的哈希值,用于变更检测
|
||||
lastConfig interface{} // 上次的配置副本
|
||||
stopChan chan struct{} // 停止通道,用于停止异步goroutine
|
||||
}
|
||||
|
||||
// ConfigNotificationService 配置通知服务
|
||||
type ConfigNotificationService struct {
|
||||
listeners map[ConfigChangeType]*ConfigListener // 监听器映射
|
||||
mu sync.RWMutex // 读写锁
|
||||
logger *log.LoggerService // 日志服务
|
||||
viper *viper.Viper // Viper实例
|
||||
}
|
||||
|
||||
// NewConfigNotificationService 创建配置通知服务
|
||||
func NewConfigNotificationService(viper *viper.Viper, logger *log.LoggerService) *ConfigNotificationService {
|
||||
return &ConfigNotificationService{
|
||||
listeners: make(map[ConfigChangeType]*ConfigListener),
|
||||
logger: logger,
|
||||
viper: viper,
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterListener 注册配置监听器
|
||||
func (cns *ConfigNotificationService) RegisterListener(listener *ConfigListener) error {
|
||||
cns.mu.Lock()
|
||||
defer cns.mu.Unlock()
|
||||
|
||||
// 检查是否已存在同类型监听器
|
||||
if existingListener, exists := cns.listeners[listener.ChangeType]; exists {
|
||||
cns.logger.Warning("ConfigNotification: Listener already exists, will be replaced",
|
||||
"existing_name", existingListener.Name,
|
||||
"new_name", listener.Name,
|
||||
"type", string(listener.ChangeType))
|
||||
|
||||
// 清理旧监听器
|
||||
cns.cleanupListener(existingListener)
|
||||
}
|
||||
|
||||
// 初始化新监听器
|
||||
listener.stopChan = make(chan struct{})
|
||||
|
||||
// 初始化监听器的配置状态
|
||||
if err := cns.initializeListenerState(listener); err != nil {
|
||||
cns.logger.Error("ConfigNotification: Failed to initialize listener state",
|
||||
"listener", listener.Name,
|
||||
"error", err)
|
||||
return fmt.Errorf("failed to initialize listener state: %w", err)
|
||||
}
|
||||
|
||||
cns.listeners[listener.ChangeType] = listener
|
||||
cns.logger.Info("ConfigNotification: Registered listener",
|
||||
"name", listener.Name,
|
||||
"type", string(listener.ChangeType))
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// cleanupListener 清理监听器资源
|
||||
func (cns *ConfigNotificationService) cleanupListener(listener *ConfigListener) {
|
||||
listener.mu.Lock()
|
||||
defer listener.mu.Unlock()
|
||||
|
||||
// 停止防抖定时器
|
||||
if listener.timer != nil {
|
||||
listener.timer.Stop()
|
||||
listener.timer = nil
|
||||
}
|
||||
|
||||
// 关闭停止通道,通知goroutine退出
|
||||
if listener.stopChan != nil {
|
||||
close(listener.stopChan)
|
||||
listener.stopChan = nil
|
||||
}
|
||||
}
|
||||
|
||||
// initializeListenerState 初始化监听器状态
|
||||
func (cns *ConfigNotificationService) initializeListenerState(listener *ConfigListener) error {
|
||||
if listener.GetConfigFunc == nil {
|
||||
return fmt.Errorf("GetConfigFunc is required")
|
||||
}
|
||||
|
||||
// 获取初始配置
|
||||
config := listener.GetConfigFunc(cns.viper)
|
||||
if config != nil {
|
||||
listener.mu.Lock()
|
||||
listener.lastConfig = cns.deepCopy(config)
|
||||
listener.lastConfigHash = cns.computeConfigHash(config)
|
||||
listener.mu.Unlock()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// UnregisterListener 注销配置监听器
|
||||
func (cns *ConfigNotificationService) UnregisterListener(changeType ConfigChangeType) {
|
||||
cns.mu.Lock()
|
||||
defer cns.mu.Unlock()
|
||||
|
||||
if listener, exists := cns.listeners[changeType]; exists {
|
||||
cns.cleanupListener(listener)
|
||||
delete(cns.listeners, changeType)
|
||||
cns.logger.Info("ConfigNotification: Unregistered listener", "type", string(changeType))
|
||||
}
|
||||
}
|
||||
|
||||
// CheckConfigChanges 检查配置变更并通知相关监听器
|
||||
func (cns *ConfigNotificationService) CheckConfigChanges() {
|
||||
cns.mu.RLock()
|
||||
listeners := make([]*ConfigListener, 0, len(cns.listeners))
|
||||
for _, listener := range cns.listeners {
|
||||
listeners = append(listeners, listener)
|
||||
}
|
||||
cns.mu.RUnlock()
|
||||
|
||||
// 检查每个监听器的配置变更
|
||||
for _, listener := range listeners {
|
||||
if hasChanges, oldConfig, newConfig := cns.checkListenerConfigChanges(listener); hasChanges {
|
||||
cns.logger.Debug("ConfigNotification: Actual config change detected",
|
||||
"type", string(listener.ChangeType),
|
||||
"listener", listener.Name)
|
||||
|
||||
// 触发防抖通知,传递新旧配置
|
||||
cns.debounceNotifyWithChanges(listener, oldConfig, newConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// checkListenerConfigChanges 检查单个监听器的配置变更
|
||||
func (cns *ConfigNotificationService) checkListenerConfigChanges(listener *ConfigListener) (bool, interface{}, interface{}) {
|
||||
if listener.GetConfigFunc == nil {
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
// 获取当前配置
|
||||
currentConfig := listener.GetConfigFunc(cns.viper)
|
||||
|
||||
// 读取监听器状态
|
||||
listener.mu.RLock()
|
||||
lastHash := listener.lastConfigHash
|
||||
lastConfig := listener.lastConfig
|
||||
listener.mu.RUnlock()
|
||||
|
||||
if currentConfig == nil {
|
||||
// 配置不存在或获取失败
|
||||
if lastConfig != nil {
|
||||
// 配置被删除,更新状态
|
||||
listener.mu.Lock()
|
||||
listener.lastConfig = nil
|
||||
listener.lastConfigHash = ""
|
||||
listener.mu.Unlock()
|
||||
return true, lastConfig, nil
|
||||
}
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
// 计算当前配置的哈希
|
||||
currentHash := cns.computeConfigHash(currentConfig)
|
||||
|
||||
// 检查是否有变更
|
||||
if currentHash != lastHash {
|
||||
// 更新监听器状态
|
||||
listener.mu.Lock()
|
||||
listener.lastConfig = cns.deepCopy(currentConfig)
|
||||
listener.lastConfigHash = currentHash
|
||||
listener.mu.Unlock()
|
||||
|
||||
return true, lastConfig, currentConfig
|
||||
}
|
||||
|
||||
return false, nil, nil
|
||||
}
|
||||
|
||||
// computeConfigHash 计算配置的哈希值 - 安全稳定版本
|
||||
func (cns *ConfigNotificationService) computeConfigHash(config interface{}) string {
|
||||
if config == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// 使用JSON序列化确保稳定性和准确性
|
||||
jsonBytes, err := json.Marshal(config)
|
||||
if err != nil {
|
||||
// 如果JSON序列化失败,回退到字符串表示
|
||||
cns.logger.Warning("ConfigNotification: JSON marshal failed, using string representation",
|
||||
"error", err)
|
||||
configStr := fmt.Sprintf("%+v", config)
|
||||
jsonBytes = []byte(configStr)
|
||||
}
|
||||
|
||||
hash := sha256.Sum256(jsonBytes)
|
||||
return fmt.Sprintf("%x", hash)
|
||||
}
|
||||
|
||||
// deepCopy 深拷贝配置对象 - 完整实现
|
||||
func (cns *ConfigNotificationService) deepCopy(src interface{}) interface{} {
|
||||
if src == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 首先尝试JSON序列化方式深拷贝(适用于大多数配置对象)
|
||||
jsonBytes, err := json.Marshal(src)
|
||||
if err != nil {
|
||||
cns.logger.Warning("ConfigNotification: JSON marshal for deep copy failed, using reflection",
|
||||
"error", err)
|
||||
return cns.reflectDeepCopy(src)
|
||||
}
|
||||
|
||||
// 创建同类型的新对象
|
||||
srcType := reflect.TypeOf(src)
|
||||
var dst interface{}
|
||||
|
||||
if srcType.Kind() == reflect.Ptr {
|
||||
// 对于指针类型,创建指向新对象的指针
|
||||
elemType := srcType.Elem()
|
||||
newObj := reflect.New(elemType)
|
||||
dst = newObj.Interface()
|
||||
} else {
|
||||
// 对于值类型,创建零值
|
||||
newObj := reflect.New(srcType)
|
||||
dst = newObj.Interface()
|
||||
}
|
||||
|
||||
// 反序列化到新对象
|
||||
err = json.Unmarshal(jsonBytes, dst)
|
||||
if err != nil {
|
||||
cns.logger.Warning("ConfigNotification: JSON unmarshal for deep copy failed, using reflection",
|
||||
"error", err)
|
||||
return cns.reflectDeepCopy(src)
|
||||
}
|
||||
|
||||
// 如果原对象不是指针类型,返回值而不是指针
|
||||
if srcType.Kind() != reflect.Ptr {
|
||||
return reflect.ValueOf(dst).Elem().Interface()
|
||||
}
|
||||
|
||||
return dst
|
||||
}
|
||||
|
||||
// reflectDeepCopy 使用反射进行深拷贝的备用方法
|
||||
func (cns *ConfigNotificationService) reflectDeepCopy(src interface{}) interface{} {
|
||||
srcValue := reflect.ValueOf(src)
|
||||
return cns.reflectDeepCopyValue(srcValue).Interface()
|
||||
}
|
||||
|
||||
// reflectDeepCopyValue 递归深拷贝reflect.Value
|
||||
func (cns *ConfigNotificationService) reflectDeepCopyValue(src reflect.Value) reflect.Value {
|
||||
if !src.IsValid() {
|
||||
return reflect.Value{}
|
||||
}
|
||||
|
||||
switch src.Kind() {
|
||||
case reflect.Ptr:
|
||||
if src.IsNil() {
|
||||
return reflect.New(src.Type()).Elem()
|
||||
}
|
||||
dst := reflect.New(src.Type().Elem())
|
||||
dst.Elem().Set(cns.reflectDeepCopyValue(src.Elem()))
|
||||
return dst
|
||||
|
||||
case reflect.Struct:
|
||||
dst := reflect.New(src.Type()).Elem()
|
||||
for i := 0; i < src.NumField(); i++ {
|
||||
if dst.Field(i).CanSet() {
|
||||
dst.Field(i).Set(cns.reflectDeepCopyValue(src.Field(i)))
|
||||
}
|
||||
}
|
||||
return dst
|
||||
|
||||
case reflect.Slice:
|
||||
if src.IsNil() {
|
||||
return reflect.Zero(src.Type())
|
||||
}
|
||||
dst := reflect.MakeSlice(src.Type(), src.Len(), src.Cap())
|
||||
for i := 0; i < src.Len(); i++ {
|
||||
dst.Index(i).Set(cns.reflectDeepCopyValue(src.Index(i)))
|
||||
}
|
||||
return dst
|
||||
|
||||
case reflect.Map:
|
||||
if src.IsNil() {
|
||||
return reflect.Zero(src.Type())
|
||||
}
|
||||
dst := reflect.MakeMap(src.Type())
|
||||
for _, key := range src.MapKeys() {
|
||||
dst.SetMapIndex(key, cns.reflectDeepCopyValue(src.MapIndex(key)))
|
||||
}
|
||||
return dst
|
||||
|
||||
default:
|
||||
return src
|
||||
}
|
||||
}
|
||||
|
||||
// debounceNotifyWithChanges 防抖通知(带变更信息)- 修复内存泄漏
|
||||
func (cns *ConfigNotificationService) debounceNotifyWithChanges(listener *ConfigListener, oldConfig, newConfig interface{}) {
|
||||
listener.mu.Lock()
|
||||
defer listener.mu.Unlock()
|
||||
|
||||
// 取消之前的定时器
|
||||
if listener.timer != nil {
|
||||
listener.timer.Stop()
|
||||
}
|
||||
|
||||
// 创建配置副本,避免在闭包中持有原始引用
|
||||
oldConfigCopy := cns.deepCopy(oldConfig)
|
||||
newConfigCopy := cns.deepCopy(newConfig)
|
||||
|
||||
// 获取监听器信息的副本
|
||||
listenerName := listener.Name
|
||||
changeType := listener.ChangeType
|
||||
stopChan := listener.stopChan
|
||||
|
||||
// 设置新的防抖定时器
|
||||
listener.timer = time.AfterFunc(listener.DebounceDelay, func() {
|
||||
cns.logger.Debug("ConfigNotification: Executing callback after debounce",
|
||||
"listener", listenerName,
|
||||
"type", string(changeType))
|
||||
|
||||
// 启动独立的goroutine处理回调,带有超时和停止信号检查
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
cns.logger.Error("ConfigNotification: Callback panic recovered",
|
||||
"listener", listenerName,
|
||||
"type", string(changeType),
|
||||
"panic", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// 检查是否收到停止信号
|
||||
select {
|
||||
case <-stopChan:
|
||||
cns.logger.Debug("ConfigNotification: Callback cancelled due to stop signal",
|
||||
"listener", listenerName)
|
||||
return
|
||||
default:
|
||||
}
|
||||
|
||||
// 执行回调,设置超时
|
||||
callbackDone := make(chan error, 1)
|
||||
go func() {
|
||||
callbackDone <- listener.Callback(changeType, oldConfigCopy, newConfigCopy)
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-stopChan:
|
||||
cns.logger.Debug("ConfigNotification: Callback interrupted by stop signal",
|
||||
"listener", listenerName)
|
||||
return
|
||||
case err := <-callbackDone:
|
||||
if err != nil {
|
||||
cns.logger.Error("ConfigNotification: Callback execution failed",
|
||||
"listener", listenerName,
|
||||
"type", string(changeType),
|
||||
"error", err)
|
||||
} else {
|
||||
cns.logger.Debug("ConfigNotification: Callback executed successfully",
|
||||
"listener", listenerName,
|
||||
"type", string(changeType))
|
||||
}
|
||||
case <-time.After(30 * time.Second): // 30秒超时
|
||||
cns.logger.Error("ConfigNotification: Callback execution timeout",
|
||||
"listener", listenerName,
|
||||
"type", string(changeType),
|
||||
"timeout", "30s")
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
cns.logger.Debug("ConfigNotification: Debounce timer scheduled",
|
||||
"listener", listenerName,
|
||||
"delay", listener.DebounceDelay)
|
||||
}
|
||||
|
||||
// Cleanup 清理所有监听器
|
||||
func (cns *ConfigNotificationService) Cleanup() {
|
||||
cns.mu.Lock()
|
||||
defer cns.mu.Unlock()
|
||||
|
||||
for changeType, listener := range cns.listeners {
|
||||
cns.cleanupListener(listener)
|
||||
delete(cns.listeners, changeType)
|
||||
}
|
||||
|
||||
cns.logger.Info("ConfigNotification: All listeners cleaned up")
|
||||
}
|
||||
|
||||
// CreateHotkeyListener 创建热键配置监听器
|
||||
func CreateHotkeyListener(callback func(enable bool, hotkey *models.HotkeyCombo) error) *ConfigListener {
|
||||
return &ConfigListener{
|
||||
Name: "HotkeyListener",
|
||||
ChangeType: ConfigChangeTypeHotkey,
|
||||
Callback: func(changeType ConfigChangeType, oldConfig, newConfig interface{}) error {
|
||||
// 处理新配置
|
||||
if newAppConfig, ok := newConfig.(*models.AppConfig); ok {
|
||||
return callback(newAppConfig.General.EnableGlobalHotkey, &newAppConfig.General.GlobalHotkey)
|
||||
}
|
||||
// 如果新配置为空,说明配置被删除,使用默认值
|
||||
if newConfig == nil {
|
||||
defaultConfig := models.NewDefaultAppConfig()
|
||||
return callback(defaultConfig.General.EnableGlobalHotkey, &defaultConfig.General.GlobalHotkey)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
DebounceDelay: 200 * time.Millisecond,
|
||||
GetConfigFunc: func(v *viper.Viper) interface{} {
|
||||
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
|
||||
}
|
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"voidraft/internal/models"
|
||||
@@ -21,12 +20,8 @@ type ConfigService struct {
|
||||
logger *log.LoggerService // 日志服务
|
||||
mu sync.RWMutex // 读写锁
|
||||
|
||||
// 热键配置变更回调
|
||||
hotkeyChangeCallback func(enable bool, hotkey *models.HotkeyCombo) error
|
||||
|
||||
// 热键变更防抖
|
||||
hotkeyNotificationTimer *time.Timer
|
||||
hotkeyNotificationMu sync.Mutex
|
||||
// 配置通知服务
|
||||
notificationService *ConfigNotificationService
|
||||
}
|
||||
|
||||
// ConfigError 配置错误
|
||||
@@ -52,184 +47,6 @@ func (e *ConfigError) Is(target error) bool {
|
||||
return ok
|
||||
}
|
||||
|
||||
// ConfigLimits 配置限制定义
|
||||
type ConfigLimits struct {
|
||||
FontSizeMin int
|
||||
FontSizeMax int
|
||||
TabSizeMin int
|
||||
TabSizeMax int
|
||||
}
|
||||
|
||||
// getConfigLimits 获取配置限制
|
||||
func getConfigLimits() ConfigLimits {
|
||||
return ConfigLimits{
|
||||
FontSizeMin: 12,
|
||||
FontSizeMax: 28,
|
||||
TabSizeMin: 2,
|
||||
TabSizeMax: 8,
|
||||
}
|
||||
}
|
||||
|
||||
// validateAndFixValue 验证并修正配置值
|
||||
func (cs *ConfigService) validateAndFixValue(key string, value interface{}) (interface{}, bool) {
|
||||
limits := getConfigLimits()
|
||||
fixed := false
|
||||
|
||||
switch key {
|
||||
case "editing.font_size":
|
||||
if intVal, ok := value.(int); ok {
|
||||
if intVal < limits.FontSizeMin {
|
||||
cs.logger.Warning("Config: Font size too small, fixing", "original", intVal, "fixed", limits.FontSizeMin)
|
||||
return limits.FontSizeMin, true
|
||||
}
|
||||
if intVal > limits.FontSizeMax {
|
||||
cs.logger.Warning("Config: Font size too large, fixing", "original", intVal, "fixed", limits.FontSizeMax)
|
||||
return limits.FontSizeMax, true
|
||||
}
|
||||
}
|
||||
|
||||
case "editing.tab_size":
|
||||
if intVal, ok := value.(int); ok {
|
||||
if intVal < limits.TabSizeMin {
|
||||
cs.logger.Warning("Config: Tab size too small, fixing", "original", intVal, "fixed", limits.TabSizeMin)
|
||||
return limits.TabSizeMin, true
|
||||
}
|
||||
if intVal > limits.TabSizeMax {
|
||||
cs.logger.Warning("Config: Tab size too large, fixing", "original", intVal, "fixed", limits.TabSizeMax)
|
||||
return limits.TabSizeMax, true
|
||||
}
|
||||
}
|
||||
|
||||
case "editing.tab_type":
|
||||
if strVal, ok := value.(string); ok {
|
||||
validTypes := []string{string(models.TabTypeSpaces), string(models.TabTypeTab)}
|
||||
isValid := false
|
||||
for _, validType := range validTypes {
|
||||
if strVal == validType {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isValid {
|
||||
cs.logger.Warning("Config: Invalid tab type, fixing", "original", strVal, "fixed", string(models.TabTypeSpaces))
|
||||
return string(models.TabTypeSpaces), true
|
||||
}
|
||||
}
|
||||
|
||||
case "appearance.language":
|
||||
if strVal, ok := value.(string); ok {
|
||||
validLanguages := []string{string(models.LangZhCN), string(models.LangEnUS)}
|
||||
isValid := false
|
||||
for _, validLang := range validLanguages {
|
||||
if strVal == validLang {
|
||||
isValid = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !isValid {
|
||||
cs.logger.Warning("Config: Invalid language, fixing", "original", strVal, "fixed", string(models.LangZhCN))
|
||||
return string(models.LangZhCN), true
|
||||
}
|
||||
}
|
||||
|
||||
case "editing.auto_save_delay":
|
||||
if intVal, ok := value.(int); ok {
|
||||
if intVal < 1000 {
|
||||
cs.logger.Warning("Config: Auto save delay too small, fixing", "original", intVal, "fixed", 1000)
|
||||
return 1000, true
|
||||
}
|
||||
if intVal > 30000 {
|
||||
cs.logger.Warning("Config: Auto save delay too large, fixing", "original", intVal, "fixed", 30000)
|
||||
return 30000, true
|
||||
}
|
||||
}
|
||||
|
||||
case "editing.change_threshold":
|
||||
if intVal, ok := value.(int); ok {
|
||||
if intVal < 10 {
|
||||
cs.logger.Warning("Config: Change threshold too small, fixing", "original", intVal, "fixed", 10)
|
||||
return 10, true
|
||||
}
|
||||
if intVal > 10000 {
|
||||
cs.logger.Warning("Config: Change threshold too large, fixing", "original", intVal, "fixed", 10000)
|
||||
return 10000, true
|
||||
}
|
||||
}
|
||||
|
||||
case "editing.min_save_interval":
|
||||
if intVal, ok := value.(int); ok {
|
||||
if intVal < 100 {
|
||||
cs.logger.Warning("Config: Min save interval too small, fixing", "original", intVal, "fixed", 100)
|
||||
return 100, true
|
||||
}
|
||||
if intVal > 10000 {
|
||||
cs.logger.Warning("Config: Min save interval too large, fixing", "original", intVal, "fixed", 10000)
|
||||
return 10000, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return value, fixed
|
||||
}
|
||||
|
||||
// validateAllConfig 验证并修正所有配置
|
||||
func (cs *ConfigService) validateAllConfig() error {
|
||||
hasChanges := false
|
||||
|
||||
// 获取当前配置
|
||||
var config models.AppConfig
|
||||
if err := cs.viper.Unmarshal(&config); err != nil {
|
||||
return &ConfigError{Operation: "unmarshal_for_validation", Err: err}
|
||||
}
|
||||
|
||||
// 验证编辑器配置
|
||||
if fixedValue, fixed := cs.validateAndFixValue("editing.font_size", config.Editing.FontSize); fixed {
|
||||
cs.viper.Set("editing.font_size", fixedValue)
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
if fixedValue, fixed := cs.validateAndFixValue("editing.tab_size", config.Editing.TabSize); fixed {
|
||||
cs.viper.Set("editing.tab_size", fixedValue)
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
if fixedValue, fixed := cs.validateAndFixValue("editing.tab_type", string(config.Editing.TabType)); fixed {
|
||||
cs.viper.Set("editing.tab_type", fixedValue)
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
if fixedValue, fixed := cs.validateAndFixValue("appearance.language", string(config.Appearance.Language)); fixed {
|
||||
cs.viper.Set("appearance.language", fixedValue)
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
// 验证保存选项配置
|
||||
if fixedValue, fixed := cs.validateAndFixValue("editing.auto_save_delay", config.Editing.AutoSaveDelay); fixed {
|
||||
cs.viper.Set("editing.auto_save_delay", fixedValue)
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
if fixedValue, fixed := cs.validateAndFixValue("editing.change_threshold", config.Editing.ChangeThreshold); fixed {
|
||||
cs.viper.Set("editing.change_threshold", fixedValue)
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
if fixedValue, fixed := cs.validateAndFixValue("editing.min_save_interval", config.Editing.MinSaveInterval); fixed {
|
||||
cs.viper.Set("editing.min_save_interval", fixedValue)
|
||||
hasChanges = true
|
||||
}
|
||||
|
||||
// 如果有修正,保存配置
|
||||
if hasChanges {
|
||||
if err := cs.viper.WriteConfig(); err != nil {
|
||||
return &ConfigError{Operation: "save_validated_config", Err: err}
|
||||
}
|
||||
cs.logger.Info("Config: Configuration validated and fixed")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewConfigService 创建新的配置服务实例
|
||||
func NewConfigService(logger *log.LoggerService) *ConfigService {
|
||||
// 设置日志服务
|
||||
@@ -268,16 +85,14 @@ func NewConfigService(logger *log.LoggerService) *ConfigService {
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
// 初始化配置通知服务
|
||||
service.notificationService = NewConfigNotificationService(v, logger)
|
||||
|
||||
// 初始化配置
|
||||
if err := service.initConfig(); err != nil {
|
||||
service.logger.Error("Config: Failed to initialize config", "error", err)
|
||||
}
|
||||
|
||||
// 验证并修正配置
|
||||
if err := service.validateAllConfig(); err != nil {
|
||||
service.logger.Error("Config: Failed to validate config", "error", err)
|
||||
}
|
||||
|
||||
// 启动配置文件监听
|
||||
service.startWatching()
|
||||
|
||||
@@ -290,7 +105,9 @@ func setDefaults(v *viper.Viper) {
|
||||
|
||||
// 通用设置默认值
|
||||
v.SetDefault("general.always_on_top", defaultConfig.General.AlwaysOnTop)
|
||||
v.SetDefault("general.data_path", defaultConfig.General.DataPath)
|
||||
v.SetDefault("general.default_data_path", defaultConfig.General.DefaultDataPath)
|
||||
v.SetDefault("general.use_custom_data_path", defaultConfig.General.UseCustomDataPath)
|
||||
v.SetDefault("general.custom_data_path", defaultConfig.General.CustomDataPath)
|
||||
v.SetDefault("general.enable_global_hotkey", defaultConfig.General.EnableGlobalHotkey)
|
||||
v.SetDefault("general.global_hotkey.ctrl", defaultConfig.General.GlobalHotkey.Ctrl)
|
||||
v.SetDefault("general.global_hotkey.shift", defaultConfig.General.GlobalHotkey.Shift)
|
||||
@@ -371,11 +188,9 @@ func (cs *ConfigService) startWatching() {
|
||||
// 设置配置变化回调
|
||||
cs.viper.OnConfigChange(func(e fsnotify.Event) {
|
||||
cs.logger.Info("Config: Config file changed", "file", e.Name, "operation", e.Op.String())
|
||||
// 注释掉自动更新时间戳,避免无限循环
|
||||
// err := cs.Set("metadata.last_updated", time.Now())
|
||||
// if err != nil {
|
||||
// cs.logger.Error("Config: Failed to update last_updated field", "error", err)
|
||||
// }
|
||||
|
||||
// 使用配置通知服务检查所有已注册的配置变更
|
||||
cs.notificationService.CheckConfigChanges()
|
||||
})
|
||||
|
||||
// 启动配置文件监听
|
||||
@@ -400,33 +215,14 @@ func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
|
||||
func (cs *ConfigService) Set(key string, value interface{}) error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
|
||||
// 验证并修正配置值
|
||||
validatedValue, wasFixed := cs.validateAndFixValue(key, value)
|
||||
|
||||
// 设置验证后的值
|
||||
cs.viper.Set(key, validatedValue)
|
||||
cs.viper.Set(key, value)
|
||||
|
||||
// 使用 WriteConfig 写入配置文件(会触发文件监听)
|
||||
if err := cs.viper.WriteConfig(); err != nil {
|
||||
return &ConfigError{Operation: "set_config", Err: err}
|
||||
}
|
||||
|
||||
if wasFixed {
|
||||
cs.logger.Debug("Config: Successfully set config with validation", "key", key, "original", value, "fixed", validatedValue)
|
||||
} else {
|
||||
cs.logger.Debug("Config: Successfully set config", "key", key, "value", value)
|
||||
}
|
||||
|
||||
// 检查是否是热键相关配置
|
||||
if cs.isHotkeyRelatedKey(key) {
|
||||
cs.logger.Info("Config: Detected hotkey configuration change", "key", key, "value", value)
|
||||
// 释放锁后通知,避免死锁
|
||||
go func() {
|
||||
cs.notifyHotkeyChange()
|
||||
}()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -455,75 +251,11 @@ func (cs *ConfigService) ResetConfig() error {
|
||||
}
|
||||
|
||||
// SetHotkeyChangeCallback 设置热键配置变更回调
|
||||
func (cs *ConfigService) SetHotkeyChangeCallback(callback func(enable bool, hotkey *models.HotkeyCombo) error) {
|
||||
func (cs *ConfigService) SetHotkeyChangeCallback(callback func(enable bool, hotkey *models.HotkeyCombo) error) error {
|
||||
cs.mu.Lock()
|
||||
defer cs.mu.Unlock()
|
||||
cs.hotkeyChangeCallback = callback
|
||||
}
|
||||
|
||||
// notifyHotkeyChange 通知热键配置变更
|
||||
func (cs *ConfigService) notifyHotkeyChange() {
|
||||
if cs.hotkeyChangeCallback == nil {
|
||||
return
|
||||
}
|
||||
|
||||
cs.hotkeyNotificationMu.Lock()
|
||||
defer cs.hotkeyNotificationMu.Unlock()
|
||||
|
||||
// 取消之前的定时器
|
||||
if cs.hotkeyNotificationTimer != nil {
|
||||
cs.hotkeyNotificationTimer.Stop()
|
||||
}
|
||||
|
||||
// 设置新的防抖定时器(200ms延迟)
|
||||
cs.hotkeyNotificationTimer = time.AfterFunc(200*time.Millisecond, func() {
|
||||
cs.logger.Debug("Config: Executing hotkey change notification after debounce")
|
||||
|
||||
// 获取当前热键配置
|
||||
config, err := cs.GetConfig()
|
||||
if err != nil {
|
||||
cs.logger.Error("Config: Failed to get config for hotkey notification", "error", err)
|
||||
return
|
||||
}
|
||||
|
||||
cs.logger.Debug("Config: Notifying hotkey service of configuration change",
|
||||
"enable", config.General.EnableGlobalHotkey,
|
||||
"ctrl", config.General.GlobalHotkey.Ctrl,
|
||||
"shift", config.General.GlobalHotkey.Shift,
|
||||
"alt", config.General.GlobalHotkey.Alt,
|
||||
"win", config.General.GlobalHotkey.Win,
|
||||
"key", config.General.GlobalHotkey.Key)
|
||||
|
||||
// 异步通知热键服务
|
||||
go func() {
|
||||
err := cs.hotkeyChangeCallback(config.General.EnableGlobalHotkey, &config.General.GlobalHotkey)
|
||||
if err != nil {
|
||||
cs.logger.Error("Config: Failed to notify hotkey change", "error", err)
|
||||
} else {
|
||||
cs.logger.Debug("Config: Successfully notified hotkey change")
|
||||
}
|
||||
}()
|
||||
})
|
||||
|
||||
cs.logger.Debug("Config: Hotkey change notification scheduled with debounce")
|
||||
}
|
||||
|
||||
// isHotkeyRelatedKey 检查是否是热键相关的配置键
|
||||
func (cs *ConfigService) isHotkeyRelatedKey(key string) bool {
|
||||
hotkeyKeys := []string{
|
||||
"general.enable_global_hotkey",
|
||||
"general.global_hotkey",
|
||||
"general.global_hotkey.ctrl",
|
||||
"general.global_hotkey.shift",
|
||||
"general.global_hotkey.alt",
|
||||
"general.global_hotkey.win",
|
||||
"general.global_hotkey.key",
|
||||
}
|
||||
|
||||
for _, hotkeyKey := range hotkeyKeys {
|
||||
if strings.HasPrefix(key, hotkeyKey) || key == hotkeyKey {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
// 创建热键监听器并注册
|
||||
hotkeyListener := CreateHotkeyListener(callback)
|
||||
return cs.notificationService.RegisterListener(hotkeyListener)
|
||||
}
|
||||
|
@@ -304,8 +304,16 @@ func (ds *DocumentService) ensureDocumentsDir() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// 获取实际的数据路径(如果启用自定义路径则使用自定义路径,否则使用默认路径)
|
||||
var dataPath string
|
||||
if config.General.UseCustomDataPath && config.General.CustomDataPath != "" {
|
||||
dataPath = config.General.CustomDataPath
|
||||
} else {
|
||||
dataPath = config.General.DefaultDataPath
|
||||
}
|
||||
|
||||
// 创建文档目录
|
||||
docsDir := filepath.Join(config.General.DataPath, "docs")
|
||||
docsDir := filepath.Join(dataPath, "docs")
|
||||
err = os.MkdirAll(docsDir, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -320,7 +328,16 @@ func (ds *DocumentService) getDocumentsDir() (string, error) {
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return filepath.Join(config.General.DataPath, "docs"), nil
|
||||
|
||||
// 获取实际的数据路径(如果启用自定义路径则使用自定义路径,否则使用默认路径)
|
||||
var dataPath string
|
||||
if config.General.UseCustomDataPath && config.General.CustomDataPath != "" {
|
||||
dataPath = config.General.CustomDataPath
|
||||
} else {
|
||||
dataPath = config.General.DefaultDataPath
|
||||
}
|
||||
|
||||
return filepath.Join(dataPath, "docs"), nil
|
||||
}
|
||||
|
||||
// getDefaultDocumentPath 获取默认文档路径
|
||||
|
@@ -33,13 +33,17 @@ func NewServiceManager() *ServiceManager {
|
||||
// 初始化热键服务
|
||||
hotkeyService := NewHotkeyService(configService, logger)
|
||||
|
||||
// 设置热键配置变更回调
|
||||
configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error {
|
||||
// 使用新的配置通知系统设置热键配置变更监听
|
||||
err := configService.SetHotkeyChangeCallback(func(enable bool, hotkey *models.HotkeyCombo) error {
|
||||
return hotkeyService.UpdateHotkey(enable, hotkey)
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Failed to set hotkey change callback", "error", err)
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// 初始化文档服务
|
||||
err := documentService.Initialize()
|
||||
err = documentService.Initialize()
|
||||
if err != nil {
|
||||
logger.Error("Failed to initialize document service", "error", err)
|
||||
panic(err)
|
||||
|
Reference in New Issue
Block a user