]> Sergey Matveev's repositories - btrtrc.git/blob - storage/mmap.go
storage.MMapTorrent should not be exported
[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 }
19
20 func NewMMap(baseDir string) Client {
21         return &mmapStorage{
22                 baseDir: baseDir,
23         }
24 }
25
26 func (s *mmapStorage) OpenTorrent(info *metainfo.InfoEx) (t Torrent, err error) {
27         span, err := mMapTorrent(&info.Info, s.baseDir)
28         t = &mmapTorrentStorage{
29                 span: span,
30         }
31         return
32 }
33
34 type mmapTorrentStorage struct {
35         span      mmap_span.MMapSpan
36         completed map[metainfo.Hash]bool
37 }
38
39 func (ts *mmapTorrentStorage) Piece(p metainfo.Piece) Piece {
40         return mmapStoragePiece{
41                 storage:  ts,
42                 p:        p,
43                 ReaderAt: io.NewSectionReader(ts.span, p.Offset(), p.Length()),
44                 WriterAt: missinggo.NewSectionWriter(ts.span, p.Offset(), p.Length()),
45         }
46 }
47
48 func (ts *mmapTorrentStorage) Close() error {
49         ts.span.Close()
50         return nil
51 }
52
53 type mmapStoragePiece struct {
54         storage *mmapTorrentStorage
55         p       metainfo.Piece
56         io.ReaderAt
57         io.WriterAt
58 }
59
60 func (sp mmapStoragePiece) GetIsComplete() bool {
61         return sp.storage.completed[sp.p.Hash()]
62 }
63
64 func (sp mmapStoragePiece) MarkComplete() error {
65         if sp.storage.completed == nil {
66                 sp.storage.completed = make(map[metainfo.Hash]bool)
67         }
68         sp.storage.completed[sp.p.Hash()] = true
69         return nil
70 }
71
72 func mMapTorrent(md *metainfo.Info, location string) (mms mmap_span.MMapSpan, err error) {
73         defer func() {
74                 if err != nil {
75                         mms.Close()
76                 }
77         }()
78         for _, miFile := range md.UpvertedFiles() {
79                 fileName := filepath.Join(append([]string{location, md.Name}, miFile.Path...)...)
80                 err = os.MkdirAll(filepath.Dir(fileName), 0777)
81                 if err != nil {
82                         err = fmt.Errorf("error creating data directory %q: %s", filepath.Dir(fileName), err)
83                         return
84                 }
85                 var file *os.File
86                 file, err = os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0666)
87                 if err != nil {
88                         return
89                 }
90                 func() {
91                         defer file.Close()
92                         var fi os.FileInfo
93                         fi, err = file.Stat()
94                         if err != nil {
95                                 return
96                         }
97                         if fi.Size() < miFile.Length {
98                                 err = file.Truncate(miFile.Length)
99                                 if err != nil {
100                                         return
101                                 }
102                         }
103                         if miFile.Length == 0 {
104                                 // Can't mmap() regions with length 0.
105                                 return
106                         }
107                         var mMap mmap.MMap
108                         mMap, err = mmap.MapRegion(file,
109                                 int(miFile.Length), // Probably not great on <64 bit systems.
110                                 mmap.RDWR, 0, 0)
111                         if err != nil {
112                                 err = fmt.Errorf("error mapping file %q, length %d: %s", file.Name(), miFile.Length, err)
113                                 return
114                         }
115                         if int64(len(mMap)) != miFile.Length {
116                                 panic("mmap has wrong length")
117                         }
118                         mms.Append(mMap)
119                 }()
120                 if err != nil {
121                         return
122                 }
123         }
124         return
125 }