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
48 func NewFileWithCustomPathMaker(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string) ClientImpl {
49 return newFileWithCustomPathMakerAndCompletion(baseDir, pathMaker, pieceCompletionForDir(baseDir))
52 func newFileWithCustomPathMakerAndCompletion(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string, completion PieceCompletion) *fileClientImpl {
54 pathMaker = defaultPathMaker
56 return &fileClientImpl{
63 func (me *fileClientImpl) Close() error {
67 func (fs *fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
68 dir := fs.pathMaker(fs.baseDir, info, infoHash)
69 err := CreateNativeZeroLengthFiles(info, dir)
73 upvertedFiles := info.UpvertedFiles()
74 return &fileTorrentImpl{
78 segments.NewIndex(common.LengthIterFromUpvertedFiles(upvertedFiles)),
84 type fileTorrentImpl struct {
87 upvertedFiles []metainfo.FileInfo
88 segmentLocater segments.Index
89 infoHash metainfo.Hash
90 completion PieceCompletion
93 func (fts *fileTorrentImpl) Piece(p metainfo.Piece) PieceImpl {
94 // Create a view onto the file-based torrent storage.
95 _io := fileTorrentImplIO{fts}
96 // Return the appropriate segments of this.
97 return &filePieceImpl{
100 missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
101 io.NewSectionReader(_io, p.Offset(), p.Length()),
105 func (fs *fileTorrentImpl) Close() error {
109 // Creates natives files for any zero-length file entries in the info. This is
110 // a helper for file-based storages, which don't address or write to zero-
111 // length files because they have no corresponding pieces.
112 func CreateNativeZeroLengthFiles(info *metainfo.Info, dir string) (err error) {
113 for _, fi := range info.UpvertedFiles() {
117 name := filepath.Join(append([]string{dir, info.Name}, fi.Path...)...)
118 os.MkdirAll(filepath.Dir(name), 0777)
120 f, err = os.Create(name)
129 // Exposes file-based storage of a torrent, as one big ReadWriterAt.
130 type fileTorrentImplIO struct {
134 // Returns EOF on short or missing file.
135 func (fst *fileTorrentImplIO) readFileAt(fi metainfo.FileInfo, b []byte, off int64) (n int, err error) {
136 f, err := os.Open(fst.fts.fileInfoName(fi))
137 if os.IsNotExist(err) {
138 // File missing is treated the same as a short file.
146 // Limit the read to within the expected bounds of this file.
147 if int64(len(b)) > fi.Length-off {
148 b = b[:fi.Length-off]
150 for off < fi.Length && len(b) != 0 {
151 n1, err1 := f.ReadAt(b, off)
163 // Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
164 func (fst fileTorrentImplIO) ReadAt(b []byte, off int64) (n int, err error) {
165 fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(b))}, func(i int, e segments.Extent) bool {
166 n1, err1 := fst.readFileAt(fst.fts.upvertedFiles[i], b[:e.Length], e.Start)
170 return err == nil // && int64(n1) == e.Length
172 if len(b) != 0 && err == nil {
178 func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) {
179 log.Printf("write at %v: %v bytes", off, len(p))
180 fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(p))}, func(i int, e segments.Extent) bool {
181 name := fst.fts.fileInfoName(fst.fts.upvertedFiles[i])
182 os.MkdirAll(filepath.Dir(name), 0777)
184 f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0666)
189 n1, err = f.WriteAt(p[:e.Length], e.Start)
190 log.Printf("%v %v wrote %v: %v", i, e, n1, err)
191 closeErr := f.Close()
197 //if err == nil && int64(n1) != e.Length {
198 // err = io.ErrShortWrite
205 func (fts *fileTorrentImpl) fileInfoName(fi metainfo.FileInfo) string {
206 return filepath.Join(append([]string{fts.dir, fts.infoName}, fi.Path...)...)