Files
voidraft/internal/services/document_service.go
2025-05-28 00:37:18 +08:00

591 lines
15 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 (
"errors"
"fmt"
"github.com/wailsapp/wails/v3/pkg/services/log"
"os"
"path/filepath"
"sync"
"time"
"voidraft/internal/models"
)
// SaveTrigger 保存触发器类型
type SaveTrigger int
const (
// SaveTriggerAuto 自动保存
SaveTriggerAuto SaveTrigger = iota
// SaveTriggerManual 手动触发保存
SaveTriggerManual
// SaveTriggerThreshold 超过阈值触发保存
SaveTriggerThreshold
// SaveTriggerShutdown 程序关闭触发保存
SaveTriggerShutdown
)
// 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
activeDoc *models.Document
docStore *Store[models.Document]
memoryCache *models.Document // 内存缓存,小改动只更新此缓存
lock sync.RWMutex
lastSaveTime time.Time
changeCounter int // 变更计数器,记录自上次保存后的变更数量
saveTimer *time.Timer // 自动保存定时器
pendingSave bool // 是否有等待保存的更改
saveChannel chan SaveTrigger // 保存通道,用于接收保存触发信号
shutdownChan chan struct{} // 关闭通道,用于程序退出时通知保存协程
shutdownWg sync.WaitGroup // 等待组,用于确保保存协程正常退出
onSaveCallback func(trigger SaveTrigger) // 保存回调函数
}
// NewDocumentService 创建新的文档服务实例
func NewDocumentService(configService *ConfigService, logger *log.LoggerService) *DocumentService {
if logger == nil {
logger = log.New()
}
service := &DocumentService{
configService: configService,
logger: logger,
saveChannel: make(chan SaveTrigger, 10),
shutdownChan: make(chan struct{}),
lastSaveTime: time.Now(),
}
return service
}
// Initialize 初始化文档服务
func (ds *DocumentService) Initialize() error {
// 确保文档目录存在
err := ds.ensureDocumentsDir()
if err != nil {
ds.logger.Error("Document: Failed to ensure documents directory", "error", err)
return &DocumentError{Operation: "initialize", Err: err}
}
// 初始化文档存储
err = ds.initDocumentStore()
if err != nil {
ds.logger.Error("Document: Failed to initialize document store", "error", err)
return &DocumentError{Operation: "init_store", Err: err}
}
// 加载默认文档
err = ds.LoadDefaultDocument()
if err != nil {
ds.logger.Error("Document: Failed to load default document", "error", err)
return &DocumentError{Operation: "load_default", Err: err}
}
// 启动保存处理协程
ds.startSaveProcessor()
return nil
}
// startSaveProcessor 启动保存处理协程
func (ds *DocumentService) startSaveProcessor() {
ds.shutdownWg.Add(1)
go func() {
defer ds.shutdownWg.Done()
for {
select {
case trigger := <-ds.saveChannel:
// 接收到保存信号,执行保存
ds.saveToStore(trigger)
case <-ds.shutdownChan:
// 接收到关闭信号,保存并退出
if ds.pendingSave {
ds.saveToStore(SaveTriggerShutdown)
}
return
}
}
}()
}
// scheduleAutoSave 安排自动保存
func (ds *DocumentService) scheduleAutoSave() {
// 获取配置
config, err := ds.configService.GetConfig()
if err != nil {
ds.logger.Error("Document: Failed to get config for auto save", "error", err)
// 使用默认值2秒
ds.scheduleTimerWithDelay(2000)
return
}
// 检查配置有效性
if config == nil {
ds.logger.Error("Document: Config is nil, using default delay")
ds.scheduleTimerWithDelay(2000)
return
}
// 打印保存设置,便于调试
ds.logger.Debug("Document: Auto save settings",
"autoSaveDelay", config.Document.AutoSaveDelay,
"changeThreshold", config.Document.ChangeThreshold,
"minSaveInterval", config.Document.MinSaveInterval)
ds.lock.Lock()
defer ds.lock.Unlock()
// 重置自动保存定时器
if ds.saveTimer != nil {
ds.saveTimer.Stop()
}
// 创建新的自动保存定时器
autoSaveDelay := config.Document.AutoSaveDelay
ds.logger.Debug("Document: Scheduling auto save", "delay", autoSaveDelay)
ds.scheduleTimerWithDelay(autoSaveDelay)
}
// scheduleTimerWithDelay 使用指定延迟创建定时器
func (ds *DocumentService) scheduleTimerWithDelay(delayMs int) {
ds.saveTimer = time.AfterFunc(time.Duration(delayMs)*time.Millisecond, func() {
// 只有在有待保存的更改时才触发保存
if ds.pendingSave {
ds.saveChannel <- SaveTriggerAuto
}
})
}
// saveToStore 保存文档到存储
func (ds *DocumentService) saveToStore(trigger SaveTrigger) {
ds.lock.Lock()
defer ds.lock.Unlock()
// 如果没有内存缓存或活动文档,直接返回
if ds.memoryCache == nil || ds.activeDoc == nil {
return
}
// 获取配置
config, err := ds.configService.GetConfig()
if err != nil {
ds.logger.Error("Document: Failed to get config for save", "error", err)
// 继续使用默认值
}
// 设置默认值
minInterval := 500 // 默认500毫秒
// 如果成功获取了配置,使用配置值
if err == nil && config != nil {
minInterval = config.Document.MinSaveInterval
}
// 如果是自动保存,检查最小保存间隔
if trigger == SaveTriggerAuto {
now := time.Now()
elapsed := now.Sub(ds.lastSaveTime).Milliseconds()
// 如果距离上次保存时间太短,重新安排保存
if elapsed < int64(minInterval) {
// 重新安排保存,延迟 = 最小间隔 - 已经过的时间
delayMs := minInterval - int(elapsed)
ds.logger.Debug("Document: Rescheduling save due to min interval",
"minInterval", minInterval,
"elapsed", elapsed,
"delayMs", delayMs)
ds.lock.Unlock() // 解锁后再启动定时器,避免死锁
ds.scheduleTimerWithDelay(delayMs)
ds.lock.Lock() // 恢复锁
return
}
}
// 更新活动文档
ds.activeDoc = ds.memoryCache
ds.activeDoc.Meta.LastUpdated = time.Now()
// 保存到存储
ds.logger.Info("Document: Saving document to disk",
"trigger", trigger,
"id", ds.activeDoc.Meta.ID,
"contentLength", len(ds.activeDoc.Content))
err = ds.docStore.Set(*ds.activeDoc)
if err != nil {
ds.logger.Error("Document: Failed to save document", "trigger", trigger, "error", err)
return
}
// 强制确保保存到磁盘
err = ds.docStore.Save()
if err != nil {
ds.logger.Error("Document: Failed to force save document", "trigger", trigger, "error", err)
return
}
// 重置计数器和状态
ds.changeCounter = 0
ds.pendingSave = false
ds.lastSaveTime = time.Now()
// 触发回调
if ds.onSaveCallback != nil {
ds.onSaveCallback(trigger)
}
ds.logger.Info("Document: Saved document", "trigger", trigger, "id", ds.activeDoc.Meta.ID)
}
// Shutdown 关闭文档服务,确保所有数据保存
func (ds *DocumentService) Shutdown() {
// 发送关闭信号
close(ds.shutdownChan)
// 等待保存协程退出
ds.shutdownWg.Wait()
// 停止定时器
if ds.saveTimer != nil {
ds.saveTimer.Stop()
}
ds.logger.Info("Document: Service shutdown completed")
}
// SetSaveCallback 设置保存回调函数
func (ds *DocumentService) SetSaveCallback(callback func(trigger SaveTrigger)) {
ds.onSaveCallback = callback
}
// initDocumentStore 初始化文档存储
func (ds *DocumentService) initDocumentStore() error {
docPath, err := ds.getDefaultDocumentPath()
if err != nil {
return err
}
ds.logger.Info("Document: Initializing document store", "path", docPath)
// 创建文档存储强制保存和Service触发的保存都使用同步保存到磁盘
ds.docStore = NewStore[models.Document](StoreOption{
FilePath: docPath,
AutoSave: true, // 启用自动保存确保Set操作直接写入磁盘
Logger: ds.logger,
})
return nil
}
// ensureDocumentsDir 确保文档目录存在
func (ds *DocumentService) ensureDocumentsDir() error {
config, err := ds.configService.GetConfig()
if err != nil {
return err
}
// 创建文档目录
docsDir := filepath.Join(config.Paths.DataPath, "docs")
err = os.MkdirAll(docsDir, 0755)
if err != nil {
return err
}
return nil
}
// getDocumentsDir 获取文档目录路径
func (ds *DocumentService) getDocumentsDir() (string, error) {
config, err := ds.configService.GetConfig()
if err != nil {
return "", err
}
return filepath.Join(config.Paths.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 {
// 从Store加载文档
doc := ds.docStore.Get()
// 检查文档是否有效
if doc.Meta.ID == "" {
// 创建默认文档
defaultDoc := models.NewDefaultDocument()
ds.lock.Lock()
ds.activeDoc = defaultDoc
ds.memoryCache = defaultDoc // 同时更新内存缓存
ds.lock.Unlock()
// 保存默认文档
err := ds.docStore.Set(*defaultDoc)
if err != nil {
return &DocumentError{Operation: "save_default", Err: err}
}
ds.logger.Info("Document: Created and saved default document")
return nil
}
// 设置为活动文档
ds.lock.Lock()
ds.activeDoc = &doc
ds.memoryCache = &doc // 同时更新内存缓存
ds.lock.Unlock()
ds.logger.Info("Document: Loaded default document", "id", doc.Meta.ID)
return nil
}
// GetActiveDocument 获取当前活动文档
func (ds *DocumentService) GetActiveDocument() (*models.Document, error) {
ds.lock.RLock()
defer ds.lock.RUnlock()
if ds.memoryCache == nil {
return nil, errors.New("no active document loaded")
}
// 返回内存缓存中的文档,确保获得最新版本
return ds.memoryCache, nil
}
// GetActiveDocumentContent 获取当前活动文档内容
func (ds *DocumentService) GetActiveDocumentContent() (string, error) {
ds.lock.RLock()
defer ds.lock.RUnlock()
if ds.memoryCache == nil {
return "", errors.New("no active document loaded")
}
return ds.memoryCache.Content, nil
}
// UpdateActiveDocumentContent 更新当前活动文档内容
func (ds *DocumentService) UpdateActiveDocumentContent(content string) error {
// 获取配置
config, err := ds.configService.GetConfig()
if err != nil {
ds.logger.Error("Document: Failed to get config for content update", "error", err)
// 出错时仍继续,使用默认行为
}
// 设置默认配置值
threshold := 100 // 默认值
// 如果成功获取了配置,使用配置值
if err == nil && config != nil {
threshold = config.Document.ChangeThreshold
}
ds.lock.Lock()
if ds.memoryCache == nil {
ds.lock.Unlock()
return errors.New("no active document loaded")
}
// 计算变更数量
oldContent := ds.memoryCache.Content
changedChars := calculateChanges(oldContent, content)
ds.changeCounter += changedChars
// 调试信息
ds.logger.Debug("Document: Content updated",
"changedChars", changedChars,
"totalChanges", ds.changeCounter,
"threshold", threshold)
// 更新内存缓存
ds.memoryCache.Content = content
ds.memoryCache.Meta.LastUpdated = time.Now()
ds.pendingSave = true
// 如果变更超过阈值,触发保存
if ds.changeCounter >= threshold {
ds.logger.Info("Document: Change threshold reached, triggering save",
"threshold", threshold,
"changes", ds.changeCounter)
// 提前解锁,避免死锁
ds.lock.Unlock()
ds.saveChannel <- SaveTriggerThreshold
} else {
// 否则安排自动保存
ds.lock.Unlock()
ds.scheduleAutoSave()
}
return nil
}
// SaveDocumentSync 同步保存文档内容 (用于页面关闭前同步保存)
func (ds *DocumentService) SaveDocumentSync(content string) error {
ds.lock.Lock()
if ds.memoryCache == nil {
ds.lock.Unlock()
return errors.New("no active document loaded")
}
// 更新内存缓存
ds.memoryCache.Content = content
ds.memoryCache.Meta.LastUpdated = time.Now()
// 直接保存到存储
doc := *ds.memoryCache
ds.lock.Unlock()
err := ds.docStore.Set(doc)
if err != nil {
return err
}
// 重置状态
ds.lock.Lock()
ds.pendingSave = false
ds.changeCounter = 0
ds.lastSaveTime = time.Now()
ds.lock.Unlock()
ds.logger.Info("Document: Synced document save completed")
return nil
}
// ForceSave 强制保存当前文档
func (ds *DocumentService) ForceSave() error {
ds.logger.Info("Document: Force save triggered")
// 获取当前文档内容
ds.lock.RLock()
if ds.memoryCache == nil {
ds.lock.RUnlock()
return errors.New("no active document loaded")
}
content := ds.memoryCache.Content
ds.lock.RUnlock()
// 使用同步方法直接保存到磁盘
if err := ds.SaveDocumentSync(content); err != nil {
ds.logger.Error("Document: Force save failed", "error", err)
return err
}
ds.logger.Info("Document: Force save completed successfully")
return nil
}
// calculateChanges 计算两个字符串之间的变更数量
func calculateChanges(old, new string) int {
// 使用详细的差分算法计算变更
result := calculateChangesDetailed(old, new)
// 返回总变更字符数
return result.TotalChanges
}
// GetDiffInfo 获取两个文本之间的详细差异信息
func (ds *DocumentService) GetDiffInfo(oldText, newText string) DiffResult {
return calculateChangesDetailed(oldText, newText)
}
// GetSaveSettings 获取文档保存设置
func (ds *DocumentService) GetSaveSettings() (*models.DocumentConfig, error) {
config, err := ds.configService.GetConfig()
if err != nil {
return nil, &DocumentError{Operation: "get_save_settings", Err: err}
}
return &config.Document, nil
}
// UpdateSaveSettings 更新文档保存设置
func (ds *DocumentService) UpdateSaveSettings(docConfig models.DocumentConfig) error {
// 使用配置服务的 Set 方法更新文档配置
if err := ds.configService.Set("document.auto_save_delay", docConfig.AutoSaveDelay); err != nil {
return &DocumentError{Operation: "update_save_settings_auto_save_delay", Err: err}
}
if err := ds.configService.Set("document.change_threshold", docConfig.ChangeThreshold); err != nil {
return &DocumentError{Operation: "update_save_settings_change_threshold", Err: err}
}
if err := ds.configService.Set("document.min_save_interval", docConfig.MinSaveInterval); err != nil {
return &DocumentError{Operation: "update_save_settings_min_save_interval", Err: err}
}
// 安排自动保存
ds.scheduleAutoSave()
ds.logger.Info("Document: Updated save settings")
return nil
}
// ServiceShutdown 实现应用程序关闭时的服务关闭逻辑
func (ds *DocumentService) ServiceShutdown() error {
ds.logger.Info("Document: Service is shutting down, saving document...")
// 获取当前活动文档
ds.lock.RLock()
if ds.memoryCache == nil {
ds.lock.RUnlock()
ds.logger.Info("Document: No active document to save on shutdown")
return nil
}
// 获取要保存的内容
content := ds.memoryCache.Content
ds.lock.RUnlock()
// 同步保存文档内容
err := ds.SaveDocumentSync(content)
if err != nil {
ds.logger.Error("Document: Failed to save document on shutdown", "error", err)
return err
}
ds.logger.Info("Document: Document saved successfully on shutdown")
// 关闭通道以通知保存协程退出
close(ds.shutdownChan)
// 等待保存协程退出
ds.shutdownWg.Wait()
// 停止所有计时器
if ds.saveTimer != nil {
ds.saveTimer.Stop()
}
return nil
}