]> Sergey Matveev's repositories - btrtrc.git/blob - storage/file.go
Got file storage working with segment index
[btrtrc.git] / storage / file.go
1 package storage
2
3 import (
4         "io"
5         "log"
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) ClientImpl {
44         return NewFileWithCustomPathMaker(baseDir, infoHashPathMaker)
45 }
46
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))
50 }
51
52 func newFileWithCustomPathMakerAndCompletion(baseDir string, pathMaker func(baseDir string, info *metainfo.Info, infoHash metainfo.Hash) string, completion PieceCompletion) *fileClientImpl {
53         if pathMaker == nil {
54                 pathMaker = defaultPathMaker
55         }
56         return &fileClientImpl{
57                 baseDir:   baseDir,
58                 pathMaker: pathMaker,
59                 pc:        completion,
60         }
61 }
62
63 func (me *fileClientImpl) Close() error {
64         return me.pc.Close()
65 }
66
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)
70         if err != nil {
71                 return nil, err
72         }
73         upvertedFiles := info.UpvertedFiles()
74         return &fileTorrentImpl{
75                 dir,
76                 info.Name,
77                 upvertedFiles,
78                 segments.NewIndex(common.LengthIterFromUpvertedFiles(upvertedFiles)),
79                 infoHash,
80                 fs.pc,
81         }, nil
82 }
83
84 type fileTorrentImpl struct {
85         dir            string
86         infoName       string
87         upvertedFiles  []metainfo.FileInfo
88         segmentLocater segments.Index
89         infoHash       metainfo.Hash
90         completion     PieceCompletion
91 }
92
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{
98                 fts,
99                 p,
100                 missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
101                 io.NewSectionReader(_io, p.Offset(), p.Length()),
102         }
103 }
104
105 func (fs *fileTorrentImpl) Close() error {
106         return nil
107 }
108
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() {
114                 if fi.Length != 0 {
115                         continue
116                 }
117                 name := filepath.Join(append([]string{dir, info.Name}, fi.Path...)...)
118                 os.MkdirAll(filepath.Dir(name), 0777)
119                 var f io.Closer
120                 f, err = os.Create(name)
121                 if err != nil {
122                         break
123                 }
124                 f.Close()
125         }
126         return
127 }
128
129 // Exposes file-based storage of a torrent, as one big ReadWriterAt.
130 type fileTorrentImplIO struct {
131         fts *fileTorrentImpl
132 }
133
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.
139                 err = io.EOF
140                 return
141         }
142         if err != nil {
143                 return
144         }
145         defer f.Close()
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]
149         }
150         for off < fi.Length && len(b) != 0 {
151                 n1, err1 := f.ReadAt(b, off)
152                 b = b[n1:]
153                 n += n1
154                 off += int64(n1)
155                 if n1 == 0 {
156                         err = err1
157                         break
158                 }
159         }
160         return
161 }
162
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)
167                 n += n1
168                 b = b[n1:]
169                 err = err1
170                 return err == nil // && int64(n1) == e.Length
171         })
172         if len(b) != 0 && err == nil {
173                 err = io.EOF
174         }
175         return
176 }
177
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)
183                 var f *os.File
184                 f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0666)
185                 if err != nil {
186                         return false
187                 }
188                 var n1 int
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()
192                 n += n1
193                 p = p[n1:]
194                 if err == nil {
195                         err = closeErr
196                 }
197                 //if err == nil && int64(n1) != e.Length {
198                 //      err = io.ErrShortWrite
199                 //}
200                 return err == nil
201         })
202         return
203 }
204
205 func (fts *fileTorrentImpl) fileInfoName(fi metainfo.FileInfo) string {
206         return filepath.Join(append([]string{fts.dir, fts.infoName}, fi.Path...)...)
207 }