Files
voidraft/internal/services/document_service.go
2025-06-06 23:49:44 +08:00

315 lines
6.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package services
import (
"os"
"path/filepath"
"sync"
"time"
"voidraft/internal/models"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
// DocumentService 提供文档管理功能
type DocumentService struct {
configService *ConfigService
logger *log.LoggerService
document *models.Document
docStore *Store[models.Document]
mutex sync.RWMutex
// 自动保存优化
saveTimer *time.Timer
isDirty bool
lastSaveTime time.Time
pendingContent string // 暂存待保存的内容
}
// NewDocumentService 创建文档服务
func NewDocumentService(configService *ConfigService, logger *log.LoggerService) *DocumentService {
if logger == nil {
logger = log.New()
}
return &DocumentService{
configService: configService,
logger: logger,
}
}
// Initialize 初始化服务
func (ds *DocumentService) Initialize() error {
if err := ds.initStore(); err != nil {
return err
}
ds.loadDocument()
ds.startAutoSave()
return nil
}
// initStore 初始化存储
func (ds *DocumentService) initStore() error {
docPath, err := ds.getDocumentPath()
if err != nil {
return err
}
// 确保目录存在
if err := os.MkdirAll(filepath.Dir(docPath), 0755); err != nil {
return err
}
ds.docStore = NewStore[models.Document](StoreOption{
FilePath: docPath,
AutoSave: false,
Logger: ds.logger,
})
return nil
}
// getDocumentPath 获取文档路径
func (ds *DocumentService) getDocumentPath() (string, error) {
config, err := ds.configService.GetConfig()
if err != nil {
return "", err
}
return filepath.Join(config.General.DataPath, "docs", "default.json"), nil
}
// loadDocument 加载文档
func (ds *DocumentService) loadDocument() {
ds.mutex.Lock()
defer ds.mutex.Unlock()
doc := ds.docStore.Get()
if doc.Meta.ID == "" {
// 创建新文档
ds.document = models.NewDefaultDocument()
ds.docStore.Set(*ds.document)
ds.logger.Info("Document: Created new document")
} else {
ds.document = &doc
ds.logger.Info("Document: Loaded existing document")
}
}
// GetActiveDocument 获取活动文档
func (ds *DocumentService) GetActiveDocument() (*models.Document, error) {
ds.mutex.RLock()
defer ds.mutex.RUnlock()
if ds.document == nil {
return nil, nil
}
// 返回副本
docCopy := *ds.document
return &docCopy, nil
}
// UpdateActiveDocumentContent 更新文档内容
func (ds *DocumentService) UpdateActiveDocumentContent(content string) error {
ds.mutex.Lock()
defer ds.mutex.Unlock()
if ds.document != nil {
// 只在内容真正改变时才标记为脏
if ds.document.Content != content {
ds.pendingContent = content
ds.isDirty = true
}
}
return nil
}
// ForceSave 强制保存
func (ds *DocumentService) ForceSave() error {
ds.mutex.Lock()
defer ds.mutex.Unlock()
if ds.document == nil {
return nil
}
// 应用待保存的内容
if ds.pendingContent != "" {
ds.document.Content = ds.pendingContent
ds.pendingContent = ""
}
now := time.Now()
ds.document.Meta.LastUpdated = 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.lastSaveTime = now
ds.logger.Info("Document: Force save completed")
return nil
}
// startAutoSave 启动自动保存
func (ds *DocumentService) startAutoSave() {
delay := 5 * time.Second // 默认延迟
if config, err := ds.configService.GetConfig(); err == nil {
delay = time.Duration(config.Editing.AutoSaveDelay) * time.Millisecond
}
ds.scheduleAutoSave(delay)
}
// scheduleAutoSave 安排自动保存
func (ds *DocumentService) scheduleAutoSave(delay time.Duration) {
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
}
// 检查距离上次保存的时间间隔,避免过于频繁的保存
now := time.Now()
if now.Sub(ds.lastSaveTime) < time.Second {
// 如果距离上次保存不到1秒跳过此次保存
// 下一个自动保存周期会重新尝试
ds.logger.Debug("Document: Skipping auto save due to recent save")
return
}
// 应用待保存的内容
if ds.pendingContent != "" {
ds.document.Content = ds.pendingContent
ds.pendingContent = ""
}
ds.document.Meta.LastUpdated = now
if err := ds.docStore.Set(*ds.document); err != nil {
ds.logger.Error("Document: Auto save failed", "error", err)
return
}
if err := ds.docStore.Save(); err != nil {
ds.logger.Error("Document: Auto save failed", "error", err)
return
}
ds.isDirty = false
ds.lastSaveTime = now
ds.logger.Debug("Document: Auto save completed")
}
// ReloadDocument 重新加载文档
func (ds *DocumentService) ReloadDocument() error {
ds.mutex.Lock()
defer ds.mutex.Unlock()
// 强制保存当前文档
if ds.document != nil && ds.isDirty {
if err := ds.forceSaveInternal(); err != nil {
ds.logger.Error("Document: Failed to save before reload", "error", err)
}
}
// 重新初始化存储
if err := ds.initStore(); err != nil {
return err
}
// 重新加载文档
doc := ds.docStore.Get()
if doc.Meta.ID == "" {
// 创建新文档
ds.document = models.NewDefaultDocument()
ds.docStore.Set(*ds.document)
ds.logger.Info("Document: Created new document after reload")
} else {
ds.document = &doc
ds.logger.Info("Document: Loaded existing document after reload")
}
// 重置状态
ds.isDirty = false
ds.pendingContent = ""
ds.lastSaveTime = time.Now()
return nil
}
// forceSaveInternal 内部强制保存(不加锁)
func (ds *DocumentService) forceSaveInternal() error {
if ds.document == nil {
return nil
}
// 应用待保存的内容
if ds.pendingContent != "" {
ds.document.Content = ds.pendingContent
ds.pendingContent = ""
}
now := time.Now()
ds.document.Meta.LastUpdated = 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.lastSaveTime = now
return nil
}
// ServiceShutdown 关闭服务
func (ds *DocumentService) ServiceShutdown() error {
// 停止定时器
if ds.saveTimer != nil {
ds.saveTimer.Stop()
}
// 最后保存
if err := ds.ForceSave(); err != nil {
ds.logger.Error("Document: Failed to save on shutdown", "error", err)
}
ds.logger.Info("Document: Service shutdown completed")
return nil
}
// OnDataPathChanged 处理数据路径变更
func (ds *DocumentService) OnDataPathChanged(oldPath, newPath string) error {
ds.logger.Info("Document: Data path changed, reloading document",
"oldPath", oldPath,
"newPath", newPath)
// 重新加载文档以使用新的路径
return ds.ReloadDocument()
}