]> Sergey Matveev's repositories - btrtrc.git/blob - storage/mmap.go
Fix issue #95: Closing torrent storage also closed client storage for some storage...
[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() bool {
62         return sp.pc.Get(sp.p)
63 }
64
65 func (sp mmapStoragePiece) MarkComplete() error {
66         sp.pc.Set(sp.p, true)
67         return nil
68 }
69
70 func mMapTorrent(md *metainfo.Info, location string) (mms mmap_span.MMapSpan, err error) {
71         defer func() {
72                 if err != nil {
73                         mms.Close()
74                 }
75         }()
76         for _, miFile := range md.UpvertedFiles() {
77                 fileName := filepath.Join(append([]string{location, md.Name}, miFile.Path...)...)
78                 err = os.MkdirAll(filepath.Dir(fileName), 0777)
79                 if err != nil {
80                         err = fmt.Errorf("error creating data directory %q: %s", filepath.Dir(fileName), err)
81                         return
82                 }
83                 var file *os.File
84                 file, err = os.OpenFile(fileName, os.O_CREATE|os.O_RDWR, 0666)
85                 if err != nil {
86                         return
87                 }
88                 func() {
89                         defer file.Close()
90                         var fi os.FileInfo
91                         fi, err = file.Stat()
92                         if err != nil {
93                                 return
94                         }
95                         if fi.Size() < miFile.Length {
96                                 err = file.Truncate(miFile.Length)
97                                 if err != nil {
98                                         return
99                                 }
100                         }
101                         if miFile.Length == 0 {
102                                 // Can't mmap() regions with length 0.
103                                 return
104                         }
105                         var mMap mmap.MMap
106                         mMap, err = mmap.MapRegion(file,
107                                 int(miFile.Length), // Probably not great on <64 bit systems.
108                                 mmap.RDWR, 0, 0)
109                         if err != nil {
110                                 err = fmt.Errorf("error mapping file %q, length %d: %s", file.Name(), miFile.Length, err)
111                                 return
112                         }
113                         if int64(len(mMap)) != miFile.Length {
114                                 panic("mmap has wrong length")
115                         }
116                         mms.Append(mMap)
117                 }()
118                 if err != nil {
119                         return
120                 }
121         }
122         return
123 }