]> Sergey Matveev's repositories - btrtrc.git/commitdiff
Remove the InfoEx type, and don't generate its infohash on the fly
authorMatt Joiner <anacrolix@gmail.com>
Fri, 26 Aug 2016 10:29:05 +0000 (20:29 +1000)
committerMatt Joiner <anacrolix@gmail.com>
Fri, 26 Aug 2016 10:29:05 +0000 (20:29 +1000)
Fixes #106.

37 files changed:
client.go
client_test.go
cmd/magnet-metainfo/main.go
cmd/torrent-create/main.go
cmd/torrent-infohash/main.go
cmd/torrent-magnet/main.go
cmd/torrent-metainfo-pprint/main.go
cmd/torrent-verify/main.go
fs/torrentfs.go
fs/torrentfs_test.go
internal/testutil/testutil.go
issue97_test.go
metainfo/info.go [new file with mode: 0644]
metainfo/info_test.go [new file with mode: 0644]
metainfo/infoex.go [deleted file]
metainfo/magnet_test.go
metainfo/metainfo.go
metainfo/metainfo_test.go
metainfo/nodes_test.go
metainfo/piece.go
metainfo/piece_key.go [new file with mode: 0644]
storage/completion.go
storage/completion_piece_map.go
storage/db.go
storage/file.go
storage/file_misc_test.go
storage/file_storage_piece.go
storage/file_test.go
storage/interface.go
storage/issue95_test.go
storage/issue96_test.go
storage/mmap.go
storage/piece_file.go
storage/piece_resource.go
t.go
torrent.go
util/dirwatch/dirwatch.go

