1 // Copyright (C) The Arvados Authors. All rights reserved.
3 // SPDX-License-Identifier: Apache-2.0
14 type CustomFileSystem interface {
16 MountByID(mount string)
17 MountProject(mount, uuid string)
18 MountUsers(mount string)
21 type customFileSystem struct {
26 staleThreshold time.Time
30 func (c *Client) CustomFileSystem(kc keepClient) CustomFileSystem {
32 fs := &customFileSystem{
34 fileSystem: fileSystem{
35 fsBackend: keepBackend{apiClient: c, keepClient: kc},
37 thr: newThrottle(concurrentWriters),
40 root.inode = &treenode{
45 mode: os.ModeDir | 0755,
48 inodes: make(map[string]inode),
53 func (fs *customFileSystem) MountByID(mount string) {
54 fs.root.inode.Child(mount, func(inode) (inode, error) {
59 inodes: make(map[string]inode),
63 mode: 0755 | os.ModeDir,
71 func (fs *customFileSystem) MountProject(mount, uuid string) {
72 fs.root.inode.Child(mount, func(inode) (inode, error) {
73 return fs.newProjectNode(fs.root, mount, uuid), nil
77 func (fs *customFileSystem) MountUsers(mount string) {
78 fs.root.inode.Child(mount, func(inode) (inode, error) {
81 loadOne: fs.usersLoadOne,
82 loadAll: fs.usersLoadAll,
86 inodes: make(map[string]inode),
90 mode: 0755 | os.ModeDir,
97 // SiteFileSystem returns a FileSystem that maps collections and other
98 // Arvados objects onto a filesystem layout.
100 // This is experimental: the filesystem layout is not stable, and
101 // there are significant known bugs and shortcomings. For example,
102 // writes are not persisted until Sync() is called.
103 func (c *Client) SiteFileSystem(kc keepClient) CustomFileSystem {
104 fs := c.CustomFileSystem(kc)
105 fs.MountByID("by_id")
106 fs.MountUsers("users")
110 func (fs *customFileSystem) Sync() error {
112 defer fs.staleLock.Unlock()
113 fs.staleThreshold = time.Now()
117 // Stale returns true if information obtained at time t should be
119 func (fs *customFileSystem) Stale(t time.Time) bool {
121 defer fs.staleLock.Unlock()
122 return !fs.staleThreshold.Before(t)
125 func (fs *customFileSystem) newNode(name string, perm os.FileMode, modTime time.Time) (node inode, err error) {
126 return nil, ErrInvalidOperation
129 func (fs *customFileSystem) mountByID(parent inode, id string) inode {
130 if strings.Contains(id, "-4zz18-") || pdhRegexp.MatchString(id) {
131 return fs.mountCollection(parent, id)
132 } else if strings.Contains(id, "-j7d0g-") {
133 return fs.newProjectNode(fs.root, id, id)
139 func (fs *customFileSystem) mountCollection(parent inode, id string) inode {
141 err := fs.RequestAndDecode(&coll, "GET", "arvados/v1/collections/"+id, nil, nil)
145 cfs, err := coll.FileSystem(fs, fs)
149 root := cfs.rootnode()
150 root.SetParent(parent, id)
154 func (fs *customFileSystem) newProjectNode(root inode, name, uuid string) inode {
157 loadOne: func(parent inode, name string) (inode, error) { return fs.projectsLoadOne(parent, uuid, name) },
158 loadAll: func(parent inode) ([]inode, error) { return fs.projectsLoadAll(parent, uuid) },
162 inodes: make(map[string]inode),
166 mode: 0755 | os.ModeDir,
172 // vdirnode wraps an inode by ignoring any requests to add/replace
173 // children, and calling a create() func when a non-existing child is
176 // create() can return either a new node, which will be added to the
177 // treenode, or nil for ENOENT.
178 type vdirnode struct {
180 create func(parent inode, name string) inode
183 func (vn *vdirnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
184 return vn.inode.Child(name, func(existing inode) (inode, error) {
185 if existing == nil && vn.create != nil {
186 existing = vn.create(vn, name)
188 existing.SetParent(vn, name)
189 vn.inode.(*treenode).fileinfo.modTime = time.Now()
194 } else if tryRepl, err := replace(existing); err != nil {
196 } else if tryRepl != existing {
197 return existing, ErrInvalidArgument