243 lines
6.3 KiB
Go
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,
|
|
},
|
|
}
|
|
}
|