package torrent import ( "context" "encoding/binary" "fmt" dlog "git.kmsign.ru/royalcat/tstor/src/log" "github.com/anacrolix/torrent/metainfo" "github.com/anacrolix/torrent/storage" "github.com/royalcat/kv" "github.com/royalcat/kv/kvbadger" ) type PieceCompletionState byte const ( PieceNotComplete PieceCompletionState = 0 PieceComplete PieceCompletionState = 1<<8 - 1 ) var _ kv.Binary = (*PieceCompletionState)(nil) // MarshalBinary implements kv.Binary. func (p PieceCompletionState) MarshalBinary() (data []byte, err error) { return []byte{byte(p)}, nil } // UnmarshalBinary implements kv.Binary. func (p *PieceCompletionState) UnmarshalBinary(data []byte) error { if len(data) != 1 { return fmt.Errorf("bad length") } switch PieceCompletionState(data[0]) { case PieceComplete: *p = PieceComplete case PieceNotComplete: *p = PieceNotComplete default: *p = PieceNotComplete } return nil } func pieceCompletionState(i bool) PieceCompletionState { if i { return PieceComplete } return PieceNotComplete } type pieceKey metainfo.PieceKey const pieceKeySize = metainfo.HashSize + 4 var _ kv.Binary = (*pieceKey)(nil) // const delimeter rune = 0x1F // MarshalBinary implements kv.Binary. func (pk pieceKey) MarshalBinary() (data []byte, err error) { key := make([]byte, 0, pieceKeySize) key = append(key, pk.InfoHash.Bytes()...) key = binary.BigEndian.AppendUint32(key, uint32(pk.Index)) return key, nil } // UnmarshalBinary implements kv.Binary. func (p *pieceKey) UnmarshalBinary(data []byte) error { if len(data) < pieceKeySize { return fmt.Errorf("data too short") } p.InfoHash = metainfo.Hash(data[:metainfo.HashSize]) p.Index = int(binary.BigEndian.Uint32(data[metainfo.HashSize:])) return nil } type badgerPieceCompletion struct { db kv.Store[pieceKey, PieceCompletionState] } var _ storage.PieceCompletion = (*badgerPieceCompletion)(nil) func newPieceCompletion(dir string) (storage.PieceCompletion, error) { opts := kvbadger.DefaultOptions[PieceCompletionState](dir) opts.Codec = kv.CodecBinary[PieceCompletionState, *PieceCompletionState]{} opts.BadgerOptions = opts.BadgerOptions.WithLogger(dlog.BadgerLogger("torrent-client", "piece-completion")) db, err := kvbadger.NewBagerKVBinaryKey[pieceKey, PieceCompletionState](opts) if err != nil { return nil, err } return &badgerPieceCompletion{ db: db, }, nil } func (c *badgerPieceCompletion) Get(pk metainfo.PieceKey) (completion storage.Completion, err error) { ctx := context.Background() state, err := c.db.Get(ctx, pieceKey(pk)) if err != nil { if err == kv.ErrKeyNotFound { return completion, nil } return completion, err } if state == PieceComplete { return storage.Completion{ Complete: true, Ok: true, }, nil } return storage.Completion{ Complete: false, Ok: true, }, nil } func (me badgerPieceCompletion) Set(pk metainfo.PieceKey, b bool) error { ctx := context.Background() if c, err := me.Get(pk); err == nil && c.Ok && c.Complete == b { return nil } return me.db.Set(ctx, pieceKey(pk), pieceCompletionState(b)) } func (me *badgerPieceCompletion) Close() error { return me.db.Close(context.Background()) }