tstor/torrent/stats/torrent.go

192 lines
3.8 KiB
Go
Raw Normal View History

package stats
import (
"errors"
"fmt"
"log"
"time"
"github.com/anacrolix/torrent"
"github.com/dustin/go-humanize"
)
var ErrTorrentNotFound = errors.New("torrent not found")
type PieceStatus string
const (
Checking PieceStatus = "H"
Partial PieceStatus = "P"
Complete PieceStatus = "C"
Waiting PieceStatus = "W"
Error PieceStatus = "?"
)
type PieceChunk struct {
Status PieceStatus
NumPieces int
}
type TorrentStats struct {
DownloadedBytes int64
UploadedBytes int64
TimePassed float64
PieceChunks []*PieceChunk
}
type GlobalTorrentStats struct {
DownloadedBytes int64
UploadedBytes int64
TimePassed float64
}
func (s *GlobalTorrentStats) speed(bytes int64) float64 {
var bs float64
t := s.TimePassed
if t != 0 {
bs = float64(bytes) / t
}
return bs
}
func (s *GlobalTorrentStats) DownloadSpeed() string {
return fmt.Sprintf(" %s/s", humanize.IBytes(uint64(s.speed(s.DownloadedBytes))))
}
func (s *GlobalTorrentStats) UploadSpeed() string {
return fmt.Sprintf(" %s/s", humanize.IBytes(uint64(s.speed(s.UploadedBytes))))
}
type stats struct {
totalDownloadBytes int64
downloadBytes int64
totalUploadBytes int64
uploadBytes int64
time time.Time
}
type Torrent struct {
torrents map[string]*torrent.Torrent
previousStats map[string]*stats
gTime time.Time
}
func NewTorrent() *Torrent {
return &Torrent{
gTime: time.Now(),
torrents: make(map[string]*torrent.Torrent),
previousStats: make(map[string]*stats),
}
}
func (s *Torrent) Add(t *torrent.Torrent) {
s.torrents[t.InfoHash().String()] = t
s.previousStats[t.InfoHash().String()] = &stats{}
}
func (s *Torrent) Torrent(hash string) (*TorrentStats, error) {
t, ok := s.torrents[hash]
if !(ok) {
return nil, ErrTorrentNotFound
}
now := time.Now()
return s.stats(now, t, true), nil
}
func (s *Torrent) List() []string {
var result []string
for hash := range s.torrents {
result = append(result, hash)
}
return result
}
func (s *Torrent) Global() *GlobalTorrentStats {
now := time.Now()
var totalDownload int64
var totalUpload int64
for _, torrent := range s.torrents {
tStats := s.stats(now, torrent, false)
totalDownload += tStats.DownloadedBytes
totalUpload += tStats.UploadedBytes
}
timePassed := now.Sub(s.gTime)
s.gTime = now
return &GlobalTorrentStats{
DownloadedBytes: totalDownload,
UploadedBytes: totalUpload,
TimePassed: timePassed.Seconds(),
}
}
func (s *Torrent) stats(now time.Time, t *torrent.Torrent, chunks bool) *TorrentStats {
ts := &TorrentStats{}
prev := s.previousStats[t.InfoHash().String()]
if s.returnPreviousMeasurements(now) {
ts.DownloadedBytes = prev.downloadBytes
ts.UploadedBytes = prev.uploadBytes
log.Println("Using previous stats")
} else {
st := t.Stats()
rd := st.BytesReadData.Int64()
wd := st.BytesWrittenData.Int64()
ist := &stats{
downloadBytes: rd - prev.totalDownloadBytes,
uploadBytes: wd - prev.totalUploadBytes,
totalDownloadBytes: rd,
totalUploadBytes: wd,
time: now,
}
ts.DownloadedBytes = ist.downloadBytes
ts.UploadedBytes = ist.uploadBytes
s.previousStats[t.InfoHash().String()] = ist
}
ts.TimePassed = now.Sub(prev.time).Seconds()
if chunks {
var pch []*PieceChunk
for _, psr := range t.PieceStateRuns() {
var s PieceStatus
switch {
case psr.Checking:
s = Checking
case psr.Partial:
s = Partial
case psr.Complete:
s = Complete
case !psr.Ok:
s = Error
default:
s = Waiting
}
pch = append(pch, &PieceChunk{
Status: s,
NumPieces: psr.Length,
})
}
ts.PieceChunks = pch
}
return ts
}
const gap time.Duration = 2 * time.Second
func (s *Torrent) returnPreviousMeasurements(now time.Time) bool {
return now.Sub(s.gTime) < gap
}