8 "github.com/anacrolix/missinggo"
10 "github.com/anacrolix/torrent/metainfo"
13 // File-based storage for torrents, that isn't yet bound to a particular
15 type fileClientImpl struct {
17 pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string
21 // The Default path maker just returns the current path
22 func defaultPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
26 func infoHashPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
27 return filepath.Join(baseDir, infoHash.HexString())
30 // All Torrent data stored in this baseDir
31 func NewFile(baseDir string) ClientImpl {
32 return NewFileWithCompletion(baseDir, pieceCompletionForDir(baseDir))
35 func NewFileWithCompletion(baseDir string, completion PieceCompletion) ClientImpl {
36 return newFileWithCustomPathMakerAndCompletion(baseDir, nil, completion)
39 // All Torrent data stored in subdirectorys by infohash
40 func NewFileByInfoHash(baseDir string) ClientImpl {
41 return NewFileWithCustomPathMaker(baseDir, infoHashPathMaker)
44 // Allows passing a function to determine the path for storing torrent data
45 func NewFileWithCustomPathMaker(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string) ClientImpl {
46 return newFileWithCustomPathMakerAndCompletion(baseDir, pathMaker, pieceCompletionForDir(baseDir))
49 func newFileWithCustomPathMakerAndCompletion(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string, completion PieceCompletion) ClientImpl {
51 pathMaker = defaultPathMaker
53 return &fileClientImpl{
60 func (me *fileClientImpl) Close() error {
64 func (fs *fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
65 dir := fs.pathMaker(fs.baseDir, info, infoHash)
66 err := CreateNativeZeroLengthFiles(info, dir)
70 return &fileTorrentImpl{
78 type fileTorrentImpl struct {
81 infoHash metainfo.Hash
82 completion PieceCompletion
85 func (fts *fileTorrentImpl) Piece(p metainfo.Piece) PieceImpl {
86 // Create a view onto the file-based torrent storage.
87 _io := fileTorrentImplIO{fts}
88 // Return the appropriate segments of this.
89 return &filePieceImpl{
92 missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
93 io.NewSectionReader(_io, p.Offset(), p.Length()),
97 func (fs *fileTorrentImpl) Close() error {
101 // Creates natives files for any zero-length file entries in the info. This is
102 // a helper for file-based storages, which don't address or write to zero-
103 // length files because they have no corresponding pieces.
104 func CreateNativeZeroLengthFiles(info *metainfo.Info, dir string) (err error) {
105 for _, fi := range info.UpvertedFiles() {
109 name := filepath.Join(append([]string{dir, info.Name}, fi.Path...)...)
110 os.MkdirAll(filepath.Dir(name), 0777)
112 f, err = os.Create(name)
121 // Exposes file-based storage of a torrent, as one big ReadWriterAt.
122 type fileTorrentImplIO struct {
126 // Returns EOF on short or missing file.
127 func (fst *fileTorrentImplIO) readFileAt(fi metainfo.FileInfo, b []byte, off int64) (n int, err error) {
128 f, err := os.Open(fst.fts.fileInfoName(fi))
129 if os.IsNotExist(err) {
130 // File missing is treated the same as a short file.
138 // Limit the read to within the expected bounds of this file.
139 if int64(len(b)) > fi.Length-off {
140 b = b[:fi.Length-off]
142 for off < fi.Length && len(b) != 0 {
143 n1, err1 := f.ReadAt(b, off)
155 // Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
156 func (fst fileTorrentImplIO) ReadAt(b []byte, off int64) (n int, err error) {
157 for _, fi := range fst.fts.info.UpvertedFiles() {
158 for off < fi.Length {
159 n1, err1 := fst.readFileAt(fi, b, off)
174 err = io.ErrUnexpectedEOF
184 func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) {
185 for _, fi := range fst.fts.info.UpvertedFiles() {
186 if off >= fi.Length {
191 if int64(n1) > fi.Length-off {
192 n1 = int(fi.Length - off)
194 name := fst.fts.fileInfoName(fi)
195 os.MkdirAll(filepath.Dir(name), 0777)
197 f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0666)
201 n1, err = f.WriteAt(p[:n1], off)
202 // TODO: On some systems, write errors can be delayed until the Close.
217 func (fts *fileTorrentImpl) fileInfoName(fi metainfo.FileInfo) string {
218 return filepath.Join(append([]string{fts.dir, fts.info.Name}, fi.Path...)...)