Complete the document saving service

This commit is contained in:
2025-05-17 15:50:34 +08:00
parent bd0bbc9674
commit 1246166231
16 changed files with 1781 additions and 30 deletions

View File

@@ -16,6 +16,21 @@ const (
TabTypeTab TabType = "tab"
)
// SaveOptions 保存选项
type SaveOptions struct {
// 自动保存延迟(毫秒)- 内容变更后多久自动保存
AutoSaveDelay int `json:"autoSaveDelay"`
// 变更字符阈值,超过此阈值立即触发保存
ChangeThreshold int `json:"changeThreshold"`
// 最小保存间隔(毫秒)- 两次保存之间的最小时间间隔避免频繁IO
MinSaveInterval int `json:"minSaveInterval"`
}
// DocumentConfig 定义文档配置
type DocumentConfig struct {
SaveOptions SaveOptions `json:"saveOptions"` // 详细保存选项
}
// EditorConfig 定义编辑器配置
type EditorConfig struct {
FontSize int `json:"fontSize"` // 字体大小
@@ -45,6 +60,7 @@ type PathsConfig struct {
// AppConfig 应用配置 - 包含业务配置和路径配置
type AppConfig struct {
Editor EditorConfig `json:"editor"` // 编辑器配置
Document DocumentConfig `json:"document"` // 文档配置
Paths PathsConfig `json:"paths"` // 路径配置
Metadata ConfigMetadata `json:"metadata"` // 配置元数据
}
@@ -65,6 +81,7 @@ func NewDefaultAppConfig() *AppConfig {
// 默认路径配置
rootDir := filepath.Join(homePath, ".voidraft")
dataDir := filepath.Join(rootDir, "data")
return &AppConfig{
Editor: EditorConfig{
@@ -75,9 +92,16 @@ func NewDefaultAppConfig() *AppConfig {
Language: LangZhCN,
AlwaysOnTop: false,
},
Document: DocumentConfig{
SaveOptions: SaveOptions{
AutoSaveDelay: 5000, // 5秒后自动保存
ChangeThreshold: 500, // 500个字符变更触发保存
MinSaveInterval: 1000, // 最小间隔1000毫秒
},
},
Paths: PathsConfig{
LogPath: filepath.Join(rootDir, "logs"),
DataPath: filepath.Join(rootDir, "data"),
DataPath: dataDir,
},
Metadata: ConfigMetadata{
Version: "1.0.0",

View File

@@ -0,0 +1,41 @@
package models
import (
"time"
)
// DocumentMeta 文档元数据
type DocumentMeta struct {
ID string `json:"id"` // 文档唯一标识
Title string `json:"title"` // 文档标题
LastUpdated time.Time `json:"lastUpdated"` // 最后更新时间
CreatedAt time.Time `json:"createdAt"` // 创建时间
}
// Document 表示一个文档
type Document struct {
Meta DocumentMeta `json:"meta"` // 元数据
Content string `json:"content"` // 文档内容
}
// DocumentInfo 文档信息(不包含内容,用于列表展示)
type DocumentInfo struct {
ID string `json:"id"` // 文档ID
Title string `json:"title"` // 文档标题
LastUpdated time.Time `json:"lastUpdated"` // 最后更新时间
Path string `json:"path"` // 文档路径
}
// NewDefaultDocument 创建默认文档
func NewDefaultDocument() *Document {
now := time.Now()
return &Document{
Meta: DocumentMeta{
ID: "default",
Title: "默认文档",
LastUpdated: now,
CreatedAt: now,
},
Content: "// 在此处编写文本...",
}
}

View File

@@ -35,9 +35,8 @@ func (p *DefaultConfigPathProvider) GetConfigPath() string {
// ConfigOption 配置服务选项
type ConfigOption struct {
Logger *log.LoggerService // 日志服务
PathProvider ConfigPath // 路径提供者
AutoSaveEnabled bool // 是否启用自动保存
Logger *log.LoggerService // 日志服务
PathProvider ConfigPath // 路径提供者
}
// ConfigService 提供配置管理功能
@@ -98,7 +97,6 @@ func NewConfigService(opt ...ConfigOption) *ConfigService {
// 创建存储
store := NewStore[models.AppConfig](StoreOption{
FilePath: configPath,
AutoSave: option.AutoSaveEnabled,
Logger: logger,
})

View File

@@ -0,0 +1,306 @@
package services
// Edit 表示编辑操作类型
type EditType int
const (
// EditInsert 插入操作
EditInsert EditType = iota
// EditDelete 删除操作
EditDelete
// EditEqual 相等部分
EditEqual
)
// Edit 表示单个编辑操作
type Edit struct {
Type EditType // 操作类型
Content string // 操作内容
}
// DiffResult 包含差异比较的结果信息
type DiffResult struct {
Edits []Edit // 编辑操作列表
InsertCount int // 插入的字符数
DeleteCount int // 删除的字符数
ChangedLines int // 变更的行数
TotalChanges int // 总变更字符数(插入+删除)
ChangedTokens int // 变更的token数如单词、标识符等
}
// calculateChangesDetailed 使用Myers差分算法计算两个字符串之间的具体变更
func calculateChangesDetailed(oldText, newText string) DiffResult {
// 将文本分割成行
oldLines := splitLines(oldText)
newLines := splitLines(newText)
// 计算行级别的差异
edits := computeLineEdits(oldLines, newLines)
// 计算变更统计
result := DiffResult{
Edits: edits,
}
// 统计变更
for _, edit := range edits {
switch edit.Type {
case EditInsert:
result.InsertCount += len(edit.Content)
result.ChangedLines++
case EditDelete:
result.DeleteCount += len(edit.Content)
result.ChangedLines++
}
}
result.TotalChanges = result.InsertCount + result.DeleteCount
result.ChangedTokens = estimateChangedTokens(edits)
return result
}
// splitLines 将文本分割成行
func splitLines(text string) []string {
var lines []string
var currentLine string
for _, char := range text {
if char == '\n' {
lines = append(lines, currentLine)
currentLine = ""
} else {
currentLine += string(char)
}
}
// 添加最后一行(如果不是以换行符结尾)
if currentLine != "" {
lines = append(lines, currentLine)
}
return lines
}
// computeLineEdits 使用Myers差分算法计算行级别的差异
func computeLineEdits(oldLines, newLines []string) []Edit {
var edits []Edit
// 使用Myers差分算法计算行级别的差异
script := myersDiff(oldLines, newLines)
// 将差异脚本转换为编辑操作
for _, op := range script {
switch op.Type {
case EditEqual:
edits = append(edits, Edit{
Type: EditEqual,
Content: oldLines[op.OldStart],
})
case EditDelete:
edits = append(edits, Edit{
Type: EditDelete,
Content: oldLines[op.OldStart],
})
case EditInsert:
edits = append(edits, Edit{
Type: EditInsert,
Content: newLines[op.NewStart],
})
}
}
return edits
}
// DiffOp 表示差分操作
type DiffOp struct {
Type EditType
OldStart int
OldEnd int
NewStart int
NewEnd int
}
// myersDiff 实现Myers差分算法
func myersDiff(oldLines, newLines []string) []DiffOp {
// 基本思路Myers差分算法通过建立编辑图来寻找最短编辑路径
// 简化版实现
var script []DiffOp
oldLen := len(oldLines)
newLen := len(newLines)
// 使用动态规划找出最长公共子序列(LCS)
lcs := longestCommonSubsequence(oldLines, newLines)
// 根据LCS构建差分脚本
oldIndex, newIndex := 0, 0
for _, entry := range lcs {
// 处理LCS之前的差异
for oldIndex < entry.OldIndex {
script = append(script, DiffOp{
Type: EditDelete,
OldStart: oldIndex,
OldEnd: oldIndex + 1,
NewStart: newIndex,
NewEnd: newIndex,
})
oldIndex++
}
for newIndex < entry.NewIndex {
script = append(script, DiffOp{
Type: EditInsert,
OldStart: oldIndex,
OldEnd: oldIndex,
NewStart: newIndex,
NewEnd: newIndex + 1,
})
newIndex++
}
// 处理相等部分
script = append(script, DiffOp{
Type: EditEqual,
OldStart: oldIndex,
OldEnd: oldIndex + 1,
NewStart: newIndex,
NewEnd: newIndex + 1,
})
oldIndex++
newIndex++
}
// 处理剩余差异
for oldIndex < oldLen {
script = append(script, DiffOp{
Type: EditDelete,
OldStart: oldIndex,
OldEnd: oldIndex + 1,
NewStart: newIndex,
NewEnd: newIndex,
})
oldIndex++
}
for newIndex < newLen {
script = append(script, DiffOp{
Type: EditInsert,
OldStart: oldIndex,
OldEnd: oldIndex,
NewStart: newIndex,
NewEnd: newIndex + 1,
})
newIndex++
}
return script
}
// LCSEntry 表示最长公共子序列中的一个条目
type LCSEntry struct {
OldIndex int
NewIndex int
}
// longestCommonSubsequence 寻找两个字符串数组的最长公共子序列
func longestCommonSubsequence(oldLines, newLines []string) []LCSEntry {
oldLen := len(oldLines)
newLen := len(newLines)
// 创建动态规划表
dp := make([][]int, oldLen+1)
for i := range dp {
dp[i] = make([]int, newLen+1)
}
// 填充DP表
for i := 1; i <= oldLen; i++ {
for j := 1; j <= newLen; j++ {
if oldLines[i-1] == newLines[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
} else {
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
}
}
}
// 回溯找出LCS
var lcs []LCSEntry
i, j := oldLen, newLen
for i > 0 && j > 0 {
if oldLines[i-1] == newLines[j-1] {
lcs = append([]LCSEntry{{OldIndex: i - 1, NewIndex: j - 1}}, lcs...)
i--
j--
} else if dp[i-1][j] > dp[i][j-1] {
i--
} else {
j--
}
}
return lcs
}
// max 返回两个整数中的较大值
func max(a, b int) int {
if a > b {
return a
}
return b
}
// estimateChangedTokens 估计变更的token数量
// 这里使用简单的单词分割来估计
func estimateChangedTokens(edits []Edit) int {
tokenCount := 0
for _, edit := range edits {
switch edit.Type {
case EditInsert, EditDelete:
// 简单地将内容按空白字符分割成单词
words := splitIntoWords(edit.Content)
tokenCount += len(words)
}
}
return tokenCount
}
// splitIntoWords 将文本分割成单词
func splitIntoWords(text string) []string {
var words []string
var currentWord string
// 简单的状态机:
// - 如果是字母、数字或下划线,添加到当前单词
// - 否则,结束当前单词并开始新单词
for _, char := range text {
if isWordChar(char) {
currentWord += string(char)
} else {
if currentWord != "" {
words = append(words, currentWord)
currentWord = ""
}
}
}
// 添加最后一个单词(如果有)
if currentWord != "" {
words = append(words, currentWord)
}
return words
}
// isWordChar 判断字符是否是单词字符(字母、数字或下划线)
func isWordChar(char rune) bool {
return (char >= 'a' && char <= 'z') ||
(char >= 'A' && char <= 'Z') ||
(char >= '0' && char <= '9') ||
char == '_'
}

View File

@@ -0,0 +1,592 @@
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.SaveOptions.AutoSaveDelay,
"changeThreshold", config.Document.SaveOptions.ChangeThreshold,
"minSaveInterval", config.Document.SaveOptions.MinSaveInterval)
ds.lock.Lock()
defer ds.lock.Unlock()
// 重置自动保存定时器
if ds.saveTimer != nil {
ds.saveTimer.Stop()
}
// 创建新的自动保存定时器
autoSaveDelay := config.Document.SaveOptions.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.SaveOptions.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.SaveOptions.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 {
// 获取当前配置
config, err := ds.configService.GetConfig()
if err != nil {
return &DocumentError{Operation: "update_save_settings", Err: err}
}
// 更新保存设置
config.Document = docConfig
// 保存配置
err = ds.configService.SaveConfig(config)
if err != nil {
return &DocumentError{Operation: "update_save_settings_save", 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
}

View File

@@ -7,8 +7,9 @@ import (
// ServiceManager 服务管理器,负责协调各个服务
type ServiceManager struct {
configService *ConfigService
logger *log.LoggerService
configService *ConfigService
documentService *DocumentService
logger *log.LoggerService
}
// NewServiceManager 创建新的服务管理器实例
@@ -18,14 +19,24 @@ func NewServiceManager() *ServiceManager {
// 初始化配置服务
configService := NewConfigService(ConfigOption{
Logger: logger,
PathProvider: nil,
AutoSaveEnabled: true,
Logger: logger,
PathProvider: nil,
})
// 初始化文档服务
documentService := NewDocumentService(configService, logger)
// 初始化文档服务
err := documentService.Initialize()
if err != nil {
logger.Error("Failed to initialize document service", "error", err)
panic(err)
}
return &ServiceManager{
configService: configService,
logger: logger,
configService: configService,
documentService: documentService,
logger: logger,
}
}
@@ -33,10 +44,6 @@ func NewServiceManager() *ServiceManager {
func (sm *ServiceManager) GetServices() []application.Service {
return []application.Service{
application.NewService(sm.configService),
application.NewService(sm.documentService),
}
}
// GetConfigService 获取配置服务实例
func (sm *ServiceManager) GetConfigService() *ConfigService {
return sm.configService
}