]> Sergey Matveev's repositories - btrtrc.git/blob - storage/mmap.go
Merge branch 'master' into dht-cleanup
[btrtrc.git] / storage / mmap.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/edsrzf/mmap-go"
11
12         "github.com/anacrolix/torrent/metainfo"
13         "github.com/anacrolix/torrent/mmap_span"
14 )
15
16 type mmapStorage struct {
17         baseDir string
18         pc      PieceCompletion
19 }
20
21 func NewMMap(baseDir string) ClientImpl {
22         return NewMMapWithCompletion(baseDir, pieceCompletionForDir(baseDir))
23 }
24
25 func NewMMapWithCompletion(baseDir string, completion PieceCompletion) ClientImpl {
26         return &mmapStorage{
27                 baseDir: baseDir,
28                 pc:      completion,
29         }
30 }
31
32 func (s *mmapStorage) OpenTorrent(info *metainfo.Info, infoHash metainfo.Hash) (t TorrentImpl, err error) {
33         span, err := mMapTorrent(info, s.baseDir)
34         t = &mmapTorrentStorage{
35                 infoHash: infoHash,
36                 span:     span,
37                 pc:       s.pc,
38         }
39         return
40 }
41
42 func (s *mmapStorage) Close() error {
43         return s.pc.Close()
44 }
45
46 type mmapTorrentStorage struct {
47         infoHash metainfo.Hash
48         span     mmap_span.MMapSpan
49         pc       PieceCompletion
50 }
51
52 func (ts *mmapTorrentStorage) Piece(p metainfo.Piece) PieceImpl {
53         return mmapStoragePiece{
54                 pc:       ts.pc,
55                 p:        p,
56                 ih:       ts.infoHash,
57                 ReaderAt: io.NewSectionReader(ts.span, p.Offset(), p.Length()),
58                 WriterAt: missinggo.NewSectionWriter(ts.span, p.Offset(), p.Length()),
59         }
60 }
61
62 func (ts *mmapTorrentStorage) Close() error {
63         ts.pc.Close()
64         return ts.span.Close()
65 }
66
67 type mmapStoragePiece struct {
68         pc PieceCompletion
69         p  metainfo.Piece
70         ih metainfo.Hash
71         io.ReaderAt
72         io.WriterAt
73 }
74
75 func (me mmapStoragePiece) pieceKey() metainfo.PieceKey {
76         return metainfo.PieceKey{me.ih, me.p.Index()}
77 }
78
79 func (sp mmapStoragePiece) GetIsComplete() (ret bool) {
80         ret, _ = sp.pc.Get(sp.pieceKey())
81         return
82 }
83
84 func (sp mmapStoragePiece) MarkComplete() error {
85         sp.pc.Set(sp.pieceKey(), true)
86         return nil
87 }
88
89 func (sp mmapStoragePiece) MarkNotComplete() error {
90         sp.pc.Set(sp.pieceKey(), false)
91         return nil
92 }
93
94 func mMapTorrent(md *metainfo.Info, location string) (mms mmap_span.MMapSpan, err error) {
95         defer func() {
96                 if err != nil {
97                         mms.Close()
98                 }
99         }()
100         for _, miFile := range md.UpvertedFiles() {
101                 fileName := filepath.Join(append([]string{location, md.Name}, miFile.Path...)...)
102                 err = os.MkdirAll(filepath.Dir(fileName), 0777)
103                 if err != nil {
104                         err = fmt.Errorf("error creating data directory %q: %s", filepath.Dir(fileName), err)
105                         return
106                 }
107                 var file *os.File
108                 file, err = os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0666)
109                 if err != nil {
110                         return
111                 }
112                 func() {
113                         defer file.Close()
114                         var fi os.FileInfo
115                         fi, err = file.Stat()
116                         if err != nil {
117                                 return
118                         }
119                         if fi.Size() < miFile.Length {
120                                 err = file.Truncate(miFile.Length)
121                                 if err != nil {
122                                         return
123                                 }
124                         }
125                         if miFile.Length == 0 {
126                                 // Can't mmap() regions with length 0.
127                                 return
128                         }
129                         var mMap mmap.MMap
130                         mMap, err = mmap.MapRegion(file,
131                                 int(miFile.Length), // Probably not great on <64 bit systems.
132                                 mmap.RDWR, 0, 0)
133                         if err != nil {
134                                 err = fmt.Errorf("error mapping file %q, length %d: %s", file.Name(), miFile.Length, err)
135                                 return
136                         }
137                         if int64(len(mMap)) != miFile.Length {
138                                 panic("mmap has wrong length")
139                         }
140                         mms.Append(mMap)
141                 }()
142                 if err != nil {
143                         return
144                 }
145         }
146         return
147 }