2024-05-19 21:24:09 +00:00
|
|
|
package torrent
|
2020-05-02 12:06:18 +00:00
|
|
|
|
|
|
|
import (
|
2024-07-16 20:58:06 +00:00
|
|
|
"context"
|
|
|
|
"encoding/json"
|
|
|
|
"path"
|
|
|
|
"slices"
|
2020-05-02 12:06:18 +00:00
|
|
|
"time"
|
|
|
|
|
2024-08-31 23:00:13 +00:00
|
|
|
"git.kmsign.ru/royalcat/tstor/src/logwrap"
|
2024-07-16 20:58:06 +00:00
|
|
|
"github.com/anacrolix/torrent/types/infohash"
|
|
|
|
"github.com/dgraph-io/badger/v4"
|
2020-05-02 12:06:18 +00:00
|
|
|
)
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
func newStatsStore(metaDir string, lifetime time.Duration) (*statsStore, error) {
|
|
|
|
db, err := badger.OpenManaged(
|
|
|
|
badger.
|
|
|
|
DefaultOptions(path.Join(metaDir, "stats")).
|
|
|
|
WithNumVersionsToKeep(int(^uint(0) >> 1)).
|
2024-08-31 23:00:13 +00:00
|
|
|
WithLogger(logwrap.BadgerLogger("stats")), // Infinity
|
2024-07-16 20:58:06 +00:00
|
|
|
)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-02 12:06:18 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
go func() {
|
|
|
|
for n := range time.NewTimer(lifetime / 2).C {
|
|
|
|
db.SetDiscardTs(uint64(n.Add(-lifetime).Unix()))
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
return &statsStore{
|
|
|
|
db: db,
|
|
|
|
}, nil
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
type statsStore struct {
|
|
|
|
db *badger.DB
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
type TorrentStats struct {
|
|
|
|
Timestamp time.Time
|
|
|
|
DownloadedBytes uint64
|
|
|
|
UploadedBytes uint64
|
|
|
|
TotalPeers uint16
|
|
|
|
ActivePeers uint16
|
|
|
|
ConnectedSeeders uint16
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
func (s TorrentStats) Same(o TorrentStats) bool {
|
|
|
|
return s.DownloadedBytes == o.DownloadedBytes &&
|
|
|
|
s.UploadedBytes == o.UploadedBytes &&
|
|
|
|
s.TotalPeers == o.TotalPeers &&
|
|
|
|
s.ActivePeers == o.ActivePeers &&
|
|
|
|
s.ConnectedSeeders == o.ConnectedSeeders
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
func (r *statsStore) addStats(key []byte, stat TorrentStats) error {
|
|
|
|
ts := uint64(stat.Timestamp.Unix())
|
2021-01-02 19:09:05 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
txn := r.db.NewTransactionAt(ts, true)
|
|
|
|
defer txn.Discard()
|
2020-05-02 12:06:18 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
item, err := txn.Get(key)
|
|
|
|
if err != nil && err != badger.ErrKeyNotFound {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-02 12:06:18 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
if err != badger.ErrKeyNotFound {
|
|
|
|
var prevStats TorrentStats
|
|
|
|
err = item.Value(func(val []byte) error {
|
|
|
|
return json.Unmarshal(val, &prevStats)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-02 12:06:18 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
if prevStats.Same(stat) {
|
|
|
|
return nil
|
|
|
|
}
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
data, err := json.Marshal(stat)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
err = txn.Set(key, data)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-11-16 12:13:58 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
return txn.CommitAt(ts, nil)
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
func (r *statsStore) AddTorrentStats(ih infohash.T, stat TorrentStats) error {
|
|
|
|
return r.addStats(ih.Bytes(), stat)
|
2020-11-08 17:19:25 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
const totalKey = "total"
|
2020-05-02 12:06:18 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
func (r *statsStore) AddTotalStats(stat TorrentStats) error {
|
|
|
|
return r.addStats([]byte(totalKey), stat)
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
func (r *statsStore) ReadTotalStatsHistory(ctx context.Context, since time.Time) ([]TorrentStats, error) {
|
|
|
|
stats := []TorrentStats{}
|
|
|
|
|
|
|
|
err := r.db.View(func(txn *badger.Txn) error {
|
|
|
|
opts := badger.DefaultIteratorOptions
|
|
|
|
opts.AllVersions = true
|
|
|
|
opts.SinceTs = uint64(since.Unix())
|
|
|
|
|
|
|
|
it := txn.NewKeyIterator([]byte(totalKey), opts)
|
|
|
|
defer it.Close()
|
|
|
|
for it.Rewind(); it.Valid(); it.Next() {
|
|
|
|
item := it.Item()
|
|
|
|
var stat TorrentStats
|
|
|
|
err := item.Value(func(v []byte) error {
|
|
|
|
return json.Unmarshal(v, &stat)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2020-05-02 12:06:18 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
stats = append(stats, stat)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
slices.SortFunc(stats, func(a, b TorrentStats) int {
|
|
|
|
return a.Timestamp.Compare(b.Timestamp)
|
|
|
|
})
|
|
|
|
stats = slices.Compact(stats)
|
|
|
|
return stats, nil
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
func (r *statsStore) ReadTorrentStatsHistory(ctx context.Context, since time.Time, ih infohash.T) ([]TorrentStats, error) {
|
|
|
|
stats := []TorrentStats{}
|
|
|
|
|
|
|
|
err := r.db.View(func(txn *badger.Txn) error {
|
|
|
|
opts := badger.DefaultIteratorOptions
|
|
|
|
opts.AllVersions = true
|
|
|
|
opts.SinceTs = uint64(since.Unix())
|
|
|
|
|
|
|
|
it := txn.NewKeyIterator(ih.Bytes(), opts)
|
|
|
|
defer it.Close()
|
|
|
|
for it.Rewind(); it.Valid(); it.Next() {
|
|
|
|
item := it.Item()
|
|
|
|
var stat TorrentStats
|
|
|
|
err := item.Value(func(v []byte) error {
|
|
|
|
return json.Unmarshal(v, &stat)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
stats = append(stats, stat)
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
2024-07-16 20:58:06 +00:00
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
2020-06-09 10:06:12 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
slices.SortFunc(stats, func(a, b TorrentStats) int {
|
|
|
|
return a.Timestamp.Compare(b.Timestamp)
|
|
|
|
})
|
|
|
|
stats = slices.Compact(stats)
|
|
|
|
return stats, nil
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
func (r *statsStore) ReadStatsHistory(ctx context.Context, since time.Time) ([]TorrentStats, error) {
|
|
|
|
stats := []TorrentStats{}
|
|
|
|
|
|
|
|
err := r.db.View(func(txn *badger.Txn) error {
|
|
|
|
opts := badger.DefaultIteratorOptions
|
|
|
|
opts.AllVersions = true
|
|
|
|
opts.SinceTs = uint64(since.Unix())
|
|
|
|
|
|
|
|
it := txn.NewIterator(opts)
|
|
|
|
defer it.Close()
|
|
|
|
for it.Rewind(); it.Valid(); it.Next() {
|
|
|
|
item := it.Item()
|
|
|
|
var stat TorrentStats
|
|
|
|
err := item.Value(func(v []byte) error {
|
|
|
|
return json.Unmarshal(v, &stat)
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
stats = append(stats, stat)
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2020-05-02 12:06:18 +00:00
|
|
|
|
2024-07-16 20:58:06 +00:00
|
|
|
slices.SortFunc(stats, func(a, b TorrentStats) int {
|
|
|
|
return a.Timestamp.Compare(b.Timestamp)
|
|
|
|
})
|
|
|
|
stats = slices.Compact(stats)
|
|
|
|
return stats, nil
|
2020-05-02 12:06:18 +00:00
|
|
|
}
|