2024-05-19 21:24:09 +00:00
|
|
|
package torrent
|
2024-01-28 20:22:49 +00:00
|
|
|
|
|
|
|
import (
|
2024-03-17 21:00:34 +00:00
|
|
|
"context"
|
2024-06-14 22:14:44 +00:00
|
|
|
"log/slog"
|
2024-01-28 20:22:49 +00:00
|
|
|
"strings"
|
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
"git.kmsign.ru/royalcat/tstor/pkg/kvsingle"
|
2024-06-14 22:14:44 +00:00
|
|
|
"git.kmsign.ru/royalcat/tstor/pkg/rlog"
|
2024-01-28 20:22:49 +00:00
|
|
|
"github.com/anacrolix/torrent"
|
2024-06-16 21:34:46 +00:00
|
|
|
"github.com/anacrolix/torrent/types"
|
|
|
|
"github.com/royalcat/kv"
|
2024-01-28 20:22:49 +00:00
|
|
|
)
|
|
|
|
|
2024-06-16 21:34:46 +00:00
|
|
|
type TorrentFileDeleter interface {
|
|
|
|
DeleteFile(file *torrent.File) error
|
|
|
|
}
|
|
|
|
|
|
|
|
type FileProperties struct {
|
|
|
|
Excluded bool `json:"excluded"`
|
|
|
|
Priority types.PiecePriority `json:"priority"`
|
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
type Controller struct {
|
2024-01-28 20:22:49 +00:00
|
|
|
torrentFilePath string
|
|
|
|
t *torrent.Torrent
|
2024-06-16 21:34:46 +00:00
|
|
|
storage TorrentFileDeleter
|
|
|
|
fileProperties kv.Store[string, FileProperties]
|
2024-06-14 22:14:44 +00:00
|
|
|
log *rlog.Logger
|
2024-01-28 20:22:49 +00:00
|
|
|
}
|
|
|
|
|
2024-06-18 19:27:18 +00:00
|
|
|
func newController(t *torrent.Torrent, torrentFileProperties kv.Store[string, FileProperties], storage TorrentFileDeleter) *Controller {
|
2024-06-14 22:14:44 +00:00
|
|
|
return &Controller{
|
2024-06-16 21:34:46 +00:00
|
|
|
t: t,
|
|
|
|
storage: storage,
|
2024-06-18 19:27:18 +00:00
|
|
|
fileProperties: torrentFileProperties,
|
2024-06-16 21:34:46 +00:00
|
|
|
log: rlog.Component("torrent-client", "controller").With(slog.String("infohash", t.InfoHash().HexString())),
|
2024-06-14 22:14:44 +00:00
|
|
|
}
|
2024-01-28 20:22:49 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (s *Controller) TorrentFilePath() string {
|
2024-01-28 20:22:49 +00:00
|
|
|
return s.torrentFilePath
|
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (s *Controller) Torrent() *torrent.Torrent {
|
2024-01-28 20:22:49 +00:00
|
|
|
return s.t
|
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (c *Controller) Name() string {
|
2024-03-17 21:00:34 +00:00
|
|
|
<-c.t.GotInfo()
|
|
|
|
if name := c.t.Name(); name != "" {
|
2024-02-22 22:54:56 +00:00
|
|
|
return name
|
|
|
|
}
|
|
|
|
|
2024-03-17 21:00:34 +00:00
|
|
|
return c.InfoHash()
|
2024-01-28 20:22:49 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (s *Controller) InfoHash() string {
|
2024-01-28 20:22:49 +00:00
|
|
|
<-s.t.GotInfo()
|
|
|
|
return s.t.InfoHash().HexString()
|
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (s *Controller) BytesCompleted() int64 {
|
2024-01-28 20:22:49 +00:00
|
|
|
<-s.t.GotInfo()
|
|
|
|
return s.t.BytesCompleted()
|
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (s *Controller) BytesMissing() int64 {
|
2024-01-28 20:22:49 +00:00
|
|
|
<-s.t.GotInfo()
|
|
|
|
return s.t.BytesMissing()
|
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (s *Controller) Length() int64 {
|
2024-03-17 21:00:34 +00:00
|
|
|
<-s.t.GotInfo()
|
|
|
|
return s.t.Length()
|
|
|
|
}
|
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
func (s *Controller) Files(ctx context.Context) ([]*FileController, error) {
|
|
|
|
ctx, span := tracer.Start(ctx, "Files")
|
|
|
|
defer span.End()
|
|
|
|
|
2024-06-16 21:34:46 +00:00
|
|
|
fps := map[string]FileProperties{}
|
|
|
|
err := s.fileProperties.Range(ctx, func(k string, v FileProperties) error {
|
|
|
|
fps[k] = v
|
|
|
|
return nil
|
|
|
|
})
|
2024-01-28 20:22:49 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2024-04-27 11:00:34 +00:00
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return nil, ctx.Err()
|
|
|
|
case <-s.t.GotInfo():
|
|
|
|
}
|
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
files := make([]*FileController, 0)
|
|
|
|
for _, v := range s.t.Files() {
|
|
|
|
if strings.Contains(v.Path(), "/.pad/") {
|
|
|
|
continue
|
2024-01-28 20:22:49 +00:00
|
|
|
}
|
2024-06-16 21:34:46 +00:00
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
props := kvsingle.New(s.fileProperties, v.Path())
|
|
|
|
ctl := NewFileController(v, props)
|
|
|
|
files = append(files, ctl)
|
|
|
|
}
|
2024-01-28 20:22:49 +00:00
|
|
|
|
2024-03-17 21:00:34 +00:00
|
|
|
return files, nil
|
|
|
|
}
|
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
func (s *Controller) GetFile(ctx context.Context, file string) (*FileController, error) {
|
2024-07-07 20:09:13 +00:00
|
|
|
files, err := s.Files(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, v := range files {
|
|
|
|
if v.Path() == file {
|
|
|
|
return v, nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
|
2024-03-17 21:00:34 +00:00
|
|
|
func Map[T, U any](ts []T, f func(T) U) []U {
|
|
|
|
us := make([]U, len(ts))
|
|
|
|
for i := range ts {
|
|
|
|
us[i] = f(ts[i])
|
2024-02-22 22:54:56 +00:00
|
|
|
}
|
2024-03-17 21:00:34 +00:00
|
|
|
return us
|
|
|
|
}
|
2024-02-22 22:54:56 +00:00
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (s *Controller) ExcludeFile(ctx context.Context, f *torrent.File) error {
|
2024-06-16 21:34:46 +00:00
|
|
|
log := s.log.With(slog.String("file", f.Path()))
|
|
|
|
log.Info(ctx, "excluding file")
|
|
|
|
|
2024-06-18 19:27:18 +00:00
|
|
|
err := s.fileProperties.Edit(ctx, f.Path(), func(ctx context.Context, v FileProperties) (FileProperties, error) {
|
2024-06-16 21:34:46 +00:00
|
|
|
v.Excluded = true
|
|
|
|
return v, nil
|
|
|
|
})
|
2024-06-18 19:27:18 +00:00
|
|
|
if err == kv.ErrKeyNotFound {
|
|
|
|
err := s.fileProperties.Set(ctx, f.Path(), FileProperties{Excluded: true})
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
} else if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.storage.DeleteFile(f)
|
2024-01-28 20:22:49 +00:00
|
|
|
}
|
|
|
|
|
2024-05-19 21:24:09 +00:00
|
|
|
func (s *Controller) isFileComplete(startIndex int, endIndex int) bool {
|
2024-02-22 22:54:56 +00:00
|
|
|
for i := startIndex; i < endIndex; i++ {
|
|
|
|
if !s.t.Piece(i).State().Complete {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
|
2024-06-14 22:14:44 +00:00
|
|
|
func (s *Controller) ValidateTorrent(ctx context.Context) error {
|
|
|
|
select {
|
|
|
|
case <-ctx.Done():
|
|
|
|
return ctx.Err()
|
|
|
|
case <-s.t.GotInfo():
|
|
|
|
}
|
|
|
|
|
|
|
|
for i := 0; i < s.t.NumPieces(); i++ {
|
|
|
|
if ctx.Err() != nil {
|
|
|
|
return ctx.Err()
|
|
|
|
}
|
|
|
|
|
|
|
|
s.t.Piece(i).VerifyData()
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
func (c *Controller) SetPriority(ctx context.Context, priority types.PiecePriority) error {
|
|
|
|
// log := c.log.With(slog.Int("priority", int(priority)))
|
2024-07-07 20:09:13 +00:00
|
|
|
|
|
|
|
for _, f := range c.t.Files() {
|
|
|
|
err := c.setFilePriority(ctx, f, priority)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
2024-06-16 21:34:46 +00:00
|
|
|
|
2024-07-07 20:09:13 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
const defaultPriority = types.PiecePriorityNone
|
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
func (c *Controller) Priority(ctx context.Context) (types.PiecePriority, error) {
|
|
|
|
prio := defaultPriority
|
|
|
|
files, err := c.Files(ctx)
|
2024-07-07 20:09:13 +00:00
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
2024-07-08 21:19:04 +00:00
|
|
|
for _, v := range files {
|
|
|
|
props, err := v.Properties(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
|
|
}
|
|
|
|
if props.Priority > prio {
|
|
|
|
prio = props.Priority
|
|
|
|
}
|
|
|
|
}
|
2024-07-07 20:09:13 +00:00
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
return prio, nil
|
2024-07-07 20:09:13 +00:00
|
|
|
}
|
|
|
|
func (c *Controller) setFilePriority(ctx context.Context, file *torrent.File, priority types.PiecePriority) error {
|
2024-06-16 21:34:46 +00:00
|
|
|
err := c.fileProperties.Edit(ctx, file.Path(), func(ctx context.Context, v FileProperties) (FileProperties, error) {
|
|
|
|
v.Priority = priority
|
|
|
|
return v, nil
|
|
|
|
})
|
2024-07-08 21:19:04 +00:00
|
|
|
|
|
|
|
if err == kv.ErrKeyNotFound {
|
|
|
|
seterr := c.fileProperties.Set(ctx, file.Path(), FileProperties{Priority: priority})
|
|
|
|
if seterr != nil {
|
|
|
|
return seterr
|
2024-07-07 20:09:13 +00:00
|
|
|
}
|
2024-07-08 21:19:04 +00:00
|
|
|
err = nil
|
|
|
|
}
|
2024-07-07 20:09:13 +00:00
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
if err != nil {
|
2024-06-16 21:34:46 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
file.SetPriority(priority)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2024-06-14 22:14:44 +00:00
|
|
|
func (c *Controller) initializeTorrentPriories(ctx context.Context) error {
|
2024-07-08 21:19:04 +00:00
|
|
|
ctx, span := tracer.Start(ctx, "initializeTorrentPriories")
|
|
|
|
defer span.End()
|
|
|
|
log := c.log
|
2024-06-14 22:14:44 +00:00
|
|
|
|
2024-06-16 21:34:46 +00:00
|
|
|
files, err := c.Files(ctx)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, file := range files {
|
2024-07-08 21:19:04 +00:00
|
|
|
props, err := file.Properties(ctx)
|
2024-06-16 21:34:46 +00:00
|
|
|
if err != nil {
|
2024-07-08 21:19:04 +00:00
|
|
|
log.Error(ctx, "failed to get file properties", rlog.Error(err))
|
|
|
|
continue
|
2024-06-16 21:34:46 +00:00
|
|
|
}
|
2024-07-08 21:19:04 +00:00
|
|
|
log = log.With(slog.Int("priority", int(props.Priority)))
|
2024-06-14 22:14:44 +00:00
|
|
|
|
2024-07-08 21:19:04 +00:00
|
|
|
file.file.SetPriority(props.Priority)
|
2024-06-16 21:34:46 +00:00
|
|
|
}
|
2024-06-14 22:14:44 +00:00
|
|
|
|
|
|
|
log.Info(ctx, "torrent initialization complete", slog.String("infohash", c.InfoHash()), slog.String("torrent_name", c.Name()))
|
|
|
|
|
2024-01-28 20:22:49 +00:00
|
|
|
return nil
|
|
|
|
}
|