Files
voidraft/internal/services/document_service.go
2025-06-05 02:12:40 +08:00

369 lines
8.6 KiB
Go

package services
import (
"errors"
"fmt"
"github.com/wailsapp/wails/v3/pkg/services/log"
"os"
"path/filepath"
"sync"
"time"
"voidraft/internal/models"
)
// DocumentError 文档操作错误
type DocumentError struct {
Operation string // 操作名称
Err error // 原始错误
}
// Error 实现error接口
func (e *DocumentError) Error() string {
return fmt.Sprintf("document error during %s: %v", e.Operation, e.Err)
}
// Unwrap 获取原始错误
func (e *DocumentError) Unwrap() error {
return e.Err
}
// DocumentService 提供文档管理功能
type DocumentService struct {
configService *ConfigService
logger *log.LoggerService
document *models.Document // 文档实例
docStore *Store[models.Document]
mutex sync.RWMutex // 保护document的读写
// 定时保存
saveTimer *time.Timer
timerMutex sync.Mutex // 保护定时器操作
isDirty bool // 文档是否有未保存的变更
// 服务控制
shutdown chan struct{}
shutdownOnce sync.Once
}
// NewDocumentService 创建新的文档服务实例
func NewDocumentService(configService *ConfigService, logger *log.LoggerService) *DocumentService {
if logger == nil {
logger = log.New()
}
service := &DocumentService{
configService: configService,
logger: logger,
shutdown: make(chan struct{}),
}
return service
}
// Initialize 初始化文档服务
func (ds *DocumentService) Initialize() error {
// 确保文档目录存在
if err := ds.ensureDocumentsDir(); err != nil {
ds.logger.Error("Document: Failed to ensure documents directory", "error", err)
return &DocumentError{Operation: "initialize", Err: err}
}
// 初始化文档存储
if err := ds.initDocumentStore(); err != nil {
ds.logger.Error("Document: Failed to initialize document store", "error", err)
return &DocumentError{Operation: "init_store", Err: err}
}
// 加载默认文档
if err := ds.loadDefaultDocument(); err != nil {
ds.logger.Error("Document: Failed to load default document", "error", err)
return &DocumentError{Operation: "load_default", Err: err}
}
// 启动定时保存
ds.startAutoSave()
return nil
}
// startAutoSave 启动定时保存
func (ds *DocumentService) startAutoSave() {
config, err := ds.configService.GetConfig()
if err != nil {
ds.logger.Error("Document: Failed to get config for auto save", "error", err)
ds.scheduleNextSave(5 * time.Second) // 默认5秒
return
}
delay := time.Duration(config.Editing.AutoSaveDelay) * time.Millisecond
ds.scheduleNextSave(delay)
}
// scheduleNextSave 安排下次保存
func (ds *DocumentService) scheduleNextSave(delay time.Duration) {
ds.timerMutex.Lock()
defer ds.timerMutex.Unlock()
// 停止现有定时器
if ds.saveTimer != nil {
ds.saveTimer.Stop()
}
// 创建新的定时器
ds.saveTimer = time.AfterFunc(delay, func() {
ds.performAutoSave()
// 安排下次保存
ds.startAutoSave()
})
}
// performAutoSave 执行自动保存
func (ds *DocumentService) performAutoSave() {
ds.mutex.Lock()
defer ds.mutex.Unlock()
// 如果没有变更,跳过保存
if !ds.isDirty || ds.document == nil {
return
}
// 更新元数据并保存
ds.document.Meta.LastUpdated = time.Now()
ds.logger.Info("Document: Auto saving document",
"id", ds.document.Meta.ID,
"contentLength", len(ds.document.Content))
if err := ds.docStore.Set(*ds.document); err != nil {
ds.logger.Error("Document: Failed to auto save document", "error", err)
return
}
if err := ds.docStore.Save(); err != nil {
ds.logger.Error("Document: Failed to force save document", "error", err)
return
}
// 重置脏标记
ds.isDirty = false
ds.logger.Info("Document: Auto save completed")
}
// stopTimer 停止定时器
func (ds *DocumentService) stopTimer() {
ds.timerMutex.Lock()
defer ds.timerMutex.Unlock()
if ds.saveTimer != nil {
ds.saveTimer.Stop()
ds.saveTimer = nil
}
}
// initDocumentStore 初始化文档存储
func (ds *DocumentService) initDocumentStore() error {
docPath, err := ds.getDefaultDocumentPath()
if err != nil {
return err
}
ds.logger.Info("Document: Initializing document store", "path", docPath)
ds.docStore = NewStore[models.Document](StoreOption{
FilePath: docPath,
AutoSave: true,
Logger: ds.logger,
})
return nil
}
// ensureDocumentsDir 确保文档目录存在
func (ds *DocumentService) ensureDocumentsDir() error {
config, err := ds.configService.GetConfig()
if err != nil {
return err
}
var dataPath string
if config.General.UseCustomDataPath && config.General.CustomDataPath != "" {
dataPath = config.General.CustomDataPath
} else {
dataPath = config.General.DefaultDataPath
}
docsDir := filepath.Join(dataPath, "docs")
return os.MkdirAll(docsDir, 0755)
}
// getDocumentsDir 获取文档目录路径
func (ds *DocumentService) getDocumentsDir() (string, error) {
config, err := ds.configService.GetConfig()
if err != nil {
return "", err
}
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 获取默认文档路径
func (ds *DocumentService) getDefaultDocumentPath() (string, error) {
docsDir, err := ds.getDocumentsDir()
if err != nil {
return "", err
}
return filepath.Join(docsDir, "default.json"), nil
}
// loadDefaultDocument 加载默认文档
func (ds *DocumentService) loadDefaultDocument() error {
doc := ds.docStore.Get()
ds.mutex.Lock()
defer ds.mutex.Unlock()
if doc.Meta.ID == "" {
// 创建默认文档
ds.document = models.NewDefaultDocument()
// 保存默认文档
if err := ds.docStore.Set(*ds.document); err != nil {
return &DocumentError{Operation: "save_default", Err: err}
}
ds.logger.Info("Document: Created and saved default document")
} else {
ds.document = &doc
ds.logger.Info("Document: Loaded default document", "id", doc.Meta.ID)
}
return nil
}
// GetActiveDocument 获取当前活动文档
func (ds *DocumentService) GetActiveDocument() (*models.Document, error) {
ds.mutex.RLock()
defer ds.mutex.RUnlock()
if ds.document == nil {
return nil, errors.New("no active document loaded")
}
// 返回副本以防止外部修改
docCopy := *ds.document
return &docCopy, nil
}
// GetActiveDocumentContent 获取当前活动文档内容
func (ds *DocumentService) GetActiveDocumentContent() (string, error) {
ds.mutex.RLock()
defer ds.mutex.RUnlock()
if ds.document == nil {
return "", errors.New("no active document loaded")
}
return ds.document.Content, nil
}
// UpdateActiveDocumentContent 更新当前活动文档内容
func (ds *DocumentService) UpdateActiveDocumentContent(content string) error {
ds.mutex.Lock()
defer ds.mutex.Unlock()
if ds.document == nil {
return errors.New("no active document loaded")
}
// 更新文档内容并标记为脏
ds.document.Content = content
ds.document.Meta.LastUpdated = time.Now()
ds.isDirty = true
return nil
}
// SaveDocumentSync 同步保存文档内容
func (ds *DocumentService) SaveDocumentSync(content string) error {
ds.mutex.Lock()
defer ds.mutex.Unlock()
if ds.document == nil {
return errors.New("no active document loaded")
}
// 更新内容
ds.document.Content = content
ds.document.Meta.LastUpdated = time.Now()
// 立即保存
if err := ds.docStore.Set(*ds.document); err != nil {
return err
}
if err := ds.docStore.Save(); err != nil {
return err
}
// 重置脏标记
ds.isDirty = false
ds.logger.Info("Document: Sync save completed")
return nil
}
// ForceSave 强制保存当前文档
func (ds *DocumentService) ForceSave() error {
ds.logger.Info("Document: Force save triggered")
ds.mutex.RLock()
if ds.document == nil {
ds.mutex.RUnlock()
return errors.New("no active document loaded")
}
content := ds.document.Content
ds.mutex.RUnlock()
return ds.SaveDocumentSync(content)
}
// ServiceShutdown 服务关闭
func (ds *DocumentService) ServiceShutdown() error {
ds.logger.Info("Document: Service is shutting down...")
// 确保只执行一次关闭
var shutdownErr error
ds.shutdownOnce.Do(func() {
// 停止定时器
ds.stopTimer()
// 执行最后一次保存
ds.mutex.RLock()
if ds.document != nil && ds.isDirty {
content := ds.document.Content
ds.mutex.RUnlock()
if err := ds.SaveDocumentSync(content); err != nil {
ds.logger.Error("Document: Failed to save on shutdown", "error", err)
shutdownErr = err
} else {
ds.logger.Info("Document: Document saved successfully on shutdown")
}
} else {
ds.mutex.RUnlock()
}
// 关闭服务
close(ds.shutdown)
ds.logger.Info("Document: Service shutdown completed")
})
return shutdownErr
}