+++ /dev/null
-package main
-
-import (
- "bytes"
- "crypto/sha1"
- "fmt"
- "io"
- "log"
- "os"
- "path/filepath"
-
- "github.com/anacrolix/tagflag"
- "github.com/edsrzf/mmap-go"
-
- "github.com/anacrolix/torrent/metainfo"
- "github.com/anacrolix/torrent/mmap_span"
- "github.com/anacrolix/torrent/storage"
-)
-
-func mmapFile(name string) (mm storage.FileMapping, err error) {
- f, err := os.Open(name)
- if err != nil {
- return
- }
- defer func() {
- if err != nil {
- f.Close()
- }
- }()
- fi, err := f.Stat()
- if err != nil {
- return
- }
- if fi.Size() == 0 {
- return
- }
- reg, err := mmap.MapRegion(f, -1, mmap.RDONLY, mmap.COPY, 0)
- if err != nil {
- return
- }
- return storage.WrapFileMapping(reg, f), nil
-}
-
-func verifyTorrent(info *metainfo.Info, root string) error {
- span := new(mmap_span.MMapSpan)
- for _, file := range info.UpvertedFiles() {
- filename := filepath.Join(append([]string{root, info.Name}, file.Path...)...)
- mm, err := mmapFile(filename)
- if err != nil {
- return err
- }
- if int64(len(mm.Bytes())) != file.Length {
- return fmt.Errorf("file %q has wrong length", filename)
- }
- span.Append(mm)
- }
- span.InitIndex()
- for i, numPieces := 0, info.NumPieces(); i < numPieces; i += 1 {
- p := info.Piece(i)
- hash := sha1.New()
- _, err := io.Copy(hash, io.NewSectionReader(span, p.Offset(), p.Length()))
- if err != nil {
- return err
- }
- good := bytes.Equal(hash.Sum(nil), p.Hash().Bytes())
- if !good {
- return fmt.Errorf("hash mismatch at piece %d", i)
- }
- fmt.Printf("%d: %v: %v\n", i, p.Hash(), good)
- }
- return nil
-}
-
-func main() {
- log.SetFlags(log.Flags() | log.Lshortfile)
- flags := struct {
- DataDir string
- tagflag.StartPos
- TorrentFile string
- }{}
- tagflag.Parse(&flags)
- metaInfo, err := metainfo.LoadFromFile(flags.TorrentFile)
- if err != nil {
- log.Fatal(err)
- }
- info, err := metaInfo.UnmarshalInfo()
- if err != nil {
- log.Fatalf("error unmarshalling info: %s", err)
- }
- err = verifyTorrent(&info, flags.DataDir)
- if err != nil {
- log.Fatalf("torrent failed verification: %s", err)
- }
-}
package metainfo
+import (
+ g "github.com/anacrolix/generics"
+)
+
type Piece struct {
Info *Info // Can we embed the fields here instead, or is it something to do with saving memory?
i pieceIndex
return int64(p.i) * p.Info.PieceLength
}
-func (p Piece) Hash() (ret Hash) {
- copy(ret[:], p.Info.Pieces[p.i*HashSize:(p.i+1)*HashSize])
+func (p Piece) V1Hash() (ret g.Option[Hash]) {
+ if !p.Info.HasV1() {
+ return
+ }
+ copy(ret.Value[:], p.Info.Pieces[p.i*HashSize:(p.i+1)*HashSize])
+ ret.Ok = true
return
}
}
func (p *Piece) Storage() storage.Piece {
- return p.t.storage.Piece(p.Info())
+ var pieceHash g.Option[[]byte]
+ if p.hash != nil {
+ pieceHash.Set(p.hash.Bytes())
+ } else if p.hashV2.Ok {
+ pieceHash.Set(p.hashV2.Value.Bytes())
+ }
+ return p.t.storage.PieceWithHash(p.Info(), pieceHash)
}
func (p *Piece) Flush() {
import (
"io"
+ g "github.com/anacrolix/generics"
+
"github.com/anacrolix/torrent/metainfo"
)
// Data storage bound to a torrent.
type TorrentImpl struct {
+ // v2 infos might not have the piece hash available even if we have the info. The
+ // metainfo.Piece.Hash method was removed to enforce this.
Piece func(p metainfo.Piece) PieceImpl
- Close func() error
- Flush func() error
+ // Preferred over PieceWithHash. Called with the piece hash if it's available.
+ PieceWithHash func(p metainfo.Piece, pieceHash g.Option[[]byte]) PieceImpl
+ Close func() error
+ Flush func() error
// Storages that share the same space, will provide equal pointers. The function is called once
// to determine the storage for torrents sharing the same function pointer, and mutated in
// place.
// Two different torrents opened from the same storage. Closing one should not
// break the piece completion on the other.
-func testIssue95(t *testing.T, c ClientImpl) {
+func testIssue95(t *testing.T, ci ClientImpl) {
i1 := &metainfo.Info{
Files: []metainfo.FileInfo{{Path: []string{"a"}}},
Pieces: make([]byte, 20),
}
+ c := NewClient(ci)
t1, err := c.OpenTorrent(i1, metainfo.HashBytes([]byte("a")))
require.NoError(t, err)
defer t1.Close()
package storage
import (
+ g "github.com/anacrolix/generics"
"testing"
"github.com/stretchr/testify/require"
}
ts, err := cs.OpenTorrent(info, metainfo.Hash{})
require.NoError(t, err)
- p := ts.Piece(info.Piece(0))
+ p := ts.PieceWithHash(info.Piece(0), g.None[[]byte]())
require.NoError(t, p.MarkComplete())
// require.False(t, p.GetIsComplete())
n, err := p.ReadAt(make([]byte, 1), 0)
import (
"bytes"
+ "encoding/hex"
"fmt"
"io"
"path"
"sort"
"strconv"
+ g "github.com/anacrolix/generics"
"github.com/anacrolix/missinggo/v2/resource"
"github.com/anacrolix/sync"
s,
make([]sync.RWMutex, info.NumPieces()),
}
- return TorrentImpl{Piece: t.Piece, Close: t.Close}, nil
+ return TorrentImpl{PieceWithHash: t.Piece, Close: t.Close}, nil
}
-func (s piecePerResourceTorrentImpl) Piece(p metainfo.Piece) PieceImpl {
+func (s piecePerResourceTorrentImpl) Piece(p metainfo.Piece, pieceHash g.Option[[]byte]) PieceImpl {
return piecePerResourcePiece{
mp: p,
+ pieceHash: pieceHash,
piecePerResource: s.piecePerResource,
mu: &s.locks[p.Index()],
}
type piecePerResourcePiece struct {
mp metainfo.Piece
+ // The piece hash if we have it. It could be 20 or 32 bytes depending on the info version.
+ pieceHash g.Option[[]byte]
piecePerResource
// This protects operations that move complete/incomplete pieces around, which can trigger read
// errors that may cause callers to do more drastic things.
return completion.Complete
}
-func (s piecePerResourcePiece) Completion() Completion {
+func (s piecePerResourcePiece) Completion() (_ Completion) {
+ if !s.pieceHash.Ok {
+ return
+ }
s.mu.RLock()
defer s.mu.RUnlock()
fi, err := s.completed().Stat()
}
func (s piecePerResourcePiece) completedInstancePath() string {
- return path.Join("completed", s.mp.Hash().HexString())
+ return path.Join("completed", s.hashHex())
}
func (s piecePerResourcePiece) completed() resource.Instance {
}
func (s piecePerResourcePiece) incompleteDirPath() string {
- return path.Join("incompleted", s.mp.Hash().HexString())
+ return path.Join("incompleted", s.hashHex())
}
func (s piecePerResourcePiece) incompleteDir() resource.DirInstance {
}
return i.(resource.DirInstance)
}
+
+func (me piecePerResourcePiece) hashHex() string {
+ return hex.EncodeToString(me.pieceHash.Unwrap())
+}
package sqliteStorage
import (
+ "encoding/hex"
"io"
"sync"
"time"
+ g "github.com/anacrolix/generics"
"github.com/anacrolix/squirrel"
"github.com/anacrolix/torrent/metainfo"
func (c *client) OpenTorrent(*metainfo.Info, metainfo.Hash) (storage.TorrentImpl, error) {
t := torrent{c.cache}
capFunc := c.capacity
- return storage.TorrentImpl{Piece: t.Piece, Close: t.Close, Capacity: &capFunc}, nil
+ return storage.TorrentImpl{PieceWithHash: t.Piece, Close: t.Close, Capacity: &capFunc}, nil
}
type torrent struct {
c *squirrel.Cache
}
-func (t torrent) Piece(p metainfo.Piece) storage.PieceImpl {
+func (t torrent) Piece(p metainfo.Piece, pieceHash g.Option[[]byte]) storage.PieceImpl {
ret := piece{
- sb: t.c.OpenWithLength(p.Hash().HexString(), p.Length()),
+ sb: t.c.OpenWithLength(hex.EncodeToString(pieceHash.Unwrap()), p.Length()),
}
ret.ReaderAt = &ret.sb
ret.WriterAt = &ret.sb
"io"
"os"
+ g "github.com/anacrolix/generics"
"github.com/anacrolix/missinggo/v2"
"github.com/anacrolix/torrent/metainfo"
TorrentImpl
}
+// Deprecated. Use PieceWithHash, as this doesn't work with pure v2 torrents.
func (t Torrent) Piece(p metainfo.Piece) Piece {
- return Piece{t.TorrentImpl.Piece(p), p}
+ return t.PieceWithHash(p, g.Some(p.V1Hash().Unwrap().Bytes()))
+}
+
+func (t Torrent) PieceWithHash(p metainfo.Piece, pieceHash g.Option[[]byte]) Piece {
+ var pieceImpl PieceImpl
+ if t.TorrentImpl.PieceWithHash != nil {
+ pieceImpl = t.TorrentImpl.PieceWithHash(p, pieceHash)
+ } else {
+ pieceImpl = t.TorrentImpl.Piece(p)
+ }
+ return Piece{pieceImpl, p}
}
type Piece struct {