Files
voidraft/internal/services/sync_service.go
2026-03-30 00:03:23 +08:00

243 lines
6.3 KiB
Go

package services
import (
"context"
"fmt"
"path/filepath"
"time"
"voidraft/internal/common/helper"
"voidraft/internal/models"
"voidraft/internal/syncer"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/services/log"
)
const (
syncDir = "sync"
localFSHeadKey = "head.json"
)
// SyncService 提供应用层同步服务入口。
type SyncService struct {
configService *ConfigService
dbService *DatabaseService
logger *log.LogService
app *syncer.App
cancelObservers []helper.CancelFunc
}
// NewSyncService 创建新的同步服务实例。
func NewSyncService(configService *ConfigService, dbService *DatabaseService, logger *log.LogService) *SyncService {
return &SyncService{
configService: configService,
dbService: dbService,
logger: logger,
}
}
// ServiceStartup 在服务启动时初始化同步系统。
func (s *SyncService) ServiceStartup(ctx context.Context, options application.ServiceOptions) error {
_ = options
if err := s.ensureApp(); err != nil {
return err
}
s.cancelObservers = []helper.CancelFunc{
s.configService.Watch("sync", s.onSyncConfigChange),
s.configService.Watch("general.dataPath", s.onDataPathChange),
}
if err := s.Initialize(); err != nil {
s.logger.Error("initializing sync service: %v", err)
}
return nil
}
// Initialize 重新加载配置并启动自动同步。
func (s *SyncService) Initialize() error {
if err := s.ensureApp(); err != nil {
return err
}
config, err := s.buildConfig()
if err != nil {
return err
}
if err := s.app.Reconfigure(context.Background(), config); err != nil {
return fmt.Errorf("reconfigure sync app: %w", err)
}
if err := s.app.Start(context.Background()); err != nil {
return fmt.Errorf("start sync app: %w", err)
}
return nil
}
// Reinitialize 重新初始化同步服务。
func (s *SyncService) Reinitialize() error {
return s.Initialize()
}
// HandleConfigChange 在配置变化时重新应用配置。
func (s *SyncService) HandleConfigChange(config *models.SyncConfig) error {
_ = config
return s.Initialize()
}
// StartAutoSync 启动自动同步调度。
func (s *SyncService) StartAutoSync() error {
if err := s.ensureApp(); err != nil {
return err
}
return s.app.Start(context.Background())
}
// StopAutoSync 停止自动同步调度。
func (s *SyncService) StopAutoSync() {
if s.app == nil {
return
}
if err := s.app.Stop(context.Background()); err != nil {
s.logger.Warning("stop sync app: %v", err)
}
}
// Sync 执行一次手动同步。
func (s *SyncService) Sync() error {
if err := s.ensureApp(); err != nil {
return err
}
targetID, err := s.selectedTargetID()
if err != nil {
return err
}
if _, err := s.app.Sync(context.Background(), targetID); err != nil {
return err
}
return nil
}
// ServiceShutdown 停止同步服务并释放资源。
func (s *SyncService) ServiceShutdown() {
for _, cancel := range s.cancelObservers {
if cancel != nil {
cancel()
}
}
s.StopAutoSync()
}
// onSyncConfigChange 响应 sync 配置变化。
func (s *SyncService) onSyncConfigChange(oldValue interface{}, newValue interface{}) {
_, _ = oldValue, newValue
if err := s.Initialize(); err != nil {
s.logger.Error("reconfigure sync after sync config change: %v", err)
}
}
// onDataPathChange 响应数据目录变化。
func (s *SyncService) onDataPathChange(oldValue interface{}, newValue interface{}) {
_, _ = oldValue, newValue
if err := s.Reinitialize(); err != nil {
s.logger.Error("reconfigure sync after data path change: %v", err)
}
}
// ensureApp 保证同步应用已被创建。
func (s *SyncService) ensureApp() error {
if s.app != nil {
return nil
}
if s.dbService == nil || s.dbService.Client == nil {
return fmt.Errorf("sync database client is not ready")
}
s.app = syncer.NewApp(s.dbService.Client, syncer.Options{
Logger: s.logger,
MaxSyncAttempts: 3,
})
return nil
}
// buildConfig 将现有应用配置映射为同步核心配置。
func (s *SyncService) buildConfig() (syncer.Config, error) {
appConfig, err := s.configService.GetConfig()
if err != nil {
return syncer.Config{}, err
}
return syncer.Config{
Targets: []syncer.TargetConfig{
s.buildGitTargetConfig(appConfig.General.DataPath, appConfig.Sync.Git),
s.buildLocalFSTargetConfig(appConfig.Sync.LocalFS),
},
}, nil
}
// selectedTargetID 返回当前选中的同步目标标识。
func (s *SyncService) selectedTargetID() (string, error) {
appConfig, err := s.configService.GetConfig()
if err != nil {
return "", err
}
switch appConfig.Sync.Target {
case models.SyncTargetGit:
return string(models.SyncTargetGit), nil
case models.SyncTargetLocalFS:
return string(models.SyncTargetLocalFS), nil
default:
return "", fmt.Errorf("unsupported sync target: %s", appConfig.Sync.Target)
}
}
// buildGitTargetConfig 将 Git 配置转换为同步核心目标配置。
func (s *SyncService) buildGitTargetConfig(dataPath string, config models.GitSyncConfig) syncer.TargetConfig {
return syncer.TargetConfig{
Kind: syncer.TargetKindGit,
Enabled: config.Enabled,
Schedule: syncer.ScheduleConfig{
AutoSync: config.AutoSync,
Interval: time.Duration(config.SyncInterval) * time.Minute,
},
Git: &syncer.GitTargetConfig{
RepoPath: filepath.Join(dataPath, syncDir),
RepoURL: config.RepoURL,
Branch: syncer.DefaultBranch,
RemoteName: syncer.DefaultRemoteName,
AuthorName: "voidraft",
AuthorEmail: "sync@voidraft.app",
Auth: syncer.GitAuthConfig{
Method: string(config.AuthMethod),
Username: config.Username,
Password: config.Password,
Token: config.Token,
SSHKeyPath: config.SSHKeyPath,
SSHKeyPassword: config.SSHKeyPass,
},
},
}
}
// buildLocalFSTargetConfig 将 localfs 配置转换为同步核心目标配置。
func (s *SyncService) buildLocalFSTargetConfig(config models.LocalFSSyncConfig) syncer.TargetConfig {
return syncer.TargetConfig{
Kind: syncer.TargetKindLocalFS,
Enabled: config.Enabled,
Schedule: syncer.ScheduleConfig{
AutoSync: config.AutoSync,
Interval: time.Duration(config.SyncInterval) * time.Minute,
},
LocalFS: &syncer.LocalFSTargetConfig{
Namespace: string(models.SyncTargetLocalFS),
HeadKey: localFSHeadKey,
RootPath: config.RootPath,
},
}
}