tstor/src/sources/torrent/controller.go

204 lines
4.2 KiB
Go
Raw Normal View History

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
"slices"
"strings"
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-16 21:34:46 +00:00
func newController(t *torrent.Torrent, fileProperties 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,
fileProperties: fileProperties,
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-05-19 21:24:09 +00:00
func (s *Controller) Files(ctx context.Context) ([]*torrent.File, error) {
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-01-28 20:22:49 +00:00
files := s.t.Files()
files = slices.DeleteFunc(files, func(file *torrent.File) bool {
2024-04-27 11:00:34 +00:00
if file == nil {
return true
}
2024-01-28 20:22:49 +00:00
p := file.Path()
if strings.Contains(p, "/.pad/") {
2024-03-17 21:00:34 +00:00
return true
2024-01-28 20:22:49 +00:00
}
2024-06-16 21:34:46 +00:00
if props, ok := fps[p]; ok && props.Excluded {
2024-03-17 21:00:34 +00:00
return true
2024-01-28 20:22:49 +00:00
}
2024-06-16 21:34:46 +00:00
2024-03-17 21:00:34 +00:00
return false
2024-01-28 20:22:49 +00:00
})
2024-03-17 21:00:34 +00:00
return files, nil
}
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")
return s.fileProperties.Edit(ctx, f.Path(), func(ctx context.Context, v FileProperties) (FileProperties, error) {
v.Excluded = true
return v, nil
})
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-06-16 21:34:46 +00:00
func (c *Controller) SetFilePriority(ctx context.Context, file *torrent.File, priority types.PiecePriority) error {
log := c.log.With(slog.String("file", file.Path()), slog.Int("priority", int(priority)))
log.Info(ctx, "set pritority for file")
err := c.fileProperties.Edit(ctx, file.Path(), func(ctx context.Context, v FileProperties) (FileProperties, error) {
v.Priority = priority
return v, nil
})
if err != nil {
return err
}
file.SetPriority(priority)
return nil
}
2024-06-14 22:14:44 +00:00
func (c *Controller) initializeTorrentPriories(ctx context.Context) error {
log := c.log.WithComponent("initializeTorrentPriories")
2024-06-16 21:34:46 +00:00
files, err := c.Files(ctx)
if err != nil {
return err
}
for _, file := range files {
if file == nil {
continue
}
props, err := c.fileProperties.Get(ctx, file.Path())
if err != nil {
if err == kv.ErrKeyNotFound {
continue
}
log.Error(ctx, "failed to get file priority", rlog.Error(err))
}
2024-06-14 22:14:44 +00:00
2024-06-16 21:34:46 +00:00
file.SetPriority(props.Priority)
2024-06-14 22:14:44 +00:00
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
}