Merge branch '10557-setup-cleanup'
[arvados.git] / sdk / go / arvados / collection_fs.go
1 package arvados
2
3 import (
4         "io"
5         "net/http"
6         "os"
7         "path"
8         "strings"
9         "time"
10
11         "git.curoverse.com/arvados.git/sdk/go/manifest"
12 )
13
14 type File interface {
15         io.Reader
16         io.Closer
17         io.Seeker
18         Size() int64
19 }
20
21 type keepClient interface {
22         ManifestFileReader(manifest.Manifest, string) (File, error)
23 }
24
25 type collectionFile struct {
26         File
27         collection *Collection
28         name       string
29         size       int64
30 }
31
32 func (cf *collectionFile) Size() int64 {
33         return cf.size
34 }
35
36 func (cf *collectionFile) Readdir(count int) ([]os.FileInfo, error) {
37         return nil, io.EOF
38 }
39
40 func (cf *collectionFile) Stat() (os.FileInfo, error) {
41         return collectionDirent{
42                 collection: cf.collection,
43                 name:       cf.name,
44                 size:       cf.size,
45                 isDir:      false,
46         }, nil
47 }
48
49 type collectionDir struct {
50         collection *Collection
51         stream     string
52         dirents    []os.FileInfo
53 }
54
55 // Readdir implements os.File.
56 func (cd *collectionDir) Readdir(count int) ([]os.FileInfo, error) {
57         ret := cd.dirents
58         if count <= 0 {
59                 cd.dirents = nil
60                 return ret, nil
61         } else if len(ret) == 0 {
62                 return nil, io.EOF
63         }
64         var err error
65         if count >= len(ret) {
66                 count = len(ret)
67                 err = io.EOF
68         }
69         cd.dirents = cd.dirents[count:]
70         return ret[:count], err
71 }
72
73 // Stat implements os.File.
74 func (cd *collectionDir) Stat() (os.FileInfo, error) {
75         return collectionDirent{
76                 collection: cd.collection,
77                 name:       path.Base(cd.stream),
78                 isDir:      true,
79                 size:       int64(len(cd.dirents)),
80         }, nil
81 }
82
83 // Close implements os.File.
84 func (cd *collectionDir) Close() error {
85         return nil
86 }
87
88 // Read implements os.File.
89 func (cd *collectionDir) Read([]byte) (int, error) {
90         return 0, nil
91 }
92
93 // Seek implements os.File.
94 func (cd *collectionDir) Seek(int64, int) (int64, error) {
95         return 0, nil
96 }
97
98 // collectionDirent implements os.FileInfo.
99 type collectionDirent struct {
100         collection *Collection
101         name       string
102         isDir      bool
103         mode       os.FileMode
104         size       int64
105 }
106
107 // Name implements os.FileInfo.
108 func (e collectionDirent) Name() string {
109         return e.name
110 }
111
112 // ModTime implements os.FileInfo.
113 func (e collectionDirent) ModTime() time.Time {
114         if e.collection.ModifiedAt == nil {
115                 return time.Now()
116         }
117         return *e.collection.ModifiedAt
118 }
119
120 // Mode implements os.FileInfo.
121 func (e collectionDirent) Mode() os.FileMode {
122         if e.isDir {
123                 return 0555
124         } else {
125                 return 0444
126         }
127 }
128
129 // IsDir implements os.FileInfo.
130 func (e collectionDirent) IsDir() bool {
131         return e.isDir
132 }
133
134 // Size implements os.FileInfo.
135 func (e collectionDirent) Size() int64 {
136         return e.size
137 }
138
139 // Sys implements os.FileInfo.
140 func (e collectionDirent) Sys() interface{} {
141         return nil
142 }
143
144 // collectionFS implements http.FileSystem.
145 type collectionFS struct {
146         collection *Collection
147         client     *Client
148         kc         keepClient
149 }
150
151 // FileSystem returns an http.FileSystem for the collection.
152 func (c *Collection) FileSystem(client *Client, kc keepClient) http.FileSystem {
153         return &collectionFS{
154                 collection: c,
155                 client:     client,
156                 kc:         kc,
157         }
158 }
159
160 func (c *collectionFS) Open(name string) (http.File, error) {
161         // Ensure name looks the way it does in a manifest.
162         name = path.Clean("/" + name)
163         if name == "/" || name == "./" {
164                 name = "."
165         } else if strings.HasPrefix(name, "/") {
166                 name = "." + name
167         }
168
169         m := manifest.Manifest{Text: c.collection.ManifestText}
170
171         filesizes := c.fileSizes()
172
173         // Return a file if it exists.
174         if size, ok := filesizes[name]; ok {
175                 reader, err := c.kc.ManifestFileReader(m, name)
176                 if err != nil {
177                         return nil, err
178                 }
179                 return &collectionFile{
180                         File:       reader,
181                         collection: c.collection,
182                         name:       path.Base(name),
183                         size:       size,
184                 }, nil
185         }
186
187         // Return a directory if it's the root dir or there are file
188         // entries below it.
189         children := map[string]collectionDirent{}
190         for fnm, size := range filesizes {
191                 if !strings.HasPrefix(fnm, name+"/") {
192                         continue
193                 }
194                 isDir := false
195                 ent := fnm[len(name)+1:]
196                 if i := strings.Index(ent, "/"); i >= 0 {
197                         ent = ent[:i]
198                         isDir = true
199                 }
200                 e := children[ent]
201                 e.collection = c.collection
202                 e.isDir = isDir
203                 e.name = ent
204                 e.size = size
205                 children[ent] = e
206         }
207         if len(children) == 0 && name != "." {
208                 return nil, os.ErrNotExist
209         }
210         dirents := make([]os.FileInfo, 0, len(children))
211         for _, ent := range children {
212                 dirents = append(dirents, ent)
213         }
214         return &collectionDir{
215                 collection: c.collection,
216                 stream:     name,
217                 dirents:    dirents,
218         }, nil
219 }
220
221 // fileSizes returns a map of files that can be opened. Each key
222 // starts with "./".
223 func (c *collectionFS) fileSizes() map[string]int64 {
224         var sizes map[string]int64
225         m := manifest.Manifest{Text: c.collection.ManifestText}
226         for ms := range m.StreamIter() {
227                 for _, fss := range ms.FileStreamSegments {
228                         if sizes == nil {
229                                 sizes = map[string]int64{}
230                         }
231                         sizes[ms.StreamName+"/"+fss.Name] += int64(fss.SegLen)
232                 }
233         }
234         return sizes
235 }