Восстановление зависших загрузок и уведомления о падении (state-reconciliation)
Долгий metaDL больше не убивается агрессивным таймаутом: дефолт magnet_timeout 30m → 24h (страховочный предохранитель), базис отсчёта — added_on из qBittorrent, а не created_at (переживает retry/усыновление). Авто-восстановление: фоновая сверка возвращает в поток задачи, упавшие по нашей нетерпеливости (magnet_timeout/stalled), когда источник ожил и продвинулся за условие падения (downloading/completed по статусу торрента); qbit_error не воскрешается. Конфликт idempotency (infohash занят другой активной задачей) — оставляем в failed. Уведомления: любой переход в failed/stuck пингует автора (включая приёмный qbit_add через ingest), с дебаунсом против спама при флаппинге stalled. Ручной retry добавлен в веб-UI и Telegram; Retry перецепляется к живому торренту вместо слепого Add. Дельта state-reconciliation влита в живые спеки; обновлён workflow.md. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -31,6 +31,10 @@ func (b *Bot) renderCard(rd *worker.ReviewData) (string, *tgbotapi.InlineKeyboar
|
||||
if msg := rd.Download.ErrorMsg.String; msg != "" {
|
||||
text += "\n" + msg
|
||||
}
|
||||
// failed/stuck — даём кнопку повтора; остальное только «в вебе».
|
||||
if state == store.StateFailed || state == store.StateStuck {
|
||||
return text, b.retryKeyboard(id)
|
||||
}
|
||||
return text, b.webOnly(id)
|
||||
}
|
||||
}
|
||||
@@ -111,6 +115,41 @@ func (b *Bot) renderDesync(rd *worker.ReviewData, event worker.NotifyEvent) stri
|
||||
}
|
||||
}
|
||||
|
||||
// renderFailed — уведомление об упавшей/зависшей задаче с кнопкой повтора.
|
||||
func (b *Bot) renderFailed(rd *worker.ReviewData) (string, *tgbotapi.InlineKeyboardMarkup) {
|
||||
id := rd.Download.ID
|
||||
var sb strings.Builder
|
||||
verb := "не удалась"
|
||||
if rd.Download.State == store.StateStuck {
|
||||
verb = "зависла"
|
||||
}
|
||||
fmt.Fprintf(&sb, "❌ Задача #%d %s", id, verb)
|
||||
if code := rd.Download.ErrorCode.String; code != "" {
|
||||
fmt.Fprintf(&sb, " (%s)", code)
|
||||
}
|
||||
sb.WriteString(".")
|
||||
if msg := rd.Download.ErrorMsg.String; msg != "" {
|
||||
sb.WriteString("\n")
|
||||
sb.WriteString(msg)
|
||||
}
|
||||
if src := contextOrSource(rd); src != "" {
|
||||
fmt.Fprintf(&sb, "\nИсточник: %s", shorten(src, 80))
|
||||
}
|
||||
return sb.String(), b.retryKeyboard(id)
|
||||
}
|
||||
|
||||
// retryKeyboard — клавиатура для failed/stuck: повтор + опц. ссылка в веб.
|
||||
func (b *Bot) retryKeyboard(id int64) *tgbotapi.InlineKeyboardMarkup {
|
||||
row := []tgbotapi.InlineKeyboardButton{
|
||||
tgbotapi.NewInlineKeyboardButtonData("🔄 Повторить", "retry:"+itoa(id)),
|
||||
}
|
||||
if url := b.reviewURL(id); url != "" {
|
||||
row = append(row, tgbotapi.NewInlineKeyboardButtonURL("🌐 В вебе", url))
|
||||
}
|
||||
kb := tgbotapi.NewInlineKeyboardMarkup(tgbotapi.NewInlineKeyboardRow(row...))
|
||||
return &kb
|
||||
}
|
||||
|
||||
func (b *Bot) webOnly(id int64) *tgbotapi.InlineKeyboardMarkup {
|
||||
url := b.reviewURL(id)
|
||||
if url == "" {
|
||||
|
||||
Reference in New Issue
Block a user