Files
transmission-telegram/internal/monitor/monitor.go

126 lines
3.2 KiB
Go

package monitor
import (
"context"
"sync"
"time"
"github.com/pyed/transmission"
"transmission-telegram/internal/logger"
transmissionClient "transmission-telegram/internal/transmission"
)
// torrentState stores the state of a torrent for completion detection
type torrentState struct {
Status int
PercentDone float64
}
// Monitor monitors torrent completion via RPC API polling
type Monitor struct {
client transmissionClient.Client
logger *logger.Logger
states map[int]torrentState
statesMutex sync.RWMutex
chatID *int64
chatMutex sync.RWMutex
onComplete func(torrent *transmission.Torrent)
interval time.Duration
}
// NewMonitor creates a new torrent completion monitor
func NewMonitor(client transmissionClient.Client, log *logger.Logger, interval time.Duration) *Monitor {
return &Monitor{
client: client,
logger: log,
states: make(map[int]torrentState),
interval: interval,
}
}
// SetChatID sets the chat ID for notifications
func (m *Monitor) SetChatID(chatID int64) {
m.chatMutex.Lock()
m.chatID = &chatID
m.chatMutex.Unlock()
}
// SetOnComplete sets the callback function for completion notifications
func (m *Monitor) SetOnComplete(callback func(torrent *transmission.Torrent)) {
m.onComplete = callback
}
// Start starts the monitoring loop
func (m *Monitor) Start(ctx context.Context) {
ticker := time.NewTicker(m.interval)
defer ticker.Stop()
m.logger.Printf("[INFO] Starting torrent completion monitoring via RPC polling")
for {
select {
case <-ctx.Done():
m.logger.Printf("[INFO] Stopping torrent completion monitoring")
return
case <-ticker.C:
m.checkCompletions()
}
}
}
// checkCompletions checks for completed torrents
func (m *Monitor) checkCompletions() {
m.chatMutex.RLock()
if m.chatID == nil || *m.chatID == 0 {
m.chatMutex.RUnlock()
return
}
m.chatMutex.RUnlock()
torrents, err := m.client.GetTorrents()
if err != nil {
m.logger.Printf("[ERROR] Failed to get torrents for monitoring: %s", err)
return
}
m.statesMutex.Lock()
defer m.statesMutex.Unlock()
for _, torrent := range torrents {
prevState, exists := m.states[torrent.ID]
// Detect completion: transition from Downloading to Seeding with 100% done
completed := false
if exists && prevState.Status == transmission.StatusDownloading {
// Case 1: Status changed from Downloading to Seeding (most common)
if torrent.Status == transmission.StatusSeeding && torrent.PercentDone >= 1.0 {
completed = true
}
// Case 2: Torrent reached 100% while still in Downloading status
if torrent.Status == transmission.StatusDownloading &&
prevState.PercentDone < 1.0 &&
torrent.PercentDone >= 1.0 {
completed = true
}
}
if completed && m.onComplete != nil {
m.onComplete(torrent)
m.logger.Printf("[INFO] Torrent completed: %s (ID: %d)", torrent.Name, torrent.ID)
}
// Update state - only track downloading torrents to save memory
if torrent.Status == transmission.StatusDownloading ||
torrent.Status == transmission.StatusDownloadPending {
m.states[torrent.ID] = torrentState{
Status: torrent.Status,
PercentDone: torrent.PercentDone,
}
} else {
// Remove completed or stopped torrents from tracking
delete(m.states, torrent.ID)
}
}
}