github.com/anacrolix/torrent@v1.61.0/storage/file-torrent-io.go (about)

     1  package storage
     2  
     3  import (
     4  	"errors"
     5  	"io"
     6  	"io/fs"
     7  
     8  	"github.com/anacrolix/missinggo/v2/panicif"
     9  
    10  	"github.com/anacrolix/torrent/segments"
    11  )
    12  
    13  // Exposes file-based storage of a torrent, as one big ReadWriterAt.
    14  type fileTorrentImplIO struct {
    15  	fts *fileTorrentImpl
    16  }
    17  
    18  // Returns EOF on short or missing file.
    19  func (fst fileTorrentImplIO) readFileAt(file file, b []byte, off int64) (n int, err error) {
    20  	fst.fts.logger().Debug("readFileAt", "file.safeOsPath", file.safeOsPath)
    21  	f, err := fst.fts.openSharedFile(file)
    22  	if errors.Is(err, fs.ErrNotExist) {
    23  		// File missing is treated the same as a short file. Should we propagate this through the
    24  		// interface now that fs.ErrNotExist is a thing?
    25  		err = io.EOF
    26  		return
    27  	}
    28  	if err != nil {
    29  		return
    30  	}
    31  	defer f.Close()
    32  	// Limit the read to within the expected bounds of this file.
    33  	if int64(len(b)) > file.length()-off {
    34  		b = b[:file.length()-off]
    35  	}
    36  	for off < file.length() && len(b) != 0 {
    37  		n1, err1 := f.ReadAt(b, off)
    38  		b = b[n1:]
    39  		n += n1
    40  		off += int64(n1)
    41  		if n1 == 0 {
    42  			err = err1
    43  			break
    44  		}
    45  	}
    46  	return
    47  }
    48  
    49  // Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
    50  func (fst fileTorrentImplIO) ReadAt(b []byte, off int64) (n int, err error) {
    51  	for i, e := range fst.fts.segmentLocater.LocateIter(
    52  		segments.Extent{off, int64(len(b))},
    53  	) {
    54  		n1, err1 := fst.readFileAt(fst.fts.file(i), b[:e.Length], e.Start)
    55  		n += n1
    56  		b = b[n1:]
    57  		if segments.Int(n1) == e.Length {
    58  			switch err1 {
    59  			// ReaderAt.ReadAt contract.
    60  			case nil, io.EOF:
    61  			default:
    62  				err = err1
    63  				return
    64  			}
    65  		} else {
    66  			panicif.Nil(err1)
    67  			err = err1
    68  			return
    69  		}
    70  	}
    71  	if len(b) != 0 {
    72  		// We're at the end of the torrent.
    73  		err = io.EOF
    74  	}
    75  	return
    76  }
    77  
    78  func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) {
    79  	for i, e := range fst.fts.segmentLocater.LocateIter(
    80  		segments.Extent{off, int64(len(p))},
    81  	) {
    82  		var f fileWriter
    83  		f, err = fst.fts.openForWrite(fst.fts.file(i))
    84  		if err != nil {
    85  			return
    86  		}
    87  		var n1 int
    88  		n1, err = f.WriteAt(p[:e.Length], e.Start)
    89  		closeErr := f.Close()
    90  		n += n1
    91  		p = p[n1:]
    92  		if err == nil {
    93  			err = closeErr
    94  		}
    95  		if err == nil && int64(n1) != e.Length {
    96  			err = io.ErrShortWrite
    97  		}
    98  		if err != nil {
    99  			return
   100  		}
   101  	}
   102  	return
   103  }