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