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