package torrent import ( "context" "crypto/rand" "sync" "time" "github.com/veggiedefender/torrent-client/internal/torrentfile" "github.com/veggiedefender/torrent-client/internal/tracker" ) type Engine struct { mu sync.RWMutex torrent *torrentfile.TorrentFile peers []tracker.Peer peerID [20]byte cancel context.CancelFunc lastErr error } type Status struct { Loaded bool Name string Length int PieceLength int PieceCount int Files []torrentfile.File Announce string PeerCount int PeerID string Progress float64 LastError string } func NewEngine() *Engine { return &Engine{ peerID: generatePeerID(), } } func (e *Engine) LoadTorrent(path string) error { tf, err := torrentfile.Open(path) if err != nil { e.setError(err) return err } ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second) e.swapCancel(cancel) defer e.clearCancel() peers, err := tracker.GetPeers(ctx, tf, tracker.AnnounceOptions{ PeerID: e.peerID, Port: 6881, }) if err != nil { e.mu.Lock() e.torrent = tf e.peers = nil e.lastErr = err e.mu.Unlock() return err } e.mu.Lock() e.torrent = tf e.peers = peers e.lastErr = nil e.mu.Unlock() return nil } func (e *Engine) Stop() { e.mu.Lock() defer e.mu.Unlock() if e.cancel != nil { e.cancel() e.cancel = nil } } func (e *Engine) Progress() float64 { return 0 } func (e *Engine) Status() Status { e.mu.RLock() defer e.mu.RUnlock() status := Status{ Loaded: e.torrent != nil, PeerCount: len(e.peers), PeerID: string(e.peerID[:]), Progress: e.Progress(), } if e.torrent != nil { status.Name = e.torrent.Name status.Length = e.torrent.Length status.PieceLength = e.torrent.PieceLength status.PieceCount = len(e.torrent.PieceHashes) status.Files = append(status.Files, e.torrent.Files...) status.Announce = e.torrent.Announce } if e.lastErr != nil { status.LastError = e.lastErr.Error() } return status } func (e *Engine) setError(err error) { e.mu.Lock() defer e.mu.Unlock() e.lastErr = err } func (e *Engine) swapCancel(cancel context.CancelFunc) { e.mu.Lock() defer e.mu.Unlock() if e.cancel != nil { e.cancel() } e.cancel = cancel } func (e *Engine) clearCancel() { e.mu.Lock() defer e.mu.Unlock() if e.cancel != nil { e.cancel() e.cancel = nil } } func generatePeerID() [20]byte { const prefix = "-ZT0001-" const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz" var id [20]byte copy(id[:], prefix) buf := make([]byte, len(id)-len(prefix)) if _, err := rand.Read(buf); err != nil { now := time.Now().UnixNano() for i := range buf { buf[i] = byte(now >> (i * 8)) } } for i, b := range buf { id[len(prefix)+i] = alphabet[int(b)%len(alphabet)] } return id }