In the native file-based storage, mark pieces incomplete if the necessary file data is missing, or there's a read error on a piece.
This commit is contained in:
Matt Joiner 2016-07-10 23:03:59 +10:00
parent 9943fc3c58
commit f055abe2fc
5 changed files with 173 additions and 30 deletions

View File

@ -26,6 +26,7 @@ func NewFile(baseDir string) Client {
func (fs *fileStorage) OpenTorrent(info *metainfo.InfoEx) (Torrent, error) {
return &fileTorrentStorage{
fs,
&info.Info,
pieceCompletionForDir(fs.baseDir),
}, nil
}
@ -33,15 +34,13 @@ func (fs *fileStorage) OpenTorrent(info *metainfo.InfoEx) (Torrent, error) {
// File-based torrent storage, not yet bound to a Torrent.
type fileTorrentStorage struct {
fs *fileStorage
info *metainfo.Info
completion pieceCompletion
}
func (fts *fileTorrentStorage) Piece(p metainfo.Piece) Piece {
// Create a view onto the file-based torrent storage.
_io := &fileStorageTorrent{
p.Info,
fts.fs.baseDir,
}
_io := fileStorageTorrent{fts}
// Return the appropriate segments of this.
return &fileStoragePiece{
fts,
@ -56,31 +55,14 @@ func (fs *fileTorrentStorage) Close() error {
return nil
}
type fileStoragePiece struct {
*fileTorrentStorage
p metainfo.Piece
io.WriterAt
io.ReaderAt
}
func (fs *fileStoragePiece) GetIsComplete() bool {
return fs.completion.Get(fs.p)
}
func (fs *fileStoragePiece) MarkComplete() error {
fs.completion.Set(fs.p, true)
return nil
}
// Exposes file-based storage of a torrent, as one big ReadWriterAt.
type fileStorageTorrent struct {
info *metainfo.InfoEx
baseDir string
fts *fileTorrentStorage
}
// Returns EOF on short or missing file.
func (fst *fileStorageTorrent) readFileAt(fi metainfo.FileInfo, b []byte, off int64) (n int, err error) {
f, err := os.Open(fst.fileInfoName(fi))
f, err := os.Open(fst.fts.fileInfoName(fi))
if os.IsNotExist(err) {
// File missing is treated the same as a short file.
err = io.EOF
@ -108,8 +90,8 @@ func (fst *fileStorageTorrent) readFileAt(fi metainfo.FileInfo, b []byte, off in
}
// Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
func (fst *fileStorageTorrent) ReadAt(b []byte, off int64) (n int, err error) {
for _, fi := range fst.info.UpvertedFiles() {
func (fst fileStorageTorrent) ReadAt(b []byte, off int64) (n int, err error) {
for _, fi := range fst.fts.info.UpvertedFiles() {
for off < fi.Length {
n1, err1 := fst.readFileAt(fi, b, off)
n += n1
@ -136,8 +118,8 @@ func (fst *fileStorageTorrent) ReadAt(b []byte, off int64) (n int, err error) {
return
}
func (fst *fileStorageTorrent) WriteAt(p []byte, off int64) (n int, err error) {
for _, fi := range fst.info.UpvertedFiles() {
func (fst fileStorageTorrent) WriteAt(p []byte, off int64) (n int, err error) {
for _, fi := range fst.fts.info.UpvertedFiles() {
if off >= fi.Length {
off -= fi.Length
continue
@ -146,7 +128,7 @@ func (fst *fileStorageTorrent) WriteAt(p []byte, off int64) (n int, err error) {
if int64(n1) > fi.Length-off {
n1 = int(fi.Length - off)
}
name := fst.fileInfoName(fi)
name := fst.fts.fileInfoName(fi)
os.MkdirAll(filepath.Dir(name), 0770)
var f *os.File
f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0660)
@ -168,6 +150,6 @@ func (fst *fileStorageTorrent) WriteAt(p []byte, off int64) (n int, err error) {
return
}
func (fst *fileStorageTorrent) fileInfoName(fi metainfo.FileInfo) string {
return filepath.Join(append([]string{fst.baseDir, fst.info.Name}, fi.Path...)...)
func (fts *fileTorrentStorage) fileInfoName(fi metainfo.FileInfo) string {
return filepath.Join(append([]string{fts.fs.baseDir, fts.info.Name}, fi.Path...)...)
}

29
storage/file_misc.go Normal file
View File

@ -0,0 +1,29 @@
package storage
import "github.com/anacrolix/torrent/metainfo"
func extentCompleteRequiredLengths(info *metainfo.Info, off, n int64) (ret []metainfo.FileInfo) {
if n == 0 {
return
}
for _, fi := range info.UpvertedFiles() {
if off >= fi.Length {
off -= fi.Length
continue
}
n1 := n
if off+n1 > fi.Length {
n1 = fi.Length - off
}
ret = append(ret, metainfo.FileInfo{
Path: fi.Path,
Length: off + n1,
})
n -= n1
if n == 0 {
return
}
off = 0
}
panic("extent exceeds torrent bounds")
}

40
storage/file_misc_test.go Normal file
View File

@ -0,0 +1,40 @@
package storage
import (
"testing"
"github.com/stretchr/testify/assert"
"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},
},
},
}
assert.Empty(t, extentCompleteRequiredLengths(&info.Info, 0, 0))
assert.EqualValues(t, []metainfo.FileInfo{
{Path: []string{"a"}, Length: 1},
}, extentCompleteRequiredLengths(&info.Info, 0, 1))
assert.EqualValues(t, []metainfo.FileInfo{
{Path: []string{"a"}, Length: 2},
}, extentCompleteRequiredLengths(&info.Info, 0, 2))
assert.EqualValues(t, []metainfo.FileInfo{
{Path: []string{"a"}, Length: 2},
{Path: []string{"b"}, Length: 1},
}, extentCompleteRequiredLengths(&info.Info, 0, 3))
assert.EqualValues(t, []metainfo.FileInfo{
{Path: []string{"b"}, Length: 2},
}, extentCompleteRequiredLengths(&info.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) })
}

View File

@ -0,0 +1,55 @@
package storage
import (
"io"
"os"
"github.com/anacrolix/torrent/metainfo"
)
type fileStoragePiece struct {
*fileTorrentStorage
p metainfo.Piece
io.WriterAt
r io.ReaderAt
}
func (fs *fileStoragePiece) GetIsComplete() (ret bool) {
ret = fs.completion.Get(fs.p)
if !ret {
return
}
// 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()) {
s, err := os.Stat(fs.fileInfoName(fi))
if err != nil || s.Size() < fi.Length {
ret = false
break
}
}
if ret {
return
}
// The completion was wrong, fix it.
fs.completion.Set(fs.p, false)
return
}
func (fs *fileStoragePiece) MarkComplete() error {
fs.completion.Set(fs.p, true)
return nil
}
func (fsp *fileStoragePiece) ReadAt(b []byte, off int64) (n int, err error) {
n, err = fsp.r.ReadAt(b, off)
if n != 0 {
err = nil
return
}
if off < 0 || off >= fsp.p.Length() {
return
}
fsp.completion.Set(fsp.p, false)
return
}

37
storage/issue96_test.go Normal file
View File

@ -0,0 +1,37 @@
package storage
import (
"io/ioutil"
"os"
"testing"
"github.com/stretchr/testify/require"
"github.com/anacrolix/torrent/metainfo"
)
func testMarkedCompleteMissingOnRead(t *testing.T, csf func(string) Client) {
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}},
},
}
ts, err := cs.OpenTorrent(info)
require.NoError(t, err)
p := ts.Piece(info.Piece(0))
require.NoError(t, p.MarkComplete())
require.False(t, p.GetIsComplete())
n, err := p.ReadAt(make([]byte, 1), 0)
require.Error(t, err)
require.EqualValues(t, 0, n)
require.False(t, p.GetIsComplete())
}
func TestMarkedCompleteMissingOnReadFile(t *testing.T) {
testMarkedCompleteMissingOnRead(t, NewFile)
}