🎨 Refactor save service

This commit is contained in:
2025-04-30 01:52:30 +08:00
parent d7a848e7ad
commit 198ba44ceb
17 changed files with 846 additions and 925 deletions

View File

@@ -14,13 +14,36 @@ const (
TabTypeTab TabType = "tab"
)
// EncodingType 定义文件编码格式类型
type EncodingType string
const (
// EncodingUTF8 UTF-8编码
EncodingUTF8 EncodingType = "UTF-8"
// EncodingUTF8BOM UTF-8带BOM编码
EncodingUTF8BOM EncodingType = "UTF-8-BOM"
// EncodingUTF16LE UTF-16小端编码
EncodingUTF16LE EncodingType = "UTF-16 LE"
// EncodingUTF16BE UTF-16大端编码
EncodingUTF16BE EncodingType = "UTF-16 BE"
// EncodingISO88591 ISO-8859-1编码
EncodingISO88591 EncodingType = "ISO-8859-1"
// EncodingGB18030 GB18030编码
EncodingGB18030 EncodingType = "GB18030"
// EncodingGBK GBK编码
EncodingGBK EncodingType = "GBK"
// EncodingBig5 Big5编码
EncodingBig5 EncodingType = "Big5"
)
// EditorConfig 定义编辑器配置
type EditorConfig struct {
FontSize int `json:"fontSize"` // 字体大小
Encoding string `json:"encoding"` // 文件保存的编码
EnableTabIndent bool `json:"enableTabIndent"` // 是否启用Tab缩进
TabSize int `json:"tabSize"` // Tab大小
TabType TabType `json:"tabType"` // Tab类型空格或Tab
FontSize int `json:"fontSize"` // 字体大小
Encoding EncodingType `json:"encoding"` // 文件保存的编码
EnableTabIndent bool `json:"enableTabIndent"` // 是否启用Tab缩进
TabSize int `json:"tabSize"` // Tab大小
TabType TabType `json:"tabType"` // Tab类型空格或Tab
Language LanguageType `json:"language"` // 界面语言
}
// LanguageType 语言类型定义
@@ -44,7 +67,6 @@ type AppConfig struct {
Editor EditorConfig `json:"editor"` // 编辑器配置
Paths PathConfig `json:"paths"` // 路径配置
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
Language LanguageType `json:"language"` // 界面语言
}
// ConfigMetadata 配置元数据
@@ -58,10 +80,11 @@ func NewDefaultAppConfig() *AppConfig {
return &AppConfig{
Editor: EditorConfig{
FontSize: 13,
Encoding: "UTF-8",
Encoding: EncodingUTF8,
EnableTabIndent: true,
TabSize: 4,
TabType: TabTypeSpaces,
Language: LangZhCN,
},
Paths: PathConfig{
RootDir: ".voidraft",
@@ -71,6 +94,5 @@ func NewDefaultAppConfig() *AppConfig {
Version: "1.0.0",
LastUpdated: time.Now(),
},
Language: LangZhCN,
}
}

View File

@@ -1,340 +1,150 @@
package services
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"voidraft/internal/models"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// ConfigService 提供配置管理功能
type ConfigService struct {
fileService *FileService
configPath string
rootDir string
homePath string
config *models.AppConfig
lastModTime time.Time // 上次读取配置文件的修改时间
store *Store[models.AppConfig]
homePath string
configPath string
logger *log.LoggerService
}
type Service struct{}
// NewConfigService 创建新的配置服务实例
func NewConfigService(fileService *FileService) *ConfigService {
func NewConfigService() *ConfigService {
// 初始化日志服务
logger := log.New()
// 获取用户主目录
homePath, err := os.UserHomeDir()
if err != nil {
log.Printf("Failed to get user home directory: %v", err)
logger.Error("Config: Failed to get user home directory", "error", err)
homePath = "."
}
log.Printf("User home directory: %s", homePath)
// 创建默认配置
defaultConfig := models.NewDefaultAppConfig()
// 构造服务实例
service := &ConfigService{
fileService: fileService,
rootDir: defaultConfig.Paths.RootDir,
configPath: defaultConfig.Paths.ConfigPath,
homePath: homePath,
config: defaultConfig,
// 构建完整的配置文件路径
configFilePath := filepath.Join(homePath, defaultConfig.Paths.RootDir, defaultConfig.Paths.ConfigPath)
// 创建Store选项
storeOption := StoreOption{
FilePath: configFilePath,
AutoSave: true,
Logger: logger,
}
// 初始化配置目录和文件
service.initializeConfig()
// 创建存储服务
store := NewStore[models.AppConfig](storeOption)
// 构造配置服务实例
service := &ConfigService{
store: store,
homePath: homePath,
configPath: configFilePath,
logger: logger,
}
// 检查是否需要设置默认配置
config := store.Get()
if isEmptyConfig(config) {
err := store.Set(*defaultConfig)
if err != nil {
logger.Error("Config: Failed to set default config", "error", err)
}
}
return service
}
// initializeConfig 初始化配置目录和文件
func (cs *ConfigService) initializeConfig() {
// 确保配置目录存在
dirPath := filepath.Join(cs.homePath, cs.rootDir)
log.Printf("Creating config directory: %s", dirPath)
// 确保主目录存在
if err := cs.fileService.EnsureDir(dirPath); err != nil {
log.Printf("Failed to create config directory: %v", err)
return
// isEmptyConfig 检查配置是否为空
func isEmptyConfig(config models.AppConfig) bool {
// 检查基本字段
if config.Editor.FontSize == 0 && config.Paths.RootDir == "" {
return true
}
return false
}
// 确保配置文件所在目录存在
configDir := filepath.Dir(cs.GetFullConfigPath())
if configDir != dirPath {
if err := cs.fileService.EnsureDir(configDir); err != nil {
log.Printf("Failed to create config file directory: %v", err)
return
}
}
// GetConfig 获取完整应用配置
func (cs *ConfigService) GetConfig() (*models.AppConfig, error) {
config := cs.store.Get()
// 检查配置文件是否存在
configFilePath := cs.GetFullConfigPath()
log.Printf("Config file path: %s", configFilePath)
if !cs.fileService.FileExists(configFilePath) {
log.Printf("Config file not found, creating default config")
// 创建默认配置文件
// 如果配置为空,返回默认配置
if isEmptyConfig(config) {
defaultConfig := models.NewDefaultAppConfig()
if err := cs.saveAppConfig(defaultConfig); err != nil {
log.Printf("Failed to save default config: %v", err)
}
} else {
// 加载现有配置
log.Printf("Loading existing config file")
existingConfig, modTime, err := cs.loadAppConfigWithModTime()
if err != nil {
log.Printf("Failed to load existing config, using default: %v", err)
} else {
cs.config = existingConfig
cs.lastModTime = modTime
if err := cs.store.Set(*defaultConfig); err != nil {
cs.logger.Error("Config: Failed to save default config", "error", err)
}
return defaultConfig, nil
}
return &config, nil
}
// GetFullConfigPath 获取完整的配置文件路径
func (cs *ConfigService) GetFullConfigPath() string {
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 {
// SaveConfig 保存完整应用配置
func (cs *ConfigService) SaveConfig(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))
return cs.store.Set(*config)
}
err := cs.fileService.SaveJSONWithCheck(
configPath,
config,
cs.lastModTime,
cs.handleConfigConflict,
)
// ResetConfig 重置为默认配置
func (cs *ConfigService) ResetConfig() error {
defaultConfig := models.NewDefaultAppConfig()
err := cs.store.Set(*defaultConfig)
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
cs.logger.Error("Config: Failed to save default config", "error", err)
return fmt.Errorf("failed to reset config: %w", err)
}
return nil
}
// GetAppConfig 获取应用配置
func (cs *ConfigService) GetAppConfig() (*models.AppConfig, error) {
// 返回内存中的配置副本
if cs.config != nil {
return cs.config, nil
}
// 从文件加载
config, err := cs.loadAppConfig()
// GetEditorConfig 获取编辑器配置
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
config, err := cs.GetConfig()
if err != nil {
log.Printf("GetAppConfig: Failed to load config: %v", err)
// 使用默认配置
defaultConfig := models.NewDefaultAppConfig()
cs.config = defaultConfig
// 保存默认配置到文件
if err := cs.saveAppConfig(defaultConfig); err != nil {
log.Printf("GetAppConfig: Failed to save default config: %v", err)
}
return defaultConfig, nil
return models.EditorConfig{}, err
}
// 更新内存中的配置
cs.config = config
return config, nil
return config.Editor, nil
}
// SaveAppConfig 保存应用配置
func (cs *ConfigService) SaveAppConfig(config *models.AppConfig) error {
return cs.saveAppConfig(config)
// UpdateEditorConfig 更新编辑器配置
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
// 获取当前配置
config, err := cs.GetConfig()
if err != nil {
return err
}
// 更新编辑器配置
config.Editor = editorConfig
config.Metadata.LastUpdated = time.Now()
// 保存更新后的配置
return cs.store.Set(*config)
}
// GetLanguage 获取当前语言设置
func (cs *ConfigService) GetLanguage() (models.LanguageType, error) {
// 如果内存中已有配置,直接返回
if cs.config != nil {
return cs.config.Language, nil
}
// 否则从文件加载
config, err := cs.loadAppConfig()
editorConfig, err := cs.GetEditorConfig()
if err != nil {
log.Printf("GetLanguage: Failed to load config: %v", err)
// 使用默认配置
defaultConfig := models.NewDefaultAppConfig()
cs.config = defaultConfig
return defaultConfig.Language, nil
return "", err
}
// 更新内存中的配置
cs.config = config
log.Printf("GetLanguage: Retrieved language: %s", config.Language)
return config.Language, nil
return editorConfig.Language, nil
}
// SetLanguage 设置语言
@@ -344,89 +154,75 @@ func (cs *ConfigService) SetLanguage(language models.LanguageType) error {
return fmt.Errorf("unsupported language: %s", language)
}
// 如果内存中已有配置,直接更新
if cs.config != nil {
log.Printf("SetLanguage: Updating language to: %s", language)
cs.config.Language = language
return cs.saveAppConfig(cs.config)
}
// 没有内存中的配置,需要先加载
config, err := cs.loadAppConfig()
// 获取当前配置
config, err := cs.GetConfig()
if err != nil {
log.Printf("SetLanguage: Failed to load config: %v", err)
// 使用默认配置
config = models.NewDefaultAppConfig()
return err
}
// 更新语言
config.Language = language
// 更新语言
config.Editor.Language = language
config.Metadata.LastUpdated = time.Now()
// 保存到文件
return cs.saveAppConfig(config)
// 保存更新后的配置
return cs.store.Set(*config)
}
// UpdateEditorConfig 更新编辑器配置
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
// 如果内存中已有配置,直接更新
if cs.config != nil {
log.Printf("UpdateEditorConfig: Updating in-memory editor config: %+v", editorConfig)
cs.config.Editor = editorConfig
return cs.saveAppConfig(cs.config)
}
// 没有内存中的配置,需要先加载
config, err := cs.loadAppConfig()
// GetPathConfig 获取路径配置
func (cs *ConfigService) GetPathConfig() (models.PathConfig, error) {
config, err := cs.GetConfig()
if err != nil {
log.Printf("UpdateEditorConfig: Failed to load config: %v", err)
// 使用默认配置
config = models.NewDefaultAppConfig()
return models.PathConfig{}, err
}
// 更新编辑器配置
config.Editor = editorConfig
// 保存到文件
return cs.saveAppConfig(config)
return config.Paths, nil
}
// GetEditorConfig 获取编辑器配置
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
// 如果内存中已有配置,直接返回
if cs.config != nil {
return cs.config.Editor, nil
}
// 否则从文件加载
config, err := cs.loadAppConfig()
// UpdatePathConfig 更新路径配置
func (cs *ConfigService) UpdatePathConfig(pathConfig models.PathConfig) error {
// 获取当前配置
config, err := cs.GetConfig()
if err != nil {
log.Printf("GetEditorConfig: Failed to load config: %v", err)
// 使用默认配置
defaultConfig := models.NewDefaultAppConfig()
cs.config = defaultConfig
return defaultConfig.Editor, nil
return err
}
// 更新内存中的配置
cs.config = config
log.Printf("GetEditorConfig: Retrieved editor config: %+v", config.Editor)
return config.Editor, nil
// 更新路径配置
config.Paths = pathConfig
config.Metadata.LastUpdated = time.Now()
// 保存更新后的配置
return cs.store.Set(*config)
}
// ResetToDefault 重置为默认配置
func (cs *ConfigService) ResetToDefault() error {
// 创建默认配置
defaultConfig := models.NewDefaultAppConfig()
log.Printf("ResetToDefault: Resetting to default config")
// 保存到文件
err := cs.saveAppConfig(defaultConfig)
// GetMetadata 获取配置元数据
func (cs *ConfigService) GetMetadata() (models.ConfigMetadata, error) {
config, err := cs.GetConfig()
if err != nil {
log.Printf("ResetToDefault: Failed to save default config: %v", err)
return fmt.Errorf("failed to save default config: %w", err)
return models.ConfigMetadata{}, err
}
return config.Metadata, nil
}
// UpdateMetadata 更新配置元数据
func (cs *ConfigService) UpdateMetadata(metadata models.ConfigMetadata) error {
// 获取当前配置
config, err := cs.GetConfig()
if err != nil {
return err
}
log.Printf("ResetToDefault: Successfully reset to default config")
// 更新元数据
config.Metadata = metadata
config.Metadata.LastUpdated = time.Now()
// 保存更新后的配置
return cs.store.Set(*config)
}
// OnShutdown 服务关闭时调用
func (cs *ConfigService) OnShutdown() error {
// 如果有未保存的更改,保存数据
if cs.store.HasUnsavedChanges() {
return cs.store.Save()
}
return nil
}

View File

@@ -1,195 +0,0 @@
package services
import (
"encoding/json"
"fmt"
"log"
"os"
"path/filepath"
"time"
)
// FileService 提供文件操作
type FileService struct{}
// NewFileService 创建新的文件服务实例
func NewFileService() *FileService {
return &FileService{}
}
// EnsureDir 确保目录存在,如不存在则创建
func (fs *FileService) EnsureDir(dirPath string) error {
log.Printf("EnsureDir: Checking directory: %s", dirPath)
if _, err := os.Stat(dirPath); os.IsNotExist(err) {
log.Printf("EnsureDir: Directory does not exist, creating: %s", dirPath)
err := os.MkdirAll(dirPath, 0755)
if err != nil {
log.Printf("EnsureDir: Failed to create directory: %v", err)
return err
}
log.Printf("EnsureDir: Directory created successfully: %s", dirPath)
} else {
log.Printf("EnsureDir: Directory already exists: %s", dirPath)
}
return nil
}
// GetFileModTime 获取文件的修改时间
func (fs *FileService) GetFileModTime(filePath string) (time.Time, error) {
log.Printf("GetFileModTime: Getting modification time for 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)
if err := fs.EnsureDir(dir); err != nil {
log.Printf("SaveJSONWithCheck: Failed to create directory: %v", err)
return fmt.Errorf("failed to create directory: %w", err)
}
// 将数据编码为JSON
jsonData, err := json.MarshalIndent(data, "", " ")
if err != nil {
log.Printf("SaveJSONWithCheck: Failed to encode JSON: %v", err)
return fmt.Errorf("failed to encode JSON: %w", err)
}
// 先写入临时文件
tempFile := filePath + ".tmp"
log.Printf("SaveJSONWithCheck: Writing to temporary file: %s", tempFile)
if err := os.WriteFile(tempFile, jsonData, 0644); err != nil {
log.Printf("SaveJSONWithCheck: Failed to write temporary file: %v", err)
return fmt.Errorf("failed to write temporary file: %w", err)
}
// 原子替换原文件
log.Printf("SaveJSONWithCheck: Replacing original file with temporary file")
if err := os.Rename(tempFile, filePath); err != nil {
os.Remove(tempFile) // 清理临时文件
log.Printf("SaveJSONWithCheck: Failed to replace file: %v", err)
return fmt.Errorf("failed to replace file: %w", err)
}
log.Printf("SaveJSONWithCheck: File saved successfully: %s", filePath)
return nil
}
// SaveJSON 保存JSON数据到文件
func (fs *FileService) SaveJSON(filePath string, data interface{}) error {
// 调用带并发检查的版本,但不提供冲突解决函数(保持原有行为)
return fs.SaveJSONWithCheck(filePath, data, time.Time{}, nil)
}
// LoadJSON 从文件加载JSON数据
func (fs *FileService) LoadJSON(filePath string, target interface{}) error {
log.Printf("LoadJSON: Loading from file: %s", filePath)
// 检查文件是否存在
if _, err := os.Stat(filePath); os.IsNotExist(err) {
log.Printf("LoadJSON: File does not exist: %s", filePath)
return fmt.Errorf("file does not exist: %w", err)
}
// 读取文件内容
data, err := os.ReadFile(filePath)
if err != nil {
log.Printf("LoadJSON: Failed to read file: %v", err)
return fmt.Errorf("failed to read file: %w", err)
}
// 解析JSON数据
if err := json.Unmarshal(data, target); err != nil {
log.Printf("LoadJSON: Failed to parse JSON: %v", err)
return fmt.Errorf("failed to parse JSON: %w", err)
}
log.Printf("LoadJSON: File loaded successfully: %s", filePath)
return nil
}
// FileExists 检查文件是否存在
func (fs *FileService) FileExists(filePath string) bool {
_, err := os.Stat(filePath)
exists := !os.IsNotExist(err)
log.Printf("FileExists: Checking if file exists: %s, exists: %v", filePath, exists)
return exists
}
// DeleteFile 删除文件
func (fs *FileService) DeleteFile(filePath string) error {
log.Printf("DeleteFile: Deleting file: %s", filePath)
if !fs.FileExists(filePath) {
log.Printf("DeleteFile: File does not exist, nothing to delete: %s", filePath)
return nil // 文件不存在视为删除成功
}
err := os.Remove(filePath)
if err != nil {
log.Printf("DeleteFile: Failed to delete file: %v", err)
} else {
log.Printf("DeleteFile: File deleted successfully: %s", filePath)
}
return err
}

View File

@@ -6,20 +6,15 @@ import (
// ServiceManager 服务管理器,负责协调各个服务
type ServiceManager struct {
fileService *FileService
configService *ConfigService
}
// NewServiceManager 创建新的服务管理器实例
func NewServiceManager() *ServiceManager {
// 初始化文件服务
fileService := NewFileService()
// 初始化配置服务
configService := NewConfigService(fileService)
configService := NewConfigService()
return &ServiceManager{
fileService: fileService,
configService: configService,
}
}
@@ -27,16 +22,10 @@ func NewServiceManager() *ServiceManager {
// GetServices 获取所有wails服务列表
func (sm *ServiceManager) GetServices() []application.Service {
return []application.Service{
application.NewService(sm.fileService),
application.NewService(sm.configService),
}
}
// GetFileService 获取文件服务实例
func (sm *ServiceManager) GetFileService() *FileService {
return sm.fileService
}
// GetConfigService 获取配置服务实例
func (sm *ServiceManager) GetConfigService() *ConfigService {
return sm.configService

View File

@@ -0,0 +1,232 @@
package services
import (
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sync"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// StoreOption 存储服务配置选项
type StoreOption struct {
FilePath string // 存储文件路径
AutoSave bool // 是否自动保存
Logger *log.LoggerService
}
// Store 泛型存储服务
type Store[T any] struct {
option StoreOption
data T
dataMap map[string]any
unsaved bool
lock sync.RWMutex
logger *log.LoggerService
}
// NewStore 存储服务
func NewStore[T any](option StoreOption) *Store[T] {
logger := option.Logger
if logger == nil {
logger = log.New()
}
// 确保目录存在
if option.FilePath != "" {
dir := filepath.Dir(option.FilePath)
if err := os.MkdirAll(dir, 0755); err != nil {
logger.Error("store: Failed to create directory", "path", dir, "error", err)
}
}
store := &Store[T]{
option: option,
dataMap: make(map[string]any),
logger: logger,
}
// 加载数据
if err := store.load(); err != nil {
logger.Error("store: Failed to load data", "error", err)
}
return store
}
// load 加载数据
func (s *Store[T]) load() error {
if s.option.FilePath == "" {
return fmt.Errorf("store: FilePath not set")
}
// 如果文件不存在
if _, err := os.Stat(s.option.FilePath); os.IsNotExist(err) {
return nil
}
file, err := os.Open(s.option.FilePath)
if err != nil {
return fmt.Errorf("store: Failed to open file: %w", err)
}
defer file.Close()
bytes, err := io.ReadAll(file)
if err != nil {
return fmt.Errorf("store: Failed to read file: %w", err)
}
if len(bytes) == 0 {
return nil
}
s.lock.Lock()
defer s.lock.Unlock()
if err := json.Unmarshal(bytes, &s.data); err != nil {
// 尝试加载为map格式
if err := json.Unmarshal(bytes, &s.dataMap); err != nil {
return fmt.Errorf("store: Failed to parse data: %w", err)
}
}
return nil
}
// Save 保存数据
func (s *Store[T]) Save() error {
s.lock.Lock()
defer s.lock.Unlock()
err := s.saveInternal()
if err != nil {
s.logger.Error("store: Failed to save", "error", err)
return fmt.Errorf("store: Failed to save: %w", err)
}
s.unsaved = false
return nil
}
// saveInternal 内部保存实现
func (s *Store[T]) saveInternal() error {
if s.option.FilePath == "" {
return fmt.Errorf("store: FilePath not set")
}
// 创建临时文件
dir := filepath.Dir(s.option.FilePath)
tempFile, err := os.CreateTemp(dir, "store-*.tmp")
if err != nil {
return fmt.Errorf("store: Failed to create temp file: %w", err)
}
tempFilePath := tempFile.Name()
defer func() {
tempFile.Close()
// 如果出错,删除临时文件
if err != nil {
os.Remove(tempFilePath)
}
}()
// 序列化数据
bytes, err := json.MarshalIndent(s.data, "", " ")
if err != nil {
return fmt.Errorf("store: Failed to serialize data: %w", err)
}
// 写入临时文件
if _, err = tempFile.Write(bytes); err != nil {
return fmt.Errorf("store: Failed to write temp file: %w", err)
}
// 确保所有数据已写入磁盘
if err = tempFile.Sync(); err != nil {
return fmt.Errorf("store: Failed to sync file: %w", err)
}
// 关闭临时文件
if err = tempFile.Close(); err != nil {
return fmt.Errorf("store: Failed to close temp file: %w", err)
}
// 原子替换文件
if err = os.Rename(tempFilePath, s.option.FilePath); err != nil {
return fmt.Errorf("store: Failed to rename file: %w", err)
}
return nil
}
// Get 获取数据
func (s *Store[T]) Get() T {
s.lock.RLock()
defer s.lock.RUnlock()
return s.data
}
// GetProperty 获取指定属性
func (s *Store[T]) GetProperty(key string) any {
s.lock.RLock()
defer s.lock.RUnlock()
if key == "" {
return s.dataMap
}
return s.dataMap[key]
}
// Set 设置数据
func (s *Store[T]) Set(data T) error {
s.lock.Lock()
defer s.lock.Unlock()
s.data = data
s.unsaved = true
if s.option.AutoSave {
return s.saveInternal()
}
return nil
}
// SetProperty 设置指定属性
func (s *Store[T]) SetProperty(key string, value any) error {
s.lock.Lock()
defer s.lock.Unlock()
s.dataMap[key] = value
s.unsaved = true
if s.option.AutoSave {
return s.saveInternal()
}
return nil
}
// Delete 删除指定属性
func (s *Store[T]) Delete(key string) error {
s.lock.Lock()
defer s.lock.Unlock()
delete(s.dataMap, key)
s.unsaved = true
if s.option.AutoSave {
return s.saveInternal()
}
return nil
}
// HasUnsavedChanges 是否有未保存的更改
func (s *Store[T]) HasUnsavedChanges() bool {
s.lock.RLock()
defer s.lock.RUnlock()
return s.unsaved
}