158 lines
2.7 KiB
Go
158 lines
2.7 KiB
Go
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
|
|
}
|