Files
QuoteForge/internal/services/sync/worker.go
Michael Chus 3992dbf919 refactor: убрать qt_pricelist_sync_status, lot_log и лишние права БД
- Удалить все записи в qt_pricelist_sync_status (RecordSyncHeartbeat и
  ensureUserSyncStatusTable); ListUserSyncStatuses переведён на
  qt_client_schema_state
- Подавлять устаревший OFFLINE_UNVERIFIED_SCHEMA в UI когда соединение
  уже восстановлено
- Удалить все обращения к lot_log: repository/price.go, сортировка
  quote_count в component.go, UpdatePopularityScores в stats.go,
  модель LotLog

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-02 16:18:52 +03:00

105 lines
2.5 KiB
Go

package sync
import (
"context"
"log/slog"
"time"
"git.mchus.pro/mchus/quoteforge/internal/db"
)
// Worker performs background synchronization at regular intervals
type Worker struct {
service *Service
connMgr *db.ConnectionManager
interval time.Duration
logger *slog.Logger
stopCh chan struct{}
}
// NewWorker creates a new background sync worker
func NewWorker(service *Service, connMgr *db.ConnectionManager, interval time.Duration) *Worker {
return &Worker{
service: service,
connMgr: connMgr,
interval: interval,
logger: slog.Default(),
stopCh: make(chan struct{}),
}
}
// isOnline checks if the database connection is available
func (w *Worker) isOnline() bool {
return w.connMgr.IsOnline()
}
// Start begins the background sync loop in a goroutine
func (w *Worker) Start(ctx context.Context) {
w.logger.Info("starting background sync worker", "interval", w.interval)
ticker := time.NewTicker(w.interval)
defer ticker.Stop()
// Run once immediately
w.runSync()
for {
select {
case <-ctx.Done():
w.logger.Info("background sync worker stopped by context")
return
case <-w.stopCh:
w.logger.Info("background sync worker stopped")
return
case <-ticker.C:
w.runSync()
}
}
}
// Stop gracefully stops the worker
func (w *Worker) Stop() {
w.logger.Info("stopping background sync worker")
close(w.stopCh)
}
// runSync performs a single sync iteration
func (w *Worker) runSync() {
// Check if online
if !w.isOnline() {
w.logger.Debug("offline, skipping background sync")
return
}
if readiness, err := w.service.EnsureReadinessForSync(); err != nil {
w.logger.Warn("background sync: blocked by readiness guard",
"error", err,
"reason_code", readiness.ReasonCode,
"reason_text", readiness.ReasonText,
)
return
}
// Populate component catalog on first run (empty local_components)
if err := w.service.SyncComponentsIfEmpty(); err != nil {
w.logger.Warn("background sync: initial component sync failed", "error", err)
}
// Push pending changes first
pushed, err := w.service.PushPendingChanges()
if err != nil {
w.logger.Warn("background sync: failed to push pending changes", "error", err)
} else if pushed > 0 {
w.logger.Info("background sync: pushed pending changes", "count", pushed)
}
// Then check for new pricelists
err = w.service.SyncPricelistsIfNeeded()
if err != nil {
w.logger.Warn("background sync: failed to sync pricelists", "error", err)
return
}
w.logger.Info("background sync cycle completed")
}