369 lines
8.6 KiB
Go
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
|
|
}
|