]> Sergey Matveev's repositories - btrtrc.git/blob - storage/file.go
Fix nil context being passed to function
[btrtrc.git] / storage / file.go
1 package storage
2
3 import (
4         "fmt"
5         "io"
6         "os"
7         "path/filepath"
8
9         "github.com/anacrolix/missinggo"
10         "github.com/anacrolix/torrent/common"
11         "github.com/anacrolix/torrent/segments"
12
13         "github.com/anacrolix/torrent/metainfo"
14 )
15
16 // File-based storage for torrents, that isn't yet bound to a particular
17 // torrent.
18 type fileClientImpl struct {
19         baseDir   string
20         pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string
21         pc        PieceCompletion
22 }
23
24 // The Default path maker just returns the current path
25 func defaultPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
26         return baseDir
27 }
28
29 func infoHashPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
30         return filepath.Join(baseDir, infoHash.HexString())
31 }
32
33 // All Torrent data stored in this baseDir
34 func NewFile(baseDir string) ClientImplCloser {
35         return NewFileWithCompletion(baseDir, pieceCompletionForDir(baseDir))
36 }
37
38 func NewFileWithCompletion(baseDir string, completion PieceCompletion) *fileClientImpl {
39         return NewFileWithCustomPathMakerAndCompletion(baseDir, nil, completion)
40 }
41
42 // File storage with data partitioned by infohash.
43 func NewFileByInfoHash(baseDir string) ClientImplCloser {
44         return NewFileWithCustomPathMaker(baseDir, infoHashPathMaker)
45 }
46
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
49 // info.Name).
50 func NewFileWithCustomPathMaker(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string) ClientImplCloser {
51         return NewFileWithCustomPathMakerAndCompletion(baseDir, pathMaker, pieceCompletionForDir(baseDir))
52 }
53
54 // Allows passing custom PieceCompletion 
55 func NewFileWithCustomPathMakerAndCompletion(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string, completion PieceCompletion) *fileClientImpl {
56         if pathMaker == nil {
57                 pathMaker = defaultPathMaker
58         }
59         return &fileClientImpl{
60                 baseDir:   baseDir,
61                 pathMaker: pathMaker,
62                 pc:        completion,
63         }
64 }
65
66 func (me *fileClientImpl) Close() error {
67         return me.pc.Close()
68 }
69
70 func (fs *fileClientImpl) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
71         dir := fs.pathMaker(fs.baseDir, info, infoHash)
72         upvertedFiles := info.UpvertedFiles()
73         files := make([]file, 0, len(upvertedFiles))
74         for i, fileInfo := range upvertedFiles {
75                 s, err := ToSafeFilePath(append([]string{info.Name}, fileInfo.Path...)...)
76                 if err != nil {
77                         return nil, fmt.Errorf("file %v has unsafe path %q: %w", i, fileInfo.Path, err)
78                 }
79                 f := file{
80                         path:   filepath.Join(dir, s),
81                         length: fileInfo.Length,
82                 }
83                 if f.length == 0 {
84                         err = CreateNativeZeroLengthFile(f.path)
85                         if err != nil {
86                                 return nil, fmt.Errorf("creating zero length file: %w", err)
87                         }
88                 }
89                 files = append(files, f)
90         }
91         return &fileTorrentImpl{
92                 files,
93                 segments.NewIndex(common.LengthIterFromUpvertedFiles(upvertedFiles)),
94                 infoHash,
95                 fs.pc,
96         }, nil
97 }
98
99 type file struct {
100         // The safe, OS-local file path.
101         path   string
102         length int64
103 }
104
105 type fileTorrentImpl struct {
106         files          []file
107         segmentLocater segments.Index
108         infoHash       metainfo.Hash
109         completion     PieceCompletion
110 }
111
112 func (fts *fileTorrentImpl) Piece(p metainfo.Piece) PieceImpl {
113         // Create a view onto the file-based torrent storage.
114         _io := fileTorrentImplIO{fts}
115         // Return the appropriate segments of this.
116         return &filePieceImpl{
117                 fts,
118                 p,
119                 missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
120                 io.NewSectionReader(_io, p.Offset(), p.Length()),
121         }
122 }
123
124 func (fs *fileTorrentImpl) Close() error {
125         return nil
126 }
127
128 // A helper to create zero-length files which won't appear for file-orientated storage since no
129 // writes will ever occur to them (no torrent data is associated with a zero-length file). The
130 // caller should make sure the file name provided is safe/sanitized.
131 func CreateNativeZeroLengthFile(name string) error {
132         os.MkdirAll(filepath.Dir(name), 0777)
133         var f io.Closer
134         f, err := os.Create(name)
135         if err != nil {
136                 return err
137         }
138         return f.Close()
139 }
140
141 // Exposes file-based storage of a torrent, as one big ReadWriterAt.
142 type fileTorrentImplIO struct {
143         fts *fileTorrentImpl
144 }
145
146 // Returns EOF on short or missing file.
147 func (fst *fileTorrentImplIO) readFileAt(file file, b []byte, off int64) (n int, err error) {
148         f, err := os.Open(file.path)
149         if os.IsNotExist(err) {
150                 // File missing is treated the same as a short file.
151                 err = io.EOF
152                 return
153         }
154         if err != nil {
155                 return
156         }
157         defer f.Close()
158         // Limit the read to within the expected bounds of this file.
159         if int64(len(b)) > file.length-off {
160                 b = b[:file.length-off]
161         }
162         for off < file.length && len(b) != 0 {
163                 n1, err1 := f.ReadAt(b, off)
164                 b = b[n1:]
165                 n += n1
166                 off += int64(n1)
167                 if n1 == 0 {
168                         err = err1
169                         break
170                 }
171         }
172         return
173 }
174
175 // Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
176 func (fst fileTorrentImplIO) ReadAt(b []byte, off int64) (n int, err error) {
177         fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(b))}, func(i int, e segments.Extent) bool {
178                 n1, err1 := fst.readFileAt(fst.fts.files[i], b[:e.Length], e.Start)
179                 n += n1
180                 b = b[n1:]
181                 err = err1
182                 return err == nil // && int64(n1) == e.Length
183         })
184         if len(b) != 0 && err == nil {
185                 err = io.EOF
186         }
187         return
188 }
189
190 func (fst fileTorrentImplIO) WriteAt(p []byte, off int64) (n int, err error) {
191         //log.Printf("write at %v: %v bytes", off, len(p))
192         fst.fts.segmentLocater.Locate(segments.Extent{off, int64(len(p))}, func(i int, e segments.Extent) bool {
193                 name := fst.fts.files[i].path
194                 os.MkdirAll(filepath.Dir(name), 0777)
195                 var f *os.File
196                 f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0666)
197                 if err != nil {
198                         return false
199                 }
200                 var n1 int
201                 n1, err = f.WriteAt(p[:e.Length], e.Start)
202                 //log.Printf("%v %v wrote %v: %v", i, e, n1, err)
203                 closeErr := f.Close()
204                 n += n1
205                 p = p[n1:]
206                 if err == nil {
207                         err = closeErr
208                 }
209                 if err == nil && int64(n1) != e.Length {
210                         err = io.ErrShortWrite
211                 }
212                 return err == nil
213         })
214         return
215 }