index 5d2c4e02abd4d1f98cc4ead3ddbe66b54240b948..00a474c584b4c97c4370daee92e3a0c10e86d279 100644 (file)
--- a/client.go
+++ b/client.go
@@ -1407,7 +1407,7 @@ type TorrentSpec struct {
        // The tiered tracker URIs.
        Trackers [][]string
        InfoHash metainfo.Hash
-       Info     *metainfo.InfoEx
+       InfoBytes []byte
        // The name to use if the Name field from the Info isn't available.
        DisplayName string
        // The chunk size to use for outbound requests. Defaults to 16KiB if not
@@ -1430,11 +1430,12 @@ func TorrentSpecFromMagnetURI(uri string) (spec *TorrentSpec, err error) {
 }
 
 func TorrentSpecFromMetaInfo(mi *metainfo.MetaInfo) (spec *TorrentSpec) {
+       info := mi.UnmarshalInfo()
        spec = &TorrentSpec{
                Trackers:    mi.AnnounceList,
-               Info:        &mi.Info,
-               DisplayName: mi.Info.Name,
-               InfoHash:    mi.Info.Hash(),
+               InfoBytes:   mi.InfoBytes,
+               DisplayName: info.Name,
+               InfoHash:    mi.HashInfoBytes(),
        }
        if spec.Trackers == nil && mi.Announce != "" {
                spec.Trackers = [][]string{{mi.Announce}}
@@ -1470,8 +1471,8 @@ func (cl *Client) AddTorrentSpec(spec *TorrentSpec) (t *Torrent, new bool, err e
        if spec.DisplayName != "" {
                t.SetDisplayName(spec.DisplayName)
        }
-       if spec.Info != nil {
-               err = t.SetInfoBytes(spec.Info.Bytes)
+       if spec.InfoBytes != nil {
+               err = t.SetInfoBytes(spec.InfoBytes)
                if err != nil {
                        return
                }
index 33d0451ee673c742243c9b43409f34265eaf4523..4ab86ee11e10e77b666280e36935c398f406c551 100644 (file)
@@ -88,14 +88,14 @@ func TestTorrentInitialState(t *testing.T) {
        dir, mi := testutil.GreetingTestTorrent()
        defer os.RemoveAll(dir)
        tor := &Torrent{
-               infoHash:          mi.Info.Hash(),
+               infoHash:          mi.HashInfoBytes(),
                pieceStateChanges: pubsub.NewPubSub(),
        }
        tor.chunkSize = 2
        tor.storageOpener = storage.NewFile("/dev/null")
        // Needed to lock for asynchronous piece verification.
        tor.cl = new(Client)
-       err := tor.setInfoBytes(mi.Info.Bytes)
+       err := tor.setInfoBytes(mi.InfoBytes)
        require.NoError(t, err)
        require.Len(t, tor.pieces, 3)
        tor.pendAllChunkSpecs(0)
@@ -492,7 +492,7 @@ func TestMergingTrackersByAddingSpecs(t *testing.T) {
 
 type badStorage struct{}
 
-func (bs badStorage) OpenTorrent(*metainfo.InfoEx) (storage.Torrent, error) {
+func (bs badStorage) OpenTorrent(*metainfo.Info, metainfo.Hash) (storage.Torrent, error) {
        return bs, nil
 }
 
@@ -536,26 +536,24 @@ func TestCompletedPieceWrongSize(t *testing.T) {
        cl, err := NewClient(&cfg)
        require.NoError(t, err)
        defer cl.Close()
-       ie := metainfo.InfoEx{
-               Info: metainfo.Info{
-                       PieceLength: 15,
-                       Pieces:      make([]byte, 20),
-                       Files: []metainfo.FileInfo{
-                               metainfo.FileInfo{Path: []string{"greeting"}, Length: 13},
-                       },
+       info := metainfo.Info{
+               PieceLength: 15,
+               Pieces:      make([]byte, 20),
+               Files: []metainfo.FileInfo{
+                       metainfo.FileInfo{Path: []string{"greeting"}, Length: 13},
                },
        }
-       ie.UpdateBytes()
+       b, err := bencode.Marshal(info)
        tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
-               Info:     &ie,
-               InfoHash: ie.Hash(),
+               InfoBytes: b,
+               InfoHash:  metainfo.HashBytes(b),
        })
        require.NoError(t, err)
        defer tt.Drop()
        assert.True(t, new)
        r := tt.NewReader()
        defer r.Close()
-       b, err := ioutil.ReadAll(r)
+       b, err = ioutil.ReadAll(r)
        assert.Len(t, b, 13)
        assert.NoError(t, err)
 }
@@ -681,7 +679,7 @@ func TestAddTorrentSpecMerging(t *testing.T) {
        dir, mi := testutil.GreetingTestTorrent()
        defer os.RemoveAll(dir)
        tt, new, err := cl.AddTorrentSpec(&TorrentSpec{
-               InfoHash: mi.Info.Hash(),
+               InfoHash: mi.HashInfoBytes(),
        })
        require.NoError(t, err)
        require.True(t, new)
@@ -698,7 +696,7 @@ func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
        cl, _ := NewClient(&TestingConfig)
        defer cl.Close()
        tt, _, _ := cl.AddTorrentSpec(&TorrentSpec{
-               InfoHash: mi.Info.Hash(),
+               InfoHash: mi.HashInfoBytes(),
        })
        tt.Drop()
        assert.EqualValues(t, 0, len(cl.Torrents()))
@@ -709,7 +707,7 @@ func TestTorrentDroppedBeforeGotInfo(t *testing.T) {
        }
 }
 
-func writeTorrentData(ts storage.Torrent, info *metainfo.InfoEx, b []byte) {
+func writeTorrentData(ts storage.Torrent, info metainfo.Info, b []byte) {
        for i := range iter.N(info.NumPieces()) {
                n, _ := ts.Piece(info.Piece(i)).WriteAt(b, 0)
                b = b[n:]
@@ -725,13 +723,15 @@ func testAddTorrentPriorPieceCompletion(t *testing.T, alreadyCompleted bool, csf
        greetingDataTempDir, greetingMetainfo := testutil.GreetingTestTorrent()
        defer os.RemoveAll(greetingDataTempDir)
        filePieceStore := csf(fileCache)
-       greetingData, err := filePieceStore.OpenTorrent(&greetingMetainfo.Info)
+       info := greetingMetainfo.UnmarshalInfo()
+       ih := greetingMetainfo.HashInfoBytes()
+       greetingData, err := filePieceStore.OpenTorrent(&info, ih)
        require.NoError(t, err)
-       writeTorrentData(greetingData, &greetingMetainfo.Info, []byte(testutil.GreetingFileContents))
+       writeTorrentData(greetingData, info, []byte(testutil.GreetingFileContents))
        // require.Equal(t, len(testutil.GreetingFileContents), written)
        // require.NoError(t, err)
-       for i := 0; i < greetingMetainfo.Info.NumPieces(); i++ {
-               p := greetingMetainfo.Info.Piece(i)
+       for i := 0; i < info.NumPieces(); i++ {
+               p := info.Piece(i)
                if alreadyCompleted {
                        err := greetingData.Piece(p).MarkComplete()
                        assert.NoError(t, err)
@@ -871,17 +871,16 @@ func TestPeerInvalidHave(t *testing.T) {
        cl, err := NewClient(&TestingConfig)
        require.NoError(t, err)
        defer cl.Close()
-       ie := metainfo.InfoEx{
-               Info: metainfo.Info{
-                       PieceLength: 1,
-                       Pieces:      make([]byte, 20),
-                       Files:       []metainfo.FileInfo{{Length: 1}},
-               },
+       info := metainfo.Info{
+               PieceLength: 1,
+               Pieces:      make([]byte, 20),
+               Files:       []metainfo.FileInfo{{Length: 1}},
        }
-       ie.UpdateBytes()
+       infoBytes, err := bencode.Marshal(info)
+       require.NoError(t, err)
        tt, _new, err := cl.AddTorrentSpec(&TorrentSpec{
-               Info:     &ie,
-               InfoHash: ie.Hash(),
+               InfoBytes: infoBytes,
+               InfoHash:  metainfo.HashBytes(infoBytes),
        })
        require.NoError(t, err)
        assert.True(t, _new)
@@ -901,7 +900,7 @@ func TestPieceCompletedInStorageButNotClient(t *testing.T) {
        seeder, err := NewClient(&TestingConfig)
        require.NoError(t, err)
        seeder.AddTorrentSpec(&TorrentSpec{
-               Info: &greetingMetainfo.Info,
+               InfoBytes: greetingMetainfo.InfoBytes,
        })
 }
 
@@ -980,7 +979,7 @@ func totalConns(tts []*Torrent) (ret int) {
 
 func TestSetMaxEstablishedConn(t *testing.T) {
        var tts []*Torrent
-       ih := testutil.GreetingMetaInfo().Info.Hash()
+       ih := testutil.GreetingMetaInfo().HashInfoBytes()
        cfg := TestingConfig
        for i := range iter.N(3) {
                cl, err := NewClient(&cfg)
index 8bad76fd6db40388910e4df3d32fea5d52d99cb7..f7b7dce0eedf18ec3a0d2550ec2581295eef0293 100644 (file)
@@ -29,7 +29,7 @@ func main() {
                        <-t.GotInfo()
                        mi := t.Metainfo()
                        t.Drop()
-                       f, err := os.Create(mi.Info.Name + ".torrent")
+                       f, err := os.Create(mi.UnmarshalInfo().Name + ".torrent")
                        if err != nil {
                                log.Fatalf("error creating torrent metainfo file: %s", err)
                        }
index 399c06811f4ae5f5ebb1a308e83286a218bd1f97..4a429e9bcaeb7c7ace434b617e53b4b00250ae25 100644 (file)
@@ -6,6 +6,7 @@ import (
 
        "github.com/anacrolix/tagflag"
 
+       "github.com/anacrolix/torrent/bencode"
        "github.com/anacrolix/torrent/metainfo"
 )
 
@@ -32,7 +33,14 @@ func main() {
                mi.AnnounceList = append(mi.AnnounceList, []string{a})
        }
        mi.SetDefaults()
-       err := mi.Info.BuildFromFilePath(args.Root)
+       info := metainfo.Info{
+               PieceLength: 256 * 1024,
+       }
+       err := info.BuildFromFilePath(args.Root)
+       if err != nil {
+               log.Fatal(err)
+       }
+       mi.InfoBytes, err = bencode.Marshal(info)
        if err != nil {
                log.Fatal(err)
        }
index 93ae336a11384606a31c17c91faa2948e76479ca..81319c99a4692778462de66c58dd79826db7ee1b 100644 (file)
@@ -20,6 +20,6 @@ func main() {
                if err != nil {
                        log.Fatal(err)
                }
-               fmt.Printf("%s: %s\n", mi.Info.Hash().HexString(), arg)
+               fmt.Printf("%s: %s\n", mi.HashInfoBytes().HexString(), arg)
        }
 }
index 18824148ddf6f228104e242f1ac0ec81df73b7cc..50e57b16fbb52e6ffc757b7984ad41d4a2b6acfe 100644 (file)
@@ -17,6 +17,7 @@ func main() {
                fmt.Fprintf(os.Stderr, "error reading metainfo from stdin: %s", err)
                os.Exit(1)
        }
+       info := mi.UnmarshalInfo()
 
-       fmt.Fprintf(os.Stdout, "%s\n", mi.Magnet().String())
+       fmt.Fprintf(os.Stdout, "%s\n", mi.Magnet(info.Name, mi.HashInfoBytes()).String())
 }
index 8495ae10d6546e3b9b695f83e8cb0174d61e79b9..4f99a9974ab7ee996eba588debfe209877fba479 100644 (file)
@@ -29,16 +29,16 @@ func main() {
                        log.Print(err)
                        continue
                }
-               info := &metainfo.Info.Info
+               info := metainfo.UnmarshalInfo()
                if flags.JustName {
-                       fmt.Printf("%s\n", metainfo.Info.Name)
+                       fmt.Printf("%s\n", info.Name)
                        continue
                }
                d := map[string]interface{}{
                        "Name":         info.Name,
                        "NumPieces":    info.NumPieces(),
                        "PieceLength":  info.PieceLength,
-                       "InfoHash":     metainfo.Info.Hash().HexString(),
+                       "InfoHash":     metainfo.HashInfoBytes().HexString(),
                        "NumFiles":     len(info.UpvertedFiles()),
                        "TotalLength":  info.TotalLength(),
                        "Announce":     metainfo.Announce,
index f7457b8564612c7c3457cca9bf5406f117a3d05a..7f329302b75f701ed608996ff26ea8edeebb9b4c 100644 (file)
@@ -49,22 +49,22 @@ func main() {
        if err != nil {
                log.Fatal(err)
        }
+       info := metaInfo.UnmarshalInfo()
        mMapSpan := &mmap_span.MMapSpan{}
-       if len(metaInfo.Info.Files) > 0 {
-               for _, file := range metaInfo.Info.Files {
-                       filename := filepath.Join(append([]string{*dataPath, metaInfo.Info.Name}, file.Path...)...)
+       if len(info.Files) > 0 {
+               for _, file := range info.Files {
+                       filename := filepath.Join(append([]string{*dataPath, info.Name}, file.Path...)...)
                        goMMap := fileToMmap(filename, file.Length)
                        mMapSpan.Append(goMMap)
                }
-               log.Println(len(metaInfo.Info.Files))
+               log.Println(len(info.Files))
        } else {
-               goMMap := fileToMmap(*dataPath, metaInfo.Info.Length)
+               goMMap := fileToMmap(*dataPath, info.Length)
                mMapSpan.Append(goMMap)
        }
        log.Println(mMapSpan.Size())
-       log.Println(len(metaInfo.Info.Pieces))
-       info := metaInfo.Info
-       for i := range iter.N(metaInfo.Info.NumPieces()) {
+       log.Println(len(info.Pieces))
+       for i := range iter.N(info.NumPieces()) {
                p := info.Piece(i)
                hash := sha1.New()
                _, err := io.Copy(hash, io.NewSectionReader(mMapSpan, p.Offset(), p.Length()))
index 55834a241379e4a07d9aa3e8ca45590d6984e525..06e05e008fd48599af4ea19da1bd096c19777f19 100644 (file)
@@ -49,7 +49,7 @@ type rootNode struct {
 
 type node struct {
        path     string
-       metadata *metainfo.InfoEx
+       metadata *metainfo.Info
        FS       *TorrentFS
        t        *torrent.Torrent
 }
index f3564124d724ada75df60b285b7143b0b757242d..e77d63cd3995c581f543e11e474a3b5ff084e1d7 100644 (file)
@@ -97,7 +97,7 @@ func TestUnmountWedged(t *testing.T) {
        })
        require.NoError(t, err)
        defer client.Close()
-       _, err = client.AddTorrent(layout.Metainfo)
+       tt, err := client.AddTorrent(layout.Metainfo)
        require.NoError(t, err)
        fs := New(client)
        fuseConn, err := fuse.Mount(layout.MountDir)
@@ -124,7 +124,7 @@ func TestUnmountWedged(t *testing.T) {
        // "wedge" FUSE, requiring the fs object to be forcibly destroyed. The
        // read call will return with a FS error.
        go func() {
-               _, err := ioutil.ReadFile(filepath.Join(layout.MountDir, layout.Metainfo.Info.Name))
+               _, err := ioutil.ReadFile(filepath.Join(layout.MountDir, tt.Info().Name))
                if err == nil {
                        t.Fatal("expected error reading greeting")
                }
@@ -169,7 +169,7 @@ func TestDownloadOnDemand(t *testing.T) {
        require.NoError(t, err)
        defer seeder.Close()
        testutil.ExportStatusWriter(seeder, "s")
-       _, err = seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%s", layout.Metainfo.Info.Hash().HexString()))
+       _, err = seeder.AddMagnet(fmt.Sprintf("magnet:?xt=urn:btih:%s", layout.Metainfo.HashInfoBytes().HexString()))
        require.NoError(t, err)
        leecher, err := torrent.NewClient(&torrent.Config{
                DisableTrackers: true,
index 1f236a424e96b475c214739b97404287cb57075c..6431a1f187a6f6cab6a867732576136bdda29004 100644 (file)
@@ -16,6 +16,7 @@ import (
 
        "github.com/anacrolix/missinggo"
 
+       "github.com/anacrolix/torrent/bencode"
        "github.com/anacrolix/torrent/metainfo"
 )
 
@@ -30,19 +31,25 @@ func CreateDummyTorrentData(dirName string) string {
        f.WriteString(GreetingFileContents)
        return f.Name()
 }
-func GreetingMetaInfo() (mi *metainfo.MetaInfo) {
-       mi = new(metainfo.MetaInfo)
-       mi.Info.Name = GreetingFileName
-       mi.Info.Length = int64(len(GreetingFileContents))
-       mi.Info.PieceLength = 5
-       err := mi.Info.GeneratePieces(func(metainfo.FileInfo) (io.ReadCloser, error) {
+
+func GreetingMetaInfo() *metainfo.MetaInfo {
+       info := metainfo.Info{
+               Name:        GreetingFileName,
+               Length:      int64(len(GreetingFileContents)),
+               PieceLength: 5,
+       }
+       err := info.GeneratePieces(func(metainfo.FileInfo) (io.ReadCloser, error) {
                return ioutil.NopCloser(strings.NewReader(GreetingFileContents)), nil
        })
        if err != nil {
                panic(err)
        }
-       mi.Info.UpdateBytes()
-       return
+       mi := &metainfo.MetaInfo{}
+       mi.InfoBytes, err = bencode.Marshal(info)
+       if err != nil {
+               panic(err)
+       }
+       return mi
 }
 
 // Gives a temporary directory containing the completed "greeting" torrent,
index 3f4e16d2d0d1b89b911d460543a150c8a0b2d437..1e6373cc64fe7e760b0432b403fa07e8b01068aa 100644 (file)
@@ -17,9 +17,11 @@ func TestHashPieceAfterStorageClosed(t *testing.T) {
        defer os.RemoveAll(td)
        cs := storage.NewFile(td)
        tt := &Torrent{}
-       tt.info = &testutil.GreetingMetaInfo().Info
+       mi := testutil.GreetingMetaInfo()
+       info := mi.UnmarshalInfo()
+       tt.info = &info
        tt.makePieces()
-       tt.storage, err = cs.OpenTorrent(tt.info)
+       tt.storage, err = cs.OpenTorrent(tt.info, mi.HashInfoBytes())
        require.NoError(t, err)
        require.NoError(t, tt.storage.Close())
        tt.hashPiece(0)
diff --git a/metainfo/info.go b/metainfo/info.go
new file mode 100644 (file)
index 0000000..8563e25
--- /dev/null
@@ -0,0 +1,156 @@
+package metainfo
+
+import (
+       "crypto/sha1"
+       "errors"
+       "fmt"
+       "io"
+       "log"
+       "os"
+       "path/filepath"
+       "strings"
+
+       "github.com/anacrolix/missinggo/slices"
+)
+
+// The info dictionary.
+type Info struct {
+       PieceLength int64      `bencode:"piece length"`
+       Pieces      []byte     `bencode:"pieces"`
+       Name        string     `bencode:"name"`
+       Length      int64      `bencode:"length,omitempty"`
+       Private     *bool      `bencode:"private,omitempty"`
+       Files       []FileInfo `bencode:"files,omitempty"`
+}
+
+// This is a helper that sets Files and Pieces from a root path and its
+// children.
+func (info *Info) BuildFromFilePath(root string) (err error) {
+       info.Name = filepath.Base(root)
+       info.Files = nil
+       err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
+               if err != nil {
+                       return err
+               }
+               if fi.IsDir() {
+                       // Directories are implicit in torrent files.
+                       return nil
+               } else if path == root {
+                       // The root is a file.
+                       info.Length = fi.Size()
+                       return nil
+               }
+               relPath, err := filepath.Rel(root, path)
+               log.Println(relPath, err)
+               if err != nil {
+                       return fmt.Errorf("error getting relative path: %s", err)
+               }
+               info.Files = append(info.Files, FileInfo{
+                       Path:   strings.Split(relPath, string(filepath.Separator)),
+                       Length: fi.Size(),
+               })
+               return nil
+       })
+       if err != nil {
+               return
+       }
+       slices.Sort(info.Files, func(l, r FileInfo) bool {
+               return strings.Join(l.Path, "/") < strings.Join(r.Path, "/")
+       })
+       err = info.GeneratePieces(func(fi FileInfo) (io.ReadCloser, error) {
+               return os.Open(filepath.Join(root, strings.Join(fi.Path, string(filepath.Separator))))
+       })
+       if err != nil {
+               err = fmt.Errorf("error generating pieces: %s", err)
+       }
+       return
+}
+
+func (info *Info) writeFiles(w io.Writer, open func(fi FileInfo) (io.ReadCloser, error)) error {
+       for _, fi := range info.UpvertedFiles() {
+               r, err := open(fi)
+               if err != nil {
+                       return fmt.Errorf("error opening %v: %s", fi, err)
+               }
+               wn, err := io.CopyN(w, r, fi.Length)
+               r.Close()
+               if wn != fi.Length || err != nil {
+                       return fmt.Errorf("error hashing %v: %s", fi, err)
+               }
+       }
+       return nil
+}
+
+// Set info.Pieces by hashing info.Files.
+func (info *Info) GeneratePieces(open func(fi FileInfo) (io.ReadCloser, error)) error {
+       if info.PieceLength == 0 {
+               return errors.New("piece length must be non-zero")
+       }
+       pr, pw := io.Pipe()
+       go func() {
+               err := info.writeFiles(pw, open)
+               pw.CloseWithError(err)
+       }()
+       defer pr.Close()
+       var pieces []byte
+       for {
+               hasher := sha1.New()
+               wn, err := io.CopyN(hasher, pr, info.PieceLength)
+               if err == io.EOF {
+                       err = nil
+               }
+               if err != nil {
+                       return err
+               }
+               if wn == 0 {
+                       break
+               }
+               pieces = hasher.Sum(pieces)
+               if wn < info.PieceLength {
+                       break
+               }
+       }
+       info.Pieces = pieces
+       return nil
+}
+
+func (info *Info) TotalLength() (ret int64) {
+       if info.IsDir() {
+               for _, fi := range info.Files {
+                       ret += fi.Length
+               }
+       } else {
+               ret = info.Length
+       }
+       return
+}
+
+func (info *Info) NumPieces() int {
+       if len(info.Pieces)%20 != 0 {
+               panic(len(info.Pieces))
+       }
+       return len(info.Pieces) / 20
+}
+
+func (info *Info) IsDir() bool {
+       return len(info.Files) != 0
+}
+
+// The files field, converted up from the old single-file in the parent info
+// dict if necessary. This is a helper to avoid having to conditionally handle
+// single and multi-file torrent infos.
+func (info *Info) UpvertedFiles() []FileInfo {
+       if len(info.Files) == 0 {
+               return []FileInfo{{
+                       Length: info.Length,
+                       // Callers should determine that Info.Name is the basename, and
+                       // thus a regular file.
+                       Path: nil,
+               }}
+       }
+       return info.Files
+}
+
+func (info *Info) Piece(index int) Piece {
+       return Piece{info, index}
+}
diff --git a/metainfo/info_test.go b/metainfo/info_test.go
new file mode 100644 (file)
index 0000000..cf1eaf8
--- /dev/null
@@ -0,0 +1,16 @@
+package metainfo
+
+import (
+       "testing"
+
+       "github.com/stretchr/testify/assert"
+
+       "github.com/anacrolix/torrent/bencode"
+)
+
+func TestMarshalInfo(t *testing.T) {
+       var info Info
+       b, err := bencode.Marshal(info)
+       assert.NoError(t, err)
+       assert.EqualValues(t, "d4:name0:12:piece lengthi0e6:pieceslee", string(b))
+}
diff --git a/metainfo/infoex.go b/metainfo/infoex.go
deleted file mode 100644 (file)
index 1e4610f..0000000
+++ /dev/null
@@ -1,47 +0,0 @@
-package metainfo
-
-import "github.com/anacrolix/torrent/bencode"
-
-// A wrapper around Info that exposes the Bytes directly, in case marshalling
-// and unmarshalling Info doesn't produce the same bytes.
-type InfoEx struct {
-       Info
-       // Set when unmarshalling, and used when marshalling. Call .UpdateBytes to
-       // set it by bencoding Info.
-       Bytes []byte
-}
-
-var (
-       _ bencode.Marshaler   = &InfoEx{}
-       _ bencode.Unmarshaler = &InfoEx{}
-)
-
-// Marshals .Info, and sets .Bytes with the result.
-func (ie *InfoEx) UpdateBytes() {
-       var err error
-       ie.Bytes, err = bencode.Marshal(&ie.Info)
-       if err != nil {
-               panic(err)
-       }
-}
-
-// Returns the SHA1 hash of .Bytes.
-func (ie *InfoEx) Hash() Hash {
-       return HashBytes(ie.Bytes)
-}
-
-func (ie *InfoEx) UnmarshalBencode(data []byte) error {
-       ie.Bytes = append([]byte(nil), data...)
-       return bencode.Unmarshal(data, &ie.Info)
-}
-
-func (ie *InfoEx) MarshalBencode() ([]byte, error) {
-       if ie.Bytes == nil {
-               ie.UpdateBytes()
-       }
-       return ie.Bytes, nil
-}
-
-func (info *InfoEx) Piece(i int) Piece {
-       return Piece{info, i}
-}
index 065b662ffe6eb65f56f096f59735ac4872fae18a..1ad4b170395a41ff83c74601be0159763a185aa2 100644 (file)
@@ -70,11 +70,11 @@ func TestParseMagnetURI(t *testing.T) {
 
 }
 
-func Test_Magnetize(t *testing.T) {
+func TestMagnetize(t *testing.T) {
        mi, err := LoadFromFile("../testdata/bootstrap.dat.torrent")
        require.NoError(t, err)
 
-       m := mi.Magnet()
+       m := mi.Magnet(mi.UnmarshalInfo().Name, mi.HashInfoBytes())
 
        assert.EqualValues(t, "bootstrap.dat", m.DisplayName)
 
index b9a65a9def1e92b850c93db798939808ddf2bf11..6d1d7e2efecfc44b46a51a7a4a05c6ed77d58e07 100644 (file)
@@ -1,20 +1,26 @@
 package metainfo
 
 import (
-       "crypto/sha1"
-       "errors"
        "fmt"
        "io"
-       "log"
        "os"
-       "path/filepath"
-       "strings"
        "time"
 
-       "github.com/anacrolix/missinggo/slices"
        "github.com/anacrolix/torrent/bencode"
 )
 
+type MetaInfo struct {
+       InfoBytes    bencode.Bytes `bencode:"info"`
+       Announce     string        `bencode:"announce,omitempty"`
+       AnnounceList [][]string    `bencode:"announce-list,omitempty"`
+       Nodes        []Node        `bencode:"nodes,omitempty"`
+       CreationDate int64         `bencode:"creation date,omitempty"`
+       Comment      string        `bencode:"comment,omitempty"`
+       CreatedBy    string        `bencode:"created by,omitempty"`
+       Encoding     string        `bencode:"encoding,omitempty"`
+       URLList      interface{}   `bencode:"url-list,omitempty"`
+}
+
 // Information specific to a single file inside the MetaInfo structure.
 type FileInfo struct {
        Length int64    `bencode:"length"`
@@ -43,158 +49,20 @@ func LoadFromFile(filename string) (*MetaInfo, error) {
        return Load(f)
 }
 
-// The info dictionary.
-type Info struct {
-       PieceLength int64      `bencode:"piece length"`
-       Pieces      []byte     `bencode:"pieces"`
-       Name        string     `bencode:"name"`
-       Length      int64      `bencode:"length,omitempty"`
-       Private     *bool      `bencode:"private,omitempty"`
-       Files       []FileInfo `bencode:"files,omitempty"`
-}
-
-// This is a helper that sets Files and Pieces from a root path and its
-// children.
-func (info *Info) BuildFromFilePath(root string) (err error) {
-       info.Name = filepath.Base(root)
-       info.Files = nil
-       err = filepath.Walk(root, func(path string, fi os.FileInfo, err error) error {
-               if err != nil {
-                       return err
-               }
-               if fi.IsDir() {
-                       // Directories are implicit in torrent files.
-                       return nil
-               } else if path == root {
-                       // The root is a file.
-                       info.Length = fi.Size()
-                       return nil
-               }
-               relPath, err := filepath.Rel(root, path)
-               log.Println(relPath, err)
-               if err != nil {
-                       return fmt.Errorf("error getting relative path: %s", err)
-               }
-               info.Files = append(info.Files, FileInfo{
-                       Path:   strings.Split(relPath, string(filepath.Separator)),
-                       Length: fi.Size(),
-               })
-               return nil
-       })
-       if err != nil {
-               return
-       }
-       slices.Sort(info.Files, func(l, r FileInfo) bool {
-               return strings.Join(l.Path, "/") < strings.Join(r.Path, "/")
-       })
-       err = info.GeneratePieces(func(fi FileInfo) (io.ReadCloser, error) {
-               return os.Open(filepath.Join(root, strings.Join(fi.Path, string(filepath.Separator))))
-       })
+func (mi MetaInfo) UnmarshalInfo() (info Info) {
+       err := bencode.Unmarshal(mi.InfoBytes, &info)
        if err != nil {
-               err = fmt.Errorf("error generating pieces: %s", err)
+               panic(fmt.Sprintf("bad info bytes: %s", err))
        }
        return
 }
 
-func (info *Info) writeFiles(w io.Writer, open func(fi FileInfo) (io.ReadCloser, error)) error {
-       for _, fi := range info.UpvertedFiles() {
-               r, err := open(fi)
-               if err != nil {
-                       return fmt.Errorf("error opening %v: %s", fi, err)
-               }
-               wn, err := io.CopyN(w, r, fi.Length)
-               r.Close()
-               if wn != fi.Length || err != nil {
-                       return fmt.Errorf("error hashing %v: %s", fi, err)
-               }
-       }
-       return nil
-}
-
-// Set info.Pieces by hashing info.Files.
-func (info *Info) GeneratePieces(open func(fi FileInfo) (io.ReadCloser, error)) error {
-       if info.PieceLength == 0 {
-               return errors.New("piece length must be non-zero")
-       }
-       pr, pw := io.Pipe()
-       go func() {
-               err := info.writeFiles(pw, open)
-               pw.CloseWithError(err)
-       }()
-       defer pr.Close()
-       var pieces []byte
-       for {
-               hasher := sha1.New()
-               wn, err := io.CopyN(hasher, pr, info.PieceLength)
-               if err == io.EOF {
-                       err = nil
-               }
-               if err != nil {
-                       return err
-               }
-               if wn == 0 {
-                       break
-               }
-               pieces = hasher.Sum(pieces)
-               if wn < info.PieceLength {
-                       break
-               }
-       }
-       info.Pieces = pieces
-       return nil
-}
-
-func (info *Info) TotalLength() (ret int64) {
-       if info.IsDir() {
-               for _, fi := range info.Files {
-                       ret += fi.Length
-               }
-       } else {
-               ret = info.Length
-       }
-       return
-}
-
-func (info *Info) NumPieces() int {
-       if len(info.Pieces)%20 != 0 {
-               panic(len(info.Pieces))
-       }
-       return len(info.Pieces) / 20
-}
-
-func (info *Info) IsDir() bool {
-       return len(info.Files) != 0
-}
-
-// The files field, converted up from the old single-file in the parent info
-// dict if necessary. This is a helper to avoid having to conditionally handle
-// single and multi-file torrent infos.
-func (info *Info) UpvertedFiles() []FileInfo {
-       if len(info.Files) == 0 {
-               return []FileInfo{{
-                       Length: info.Length,
-                       // Callers should determine that Info.Name is the basename, and
-                       // thus a regular file.
-                       Path: nil,
-               }}
-       }
-       return info.Files
-}
-
-type MetaInfo struct {
-       Info         InfoEx      `bencode:"info"`
-       Announce     string      `bencode:"announce,omitempty"`
-       AnnounceList [][]string  `bencode:"announce-list,omitempty"`
-       Nodes        []Node      `bencode:"nodes,omitempty"`
-       CreationDate int64       `bencode:"creation date,omitempty"`
-       Comment      string      `bencode:"comment,omitempty"`
-       CreatedBy    string      `bencode:"created by,omitempty"`
-       Encoding     string      `bencode:"encoding,omitempty"`
-       URLList      interface{} `bencode:"url-list,omitempty"`
+func (mi MetaInfo) HashInfoBytes() (infoHash Hash) {
+       return HashBytes(mi.InfoBytes)
 }
 
 // Encode to bencoded form.
-func (mi *MetaInfo) Write(w io.Writer) error {
+func (mi MetaInfo) Write(w io.Writer) error {
        return bencode.NewEncoder(w).Encode(mi)
 }
 
@@ -203,11 +71,11 @@ func (mi *MetaInfo) SetDefaults() {
        mi.Comment = "yoloham"
        mi.CreatedBy = "github.com/anacrolix/torrent"
        mi.CreationDate = time.Now().Unix()
-       mi.Info.PieceLength = 256 * 1024
+       // mi.Info.PieceLength = 256 * 1024
 }
 
 // Creates a Magnet from a MetaInfo.
-func (mi *MetaInfo) Magnet() (m Magnet) {
+func (mi *MetaInfo) Magnet(displayName string, infoHash Hash) (m Magnet) {
        for _, tier := range mi.AnnounceList {
                for _, tracker := range tier {
                        m.Trackers = append(m.Trackers, tracker)
@@ -216,7 +84,7 @@ func (mi *MetaInfo) Magnet() (m Magnet) {
        if m.Trackers == nil && mi.Announce != "" {
                m.Trackers = []string{mi.Announce}
        }
-       m.DisplayName = mi.Info.Name
-       m.InfoHash = mi.Info.Hash()
+       m.DisplayName = displayName
+       m.InfoHash = infoHash
        return
 }
index a119f31381eab3e0733401b88f15ddb8d700345f..de81b8da67c0f8fc11c787084c8b67b513944320 100644 (file)
@@ -19,11 +19,12 @@ func testFile(t *testing.T, filename string) {
        mi, err := LoadFromFile(filename)
        require.NoError(t, err)
 
-       if len(mi.Info.Files) == 1 {
-               t.Logf("Single file: %s (length: %d)\n", mi.Info.Name, mi.Info.Files[0].Length)
+       info := mi.UnmarshalInfo()
+       if len(info.Files) == 1 {
+               t.Logf("Single file: %s (length: %d)\n", info.Name, info.Files[0].Length)
        } else {
-               t.Logf("Multiple files: %s\n", mi.Info.Name)
-               for _, f := range mi.Info.Files {
+               t.Logf("Multiple files: %s\n", info.Name)
+               for _, f := range info.Files {
                        t.Logf(" - %s (length: %d)\n", path.Join(f.Path...), f.Length)
                }
        }
@@ -34,9 +35,9 @@ func testFile(t *testing.T, filename string) {
                }
        }
 
-       b, err := bencode.Marshal(&mi.Info.Info)
+       b, err := bencode.Marshal(&info)
        require.NoError(t, err)
-       assert.EqualValues(t, string(b), string(mi.Info.Bytes))
+       assert.EqualValues(t, string(b), string(mi.InfoBytes))
 }
 
 func TestFile(t *testing.T) {
@@ -96,3 +97,21 @@ func TestBuildFromFilePathOrder(t *testing.T) {
                Path: []string{"b"},
        }}, info.Files)
 }
+
+func testUnmarshal(t *testing.T, input string, expected *MetaInfo) {
+       var actual MetaInfo
+       err := bencode.Unmarshal([]byte(input), &actual)
+       if expected == nil {
+               assert.Error(t, err)
+               return
+       }
+       assert.NoError(t, err)
+       assert.EqualValues(t, *expected, actual)
+}
+
+func TestUnmarshal(t *testing.T) {
+       testUnmarshal(t, `de`, &MetaInfo{})
+       testUnmarshal(t, `d4:infoe`, &MetaInfo{})
+       testUnmarshal(t, `d4:infoabce`, nil)
+       testUnmarshal(t, `d4:infodee`, &MetaInfo{InfoBytes: []byte("de")})
+}
index 2b8768a32879f3a263cf32e325b4eb91b8c56759..69bb9e6704964088321165876cfbf9cd9743f4ed 100644 (file)
@@ -42,15 +42,16 @@ func TestNodesListPairsBEP5(t *testing.T) {
 }
 
 func testMarshalMetainfo(t *testing.T, expected string, mi *MetaInfo) {
-       b, err := bencode.Marshal(mi)
+       b, err := bencode.Marshal(*mi)
        assert.NoError(t, err)
        assert.EqualValues(t, expected, string(b))
 }
 
 func TestMarshalMetainfoNodes(t *testing.T) {
-       testMarshalMetainfo(t, "d4:infod4:name0:12:piece lengthi0e6:piecesleee", &MetaInfo{})
-       testMarshalMetainfo(t, "d4:infod4:name0:12:piece lengthi0e6:pieceslee5:nodesl12:1.2.3.4:555514:not a hostportee", &MetaInfo{
-               Nodes: []Node{"1.2.3.4:5555", "not a hostport"},
+       testMarshalMetainfo(t, "d4:infodee", &MetaInfo{InfoBytes: []byte("de")})
+       testMarshalMetainfo(t, "d4:infod2:hi5:theree5:nodesl12:1.2.3.4:555514:not a hostportee", &MetaInfo{
+               Nodes:     []Node{"1.2.3.4:5555", "not a hostport"},
+               InfoBytes: []byte("d2:hi5:theree"),
        })
 }
 
index a68f1bce7f538c196b24dbde6bedd64005dcd1a7..ff753bd3c88d0f83d475fa91e5c354be1f06ae10 100644 (file)
@@ -3,7 +3,7 @@ package metainfo
 import "github.com/anacrolix/missinggo"
 
 type Piece struct {
-       Info *InfoEx
+       Info *Info
        i    int
 }
 
@@ -26,12 +26,3 @@ func (p Piece) Hash() (ret Hash) {
 func (p Piece) Index() int {
        return p.i
 }
-
-func (p Piece) Key() PieceKey {
-       return PieceKey{p.Info.Hash(), p.i}
-}
-
-type PieceKey struct {
-       Hash  Hash
-       Index int
-}
diff --git a/metainfo/piece_key.go b/metainfo/piece_key.go
new file mode 100644 (file)
index 0000000..b5aa492
--- /dev/null
@@ -0,0 +1,6 @@
+package metainfo
+
+type PieceKey struct {
+       InfoHash Hash
+       Index    int
+}
index a5c28fcb4a6442a342e378e8f333bfd0ccfb3ef1..c3047be2189891835df6a60e241386585ec0db6e 100644 (file)
@@ -7,8 +7,8 @@ import (
 )
 
 type pieceCompletion interface {
-       Get(metainfo.Piece) (bool, error)
-       Set(metainfo.Piece, bool) error
+       Get(metainfo.PieceKey) (bool, error)
+       Set(metainfo.PieceKey, bool) error
        Close()
 }
 
index b8a2d5b4a5c8518ba55cff027b2cb4001df4bea8..26d893cec5f0fcf3a00cde708970f174a1948dca 100644 (file)
@@ -10,19 +10,19 @@ type mapPieceCompletion struct {
 
 func (mapPieceCompletion) Close() {}
 
-func (me *mapPieceCompletion) Get(p metainfo.Piece) (bool, error) {
-       _, ok := me.m[p.Key()]
+func (me *mapPieceCompletion) Get(pk metainfo.PieceKey) (bool, error) {
+       _, ok := me.m[pk]
        return ok, nil
 }
 
-func (me *mapPieceCompletion) Set(p metainfo.Piece, b bool) error {
+func (me *mapPieceCompletion) Set(pk metainfo.PieceKey, b bool) error {
        if b {
                if me.m == nil {
                        me.m = make(map[metainfo.PieceKey]struct{})
                }
-               me.m[p.Key()] = struct{}{}
+               me.m[pk] = struct{}{}
        } else {
-               delete(me.m, p.Key())
+               delete(me.m, pk)
        }
        return nil
 }
index db95a11d29ae26fa642fd8f59cbd4512df4f7b8f..8e4b90417d23b1691c25e51f570d45738f774ea3 100644 (file)
@@ -26,17 +26,17 @@ func newDBPieceCompletion(dir string) (ret *dbPieceCompletion, err error) {
        return
 }
 
-func (me *dbPieceCompletion) Get(p metainfo.Piece) (ret bool, err error) {
-       row := me.db.QueryRow(`select exists(select * from completed where infohash=? and "index"=?)`, p.Info.Hash().HexString(), p.Index())
+func (me *dbPieceCompletion) Get(pk metainfo.PieceKey) (ret bool, err error) {
+       row := me.db.QueryRow(`select exists(select * from completed where infohash=? and "index"=?)`, pk.InfoHash.HexString(), pk.Index)
        err = row.Scan(&ret)
        return
 }
 
-func (me *dbPieceCompletion) Set(p metainfo.Piece, b bool) (err error) {
+func (me *dbPieceCompletion) Set(pk metainfo.PieceKey, b bool) (err error) {
        if b {
-               _, err = me.db.Exec(`insert into completed (infohash, "index") values (?, ?)`, p.Info.Hash().HexString(), p.Index())
+               _, err = me.db.Exec(`insert into completed (infohash, "index") values (?, ?)`, pk.InfoHash.HexString(), pk.Index)
        } else {
-               _, err = me.db.Exec(`delete from completed where infohash=? and "index"=?`, p.Info.Hash().HexString(), p.Index())
+               _, err = me.db.Exec(`delete from completed where infohash=? and "index"=?`, pk.InfoHash.HexString(), pk.Index)
        }
        return
 }
index 00ec0603512855ac929f6c14e98e75bd199f163a..bae53134d054b9eae5c121430b610184d1bbb91a 100644 (file)
@@ -23,10 +23,11 @@ func NewFile(baseDir string) Client {
        }
 }
 
-func (fs *fileStorage) OpenTorrent(info *metainfo.InfoEx) (Torrent, error) {
+func (fs *fileStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error) {
        return &fileTorrentStorage{
                fs,
-               &info.Info,
+               info,
+               infoHash,
                pieceCompletionForDir(fs.baseDir),
        }, nil
 }
@@ -35,6 +36,7 @@ func (fs *fileStorage) OpenTorrent(info *metainfo.InfoEx) (Torrent, error) {
 type fileTorrentStorage struct {
        fs         *fileStorage
        info       *metainfo.Info
+       infoHash   metainfo.Hash
        completion pieceCompletion
 }
 
index d95d02476c945fd0e2a8270d63ab8c95b825e37a..8b453045c1d813282d74ee7d2a7a5fede5127c4a 100644 (file)
@@ -9,32 +9,29 @@ import (
 )
 
 func TestExtentCompleteRequiredLengths(t *testing.T) {
-       info := &metainfo.InfoEx{
-               Info: metainfo.Info{
-                       Files: []metainfo.FileInfo{
-                               {Path: []string{"a"}, Length: 2},
-                               {Path: []string{"b"}, Length: 3},
-                       },
+       info := &metainfo.Info{
+               Files: []metainfo.FileInfo{
+                       {Path: []string{"a"}, Length: 2},
+                       {Path: []string{"b"}, Length: 3},
                },
        }
-       assert.Empty(t, extentCompleteRequiredLengths(&info.Info, 0, 0))
+       assert.Empty(t, extentCompleteRequiredLengths(info, 0, 0))
        assert.EqualValues(t, []metainfo.FileInfo{
                {Path: []string{"a"}, Length: 1},
-       }, extentCompleteRequiredLengths(&info.Info, 0, 1))
+       }, extentCompleteRequiredLengths(info, 0, 1))
        assert.EqualValues(t, []metainfo.FileInfo{
                {Path: []string{"a"}, Length: 2},
-       }, extentCompleteRequiredLengths(&info.Info, 0, 2))
+       }, extentCompleteRequiredLengths(info, 0, 2))
        assert.EqualValues(t, []metainfo.FileInfo{
                {Path: []string{"a"}, Length: 2},
                {Path: []string{"b"}, Length: 1},
-       }, extentCompleteRequiredLengths(&info.Info, 0, 3))
+       }, extentCompleteRequiredLengths(info, 0, 3))
        assert.EqualValues(t, []metainfo.FileInfo{
                {Path: []string{"b"}, Length: 2},
-       }, extentCompleteRequiredLengths(&info.Info, 2, 2))
+       }, extentCompleteRequiredLengths(info, 2, 2))
        assert.EqualValues(t, []metainfo.FileInfo{
                {Path: []string{"b"}, Length: 3},
-       }, extentCompleteRequiredLengths(&info.Info, 4, 1))
-       assert.Len(t, extentCompleteRequiredLengths(&info.Info, 5, 0), 0)
-       assert.Panics(t, func() { extentCompleteRequiredLengths(&info.Info, 6, 1) })
-
+       }, extentCompleteRequiredLengths(info, 4, 1))
+       assert.Len(t, extentCompleteRequiredLengths(info, 5, 0), 0)
+       assert.Panics(t, func() { extentCompleteRequiredLengths(info, 6, 1) })
 }
index 44648c136e89a387f5c30156930449058fc556f1..915d5d0d1b637c76eec8956b880f2448ed70fdbd 100644 (file)
@@ -14,14 +14,18 @@ type fileStoragePiece struct {
        r io.ReaderAt
 }
 
+func (me *fileStoragePiece) pieceKey() metainfo.PieceKey {
+       return metainfo.PieceKey{me.infoHash, me.p.Index()}
+}
+
 func (fs *fileStoragePiece) GetIsComplete() bool {
-       ret, err := fs.completion.Get(fs.p)
+       ret, err := fs.completion.Get(fs.pieceKey())
        if err != nil || !ret {
                return false
        }
        // If it's allegedly complete, check that its constituent files have the
        // necessary length.
-       for _, fi := range extentCompleteRequiredLengths(&fs.p.Info.Info, fs.p.Offset(), fs.p.Length()) {
+       for _, fi := range extentCompleteRequiredLengths(fs.p.Info, fs.p.Offset(), fs.p.Length()) {
                s, err := os.Stat(fs.fileInfoName(fi))
                if err != nil || s.Size() < fi.Length {
                        ret = false
@@ -32,12 +36,12 @@ func (fs *fileStoragePiece) GetIsComplete() bool {
                return true
        }
        // The completion was wrong, fix it.
-       fs.completion.Set(fs.p, false)
+       fs.completion.Set(fs.pieceKey(), false)
        return false
 }
 
 func (fs *fileStoragePiece) MarkComplete() error {
-       fs.completion.Set(fs.p, true)
+       fs.completion.Set(fs.pieceKey(), true)
        return nil
 }
 
@@ -50,6 +54,6 @@ func (fsp *fileStoragePiece) ReadAt(b []byte, off int64) (n int, err error) {
        if off < 0 || off >= fsp.p.Length() {
                return
        }
-       fsp.completion.Set(fsp.p, false)
+       fsp.completion.Set(fsp.pieceKey(), false)
        return
 }
index 6e1d055e7d65c13a6e9fa32078b8a2f1c6d839b4..5c63c94cecd5c50a4769db25ce4eddc37e1622a6 100644 (file)
@@ -20,14 +20,12 @@ func TestShortFile(t *testing.T) {
        require.NoError(t, err)
        defer os.RemoveAll(td)
        s := NewFile(td)
-       info := &metainfo.InfoEx{
-               Info: metainfo.Info{
-                       Name:        "a",
-                       Length:      2,
-                       PieceLength: missinggo.MiB,
-               },
+       info := &metainfo.Info{
+               Name:        "a",
+               Length:      2,
+               PieceLength: missinggo.MiB,
        }
-       ts, err := s.OpenTorrent(info)
+       ts, err := s.OpenTorrent(info, metainfo.Hash{})
        assert.NoError(t, err)
        f, err := os.Create(filepath.Join(td, "a"))
        err = f.Truncate(1)
index b2c3724e2d8428fa853f956dd5a141cf8c78dc1b..24716e0cba1f8011fbd63d86169953a9271b9120 100644 (file)
@@ -8,7 +8,7 @@ import (
 
 // Represents data storage for an unspecified torrent.
 type Client interface {
-       OpenTorrent(info *metainfo.InfoEx) (Torrent, error)
+       OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error)
 }
 
 // Data storage bound to a torrent.
index 8641649a2e89b57a88bbddf33c60b55589022c9a..39072cdbd219448d963bcfe4ed377ecf741e8235 100644 (file)
@@ -6,31 +6,26 @@ import (
        "testing"
 
        "github.com/anacrolix/missinggo/resource"
-       "github.com/anacrolix/torrent/metainfo"
        "github.com/stretchr/testify/assert"
        "github.com/stretchr/testify/require"
+
+       "github.com/anacrolix/torrent/metainfo"
 )
 
 // 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 Client) {
-       i1 := &metainfo.InfoEx{
-               Bytes: []byte("a"),
-               Info: metainfo.Info{
-                       Files:  []metainfo.FileInfo{{Path: []string{"a"}}},
-                       Pieces: make([]byte, 20),
-               },
+       i1 := &metainfo.Info{
+               Files:  []metainfo.FileInfo{{Path: []string{"a"}}},
+               Pieces: make([]byte, 20),
        }
-       t1, err := c.OpenTorrent(i1)
+       t1, err := c.OpenTorrent(i1, metainfo.HashBytes([]byte("a")))
        require.NoError(t, err)
-       i2 := &metainfo.InfoEx{
-               Bytes: []byte("b"),
-               Info: metainfo.Info{
-                       Files:  []metainfo.FileInfo{{Path: []string{"a"}}},
-                       Pieces: make([]byte, 20),
-               },
+       i2 := &metainfo.Info{
+               Files:  []metainfo.FileInfo{{Path: []string{"a"}}},
+               Pieces: make([]byte, 20),
        }
-       t2, err := c.OpenTorrent(i2)
+       t2, err := c.OpenTorrent(i2, metainfo.HashBytes([]byte("b")))
        require.NoError(t, err)
        t2p := t2.Piece(i2.Piece(0))
        assert.NoError(t, t1.Close())
index b3bc4d941ddf3192dedb7baaf61ce590ed97aa6a..81fd178c65a1739d8c874ac29978ea60185e20ef 100644 (file)
@@ -15,13 +15,11 @@ func testMarkedCompleteMissingOnRead(t *testing.T, csf func(string) Client) {
        require.NoError(t, err)
        defer os.RemoveAll(td)
        cs := csf(td)
-       info := &metainfo.InfoEx{
-               Info: metainfo.Info{
-                       PieceLength: 1,
-                       Files:       []metainfo.FileInfo{{Path: []string{"a"}, Length: 1}},
-               },
+       info := &metainfo.Info{
+               PieceLength: 1,
+               Files:       []metainfo.FileInfo{{Path: []string{"a"}, Length: 1}},
        }
-       ts, err := cs.OpenTorrent(info)
+       ts, err := cs.OpenTorrent(info, metainfo.Hash{})
        require.NoError(t, err)
        p := ts.Piece(info.Piece(0))
        require.NoError(t, p.MarkComplete())
index 6a81f26e37d03d9f070744d04a833776f692454c..3d176f361ceded306dadcf1da0fbc240c76150c6 100644 (file)
@@ -23,8 +23,8 @@ func NewMMap(baseDir string) Client {
        }
 }
 
-func (s *mmapStorage) OpenTorrent(info *metainfo.InfoEx) (t Torrent, err error) {
-       span, err := mMapTorrent(&info.Info, s.baseDir)
+func (s *mmapStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (t Torrent, err error) {
+       span, err := mMapTorrent(info, s.baseDir)
        t = &mmapTorrentStorage{
                span: span,
                pc:   pieceCompletionForDir(s.baseDir),
@@ -54,17 +54,22 @@ func (ts *mmapTorrentStorage) Close() error {
 type mmapStoragePiece struct {
        pc pieceCompletion
        p  metainfo.Piece
+       ih metainfo.Hash
        io.ReaderAt
        io.WriterAt
 }
 
+func (me mmapStoragePiece) pieceKey() metainfo.PieceKey {
+       return metainfo.PieceKey{me.ih, me.p.Index()}
+}
+
 func (sp mmapStoragePiece) GetIsComplete() (ret bool) {
-       ret, _ = sp.pc.Get(sp.p)
+       ret, _ = sp.pc.Get(sp.pieceKey())
        return
 }
 
 func (sp mmapStoragePiece) MarkComplete() error {
-       sp.pc.Set(sp.p, true)
+       sp.pc.Set(sp.pieceKey(), true)
        return nil
 }
 
index aedd5c78744c50e19782cd94612ba52c60998af1..36fe0664351c91984f0f9995817a67657cabc903 100644 (file)
@@ -25,7 +25,7 @@ type pieceFileTorrentStorage struct {
        s *pieceFileStorage
 }
 
-func (s *pieceFileStorage) OpenTorrent(info *metainfo.InfoEx) (Torrent, error) {
+func (s *pieceFileStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error) {
        return &pieceFileTorrentStorage{s}, nil
 }
 
index 210de174891b2c63e0e468a79c6ff79844fd9a35..99d9e3bcb65af41917d06b18ab9b9388e23578b0 100644 (file)
@@ -20,7 +20,7 @@ func NewResourcePieces(p resource.Provider) Client {
        }
 }
 
-func (s *piecePerResource) OpenTorrent(info *metainfo.InfoEx) (Torrent, error) {
+func (s *piecePerResource) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (Torrent, error) {
        return s, nil
 }
 
diff --git a/t.go b/t.go
index e75009eca49e3c0d0a52ec9da35c5bc7a89e0c54..2b985333aea105cb632148dac61c5fbf638e8572 100644 (file)
--- a/t.go
+++ b/t.go
@@ -24,7 +24,7 @@ func (t *Torrent) GotInfo() <-chan struct{} {
 }
 
 // Returns the metainfo info dictionary, or nil if it's not yet available.
-func (t *Torrent) Info() *metainfo.InfoEx {
+func (t *Torrent) Info() *metainfo.Info {
        return t.info
 }
 
@@ -117,7 +117,7 @@ func (t *Torrent) Length() int64 {
 
 // Returns a run-time generated metainfo for the torrent that includes the
 // info bytes and announce-list as currently known to the client.
-func (t *Torrent) Metainfo() *metainfo.MetaInfo {
+func (t *Torrent) Metainfo() metainfo.MetaInfo {
        t.cl.mu.Lock()
        defer t.cl.mu.Unlock()
        return t.newMetaInfo()
index ff8f772f5d575facdac873b366840051f9d89793..a22cf3f1e740afae7a8f254024a5769150553311 100644 (file)
@@ -62,7 +62,7 @@ type Torrent struct {
        metainfo metainfo.MetaInfo
 
        // The info dict. nil if we don't have it (yet).
-       info *metainfo.InfoEx
+       info *metainfo.Info
        // Active peer connections, running message stream loops.
        conns               []*connection
        maxEstablishedConns int
@@ -210,7 +210,7 @@ func infoPieceHashes(info *metainfo.Info) (ret []string) {
 }
 
 func (t *Torrent) makePieces() {
-       hashes := infoPieceHashes(&t.info.Info)
+       hashes := infoPieceHashes(t.info)
        t.pieces = make([]piece, len(hashes))
        for i, hash := range hashes {
                piece := &t.pieces[i]
@@ -226,24 +226,24 @@ func (t *Torrent) setInfoBytes(b []byte) error {
        if t.haveInfo() {
                return nil
        }
-       var ie *metainfo.InfoEx
-       err := bencode.Unmarshal(b, &ie)
+       if metainfo.HashBytes(b) != t.infoHash {
+               return errors.New("info bytes have wrong hash")
+       }
+       var info metainfo.Info
+       err := bencode.Unmarshal(b, &info)
        if err != nil {
                return fmt.Errorf("error unmarshalling info bytes: %s", err)
        }
-       if ie.Hash() != t.infoHash {
-               return errors.New("info bytes have wrong hash")
-       }
-       err = validateInfo(&ie.Info)
+       err = validateInfo(&info)
        if err != nil {
                return fmt.Errorf("bad info: %s", err)
        }
        defer t.updateWantPeersEvent()
-       t.info = ie
+       t.info = &info
        t.displayName = "" // Save a few bytes lol.
        t.cl.event.Broadcast()
        t.gotMetainfo.Set()
-       t.storage, err = t.storageOpener.OpenTorrent(t.info)
+       t.storage, err = t.storageOpener.OpenTorrent(t.info, t.infoHash)
        if err != nil {
                return fmt.Errorf("error opening torrent storage: %s", err)
        }
@@ -475,17 +475,14 @@ func (t *Torrent) announceList() (al [][]string) {
 
 // Returns a run-time generated MetaInfo that includes the info bytes and
 // announce-list as currently known to the client.
-func (t *Torrent) newMetaInfo() (mi *metainfo.MetaInfo) {
-       mi = &metainfo.MetaInfo{
+func (t *Torrent) newMetaInfo() metainfo.MetaInfo {
+       return metainfo.MetaInfo{
                CreationDate: time.Now().Unix(),
                Comment:      "dynamic metainfo from client",
                CreatedBy:    "go.torrent",
                AnnounceList: t.announceList(),
+               InfoBytes:    t.metadataBytes,
        }
-       if t.info != nil {
-               mi.Info = *t.info
-       }
-       return
 }
 
 func (t *Torrent) BytesMissing() int64 {
index 1d5fe65a20b7efb0a5e0bae01d4935ea426551f0..3c506637659cbc1325ee492820d842a5075327e6 100644 (file)
@@ -69,7 +69,7 @@ func torrentFileInfoHash(fileName string) (ih metainfo.Hash, ok bool) {
        if mi == nil {
                return
        }
-       ih = mi.Info.Hash()
+       ih = mi.HashInfoBytes()
        ok = true
        return
 }