13111: Propagate errors in Child().
[arvados.git] / sdk / go / arvados / fs_site.go
1 // Copyright (C) The Arvados Authors. All rights reserved.
2 //
3 // SPDX-License-Identifier: Apache-2.0
4
5 package arvados
6
7 import (
8         "os"
9         "time"
10 )
11
12 type siteFileSystem struct {
13         fileSystem
14 }
15
16 // SiteFileSystem returns a FileSystem that maps collections and other
17 // Arvados objects onto a filesystem layout.
18 //
19 // This is experimental: the filesystem layout is not stable, and
20 // there are significant known bugs and shortcomings. For example,
21 // writes are not persisted until Sync() is called.
22 func (c *Client) SiteFileSystem(kc keepClient) FileSystem {
23         root := &vdirnode{}
24         fs := &siteFileSystem{
25                 fileSystem: fileSystem{
26                         fsBackend: keepBackend{apiClient: c, keepClient: kc},
27                         root:      root,
28                 },
29         }
30         root.inode = &treenode{
31                 fs:     fs,
32                 parent: root,
33                 fileinfo: fileinfo{
34                         name:    "/",
35                         mode:    os.ModeDir | 0755,
36                         modTime: time.Now(),
37                 },
38                 inodes: make(map[string]inode),
39         }
40         root.inode.Child("by_id", func(inode) (inode, error) {
41                 return &vdirnode{
42                         inode: &treenode{
43                                 fs:     fs,
44                                 parent: fs.root,
45                                 inodes: make(map[string]inode),
46                                 fileinfo: fileinfo{
47                                         name:    "by_id",
48                                         modTime: time.Now(),
49                                         mode:    0755 | os.ModeDir,
50                                 },
51                         },
52                         create: fs.mountCollection,
53                 }, nil
54         })
55         root.inode.Child("home", func(inode) (inode, error) {
56                 return fs.newProjectNode(fs.root, "home", ""), nil
57         })
58         return fs
59 }
60
61 func (fs *siteFileSystem) newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
62         return nil, ErrInvalidOperation
63 }
64
65 func (fs *siteFileSystem) mountCollection(parent inode, id string) inode {
66         var coll Collection
67         err := fs.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+id, nil, nil)
68         if err != nil {
69                 return nil
70         }
71         cfs, err := coll.FileSystem(fs, fs)
72         if err != nil {
73                 return nil
74         }
75         root := cfs.rootnode()
76         root.SetParent(parent, id)
77         return root
78 }
79
80 func (fs *siteFileSystem) newProjectNode(root inode, name, uuid string) inode {
81         return &projectnode{
82                 uuid: uuid,
83                 inode: &treenode{
84                         fs:     fs,
85                         parent: root,
86                         inodes: make(map[string]inode),
87                         fileinfo: fileinfo{
88                                 name:    name,
89                                 modTime: time.Now(),
90                                 mode:    0755 | os.ModeDir,
91                         },
92                 },
93         }
94 }
95
96 // vdirnode wraps an inode by ignoring any requests to add/replace
97 // children, and calling a create() func when a non-existing child is
98 // looked up.
99 //
100 // create() can return either a new node, which will be added to the
101 // treenode, or nil for ENOENT.
102 type vdirnode struct {
103         inode
104         create func(parent inode, name string) inode
105 }
106
107 func (vn *vdirnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
108         return vn.inode.Child(name, func(existing inode) (inode, error) {
109                 if existing == nil && vn.create != nil {
110                         existing = vn.create(vn, name)
111                         if existing != nil {
112                                 existing.SetParent(vn, name)
113                                 vn.inode.(*treenode).fileinfo.modTime = time.Now()
114                         }
115                 }
116                 if replace == nil {
117                         return existing, nil
118                 } else if tryRepl, err := replace(existing); err != nil {
119                         return existing, err
120                 } else if tryRepl != existing {
121                         return existing, ErrInvalidArgument
122                 } else {
123                         return existing, nil
124                 }
125         })
126 }