9 "github.com/anacrolix/missinggo"
10 "github.com/anacrolix/torrent/common"
11 "github.com/anacrolix/torrent/segments"
13 "github.com/anacrolix/torrent/metainfo"
16 // File-based storage for torrents, that isn't yet bound to a particular
18 type fileClientImpl struct {
20 pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string
24 // The Default path maker just returns the current path
25 func defaultPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
29 func infoHashPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
30 return filepath.Join(baseDir, infoHash.HexString())
33 // All Torrent data stored in this baseDir
34 func NewFile(baseDir string) ClientImplCloser {
35 return NewFileWithCompletion(baseDir, pieceCompletionForDir(baseDir))
38 func NewFileWithCompletion(baseDir string, completion PieceCompletion) *fileClientImpl {
39 return newFileWithCustomPathMakerAndCompletion(baseDir, nil, completion)
42 // File storage with data partitioned by infohash.
43 func NewFileByInfoHash(baseDir string) ClientImpl {
44 return NewFileWithCustomPathMaker(baseDir, infoHashPathMaker)
47 // Allows passing a function to determine the path for storing torrent data. The function is
48 // responsible for sanitizing the info if it uses some part of it (for example sanitizing
50 func NewFileWithCustomPathMaker(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string) ClientImpl {
51 return newFileWithCustomPathMakerAndCompletion(baseDir, pathMaker, pieceCompletionForDir(baseDir))
54 func newFileWithCustomPathMakerAndCompletion(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string, completion PieceCompletion) *fileClientImpl {
56 pathMaker = defaultPathMaker
58 return &fileClientImpl{
65 func (me *fileClientImpl) Close() error {
69 func (fs *fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
70 dir := fs.pathMaker(fs.baseDir, info, infoHash)
71 upvertedFiles := info.UpvertedFiles()
72 files := make([]file, 0, len(upvertedFiles))
73 for i, fileInfo := range upvertedFiles {
74 s, err := ToSafeFilePath(append([]string{info.Name}, fileInfo.Path...)...)
76 return nil, fmt.Errorf("file %v has unsafe path %q: %w", i, fileInfo.Path, err)
79 path: filepath.Join(dir, s),
80 length: fileInfo.Length,
83 err = CreateNativeZeroLengthFile(f.path)
85 return nil, fmt.Errorf("creating zero length file: %w", err)
88 files = append(files, f)
90 return &fileTorrentImpl{
92 segments.NewIndex(common.LengthIterFromUpvertedFiles(upvertedFiles)),
99 // The safe, OS-local file path.
104 type fileTorrentImpl struct {
106 segmentLocater segments.Index
107 infoHash metainfo.Hash
108 completion PieceCompletion
111 func (fts *fileTorrentImpl) Piece(p metainfo.Piece) PieceImpl {
112 // Create a view onto the file-based torrent storage.
113 _io := fileTorrentImplIO{fts}
114 // Return the appropriate segments of this.
115 return &filePieceImpl{
118 missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
119 io.NewSectionReader(_io, p.Offset(), p.Length()),
123 func (fs *fileTorrentImpl) Close() error {
127 // A helper to create zero-length files which won't appear for file-orientated storage since no
128 // writes will ever occur to them (no torrent data is associated with a zero-length file). The
129 // caller should make sure the file name provided is safe/sanitized.
130 func CreateNativeZeroLengthFile(name string) error {
131 os.MkdirAll(filepath.Dir(name), 0777)
133 f, err := os.Create(name)
140 // Exposes file-based storage of a torrent, as one big ReadWriterAt.
141 type fileTorrentImplIO struct {
145 // Returns EOF on short or missing file.
146 func (fst *fileTorrentImplIO) readFileAt(file file, b []byte, off int64) (n int, err error) {
147 f, err := os.Open(file.path)
148 if os.IsNotExist(err) {
149 // File missing is treated the same as a short file.
157 // Limit the read to within the expected bounds of this file.
158 if int64(len(b)) > file.length-off {
159 b = b[:file.length-off]
161 for off < file.length && len(b) != 0 {
162 n1, err1 := f.ReadAt(b, off)
174 // Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
175 func (fst fileTorrentImplIO) ReadAt(b []byte, off int64) (n int, err error) {
176 fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(b))}, func(i int, e segments.Extent) bool {
177 n1, err1 := fst.readFileAt(fst.fts.files[i], b[:e.Length], e.Start)
181 return err == nil // && int64(n1) == e.Length
183 if len(b) != 0 && err == nil {
189 func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) {
190 //log.Printf("write at %v: %v bytes", off, len(p))
191 fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(p))}, func(i int, e segments.Extent) bool {
192 name := fst.fts.files[i].path
193 os.MkdirAll(filepath.Dir(name), 0777)
195 f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0666)
200 n1, err = f.WriteAt(p[:e.Length], e.Start)
201 //log.Printf("%v %v wrote %v: %v", i, e, n1, err)
202 closeErr := f.Close()
208 if err == nil && int64(n1) != e.Length {
209 err = io.ErrShortWrite