Files
voidraft/internal/services/store/store_service.go
2025-05-01 00:51:16 +08:00

233 lines
4.5 KiB
Go

package store
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
}