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