]> Sergey Matveev's repositories - btrtrc.git/blob - storage/file.go
Rework storage interfaces to make them simpler to implement
[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         _ "github.com/mattn/go-sqlite3"
10
11         "github.com/anacrolix/torrent/metainfo"
12 )
13
14 // File-based storage for torrents, that isn't yet bound to a particular
15 // torrent.
16 type fileStorage struct {
17         baseDir string
18 }
19
20 func NewFile(baseDir string) ClientImpl {
21         return &fileStorage{
22                 baseDir: baseDir,
23         }
24 }
25
26 func (fs *fileStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (TorrentImpl, error) {
27         return &fileTorrentStorage{
28                 fs,
29                 info,
30                 infoHash,
31                 pieceCompletionForDir(fs.baseDir),
32         }, nil
33 }
34
35 // File-based torrent storage, not yet bound to a Torrent.
36 type fileTorrentStorage struct {
37         fs         *fileStorage
38         info       *metainfo.Info
39         infoHash   metainfo.Hash
40         completion pieceCompletion
41 }
42
43 func (fts *fileTorrentStorage) Piece(p metainfo.Piece) PieceImpl {
44         // Create a view onto the file-based torrent storage.
45         _io := fileStorageTorrent{fts}
46         // Return the appropriate segments of this.
47         return &fileStoragePiece{
48                 fts,
49                 p,
50                 missinggo.NewSectionWriter(_io, p.Offset(), p.Length()),
51                 io.NewSectionReader(_io, p.Offset(), p.Length()),
52         }
53 }
54
55 func (fs *fileTorrentStorage) Close() error {
56         fs.completion.Close()
57         return nil
58 }
59
60 // Exposes file-based storage of a torrent, as one big ReadWriterAt.
61 type fileStorageTorrent struct {
62         fts *fileTorrentStorage
63 }
64
65 // Returns EOF on short or missing file.
66 func (fst *fileStorageTorrent) readFileAt(fi metainfo.FileInfo, b []byte, off int64) (n int, err error) {
67         f, err := os.Open(fst.fts.fileInfoName(fi))
68         if os.IsNotExist(err) {
69                 // File missing is treated the same as a short file.
70                 err = io.EOF
71                 return
72         }
73         if err != nil {
74                 return
75         }
76         defer f.Close()
77         // Limit the read to within the expected bounds of this file.
78         if int64(len(b)) > fi.Length-off {
79                 b = b[:fi.Length-off]
80         }
81         for off < fi.Length && len(b) != 0 {
82                 n1, err1 := f.ReadAt(b, off)
83                 b = b[n1:]
84                 n += n1
85                 off += int64(n1)
86                 if n1 == 0 {
87                         err = err1
88                         break
89                 }
90         }
91         return
92 }
93
94 // Only returns EOF at the end of the torrent. Premature EOF is ErrUnexpectedEOF.
95 func (fst fileStorageTorrent) ReadAt(b []byte, off int64) (n int, err error) {
96         for _, fi := range fst.fts.info.UpvertedFiles() {
97                 for off < fi.Length {
98                         n1, err1 := fst.readFileAt(fi, b, off)
99                         n += n1
100                         off += int64(n1)
101                         b = b[n1:]
102                         if len(b) == 0 {
103                                 // Got what we need.
104                                 return
105                         }
106                         if n1 != 0 {
107                                 // Made progress.
108                                 continue
109                         }
110                         err = err1
111                         if err == io.EOF {
112                                 // Lies.
113                                 err = io.ErrUnexpectedEOF
114                         }
115                         return
116                 }
117                 off -= fi.Length
118         }
119         err = io.EOF
120         return
121 }
122
123 func (fst fileStorageTorrent) WriteAt(p []byte, off int64) (n int, err error) {
124         for _, fi := range fst.fts.info.UpvertedFiles() {
125                 if off >= fi.Length {
126                         off -= fi.Length
127                         continue
128                 }
129                 n1 := len(p)
130                 if int64(n1) > fi.Length-off {
131                         n1 = int(fi.Length - off)
132                 }
133                 name := fst.fts.fileInfoName(fi)
134                 os.MkdirAll(filepath.Dir(name), 0770)
135                 var f *os.File
136                 f, err = os.OpenFile(name, os.O_WRONLY|os.O_CREATE, 0660)
137                 if err != nil {
138                         return
139                 }
140                 n1, err = f.WriteAt(p[:n1], off)
141                 f.Close()
142                 if err != nil {
143                         return
144                 }
145                 n += n1
146                 off = 0
147                 p = p[n1:]
148                 if len(p) == 0 {
149                         break
150                 }
151         }
152         return
153 }
154
155 func (fts *fileTorrentStorage) fileInfoName(fi metainfo.FileInfo) string {
156         return filepath.Join(append([]string{fts.fs.baseDir, fts.info.Name}, fi.Path...)...)
157 }