✨ Add optimistic concurrency control and atomic file operations
This commit is contained in:
@@ -2,7 +2,7 @@
|
|||||||
// This file is automatically generated. DO NOT EDIT
|
// This file is automatically generated. DO NOT EDIT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FileService 提供原子化文件操作
|
* FileService 提供文件操作
|
||||||
* @module
|
* @module
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -10,6 +10,10 @@
|
|||||||
// @ts-ignore: Unused imports
|
// @ts-ignore: Unused imports
|
||||||
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
import {Call as $Call, Create as $Create} from "@wailsio/runtime";
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-ignore: Unused imports
|
||||||
|
import * as time$0 from "../../../time/models.js";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* DeleteFile 删除文件
|
* DeleteFile 删除文件
|
||||||
*/
|
*/
|
||||||
@@ -34,6 +38,14 @@ export function FileExists(filePath: string): Promise<boolean> & { cancel(): voi
|
|||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GetFileModTime 获取文件的修改时间
|
||||||
|
*/
|
||||||
|
export function GetFileModTime(filePath: string): Promise<time$0.Time> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(2240854203, filePath) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* LoadJSON 从文件加载JSON数据
|
* LoadJSON 从文件加载JSON数据
|
||||||
*/
|
*/
|
||||||
@@ -43,9 +55,19 @@ export function LoadJSON(filePath: string, target: any): Promise<void> & { cance
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* SaveJSON 原子化保存JSON数据到文件
|
* SaveJSON 保存JSON数据到文件
|
||||||
*/
|
*/
|
||||||
export function SaveJSON(filePath: string, data: any): Promise<void> & { cancel(): void } {
|
export function SaveJSON(filePath: string, data: any): Promise<void> & { cancel(): void } {
|
||||||
let $resultPromise = $Call.ByID(3646933935, filePath, data) as any;
|
let $resultPromise = $Call.ByID(3646933935, filePath, data) as any;
|
||||||
return $resultPromise;
|
return $resultPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* SaveJSONWithCheck 保存JSON数据到文件,带并发检查
|
||||||
|
* expectedModTime是期望的文件修改时间,如果为零值则不检查
|
||||||
|
* onConflict是冲突处理函数,如果文件已被修改且此函数不为nil,则调用此函数合并数据
|
||||||
|
*/
|
||||||
|
export function SaveJSONWithCheck(filePath: string, data: any, expectedModTime: time$0.Time, onConflict: any): Promise<void> & { cancel(): void } {
|
||||||
|
let $resultPromise = $Call.ByID(4074237977, filePath, data, expectedModTime, onConflict) as any;
|
||||||
|
return $resultPromise;
|
||||||
|
}
|
||||||
|
@@ -1,11 +1,13 @@
|
|||||||
package services
|
package services
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
"voidraft/internal/models"
|
"voidraft/internal/models"
|
||||||
)
|
)
|
||||||
@@ -16,8 +18,8 @@ type ConfigService struct {
|
|||||||
configPath string
|
configPath string
|
||||||
rootDir string
|
rootDir string
|
||||||
homePath string
|
homePath string
|
||||||
mutex sync.RWMutex
|
config *models.AppConfig
|
||||||
config *models.AppConfig // 缓存最新配置
|
lastModTime time.Time // 上次读取配置文件的修改时间
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewConfigService 创建新的配置服务实例
|
// NewConfigService 创建新的配置服务实例
|
||||||
@@ -40,16 +42,16 @@ func NewConfigService(fileService *FileService) *ConfigService {
|
|||||||
rootDir: defaultConfig.Paths.RootDir,
|
rootDir: defaultConfig.Paths.RootDir,
|
||||||
configPath: defaultConfig.Paths.ConfigPath,
|
configPath: defaultConfig.Paths.ConfigPath,
|
||||||
homePath: homePath,
|
homePath: homePath,
|
||||||
config: defaultConfig, // 初始化缓存配置
|
config: defaultConfig,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 初始化配置目录和文件(非锁定方式)
|
// 初始化配置目录和文件
|
||||||
service.initializeConfig()
|
service.initializeConfig()
|
||||||
|
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
// initializeConfig 初始化配置目录和文件,避免死锁
|
// initializeConfig 初始化配置目录和文件
|
||||||
func (cs *ConfigService) initializeConfig() {
|
func (cs *ConfigService) initializeConfig() {
|
||||||
// 确保配置目录存在
|
// 确保配置目录存在
|
||||||
dirPath := filepath.Join(cs.homePath, cs.rootDir)
|
dirPath := filepath.Join(cs.homePath, cs.rootDir)
|
||||||
@@ -78,149 +80,251 @@ func (cs *ConfigService) initializeConfig() {
|
|||||||
log.Printf("Config file not found, creating default config")
|
log.Printf("Config file not found, creating default config")
|
||||||
// 创建默认配置文件
|
// 创建默认配置文件
|
||||||
defaultConfig := models.NewDefaultAppConfig()
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
if err := cs.saveAppConfigInitial(defaultConfig); err != nil {
|
if err := cs.saveAppConfig(defaultConfig); err != nil {
|
||||||
log.Printf("Failed to save default config: %v", err)
|
log.Printf("Failed to save default config: %v", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 加载现有配置
|
// 加载现有配置
|
||||||
log.Printf("Loading existing config file")
|
log.Printf("Loading existing config file")
|
||||||
existingConfig, err := cs.loadAppConfigInitial()
|
existingConfig, modTime, err := cs.loadAppConfigWithModTime()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to load existing config, using default: %v", err)
|
log.Printf("Failed to load existing config, using default: %v", err)
|
||||||
} else {
|
} else {
|
||||||
cs.config = existingConfig
|
cs.config = existingConfig
|
||||||
|
cs.lastModTime = modTime
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// saveAppConfigInitial 初始化时保存配置,不使用互斥锁
|
|
||||||
func (cs *ConfigService) saveAppConfigInitial(config *models.AppConfig) error {
|
|
||||||
// 更新配置元数据
|
|
||||||
config.Metadata.LastUpdated = time.Now()
|
|
||||||
|
|
||||||
// 保存到文件
|
|
||||||
configPath := cs.GetFullConfigPath()
|
|
||||||
log.Printf("saveAppConfigInitial: Saving to %s", configPath)
|
|
||||||
|
|
||||||
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
|
|
||||||
return fmt.Errorf("failed to save initial config file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新内存中的配置
|
|
||||||
cs.config = config
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// loadAppConfigInitial 初始化时加载配置,不使用互斥锁
|
|
||||||
func (cs *ConfigService) loadAppConfigInitial() (*models.AppConfig, error) {
|
|
||||||
config := &models.AppConfig{}
|
|
||||||
configPath := cs.GetFullConfigPath()
|
|
||||||
|
|
||||||
if err := cs.fileService.LoadJSON(configPath, config); err != nil {
|
|
||||||
return nil, fmt.Errorf("failed to load initial config file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
return config, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// initConfigDir 确保目录存在,如不存在则创建
|
|
||||||
func (cs *ConfigService) initConfigDir() error {
|
|
||||||
configDir := filepath.Join(cs.homePath, cs.rootDir)
|
|
||||||
return cs.fileService.EnsureDir(configDir)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetFullConfigPath 获取完整的配置文件路径
|
// GetFullConfigPath 获取完整的配置文件路径
|
||||||
func (cs *ConfigService) GetFullConfigPath() string {
|
func (cs *ConfigService) GetFullConfigPath() string {
|
||||||
return filepath.Join(cs.homePath, cs.rootDir, cs.configPath)
|
return filepath.Join(cs.homePath, cs.rootDir, cs.configPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// mergeConfigs 合并两个配置,实现智能合并策略
|
||||||
|
func (cs *ConfigService) mergeConfigs(currentConfig *models.AppConfig, existingData []byte) (*models.AppConfig, error) {
|
||||||
|
// 解析磁盘配置
|
||||||
|
var diskConfig models.AppConfig
|
||||||
|
if err := json.Unmarshal(existingData, &diskConfig); err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse existing config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建合并后的配置(基于磁盘配置)
|
||||||
|
mergedConfig := diskConfig
|
||||||
|
|
||||||
|
// ===== 1. 编辑器配置合并策略 =====
|
||||||
|
// 编辑器配置是用户最直接的操作,总是保留当前配置
|
||||||
|
log.Printf("Merge: Preserving current editor config")
|
||||||
|
mergedConfig.Editor = currentConfig.Editor
|
||||||
|
|
||||||
|
// ===== 2. 路径配置合并策略 =====
|
||||||
|
// 如果当前实例已经修改了根目录(非默认值),保留当前配置
|
||||||
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
|
|
||||||
|
if currentConfig.Paths.RootDir != defaultConfig.Paths.RootDir &&
|
||||||
|
currentConfig.Paths.RootDir != diskConfig.Paths.RootDir {
|
||||||
|
log.Printf("Merge: Using custom root directory from current config: %s", currentConfig.Paths.RootDir)
|
||||||
|
mergedConfig.Paths.RootDir = currentConfig.Paths.RootDir
|
||||||
|
} else {
|
||||||
|
log.Printf("Merge: Using root directory from disk config: %s", diskConfig.Paths.RootDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 对配置路径采用相同策略
|
||||||
|
if currentConfig.Paths.ConfigPath != defaultConfig.Paths.ConfigPath &&
|
||||||
|
currentConfig.Paths.ConfigPath != diskConfig.Paths.ConfigPath {
|
||||||
|
log.Printf("Merge: Using custom config path from current config: %s", currentConfig.Paths.ConfigPath)
|
||||||
|
mergedConfig.Paths.ConfigPath = currentConfig.Paths.ConfigPath
|
||||||
|
} else {
|
||||||
|
log.Printf("Merge: Using config path from disk config: %s", diskConfig.Paths.ConfigPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== 3. 元数据合并策略 =====
|
||||||
|
// 版本号使用较高的版本
|
||||||
|
if compareVersions(currentConfig.Metadata.Version, diskConfig.Metadata.Version) > 0 {
|
||||||
|
log.Printf("Merge: Using higher version from current config: %s", currentConfig.Metadata.Version)
|
||||||
|
mergedConfig.Metadata.Version = currentConfig.Metadata.Version
|
||||||
|
} else {
|
||||||
|
log.Printf("Merge: Using version from disk config: %s", diskConfig.Metadata.Version)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新时间总是使用当前时间
|
||||||
|
mergedConfig.Metadata.LastUpdated = time.Now()
|
||||||
|
|
||||||
|
// ===== 4. 其他配置项 =====
|
||||||
|
|
||||||
|
log.Printf("Merge: Completed using comprehensive merge strategy")
|
||||||
|
return &mergedConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// compareVersions 比较两个版本号
|
||||||
|
// 返回: 1 如果v1>v2, 0 如果v1=v2, -1 如果v1<v2
|
||||||
|
func compareVersions(v1, v2 string) int {
|
||||||
|
// 分割版本号
|
||||||
|
parts1 := strings.Split(v1, ".")
|
||||||
|
parts2 := strings.Split(v2, ".")
|
||||||
|
|
||||||
|
// 确保两个版本号有相同数量的部分
|
||||||
|
for len(parts1) < len(parts2) {
|
||||||
|
parts1 = append(parts1, "0")
|
||||||
|
}
|
||||||
|
for len(parts2) < len(parts1) {
|
||||||
|
parts2 = append(parts2, "0")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 逐个比较各部分
|
||||||
|
for i := 0; i < len(parts1); i++ {
|
||||||
|
num1, err1 := strconv.Atoi(parts1[i])
|
||||||
|
num2, err2 := strconv.Atoi(parts2[i])
|
||||||
|
|
||||||
|
// 如果有无法解析为数字的部分,按字符串比较
|
||||||
|
if err1 != nil || err2 != nil {
|
||||||
|
if parts1[i] > parts2[i] {
|
||||||
|
return 1
|
||||||
|
} else if parts1[i] < parts2[i] {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数值比较
|
||||||
|
if num1 > num2 {
|
||||||
|
return 1
|
||||||
|
} else if num1 < num2 {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有部分都相等
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// loadAppConfigWithModTime 从文件加载配置,同时返回文件修改时间
|
||||||
|
func (cs *ConfigService) loadAppConfigWithModTime() (*models.AppConfig, time.Time, error) {
|
||||||
|
config := &models.AppConfig{}
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
|
||||||
|
// 先获取文件修改时间
|
||||||
|
modTime, err := cs.fileService.GetFileModTime(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("failed to get config file modification time: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 然后加载文件内容
|
||||||
|
if err := cs.fileService.LoadJSON(configPath, config); err != nil {
|
||||||
|
return nil, time.Time{}, fmt.Errorf("failed to load config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return config, modTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// handleConfigConflict 处理配置冲突的回调函数
|
||||||
|
func (cs *ConfigService) handleConfigConflict(existingData []byte) (interface{}, error) {
|
||||||
|
// 如果没有当前配置,无法合并
|
||||||
|
if cs.config == nil {
|
||||||
|
return nil, fmt.Errorf("no current config to merge")
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并配置
|
||||||
|
mergedConfig, err := cs.mergeConfigs(cs.config, existingData)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新内存中的配置
|
||||||
|
cs.config = mergedConfig
|
||||||
|
|
||||||
|
return mergedConfig, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部方法:从文件加载配置
|
||||||
|
func (cs *ConfigService) loadAppConfig() (*models.AppConfig, error) {
|
||||||
|
config, modTime, err := cs.loadAppConfigWithModTime()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新最后修改时间
|
||||||
|
cs.lastModTime = modTime
|
||||||
|
return config, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 内部方法:保存配置到文件
|
||||||
|
func (cs *ConfigService) saveAppConfig(config *models.AppConfig) error {
|
||||||
|
// 更新配置元数据
|
||||||
|
config.Metadata.LastUpdated = time.Now()
|
||||||
|
|
||||||
|
// 保存到文件,使用乐观并发控制
|
||||||
|
configPath := cs.GetFullConfigPath()
|
||||||
|
log.Printf("saveAppConfig: Saving to %s with last mod time: %s",
|
||||||
|
configPath, cs.lastModTime.Format(time.RFC3339))
|
||||||
|
|
||||||
|
err := cs.fileService.SaveJSONWithCheck(
|
||||||
|
configPath,
|
||||||
|
config,
|
||||||
|
cs.lastModTime,
|
||||||
|
cs.handleConfigConflict,
|
||||||
|
)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to save config file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新内存中的配置和最后修改时间
|
||||||
|
cs.config = config
|
||||||
|
|
||||||
|
// 获取最新的修改时间
|
||||||
|
newModTime, err := cs.fileService.GetFileModTime(configPath)
|
||||||
|
if err == nil {
|
||||||
|
cs.lastModTime = newModTime
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetAppConfig 获取应用配置
|
// GetAppConfig 获取应用配置
|
||||||
func (cs *ConfigService) GetAppConfig() (*models.AppConfig, error) {
|
func (cs *ConfigService) GetAppConfig() (*models.AppConfig, error) {
|
||||||
cs.mutex.RLock()
|
|
||||||
defer cs.mutex.RUnlock()
|
|
||||||
|
|
||||||
// 返回内存中的配置副本
|
// 返回内存中的配置副本
|
||||||
if cs.config != nil {
|
if cs.config != nil {
|
||||||
return cs.config, nil
|
return cs.config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从文件加载
|
// 从文件加载
|
||||||
config := &models.AppConfig{}
|
config, err := cs.loadAppConfig()
|
||||||
configPath := cs.GetFullConfigPath()
|
if err != nil {
|
||||||
|
log.Printf("GetAppConfig: Failed to load config: %v", err)
|
||||||
log.Printf("GetAppConfig: Loading from %s", configPath)
|
// 使用默认配置
|
||||||
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
// 如果配置文件存在,则加载
|
cs.config = defaultConfig
|
||||||
if cs.fileService.FileExists(configPath) {
|
|
||||||
if err := cs.fileService.LoadJSON(configPath, config); err != nil {
|
|
||||||
log.Printf("GetAppConfig: Failed to load config: %v", err)
|
|
||||||
return nil, fmt.Errorf("failed to load config file: %w", err)
|
|
||||||
}
|
|
||||||
log.Printf("GetAppConfig: Successfully loaded config")
|
|
||||||
// 更新内存中的配置
|
|
||||||
cs.config = config
|
|
||||||
} else {
|
|
||||||
// 文件不存在,使用默认配置
|
|
||||||
log.Printf("GetAppConfig: Config file not found, using default")
|
|
||||||
config = models.NewDefaultAppConfig()
|
|
||||||
|
|
||||||
// 保存默认配置到文件
|
// 保存默认配置到文件
|
||||||
if err := cs.SaveAppConfig(config); err != nil {
|
if err := cs.saveAppConfig(defaultConfig); err != nil {
|
||||||
log.Printf("GetAppConfig: Failed to save default config: %v", err)
|
log.Printf("GetAppConfig: Failed to save default config: %v", err)
|
||||||
return nil, fmt.Errorf("failed to save default config: %w", err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return defaultConfig, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新内存中的配置
|
||||||
|
cs.config = config
|
||||||
return config, nil
|
return config, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveAppConfig 保存应用配置
|
// SaveAppConfig 保存应用配置
|
||||||
func (cs *ConfigService) SaveAppConfig(config *models.AppConfig) error {
|
func (cs *ConfigService) SaveAppConfig(config *models.AppConfig) error {
|
||||||
cs.mutex.Lock()
|
return cs.saveAppConfig(config)
|
||||||
defer cs.mutex.Unlock()
|
|
||||||
|
|
||||||
// 更新配置元数据
|
|
||||||
config.Metadata.LastUpdated = time.Now()
|
|
||||||
|
|
||||||
// 保存到文件
|
|
||||||
configPath := cs.GetFullConfigPath()
|
|
||||||
log.Printf("SaveAppConfig: Saving to %s", configPath)
|
|
||||||
|
|
||||||
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
|
|
||||||
log.Printf("SaveAppConfig: Failed to save config: %v", err)
|
|
||||||
return fmt.Errorf("failed to save config file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新内存中的配置
|
|
||||||
cs.config = config
|
|
||||||
log.Printf("SaveAppConfig: Successfully saved config")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateEditorConfig 更新编辑器配置
|
// UpdateEditorConfig 更新编辑器配置
|
||||||
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
|
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
|
||||||
cs.mutex.Lock()
|
|
||||||
defer cs.mutex.Unlock()
|
|
||||||
|
|
||||||
// 如果内存中已有配置,直接更新
|
// 如果内存中已有配置,直接更新
|
||||||
if cs.config != nil {
|
if cs.config != nil {
|
||||||
log.Printf("UpdateEditorConfig: Updating in-memory editor config: %+v", editorConfig)
|
log.Printf("UpdateEditorConfig: Updating in-memory editor config: %+v", editorConfig)
|
||||||
cs.config.Editor = editorConfig
|
cs.config.Editor = editorConfig
|
||||||
|
return cs.saveAppConfig(cs.config)
|
||||||
// 保存到文件
|
|
||||||
configPath := cs.GetFullConfigPath()
|
|
||||||
if err := cs.fileService.SaveJSON(configPath, cs.config); err != nil {
|
|
||||||
log.Printf("UpdateEditorConfig: Failed to save config: %v", err)
|
|
||||||
return fmt.Errorf("failed to save config file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Printf("UpdateEditorConfig: Successfully saved updated config")
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 没有内存中的配置,需要先加载
|
// 没有内存中的配置,需要先加载
|
||||||
config, err := cs.loadAppConfigInitial()
|
config, err := cs.loadAppConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("UpdateEditorConfig: Failed to load config: %v", err)
|
log.Printf("UpdateEditorConfig: Failed to load config: %v", err)
|
||||||
// 使用默认配置
|
// 使用默认配置
|
||||||
@@ -230,35 +334,19 @@ func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) er
|
|||||||
// 更新编辑器配置
|
// 更新编辑器配置
|
||||||
config.Editor = editorConfig
|
config.Editor = editorConfig
|
||||||
|
|
||||||
// 更新配置元数据
|
|
||||||
config.Metadata.LastUpdated = time.Now()
|
|
||||||
|
|
||||||
// 保存到文件
|
// 保存到文件
|
||||||
configPath := cs.GetFullConfigPath()
|
return cs.saveAppConfig(config)
|
||||||
if err := cs.fileService.SaveJSON(configPath, config); err != nil {
|
|
||||||
log.Printf("UpdateEditorConfig: Failed to save config: %v", err)
|
|
||||||
return fmt.Errorf("failed to save config file: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新内存中的配置
|
|
||||||
cs.config = config
|
|
||||||
log.Printf("UpdateEditorConfig: Successfully saved config with updated editor settings")
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetEditorConfig 获取编辑器配置
|
// GetEditorConfig 获取编辑器配置
|
||||||
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
|
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
|
||||||
cs.mutex.RLock()
|
|
||||||
defer cs.mutex.RUnlock()
|
|
||||||
|
|
||||||
// 如果内存中已有配置,直接返回
|
// 如果内存中已有配置,直接返回
|
||||||
if cs.config != nil {
|
if cs.config != nil {
|
||||||
return cs.config.Editor, nil
|
return cs.config.Editor, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 否则从文件加载
|
// 否则从文件加载
|
||||||
config, err := cs.loadAppConfigInitial()
|
config, err := cs.loadAppConfig()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("GetEditorConfig: Failed to load config: %v", err)
|
log.Printf("GetEditorConfig: Failed to load config: %v", err)
|
||||||
// 使用默认配置
|
// 使用默认配置
|
||||||
@@ -275,24 +363,18 @@ func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
|
|||||||
|
|
||||||
// ResetToDefault 重置为默认配置
|
// ResetToDefault 重置为默认配置
|
||||||
func (cs *ConfigService) ResetToDefault() error {
|
func (cs *ConfigService) ResetToDefault() error {
|
||||||
cs.mutex.Lock()
|
|
||||||
defer cs.mutex.Unlock()
|
|
||||||
|
|
||||||
// 创建默认配置
|
// 创建默认配置
|
||||||
defaultConfig := models.NewDefaultAppConfig()
|
defaultConfig := models.NewDefaultAppConfig()
|
||||||
|
|
||||||
log.Printf("ResetToDefault: Resetting to default config")
|
log.Printf("ResetToDefault: Resetting to default config")
|
||||||
|
|
||||||
// 保存到文件
|
// 保存到文件
|
||||||
configPath := cs.GetFullConfigPath()
|
err := cs.saveAppConfig(defaultConfig)
|
||||||
if err := cs.fileService.SaveJSON(configPath, defaultConfig); err != nil {
|
if err != nil {
|
||||||
log.Printf("ResetToDefault: Failed to save default config: %v", err)
|
log.Printf("ResetToDefault: Failed to save default config: %v", err)
|
||||||
return fmt.Errorf("failed to save default config: %w", err)
|
return fmt.Errorf("failed to save default config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新内存中的配置
|
|
||||||
cs.config = defaultConfig
|
|
||||||
log.Printf("ResetToDefault: Successfully reset to default config")
|
log.Printf("ResetToDefault: Successfully reset to default config")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@@ -6,13 +6,11 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FileService 提供原子化文件操作
|
// FileService 提供文件操作
|
||||||
type FileService struct {
|
type FileService struct{}
|
||||||
mutex sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
// NewFileService 创建新的文件服务实例
|
// NewFileService 创建新的文件服务实例
|
||||||
func NewFileService() *FileService {
|
func NewFileService() *FileService {
|
||||||
@@ -21,14 +19,6 @@ func NewFileService() *FileService {
|
|||||||
|
|
||||||
// EnsureDir 确保目录存在,如不存在则创建
|
// EnsureDir 确保目录存在,如不存在则创建
|
||||||
func (fs *FileService) EnsureDir(dirPath string) error {
|
func (fs *FileService) EnsureDir(dirPath string) error {
|
||||||
fs.mutex.Lock()
|
|
||||||
defer fs.mutex.Unlock()
|
|
||||||
|
|
||||||
return fs.ensureDirNoLock(dirPath)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureDirNoLock 无锁版本的EnsureDir,仅供内部使用
|
|
||||||
func (fs *FileService) ensureDirNoLock(dirPath string) error {
|
|
||||||
log.Printf("EnsureDir: Checking directory: %s", dirPath)
|
log.Printf("EnsureDir: Checking directory: %s", dirPath)
|
||||||
|
|
||||||
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
|
||||||
@@ -45,52 +35,114 @@ func (fs *FileService) ensureDirNoLock(dirPath string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveJSON 原子化保存JSON数据到文件
|
// GetFileModTime 获取文件的修改时间
|
||||||
func (fs *FileService) SaveJSON(filePath string, data interface{}) error {
|
func (fs *FileService) GetFileModTime(filePath string) (time.Time, error) {
|
||||||
fs.mutex.Lock()
|
log.Printf("GetFileModTime: Getting modification time for file: %s", filePath)
|
||||||
defer fs.mutex.Unlock()
|
|
||||||
|
|
||||||
log.Printf("SaveJSON: Saving to file: %s", filePath)
|
fileInfo, err := os.Stat(filePath)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
log.Printf("GetFileModTime: File does not exist: %s", filePath)
|
||||||
|
return time.Time{}, fmt.Errorf("file does not exist: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("GetFileModTime: Failed to get file info: %v", err)
|
||||||
|
return time.Time{}, fmt.Errorf("failed to get file info: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
// 确保目录存在 - 使用无锁版本以避免死锁
|
modTime := fileInfo.ModTime()
|
||||||
|
log.Printf("GetFileModTime: File modification time: %s", modTime.Format(time.RFC3339))
|
||||||
|
return modTime, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SaveJSONWithCheck 保存JSON数据到文件,带并发检查
|
||||||
|
// expectedModTime是期望的文件修改时间,如果为零值则不检查
|
||||||
|
// onConflict是冲突处理函数,如果文件已被修改且此函数不为nil,则调用此函数合并数据
|
||||||
|
func (fs *FileService) SaveJSONWithCheck(filePath string, data interface{}, expectedModTime time.Time, onConflict func(existingData []byte) (interface{}, error)) error {
|
||||||
|
log.Printf("SaveJSONWithCheck: Saving to file with concurrency check: %s", filePath)
|
||||||
|
|
||||||
|
// 检查文件是否存在且已被修改
|
||||||
|
if !expectedModTime.IsZero() && fs.FileExists(filePath) {
|
||||||
|
currentModTime, err := fs.GetFileModTime(filePath)
|
||||||
|
if err == nil {
|
||||||
|
// 如果文件修改时间与预期不符,说明文件已被其他进程修改
|
||||||
|
if !currentModTime.Equal(expectedModTime) {
|
||||||
|
log.Printf("SaveJSONWithCheck: File has been modified since last read. Expected: %s, Current: %s",
|
||||||
|
expectedModTime.Format(time.RFC3339), currentModTime.Format(time.RFC3339))
|
||||||
|
|
||||||
|
// 如果提供了冲突处理函数,尝试解决冲突
|
||||||
|
if onConflict != nil {
|
||||||
|
log.Printf("SaveJSONWithCheck: Attempting to resolve conflict")
|
||||||
|
|
||||||
|
// 读取当前文件内容
|
||||||
|
existingData, err := os.ReadFile(filePath)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("SaveJSONWithCheck: Failed to read existing file: %v", err)
|
||||||
|
return fmt.Errorf("failed to read existing file for conflict resolution: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用冲突处理函数合并数据
|
||||||
|
mergedData, err := onConflict(existingData)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("SaveJSONWithCheck: Conflict resolution failed: %v", err)
|
||||||
|
return fmt.Errorf("conflict resolution failed: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用合并后的数据继续保存
|
||||||
|
data = mergedData
|
||||||
|
log.Printf("SaveJSONWithCheck: Conflict resolved, proceeding with merged data")
|
||||||
|
} else {
|
||||||
|
// 没有提供冲突处理函数,返回错误
|
||||||
|
return fmt.Errorf("concurrent modification detected")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
log.Printf("SaveJSONWithCheck: Could not check file modification time: %v", err)
|
||||||
|
// 继续执行,不中断操作
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保目录存在
|
||||||
dir := filepath.Dir(filePath)
|
dir := filepath.Dir(filePath)
|
||||||
if err := fs.ensureDirNoLock(dir); err != nil {
|
if err := fs.EnsureDir(dir); err != nil {
|
||||||
log.Printf("SaveJSON: Failed to create directory: %v", err)
|
log.Printf("SaveJSONWithCheck: Failed to create directory: %v", err)
|
||||||
return fmt.Errorf("failed to create directory: %w", err)
|
return fmt.Errorf("failed to create directory: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 将数据编码为JSON
|
// 将数据编码为JSON
|
||||||
jsonData, err := json.MarshalIndent(data, "", " ")
|
jsonData, err := json.MarshalIndent(data, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("SaveJSON: Failed to encode JSON: %v", err)
|
log.Printf("SaveJSONWithCheck: Failed to encode JSON: %v", err)
|
||||||
return fmt.Errorf("failed to encode JSON: %w", err)
|
return fmt.Errorf("failed to encode JSON: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 先写入临时文件
|
// 先写入临时文件
|
||||||
tempFile := filePath + ".tmp"
|
tempFile := filePath + ".tmp"
|
||||||
log.Printf("SaveJSON: Writing to temporary file: %s", tempFile)
|
log.Printf("SaveJSONWithCheck: Writing to temporary file: %s", tempFile)
|
||||||
if err := os.WriteFile(tempFile, jsonData, 0644); err != nil {
|
if err := os.WriteFile(tempFile, jsonData, 0644); err != nil {
|
||||||
log.Printf("SaveJSON: Failed to write temporary file: %v", err)
|
log.Printf("SaveJSONWithCheck: Failed to write temporary file: %v", err)
|
||||||
return fmt.Errorf("failed to write temporary file: %w", err)
|
return fmt.Errorf("failed to write temporary file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 原子替换原文件
|
// 原子替换原文件
|
||||||
log.Printf("SaveJSON: Replacing original file with temporary file")
|
log.Printf("SaveJSONWithCheck: Replacing original file with temporary file")
|
||||||
if err := os.Rename(tempFile, filePath); err != nil {
|
if err := os.Rename(tempFile, filePath); err != nil {
|
||||||
os.Remove(tempFile) // 清理临时文件
|
os.Remove(tempFile) // 清理临时文件
|
||||||
log.Printf("SaveJSON: Failed to replace file: %v", err)
|
log.Printf("SaveJSONWithCheck: Failed to replace file: %v", err)
|
||||||
return fmt.Errorf("failed to replace file: %w", err)
|
return fmt.Errorf("failed to replace file: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Printf("SaveJSON: File saved successfully: %s", filePath)
|
log.Printf("SaveJSONWithCheck: File saved successfully: %s", filePath)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SaveJSON 保存JSON数据到文件
|
||||||
|
func (fs *FileService) SaveJSON(filePath string, data interface{}) error {
|
||||||
|
// 调用带并发检查的版本,但不提供冲突解决函数(保持原有行为)
|
||||||
|
return fs.SaveJSONWithCheck(filePath, data, time.Time{}, nil)
|
||||||
|
}
|
||||||
|
|
||||||
// LoadJSON 从文件加载JSON数据
|
// LoadJSON 从文件加载JSON数据
|
||||||
func (fs *FileService) LoadJSON(filePath string, target interface{}) error {
|
func (fs *FileService) LoadJSON(filePath string, target interface{}) error {
|
||||||
fs.mutex.Lock()
|
|
||||||
defer fs.mutex.Unlock()
|
|
||||||
|
|
||||||
log.Printf("LoadJSON: Loading from file: %s", filePath)
|
log.Printf("LoadJSON: Loading from file: %s", filePath)
|
||||||
|
|
||||||
// 检查文件是否存在
|
// 检查文件是否存在
|
||||||
@@ -126,9 +178,6 @@ func (fs *FileService) FileExists(filePath string) bool {
|
|||||||
|
|
||||||
// DeleteFile 删除文件
|
// DeleteFile 删除文件
|
||||||
func (fs *FileService) DeleteFile(filePath string) error {
|
func (fs *FileService) DeleteFile(filePath string) error {
|
||||||
fs.mutex.Lock()
|
|
||||||
defer fs.mutex.Unlock()
|
|
||||||
|
|
||||||
log.Printf("DeleteFile: Deleting file: %s", filePath)
|
log.Printf("DeleteFile: Deleting file: %s", filePath)
|
||||||
|
|
||||||
if !fs.FileExists(filePath) {
|
if !fs.FileExists(filePath) {
|
||||||
|
Reference in New Issue
Block a user