]> Sergey Matveev's repositories - btrtrc.git/blob - storage/file.go
Track completion known to implementation state
[btrtrc.git] / storage / file.go
1 package storage
2
3 import (
4         "io"
5         "os"
6         "path/filepath"
7
8         "github.com/anacrolix/missinggo"
9
10         "github.com/anacrolix/torrent/metainfo"
11 )
12
13 // File-based storage for torrents, that isn't yet bound to a particular
14 // torrent.
15 type fileClientImpl struct {
16         baseDir   string
17         pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string
18         pc        PieceCompletion
19 }
20
21 // The Default path maker just returns the current path
22 func defaultPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
23         return baseDir
24 }
25
26 func infoHashPathMaker(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string {
27         return filepath.Join(baseDir, infoHash.HexString())
28 }
29
30 // All Torrent data stored in this baseDir
31 func NewFile(baseDir string) ClientImpl {
32         return NewFileWithCompletion(baseDir, pieceCompletionForDir(baseDir))
33 }
34
35 func NewFileWithCompletion(baseDir string, completion PieceCompletion) ClientImpl {
36         return newFileWithCustomPathMakerAndCompletion(baseDir, nil, completion)
37 }
38
39 // All Torrent data stored in subdirectorys by infohash
40 func NewFileByInfoHash(baseDir string) ClientImpl {
41         return NewFileWithCustomPathMaker(baseDir, infoHashPathMaker)
42 }
43
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))
47 }
48
49 func newFileWithCustomPathMakerAndCompletion(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string, completion PieceCompletion) ClientImpl {
50         if pathMaker == nil {
51                 pathMaker = defaultPathMaker
52         }
53         return &fileClientImpl{
54                 baseDir:   baseDir,
55                 pathMaker: pathMaker,
56                 pc:        completion,
57         }
58 }
59
60 func (me *fileClientImpl) Close() error {
61         return me.pc.Close()
62 }
63
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)
67         if err != nil {
68                 return nil, err
69         }
70         return &fileTorrentImpl{
71                 dir,
72                 info,
73                 infoHash,
74                 fs.pc,
75         }, nil
76 }
77
78 type fileTorrentImpl struct {
79         dir        string
80         info       *metainfo.Info
81         infoHash   metainfo.Hash
82         completion PieceCompletion
83 }
84
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{
90                 fts,
91                 p,
92                 missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
93                 io.NewSectionReader(_io, p.Offset(), p.Length()),
94         }
95 }
96
97 func (fs *fileTorrentImpl) Close() error {
98         return nil
99 }
100
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() {
106                 if fi.Length != 0 {
107                         continue
108                 }
109                 name := filepath.Join(append([]string{dir, info.Name}, fi.Path...)...)
110                 os.MkdirAll(filepath.Dir(name), 0750)
111                 var f io.Closer
112                 f, err = os.Create(name)
113                 if err != nil {
114                         break
115                 }
116                 f.Close()
117         }
118         return
119 }
120
121 // Exposes file-based storage of a torrent, as one big ReadWriterAt.
122 type fileTorrentImplIO struct {
123         fts *fileTorrentImpl
124 }
125
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.
131                 err = io.EOF
132                 return
133         }
134         if err != nil {
135                 return
136         }
137         defer f.Close()
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]
141         }
142         for off < fi.Length && len(b) != 0 {
143                 n1, err1 := f.ReadAt(b, off)
144                 b = b[n1:]
145                 n += n1
146                 off += int64(n1)
147                 if n1 == 0 {
148                         err = err1
149                         break
150                 }
151         }
152         return
153 }
154
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)
160                         n += n1
161                         off += int64(n1)
162                         b = b[n1:]
163                         if len(b) == 0 {
164                                 // Got what we need.
165                                 return
166                         }
167                         if n1 != 0 {
168                                 // Made progress.
169                                 continue
170                         }
171                         err = err1
172                         if err == io.EOF {
173                                 // Lies.
174                                 err = io.ErrUnexpectedEOF
175                         }
176                         return
177                 }
178                 off -= fi.Length
179         }
180         err = io.EOF
181         return
182 }
183
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 {
187                         off -= fi.Length
188                         continue
189                 }
190                 n1 := len(p)
191                 if int64(n1) > fi.Length-off {
192                         n1 = int(fi.Length - off)
193                 }
194                 name := fst.fts.fileInfoName(fi)
195                 os.MkdirAll(filepath.Dir(name), 0770)
196                 var f *os.File
197                 f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0660)
198                 if err != nil {
199                         return
200                 }
201                 n1, err = f.WriteAt(p[:n1], off)
202                 // TODO: On some systems, write errors can be delayed until the Close.
203                 f.Close()
204                 if err != nil {
205                         return
206                 }
207                 n += n1
208                 off = 0
209                 p = p[n1:]
210                 if len(p) == 0 {
211                         break
212                 }
213         }
214         return
215 }
216
217 func (fts *fileTorrentImpl) fileInfoName(fi metainfo.FileInfo) string {
218         return filepath.Join(append([]string{fts.dir, fts.info.Name}, fi.Path...)...)
219 }