Добавил бот для Telegram
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.vakhrushev.me/av/jellybit/internal/layout"
|
||||
"git.vakhrushev.me/av/jellybit/internal/qbt"
|
||||
@@ -15,6 +16,61 @@ import (
|
||||
"git.vakhrushev.me/av/jellybit/internal/store"
|
||||
)
|
||||
|
||||
// recordingNotifier ловит события пинга (Notify асинхронен — через канал).
|
||||
type notifyEvent struct {
|
||||
id int64
|
||||
ev NotifyEvent
|
||||
}
|
||||
type recordingNotifier struct{ ch chan notifyEvent }
|
||||
|
||||
func (n *recordingNotifier) Notify(_ context.Context, id int64, ev NotifyEvent) {
|
||||
n.ch <- notifyEvent{id, ev}
|
||||
}
|
||||
|
||||
func waitNotify(t *testing.T, n *recordingNotifier) notifyEvent {
|
||||
t.Helper()
|
||||
select {
|
||||
case e := <-n.ch:
|
||||
return e
|
||||
case <-time.After(2 * time.Second):
|
||||
t.Fatal("пинг не пришёл")
|
||||
return notifyEvent{}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifier_FiresOnReview(t *testing.T) {
|
||||
st := newMemStore()
|
||||
st.put(completedDownload(1))
|
||||
qb := &fakeQbt{
|
||||
torrents: []qbt.Torrent{{Hash: ihTest, Name: "Show", SavePath: "/d"}},
|
||||
files: []qbt.File{{Name: "Show/e1.mkv", Size: 1}},
|
||||
}
|
||||
w := testWorkerWith(st, qb, &fakeRecognizer{result: seriesResult()}, nil)
|
||||
n := &recordingNotifier{ch: make(chan notifyEvent, 4)}
|
||||
w.SetNotifier(n)
|
||||
|
||||
w.recognizeOne(context.Background(), 1)
|
||||
|
||||
e := waitNotify(t, n)
|
||||
if e.id != 1 || e.ev != EventReview {
|
||||
t.Errorf("event = %+v, want {1 review}", e)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNotifier_FiresOnDone(t *testing.T) {
|
||||
f := newApplyFixture(t, seriesResult().Plan)
|
||||
n := &recordingNotifier{ch: make(chan notifyEvent, 4)}
|
||||
f.w.SetNotifier(n)
|
||||
|
||||
if err := f.w.Apply(context.Background(), 1); err != nil {
|
||||
t.Fatalf("Apply: %v", err)
|
||||
}
|
||||
e := waitNotify(t, n)
|
||||
if e.id != 1 || e.ev != EventDone {
|
||||
t.Errorf("event = %+v, want {1 done}", e)
|
||||
}
|
||||
}
|
||||
|
||||
// memStore — полноценный in-memory store для тестов Ф3.
|
||||
type memStore struct {
|
||||
downloads map[int64]*store.Download
|
||||
|
||||
@@ -63,6 +63,19 @@ type Layouter interface {
|
||||
Undo(ctx context.Context, links []layout.Link) (int, error)
|
||||
}
|
||||
|
||||
// NotifyEvent — повод позвать пользователя.
|
||||
type NotifyEvent string
|
||||
|
||||
const (
|
||||
EventReview NotifyEvent = "review" // задача ждёт подтверждения
|
||||
EventDone NotifyEvent = "done" // раскладка завершена
|
||||
)
|
||||
|
||||
// Notifier — исходящие пинги (Telegram). Вызывается неблокирующе.
|
||||
type Notifier interface {
|
||||
Notify(ctx context.Context, downloadID int64, event NotifyEvent)
|
||||
}
|
||||
|
||||
// Config — параметры воркера.
|
||||
type Config struct {
|
||||
Category string
|
||||
@@ -81,11 +94,15 @@ type Worker struct {
|
||||
cfg Config
|
||||
log *slog.Logger
|
||||
|
||||
mu sync.Mutex // сериализует переходы (поллинг + команды)
|
||||
now func() time.Time // подменяется в тестах
|
||||
newID func() string // генератор apply_batch_id (подменяется в тестах)
|
||||
mu sync.Mutex // сериализует переходы (поллинг + команды)
|
||||
now func() time.Time // подменяется в тестах
|
||||
newID func() string // генератор apply_batch_id (подменяется в тестах)
|
||||
notifier Notifier // опц. исходящие пинги
|
||||
}
|
||||
|
||||
// SetNotifier подключает исходящие пинги (до запуска Run).
|
||||
func (w *Worker) SetNotifier(n Notifier) { w.notifier = n }
|
||||
|
||||
// New собирает воркер. recognizer/layouter могут быть nil (Ф1 без Ф3-ступеней
|
||||
// распознавания и раскладки) — тогда completed-задачи не двигаются дальше.
|
||||
func New(st Store, qb QBittorrent, rec Recognizer, lay Layouter, cfg Config, log *slog.Logger) *Worker {
|
||||
@@ -215,6 +232,17 @@ func (w *Worker) transition(ctx context.Context, d store.Download, state store.S
|
||||
}
|
||||
w.log.Info("state transition",
|
||||
"download_id", d.ID, "from", d.State, "to", state, "code", code)
|
||||
|
||||
// Пинги — неблокирующе и в отдельном контексте: вызов уходит в сеть, а
|
||||
// мы под w.mu (Notify читает состояние уже после освобождения замка).
|
||||
if w.notifier != nil {
|
||||
switch state {
|
||||
case store.StateReview:
|
||||
go w.notifier.Notify(context.Background(), d.ID, EventReview)
|
||||
case store.StateDone:
|
||||
go w.notifier.Notify(context.Background(), d.ID, EventDone)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Cancel отклоняет задачу. Торрент в qBittorrent не трогаем — он продолжает
|
||||
|
||||
Reference in New Issue
Block a user