1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
16 "git.curoverse.com/arvados.git/sdk/go/manifest"
26 type keepClient interface {
27 ManifestFileReader(manifest.Manifest, string) (File, error)
30 type collectionFile struct {
32 collection *Collection
37 func (cf *collectionFile) Size() int64 {
41 func (cf *collectionFile) Readdir(count int) ([]os.FileInfo, error) {
45 func (cf *collectionFile) Stat() (os.FileInfo, error) {
46 return collectionDirent{
47 collection: cf.collection,
54 type collectionDir struct {
55 collection *Collection
60 // Readdir implements os.File.
61 func (cd *collectionDir) Readdir(count int) ([]os.FileInfo, error) {
66 } else if len(ret) == 0 {
70 if count >= len(ret) {
74 cd.dirents = cd.dirents[count:]
75 return ret[:count], err
78 // Stat implements os.File.
79 func (cd *collectionDir) Stat() (os.FileInfo, error) {
80 return collectionDirent{
81 collection: cd.collection,
82 name: path.Base(cd.stream),
84 size: int64(len(cd.dirents)),
88 // Close implements os.File.
89 func (cd *collectionDir) Close() error {
93 // Read implements os.File.
94 func (cd *collectionDir) Read([]byte) (int, error) {
98 // Seek implements os.File.
99 func (cd *collectionDir) Seek(int64, int) (int64, error) {
103 // collectionDirent implements os.FileInfo.
104 type collectionDirent struct {
105 collection *Collection
112 // Name implements os.FileInfo.
113 func (e collectionDirent) Name() string {
117 // ModTime implements os.FileInfo.
118 func (e collectionDirent) ModTime() time.Time {
119 if e.collection.ModifiedAt == nil {
122 return *e.collection.ModifiedAt
125 // Mode implements os.FileInfo.
126 func (e collectionDirent) Mode() os.FileMode {
134 // IsDir implements os.FileInfo.
135 func (e collectionDirent) IsDir() bool {
139 // Size implements os.FileInfo.
140 func (e collectionDirent) Size() int64 {
144 // Sys implements os.FileInfo.
145 func (e collectionDirent) Sys() interface{} {
149 // A CollectionFileSystem is an http.Filesystem with an added Stat() method.
150 type CollectionFileSystem interface {
152 Stat(name string) (os.FileInfo, error)
155 // collectionFS implements CollectionFileSystem.
156 type collectionFS struct {
157 collection *Collection
160 sizes map[string]int64
164 // FileSystem returns a CollectionFileSystem for the collection.
165 func (c *Collection) FileSystem(client *Client, kc keepClient) CollectionFileSystem {
166 return &collectionFS{
173 func (c *collectionFS) Stat(name string) (os.FileInfo, error) {
174 name = canonicalName(name)
176 return collectionDirent{
177 collection: c.collection,
182 if size, ok := c.fileSizes()[name]; ok {
183 return collectionDirent{
184 collection: c.collection,
185 name: path.Base(name),
190 for fnm := range c.fileSizes() {
191 if !strings.HasPrefix(fnm, name+"/") {
194 return collectionDirent{
195 collection: c.collection,
196 name: path.Base(name),
200 return nil, os.ErrNotExist
203 func (c *collectionFS) Open(name string) (http.File, error) {
204 // Ensure name looks the way it does in a manifest.
205 name = canonicalName(name)
207 m := manifest.Manifest{Text: c.collection.ManifestText}
209 // Return a file if it exists.
210 if size, ok := c.fileSizes()[name]; ok {
211 reader, err := c.kc.ManifestFileReader(m, name)
215 return &collectionFile{
217 collection: c.collection,
218 name: path.Base(name),
223 // Return a directory if it's the root dir or there are file
225 children := map[string]collectionDirent{}
226 for fnm, size := range c.fileSizes() {
227 if !strings.HasPrefix(fnm, name+"/") {
231 ent := fnm[len(name)+1:]
232 if i := strings.Index(ent, "/"); i >= 0 {
237 e.collection = c.collection
243 if len(children) == 0 && name != "." {
244 return nil, os.ErrNotExist
246 dirents := make([]os.FileInfo, 0, len(children))
247 for _, ent := range children {
248 dirents = append(dirents, ent)
250 return &collectionDir{
251 collection: c.collection,
257 // fileSizes returns a map of files that can be opened. Each key
259 func (c *collectionFS) fileSizes() map[string]int64 {
260 c.sizesOnce.Do(func() {
261 c.sizes = map[string]int64{}
262 m := manifest.Manifest{Text: c.collection.ManifestText}
263 for ms := range m.StreamIter() {
264 for _, fss := range ms.FileStreamSegments {
265 c.sizes[ms.StreamName+"/"+fss.Name] += int64(fss.SegLen)
272 func canonicalName(name string) string {
273 name = path.Clean("/" + name)
274 if name == "/" || name == "./" {
276 } else if strings.HasPrefix(name, "/") {