This commit is contained in:
Artem Tsyrulnikov
2026-01-15 22:02:16 +03:00
parent 6ec26c96e3
commit 049024ebe8
72 changed files with 2417 additions and 2054 deletions

View File

@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"strings"
"time"
"example.com/m/bot"
"example.com/m/ui"
@@ -18,6 +19,11 @@ type MyProjects struct {
WorkspaceID string
BackState bot.State
OpenPurchases bool
// Поля для управления фоновой горутиной polling
cancel context.CancelFunc
pollDone chan struct{}
lastProjectCount int
}
func (s *MyProjects) Enter(b *bot.Bot, mode bot.RenderMode) {
@@ -70,6 +76,9 @@ func (s *MyProjects) Enter(b *bot.Bot, mode bot.RenderMode) {
}
s.renderProjects(b, mode, jwt)
// Запускаем фоновую горутину для автообновления экрана
s.startPolling(b, jwt)
}
func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string) {
@@ -80,6 +89,9 @@ func (s *MyProjects) renderProjects(b *bot.Bot, mode bot.RenderMode, jwt string)
return
}
// Сохраняем количество проектов для polling
s.lastProjectCount = page.Total
var text string
var buttons [][]echotron.InlineKeyboardButton
headerTitle := "Мои проекты"
@@ -245,3 +257,100 @@ func (s *MyProjects) HandleCallback(b *bot.Bot, u *echotron.Update) {
func (s *MyProjects) HandleMessage(_ *bot.Bot, _ *echotron.Update) { return }
func (s *MyProjects) Handle(_ *bot.Bot, _ *echotron.Update) { return }
func (s *MyProjects) Exit() {
// Останавливаем polling горутину
if s.cancel != nil {
log.Info().Msg("MyProjects: cancelling polling goroutine")
s.cancel()
}
// Ждем завершения горутины с таймаутом 100ms
// Это не критично - горутина сама завершится при следующем tick
if s.pollDone != nil {
select {
case <-s.pollDone:
log.Info().Msg("MyProjects: polling goroutine finished")
case <-time.After(100 * time.Millisecond):
log.Info().Msg("MyProjects: polling cleanup timeout, goroutine will finish in background")
}
}
}
func (s *MyProjects) startPolling(b *bot.Bot, jwt string) {
// Останавливаем предыдущую горутину если она есть
if s.cancel != nil {
s.cancel()
if s.pollDone != nil {
<-s.pollDone
}
}
// Создаем новый контекст для горутины
ctx, cancel := context.WithCancel(context.Background())
s.cancel = cancel
s.pollDone = make(chan struct{})
go s.pollProjects(ctx, b, jwt)
}
func (s *MyProjects) pollProjects(ctx context.Context, b *bot.Bot, jwt string) {
defer close(s.pollDone)
// Адаптивный интервал: первые 30 секунд проверяем часто (1 сек),
// потом переходим на более редкий polling (3 сек)
// Это улучшает UX когда пользователь только добавил проект
intervals := []struct {
duration time.Duration
count int
}{
{1 * time.Second, 30}, // Первые 30 секунд - каждую секунду
{3 * time.Second, -1}, // Потом - каждые 3 секунды (бесконечно)
}
currentIntervalIdx := 0
tickCount := 0
ticker := time.NewTicker(intervals[0].duration)
defer ticker.Stop()
for {
select {
case <-ctx.Done():
log.Info().Msg("MyProjects polling: context cancelled, stopping")
return
case <-ticker.C:
tickCount++
// Проверяем, нужно ли переключить интервал
if currentIntervalIdx < len(intervals)-1 {
if intervals[currentIntervalIdx].count > 0 && tickCount >= intervals[currentIntervalIdx].count {
currentIntervalIdx++
tickCount = 0
ticker.Reset(intervals[currentIntervalIdx].duration)
log.Info().
Dur("new_interval", intervals[currentIntervalIdx].duration).
Msg("MyProjects polling: switching to slower interval")
}
}
// Проверяем количество проектов
page, err := b.Backend.GetProjects(context.Background(), jwt, s.WorkspaceID, s.CurrentPage+1, projectsPerPage)
if err != nil {
log.Error().Err(err).Msg("MyProjects polling: failed to get projects")
continue
}
// Если количество изменилось - перерендериваем
if page.Total != s.lastProjectCount {
log.Info().
Int("old_count", s.lastProjectCount).
Int("new_count", page.Total).
Msg("MyProjects polling: project count changed, re-rendering")
s.lastProjectCount = page.Total
s.renderProjects(b, bot.EditMessage, jwt)
}
}
}
}