✨ Add configuration information file storage
This commit is contained in:
64
internal/models/config.go
Normal file
64
internal/models/config.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// TabType 定义了制表符类型
|
||||
type TabType string
|
||||
|
||||
const (
|
||||
// TabTypeSpaces 使用空格作为制表符
|
||||
TabTypeSpaces TabType = "spaces"
|
||||
// TabTypeTab 使用Tab作为制表符
|
||||
TabTypeTab TabType = "tab"
|
||||
)
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
// PathConfig 定义配置文件路径相关配置
|
||||
type PathConfig struct {
|
||||
RootDir string `json:"rootDir"` // 根目录
|
||||
ConfigPath string `json:"configPath"` // 配置文件路径
|
||||
}
|
||||
|
||||
// AppConfig 应用配置
|
||||
type AppConfig struct {
|
||||
Editor EditorConfig `json:"editor"` // 编辑器配置
|
||||
Paths PathConfig `json:"paths"` // 路径配置
|
||||
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
|
||||
}
|
||||
|
||||
// ConfigMetadata 配置元数据
|
||||
type ConfigMetadata struct {
|
||||
Version string `json:"version"` // 配置版本
|
||||
LastUpdated time.Time `json:"lastUpdated"` // 最后更新时间
|
||||
}
|
||||
|
||||
// NewDefaultAppConfig 创建默认应用配置
|
||||
func NewDefaultAppConfig() *AppConfig {
|
||||
return &AppConfig{
|
||||
Editor: EditorConfig{
|
||||
FontSize: 13,
|
||||
Encoding: "UTF-8",
|
||||
EnableTabIndent: true,
|
||||
TabSize: 4,
|
||||
TabType: TabTypeSpaces,
|
||||
},
|
||||
Paths: PathConfig{
|
||||
RootDir: ".voidraft",
|
||||
ConfigPath: "config/config.json",
|
||||
},
|
||||
Metadata: ConfigMetadata{
|
||||
Version: "1.0.0",
|
||||
LastUpdated: time.Now(),
|
||||
},
|
||||
}
|
||||
}
|
298
internal/services/config_service.go
Normal file
298
internal/services/config_service.go
Normal file
@@ -0,0 +1,298 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"time"
|
||||
"voidraft/internal/models"
|
||||
)
|
||||
|
||||
// ConfigService 提供配置管理功能
|
||||
type ConfigService struct {
|
||||
fileService *FileService
|
||||
configPath string
|
||||
rootDir string
|
||||
homePath string
|
||||
mutex sync.RWMutex
|
||||
config *models.AppConfig // 缓存最新配置
|
||||
}
|
||||
|
||||
// NewConfigService 创建新的配置服务实例
|
||||
func NewConfigService(fileService *FileService) *ConfigService {
|
||||
// 获取用户主目录
|
||||
homePath, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
log.Printf("Failed to get user home directory: %v", 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, // 初始化缓存配置
|
||||
}
|
||||
|
||||
// 初始化配置目录和文件(非锁定方式)
|
||||
service.initializeConfig()
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 确保配置文件所在目录存在
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// 检查配置文件是否存在
|
||||
configFilePath := cs.GetFullConfigPath()
|
||||
log.Printf("Config file path: %s", configFilePath)
|
||||
|
||||
if !cs.fileService.FileExists(configFilePath) {
|
||||
log.Printf("Config file not found, creating default config")
|
||||
// 创建默认配置文件
|
||||
defaultConfig := models.NewDefaultAppConfig()
|
||||
if err := cs.saveAppConfigInitial(defaultConfig); err != nil {
|
||||
log.Printf("Failed to save default config: %v", err)
|
||||
}
|
||||
} else {
|
||||
// 加载现有配置
|
||||
log.Printf("Loading existing config file")
|
||||
existingConfig, err := cs.loadAppConfigInitial()
|
||||
if err != nil {
|
||||
log.Printf("Failed to load existing config, using default: %v", err)
|
||||
} else {
|
||||
cs.config = existingConfig
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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 获取完整的配置文件路径
|
||||
func (cs *ConfigService) GetFullConfigPath() string {
|
||||
return filepath.Join(cs.homePath, cs.rootDir, cs.configPath)
|
||||
}
|
||||
|
||||
// GetAppConfig 获取应用配置
|
||||
func (cs *ConfigService) GetAppConfig() (*models.AppConfig, error) {
|
||||
cs.mutex.RLock()
|
||||
defer cs.mutex.RUnlock()
|
||||
|
||||
// 返回内存中的配置副本
|
||||
if cs.config != nil {
|
||||
return cs.config, nil
|
||||
}
|
||||
|
||||
// 从文件加载
|
||||
config := &models.AppConfig{}
|
||||
configPath := cs.GetFullConfigPath()
|
||||
|
||||
log.Printf("GetAppConfig: Loading from %s", configPath)
|
||||
|
||||
// 如果配置文件存在,则加载
|
||||
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 {
|
||||
log.Printf("GetAppConfig: Failed to save default config: %v", err)
|
||||
return nil, fmt.Errorf("failed to save default config: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return config, nil
|
||||
}
|
||||
|
||||
// SaveAppConfig 保存应用配置
|
||||
func (cs *ConfigService) SaveAppConfig(config *models.AppConfig) error {
|
||||
cs.mutex.Lock()
|
||||
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 更新编辑器配置
|
||||
func (cs *ConfigService) UpdateEditorConfig(editorConfig models.EditorConfig) error {
|
||||
cs.mutex.Lock()
|
||||
defer cs.mutex.Unlock()
|
||||
|
||||
// 如果内存中已有配置,直接更新
|
||||
if cs.config != nil {
|
||||
log.Printf("UpdateEditorConfig: Updating in-memory editor config: %+v", editorConfig)
|
||||
cs.config.Editor = editorConfig
|
||||
|
||||
// 保存到文件
|
||||
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()
|
||||
if err != nil {
|
||||
log.Printf("UpdateEditorConfig: Failed to load config: %v", err)
|
||||
// 使用默认配置
|
||||
config = models.NewDefaultAppConfig()
|
||||
}
|
||||
|
||||
// 更新编辑器配置
|
||||
config.Editor = editorConfig
|
||||
|
||||
// 更新配置元数据
|
||||
config.Metadata.LastUpdated = time.Now()
|
||||
|
||||
// 保存到文件
|
||||
configPath := cs.GetFullConfigPath()
|
||||
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 获取编辑器配置
|
||||
func (cs *ConfigService) GetEditorConfig() (models.EditorConfig, error) {
|
||||
cs.mutex.RLock()
|
||||
defer cs.mutex.RUnlock()
|
||||
|
||||
// 如果内存中已有配置,直接返回
|
||||
if cs.config != nil {
|
||||
return cs.config.Editor, nil
|
||||
}
|
||||
|
||||
// 否则从文件加载
|
||||
config, err := cs.loadAppConfigInitial()
|
||||
if err != nil {
|
||||
log.Printf("GetEditorConfig: Failed to load config: %v", err)
|
||||
// 使用默认配置
|
||||
defaultConfig := models.NewDefaultAppConfig()
|
||||
cs.config = defaultConfig
|
||||
return defaultConfig.Editor, nil
|
||||
}
|
||||
|
||||
// 更新内存中的配置
|
||||
cs.config = config
|
||||
log.Printf("GetEditorConfig: Retrieved editor config: %+v", config.Editor)
|
||||
return config.Editor, nil
|
||||
}
|
||||
|
||||
// ResetToDefault 重置为默认配置
|
||||
func (cs *ConfigService) ResetToDefault() error {
|
||||
cs.mutex.Lock()
|
||||
defer cs.mutex.Unlock()
|
||||
|
||||
// 创建默认配置
|
||||
defaultConfig := models.NewDefaultAppConfig()
|
||||
|
||||
log.Printf("ResetToDefault: Resetting to default config")
|
||||
|
||||
// 保存到文件
|
||||
configPath := cs.GetFullConfigPath()
|
||||
if err := cs.fileService.SaveJSON(configPath, defaultConfig); err != nil {
|
||||
log.Printf("ResetToDefault: Failed to save default config: %v", err)
|
||||
return fmt.Errorf("failed to save default config: %w", err)
|
||||
}
|
||||
|
||||
// 更新内存中的配置
|
||||
cs.config = defaultConfig
|
||||
log.Printf("ResetToDefault: Successfully reset to default config")
|
||||
|
||||
return nil
|
||||
}
|
146
internal/services/file_service.go
Normal file
146
internal/services/file_service.go
Normal file
@@ -0,0 +1,146 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// FileService 提供原子化文件操作
|
||||
type FileService struct {
|
||||
mutex sync.Mutex
|
||||
}
|
||||
|
||||
// NewFileService 创建新的文件服务实例
|
||||
func NewFileService() *FileService {
|
||||
return &FileService{}
|
||||
}
|
||||
|
||||
// EnsureDir 确保目录存在,如不存在则创建
|
||||
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)
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// SaveJSON 原子化保存JSON数据到文件
|
||||
func (fs *FileService) SaveJSON(filePath string, data interface{}) error {
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
|
||||
log.Printf("SaveJSON: Saving to file: %s", filePath)
|
||||
|
||||
// 确保目录存在 - 使用无锁版本以避免死锁
|
||||
dir := filepath.Dir(filePath)
|
||||
if err := fs.ensureDirNoLock(dir); err != nil {
|
||||
log.Printf("SaveJSON: 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("SaveJSON: Failed to encode JSON: %v", err)
|
||||
return fmt.Errorf("failed to encode JSON: %w", err)
|
||||
}
|
||||
|
||||
// 先写入临时文件
|
||||
tempFile := filePath + ".tmp"
|
||||
log.Printf("SaveJSON: Writing to temporary file: %s", tempFile)
|
||||
if err := os.WriteFile(tempFile, jsonData, 0644); err != nil {
|
||||
log.Printf("SaveJSON: Failed to write temporary file: %v", err)
|
||||
return fmt.Errorf("failed to write temporary file: %w", err)
|
||||
}
|
||||
|
||||
// 原子替换原文件
|
||||
log.Printf("SaveJSON: Replacing original file with temporary file")
|
||||
if err := os.Rename(tempFile, filePath); err != nil {
|
||||
os.Remove(tempFile) // 清理临时文件
|
||||
log.Printf("SaveJSON: Failed to replace file: %v", err)
|
||||
return fmt.Errorf("failed to replace file: %w", err)
|
||||
}
|
||||
|
||||
log.Printf("SaveJSON: File saved successfully: %s", filePath)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadJSON 从文件加载JSON数据
|
||||
func (fs *FileService) LoadJSON(filePath string, target interface{}) error {
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
|
||||
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 {
|
||||
fs.mutex.Lock()
|
||||
defer fs.mutex.Unlock()
|
||||
|
||||
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
|
||||
}
|
43
internal/services/service_manager.go
Normal file
43
internal/services/service_manager.go
Normal file
@@ -0,0 +1,43 @@
|
||||
package services
|
||||
|
||||
import (
|
||||
"github.com/wailsapp/wails/v3/pkg/application"
|
||||
)
|
||||
|
||||
// ServiceManager 服务管理器,负责协调各个服务
|
||||
type ServiceManager struct {
|
||||
fileService *FileService
|
||||
configService *ConfigService
|
||||
}
|
||||
|
||||
// NewServiceManager 创建新的服务管理器实例
|
||||
func NewServiceManager() *ServiceManager {
|
||||
// 初始化文件服务
|
||||
fileService := NewFileService()
|
||||
|
||||
// 初始化配置服务
|
||||
configService := NewConfigService(fileService)
|
||||
|
||||
return &ServiceManager{
|
||||
fileService: fileService,
|
||||
configService: configService,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
Reference in New Issue
Block a user