Merge branch '13111-webdav-projects'
[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         "sync"
10         "time"
11 )
12
13 type CustomFileSystem interface {
14         FileSystem
15         MountByID(mount string)
16         MountProject(mount, uuid string)
17         MountUsers(mount string)
18 }
19
20 type customFileSystem struct {
21         fileSystem
22         root *vdirnode
23
24         staleThreshold time.Time
25         staleLock      sync.Mutex
26 }
27
28 func (c *Client) CustomFileSystem(kc keepClient) CustomFileSystem {
29         root := &vdirnode{}
30         fs := &customFileSystem{
31                 root: root,
32                 fileSystem: fileSystem{
33                         fsBackend: keepBackend{apiClient: c, keepClient: kc},
34                         root:      root,
35                 },
36         }
37         root.inode = &treenode{
38                 fs:     fs,
39                 parent: root,
40                 fileinfo: fileinfo{
41                         name:    "/",
42                         mode:    os.ModeDir | 0755,
43                         modTime: time.Now(),
44                 },
45                 inodes: make(map[string]inode),
46         }
47         return fs
48 }
49
50 func (fs *customFileSystem) MountByID(mount string) {
51         fs.root.inode.Child(mount, func(inode) (inode, error) {
52                 return &vdirnode{
53                         inode: &treenode{
54                                 fs:     fs,
55                                 parent: fs.root,
56                                 inodes: make(map[string]inode),
57                                 fileinfo: fileinfo{
58                                         name:    mount,
59                                         modTime: time.Now(),
60                                         mode:    0755 | os.ModeDir,
61                                 },
62                         },
63                         create: fs.mountCollection,
64                 }, nil
65         })
66 }
67
68 func (fs *customFileSystem) MountProject(mount, uuid string) {
69         fs.root.inode.Child(mount, func(inode) (inode, error) {
70                 return fs.newProjectNode(fs.root, mount, uuid), nil
71         })
72 }
73
74 func (fs *customFileSystem) MountUsers(mount string) {
75         fs.root.inode.Child(mount, func(inode) (inode, error) {
76                 return &lookupnode{
77                         stale:   fs.Stale,
78                         loadOne: fs.usersLoadOne,
79                         loadAll: fs.usersLoadAll,
80                         inode: &treenode{
81                                 fs:     fs,
82                                 parent: fs.root,
83                                 inodes: make(map[string]inode),
84                                 fileinfo: fileinfo{
85                                         name:    mount,
86                                         modTime: time.Now(),
87                                         mode:    0755 | os.ModeDir,
88                                 },
89                         },
90                 }, nil
91         })
92 }
93
94 // SiteFileSystem returns a FileSystem that maps collections and other
95 // Arvados objects onto a filesystem layout.
96 //
97 // This is experimental: the filesystem layout is not stable, and
98 // there are significant known bugs and shortcomings. For example,
99 // writes are not persisted until Sync() is called.
100 func (c *Client) SiteFileSystem(kc keepClient) CustomFileSystem {
101         fs := c.CustomFileSystem(kc)
102         fs.MountByID("by_id")
103         fs.MountUsers("users")
104         return fs
105 }
106
107 func (fs *customFileSystem) Sync() error {
108         fs.staleLock.Lock()
109         defer fs.staleLock.Unlock()
110         fs.staleThreshold = time.Now()
111         return nil
112 }
113
114 // Stale returns true if information obtained at time t should be
115 // considered stale.
116 func (fs *customFileSystem) Stale(t time.Time) bool {
117         fs.staleLock.Lock()
118         defer fs.staleLock.Unlock()
119         return !fs.staleThreshold.Before(t)
120 }
121
122 func (fs *customFileSystem) newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
123         return nil, ErrInvalidOperation
124 }
125
126 func (fs *customFileSystem) mountCollection(parent inode, id string) inode {
127         var coll Collection
128         err := fs.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+id, nil, nil)
129         if err != nil {
130                 return nil
131         }
132         cfs, err := coll.FileSystem(fs, fs)
133         if err != nil {
134                 return nil
135         }
136         root := cfs.rootnode()
137         root.SetParent(parent, id)
138         return root
139 }
140
141 func (fs *customFileSystem) newProjectNode(root inode, name, uuid string) inode {
142         return &lookupnode{
143                 stale:   fs.Stale,
144                 loadOne: func(parent inode, name string) (inode, error) { return fs.projectsLoadOne(parent, uuid, name) },
145                 loadAll: func(parent inode) ([]inode, error) { return fs.projectsLoadAll(parent, uuid) },
146                 inode: &treenode{
147                         fs:     fs,
148                         parent: root,
149                         inodes: make(map[string]inode),
150                         fileinfo: fileinfo{
151                                 name:    name,
152                                 modTime: time.Now(),
153                                 mode:    0755 | os.ModeDir,
154                         },
155                 },
156         }
157 }
158
159 // vdirnode wraps an inode by ignoring any requests to add/replace
160 // children, and calling a create() func when a non-existing child is
161 // looked up.
162 //
163 // create() can return either a new node, which will be added to the
164 // treenode, or nil for ENOENT.
165 type vdirnode struct {
166         inode
167         create func(parent inode, name string) inode
168 }
169
170 func (vn *vdirnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
171         return vn.inode.Child(name, func(existing inode) (inode, error) {
172                 if existing == nil && vn.create != nil {
173                         existing = vn.create(vn, name)
174                         if existing != nil {
175                                 existing.SetParent(vn, name)
176                                 vn.inode.(*treenode).fileinfo.modTime = time.Now()
177                         }
178                 }
179                 if replace == nil {
180                         return existing, nil
181                 } else if tryRepl, err := replace(existing); err != nil {
182                         return existing, err
183                 } else if tryRepl != existing {
184                         return existing, ErrInvalidArgument
185                 } else {
186                         return existing, nil
187                 }
188         })
189 }