client.go | 13 +++++++------ client_test.go | 63 ++++++++++++++++++++++++++--------------------------- cmd/magnet-metainfo/main.go | 2 +- cmd/torrent-create/main.go | 10 +++++++++- cmd/torrent-infohash/main.go | 2 +- cmd/torrent-magnet/main.go | 3 ++- cmd/torrent-metainfo-pprint/main.go | 6 +++--- cmd/torrent-verify/main.go | 16 ++++++++-------- fs/torrentfs.go | 2 +- fs/torrentfs_test.go | 6 +++--- internal/testutil/testutil.go | 23 +++++++++++++++-------- issue97_test.go | 6 ++++-- metainfo/info.go | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++ metainfo/info_test.go | 16 ++++++++++++++++ metainfo/infoex.go | 47 ----------------------------------------------- metainfo/magnet_test.go | 4 ++-- metainfo/metainfo.go | 176 +++++++---------------------------------------------- metainfo/metainfo_test.go | 31 +++++++++++++++++++++++++------ metainfo/nodes_test.go | 9 +++++---- metainfo/piece.go | 11 +---------- metainfo/piece_key.go | 6 ++++++ storage/completion.go | 4 ++-- storage/completion_piece_map.go | 10 +++++----- storage/db.go | 10 +++++----- storage/file.go | 6 ++++-- storage/file_misc_test.go | 27 ++++++++++++--------------- storage/file_storage_piece.go | 14 +++++++++----- storage/file_test.go | 12 +++++------- storage/interface.go | 2 +- storage/issue95_test.go | 25 ++++++++++--------------- storage/issue96_test.go | 10 ++++------ storage/mmap.go | 13 +++++++++---- storage/piece_file.go | 2 +- storage/piece_resource.go | 2 +- t.go | 4 ++-- torrent.go | 29 +++++++++++++---------------- util/dirwatch/dirwatch.go | 2 +- diff --git a/client.go b/client.go index 5d2c4e02abd4d1f98cc4ead3ddbe66b54240b948..00a474c584b4c97c4370daee92e3a0c10e86d279 100644 --- 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 @@ return } 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 @@ t, new = cl.AddTorrentInfoHash(spec.InfoHash) 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 } diff --git a/client_test.go b/client_test.go index 33d0451ee673c742243c9b43409f34265eaf4523..4ab86ee11e10e77b666280e36935c398f406c551 100644 --- a/client_test.go +++ b/client_test.go @@ -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 @@ } 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 @@ cfg.DefaultStorage = badStorage{} 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 @@ defer cl.Close() 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 @@ os.RemoveAll(dir) 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 @@ default: } } -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 @@ require.NoError(t, err) 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 @@ cfg.DataDir = greetingTempDir seeder, err := NewClient(&TestingConfig) require.NoError(t, err) seeder.AddTorrentSpec(&TorrentSpec{ - Info: &greetingMetainfo.Info, + InfoBytes: greetingMetainfo.InfoBytes, }) } @@ -980,7 +979,7 @@ } 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) diff --git a/cmd/magnet-metainfo/main.go b/cmd/magnet-metainfo/main.go index 8bad76fd6db40388910e4df3d32fea5d52d99cb7..f7b7dce0eedf18ec3a0d2550ec2581295eef0293 100644 --- a/cmd/magnet-metainfo/main.go +++ b/cmd/magnet-metainfo/main.go @@ -29,7 +29,7 @@ defer wg.Done() <-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) } diff --git a/cmd/torrent-create/main.go b/cmd/torrent-create/main.go index 399c06811f4ae5f5ebb1a308e83286a218bd1f97..4a429e9bcaeb7c7ace434b617e53b4b00250ae25 100644 --- a/cmd/torrent-create/main.go +++ b/cmd/torrent-create/main.go @@ -6,6 +6,7 @@ "os" "github.com/anacrolix/tagflag" + "github.com/anacrolix/torrent/bencode" "github.com/anacrolix/torrent/metainfo" ) @@ -32,7 +33,14 @@ for _, a := range args.AnnounceList { 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) } diff --git a/cmd/torrent-infohash/main.go b/cmd/torrent-infohash/main.go index 93ae336a11384606a31c17c91faa2948e76479ca..81319c99a4692778462de66c58dd79826db7ee1b 100644 --- a/cmd/torrent-infohash/main.go +++ b/cmd/torrent-infohash/main.go @@ -20,6 +20,6 @@ mi, err := metainfo.LoadFromFile(arg) 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) } } diff --git a/cmd/torrent-magnet/main.go b/cmd/torrent-magnet/main.go index 18824148ddf6f228104e242f1ac0ec81df73b7cc..50e57b16fbb52e6ffc757b7984ad41d4a2b6acfe 100644 --- a/cmd/torrent-magnet/main.go +++ b/cmd/torrent-magnet/main.go @@ -17,6 +17,7 @@ if err != nil { 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()) } diff --git a/cmd/torrent-metainfo-pprint/main.go b/cmd/torrent-metainfo-pprint/main.go index 8495ae10d6546e3b9b695f83e8cb0174d61e79b9..4f99a9974ab7ee996eba588debfe209877fba479 100644 --- a/cmd/torrent-metainfo-pprint/main.go +++ b/cmd/torrent-metainfo-pprint/main.go @@ -29,16 +29,16 @@ if err != nil { 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, diff --git a/cmd/torrent-verify/main.go b/cmd/torrent-verify/main.go index f7457b8564612c7c3457cca9bf5406f117a3d05a..7f329302b75f701ed608996ff26ea8edeebb9b4c 100644 --- a/cmd/torrent-verify/main.go +++ b/cmd/torrent-verify/main.go @@ -49,22 +49,22 @@ metaInfo, err := metainfo.LoadFromFile(*torrentPath) 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())) diff --git a/fs/torrentfs.go b/fs/torrentfs.go index 55834a241379e4a07d9aa3e8ca45590d6984e525..06e05e008fd48599af4ea19da1bd096c19777f19 100644 --- a/fs/torrentfs.go +++ b/fs/torrentfs.go @@ -49,7 +49,7 @@ } type node struct { path string - metadata *metainfo.InfoEx + metadata *metainfo.Info FS *TorrentFS t *torrent.Torrent } diff --git a/fs/torrentfs_test.go b/fs/torrentfs_test.go index f3564124d724ada75df60b285b7143b0b757242d..e77d63cd3995c581f543e11e474a3b5ff084e1d7 100644 --- a/fs/torrentfs_test.go +++ b/fs/torrentfs_test.go @@ -97,7 +97,7 @@ DisableUTP: true, }) 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 @@ // Read the greeting file, though it will never be available. This should // "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 @@ }) 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, diff --git a/internal/testutil/testutil.go b/internal/testutil/testutil.go index 1f236a424e96b475c214739b97404287cb57075c..6431a1f187a6f6cab6a867732576136bdda29004 100644 --- a/internal/testutil/testutil.go +++ b/internal/testutil/testutil.go @@ -16,6 +16,7 @@ "strings" "github.com/anacrolix/missinggo" + "github.com/anacrolix/torrent/bencode" "github.com/anacrolix/torrent/metainfo" ) @@ -30,19 +31,25 @@ defer f.Close() 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, diff --git a/issue97_test.go b/issue97_test.go index 3f4e16d2d0d1b89b911d460543a150c8a0b2d437..1e6373cc64fe7e760b0432b403fa07e8b01068aa 100644 --- a/issue97_test.go +++ b/issue97_test.go @@ -17,9 +17,11 @@ require.NoError(t, err) 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 index 0000000000000000000000000000000000000000..8563e254b2631be3f6e4d16cab7263f3b24ba50b --- /dev/null +++ b/metainfo/info.go @@ -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 index 0000000000000000000000000000000000000000..cf1eaf8f0f5ec2b78f0951374ba1c7662edb12ef --- /dev/null +++ b/metainfo/info_test.go @@ -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 index 1e4610f450ba5467689c99e38df2ee3689b9b8e4..0000000000000000000000000000000000000000 --- a/metainfo/infoex.go +++ /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} -} diff --git a/metainfo/magnet_test.go b/metainfo/magnet_test.go index 065b662ffe6eb65f56f096f59735ac4872fae18a..1ad4b170395a41ff83c74601be0159763a185aa2 100644 --- a/metainfo/magnet_test.go +++ b/metainfo/magnet_test.go @@ -70,11 +70,11 @@ } } -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) diff --git a/metainfo/metainfo.go b/metainfo/metainfo.go index b9a65a9def1e92b850c93db798939808ddf2bf11..6d1d7e2efecfc44b46a51a7a4a05c6ed77d58e07 100644 --- a/metainfo/metainfo.go +++ b/metainfo/metainfo.go @@ -1,19 +1,25 @@ 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 { @@ -43,158 +49,20 @@ defer f.Close() 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) - } - 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 + panic(fmt.Sprintf("bad info bytes: %s", err)) } 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 @@ } 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 } diff --git a/metainfo/metainfo_test.go b/metainfo/metainfo_test.go index a119f31381eab3e0733401b88f15ddb8d700345f..de81b8da67c0f8fc11c787084c8b67b513944320 100644 --- a/metainfo/metainfo_test.go +++ b/metainfo/metainfo_test.go @@ -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 @@ t.Logf("Tracker: %s\n", tracker) } } - 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 @@ }, { 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")}) +} diff --git a/metainfo/nodes_test.go b/metainfo/nodes_test.go index 2b8768a32879f3a263cf32e325b4eb91b8c56759..69bb9e6704964088321165876cfbf9cd9743f4ed 100644 --- a/metainfo/nodes_test.go +++ b/metainfo/nodes_test.go @@ -42,15 +42,16 @@ }) } 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"), }) } diff --git a/metainfo/piece.go b/metainfo/piece.go index a68f1bce7f538c196b24dbde6bedd64005dcd1a7..ff753bd3c88d0f83d475fa91e5c354be1f06ae10 100644 --- a/metainfo/piece.go +++ b/metainfo/piece.go @@ -3,7 +3,7 @@ import "github.com/anacrolix/missinggo" type Piece struct { - Info *InfoEx + Info *Info i int } @@ -26,12 +26,3 @@ 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 index 0000000000000000000000000000000000000000..b5aa492e500769ea6957701ea529a4ee35df114e --- /dev/null +++ b/metainfo/piece_key.go @@ -0,0 +1,6 @@ +package metainfo + +type PieceKey struct { + InfoHash Hash + Index int +} diff --git a/storage/completion.go b/storage/completion.go index a5c28fcb4a6442a342e378e8f333bfd0ccfb3ef1..c3047be2189891835df6a60e241386585ec0db6e 100644 --- a/storage/completion.go +++ b/storage/completion.go @@ -7,8 +7,8 @@ "github.com/anacrolix/torrent/metainfo" ) type pieceCompletion interface { - Get(metainfo.Piece) (bool, error) - Set(metainfo.Piece, bool) error + Get(metainfo.PieceKey) (bool, error) + Set(metainfo.PieceKey, bool) error Close() } diff --git a/storage/completion_piece_map.go b/storage/completion_piece_map.go index b8a2d5b4a5c8518ba55cff027b2cb4001df4bea8..26d893cec5f0fcf3a00cde708970f174a1948dca 100644 --- a/storage/completion_piece_map.go +++ b/storage/completion_piece_map.go @@ -10,19 +10,19 @@ } 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 } diff --git a/storage/db.go b/storage/db.go index db95a11d29ae26fa642fd8f59cbd4512df4f7b8f..8e4b90417d23b1691c25e51f570d45738f774ea3 100644 --- a/storage/db.go +++ b/storage/db.go @@ -26,17 +26,17 @@ ret = &dbPieceCompletion{db} 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 } diff --git a/storage/file.go b/storage/file.go index 00ec0603512855ac929f6c14e98e75bd199f163a..bae53134d054b9eae5c121430b610184d1bbb91a 100644 --- a/storage/file.go +++ b/storage/file.go @@ -23,10 +23,11 @@ baseDir: baseDir, } } -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 @@ // File-based torrent storage, not yet bound to a Torrent. type fileTorrentStorage struct { fs *fileStorage info *metainfo.Info + infoHash metainfo.Hash completion pieceCompletion } diff --git a/storage/file_misc_test.go b/storage/file_misc_test.go index d95d02476c945fd0e2a8270d63ab8c95b825e37a..8b453045c1d813282d74ee7d2a7a5fede5127c4a 100644 --- a/storage/file_misc_test.go +++ b/storage/file_misc_test.go @@ -9,32 +9,29 @@ "github.com/anacrolix/torrent/metainfo" ) 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) }) } diff --git a/storage/file_storage_piece.go b/storage/file_storage_piece.go index 44648c136e89a387f5c30156930449058fc556f1..915d5d0d1b637c76eec8956b880f2448ed70fdbd 100644 --- a/storage/file_storage_piece.go +++ b/storage/file_storage_piece.go @@ -14,14 +14,18 @@ io.WriterAt 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 @@ if ret { 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 @@ } if off < 0 || off >= fsp.p.Length() { return } - fsp.completion.Set(fsp.p, false) + fsp.completion.Set(fsp.pieceKey(), false) return } diff --git a/storage/file_test.go b/storage/file_test.go index 6e1d055e7d65c13a6e9fa32078b8a2f1c6d839b4..5c63c94cecd5c50a4769db25ce4eddc37e1622a6 100644 --- a/storage/file_test.go +++ b/storage/file_test.go @@ -20,14 +20,12 @@ td, err := ioutil.TempDir("", "") 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) diff --git a/storage/interface.go b/storage/interface.go index b2c3724e2d8428fa853f956dd5a141cf8c78dc1b..24716e0cba1f8011fbd63d86169953a9271b9120 100644 --- a/storage/interface.go +++ b/storage/interface.go @@ -8,7 +8,7 @@ ) // 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. diff --git a/storage/issue95_test.go b/storage/issue95_test.go index 8641649a2e89b57a88bbddf33c60b55589022c9a..39072cdbd219448d963bcfe4ed377ecf741e8235 100644 --- a/storage/issue95_test.go +++ b/storage/issue95_test.go @@ -6,31 +6,26 @@ "os" "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()) diff --git a/storage/issue96_test.go b/storage/issue96_test.go index b3bc4d941ddf3192dedb7baaf61ce590ed97aa6a..81fd178c65a1739d8c874ac29978ea60185e20ef 100644 --- a/storage/issue96_test.go +++ b/storage/issue96_test.go @@ -15,13 +15,11 @@ td, err := ioutil.TempDir("", "") 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()) diff --git a/storage/mmap.go b/storage/mmap.go index 6a81f26e37d03d9f070744d04a833776f692454c..3d176f361ceded306dadcf1da0fbc240c76150c6 100644 --- a/storage/mmap.go +++ b/storage/mmap.go @@ -23,8 +23,8 @@ baseDir: baseDir, } } -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 @@ 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 } diff --git a/storage/piece_file.go b/storage/piece_file.go index aedd5c78744c50e19782cd94612ba52c60998af1..36fe0664351c91984f0f9995817a67657cabc903 100644 --- a/storage/piece_file.go +++ b/storage/piece_file.go @@ -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 } diff --git a/storage/piece_resource.go b/storage/piece_resource.go index 210de174891b2c63e0e468a79c6ff79844fd9a35..99d9e3bcb65af41917d06b18ab9b9388e23578b0 100644 --- a/storage/piece_resource.go +++ b/storage/piece_resource.go @@ -20,7 +20,7 @@ p: p, } } -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 --- a/t.go +++ b/t.go @@ -24,7 +24,7 @@ return t.gotMetainfo.C() } // 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 @@ } // 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() diff --git a/torrent.go b/torrent.go index ff8f772f5d575facdac873b366840051f9d89793..a22cf3f1e740afae7a8f254024a5769150553311 100644 --- a/torrent.go +++ b/torrent.go @@ -62,7 +62,7 @@ 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 @@ return } 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 @@ } // 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 { diff --git a/util/dirwatch/dirwatch.go b/util/dirwatch/dirwatch.go index 1d5fe65a20b7efb0a5e0bae01d4935ea426551f0..3c506637659cbc1325ee492820d842a5075327e6 100644 --- a/util/dirwatch/dirwatch.go +++ b/util/dirwatch/dirwatch.go @@ -69,7 +69,7 @@ mi, _ := metainfo.LoadFromFile(fileName) if mi == nil { return } - ih = mi.Info.Hash() + ih = mi.HashInfoBytes() ok = true return }