13111: Add /users virtual dir to siteFS.
[arvados.git] / sdk / go / arvados / fs_project.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 staleChecker struct {
14         mtx  sync.Mutex
15         last time.Time
16 }
17
18 func (sc *staleChecker) DoIfStale(fn func(), staleFunc func(time.Time) bool) {
19         sc.mtx.Lock()
20         defer sc.mtx.Unlock()
21         if !staleFunc(sc.last) {
22                 return
23         }
24         sc.last = time.Now()
25         fn()
26 }
27
28 // projectnode exposes an Arvados project as a filesystem directory.
29 type projectnode struct {
30         inode
31         staleChecker
32         uuid string
33         err  error
34 }
35
36 func (pn *projectnode) load() {
37         fs := pn.FS().(*customFileSystem)
38
39         if pn.uuid == "" {
40                 var resp User
41                 pn.err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/users/current", nil, nil)
42                 if pn.err != nil {
43                         return
44                 }
45                 pn.uuid = resp.UUID
46         }
47         filters := []Filter{{"owner_uuid", "=", pn.uuid}}
48         params := ResourceListParams{
49                 Filters: filters,
50                 Order:   "uuid",
51         }
52         for {
53                 var resp CollectionList
54                 pn.err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/collections", nil, params)
55                 if pn.err != nil {
56                         return
57                 }
58                 if len(resp.Items) == 0 {
59                         break
60                 }
61                 for _, i := range resp.Items {
62                         coll := i
63                         if coll.Name == "" {
64                                 continue
65                         }
66                         pn.inode.Child(coll.Name, func(inode) (inode, error) {
67                                 return deferredCollectionFS(fs, pn, coll), nil
68                         })
69                 }
70                 params.Filters = append(filters, Filter{"uuid", ">", resp.Items[len(resp.Items)-1].UUID})
71         }
72
73         filters = append(filters, Filter{"group_class", "=", "project"})
74         params.Filters = filters
75         for {
76                 var resp GroupList
77                 pn.err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/groups", nil, params)
78                 if pn.err != nil {
79                         return
80                 }
81                 if len(resp.Items) == 0 {
82                         break
83                 }
84                 for _, group := range resp.Items {
85                         if group.Name == "" || group.Name == "." || group.Name == ".." {
86                                 continue
87                         }
88                         pn.inode.Child(group.Name, func(inode) (inode, error) {
89                                 return fs.newProjectNode(pn, group.Name, group.UUID), nil
90                         })
91                 }
92                 params.Filters = append(filters, Filter{"uuid", ">", resp.Items[len(resp.Items)-1].UUID})
93         }
94         pn.err = nil
95 }
96
97 func (pn *projectnode) Readdir() ([]os.FileInfo, error) {
98         pn.staleChecker.DoIfStale(pn.load, pn.FS().(*customFileSystem).Stale)
99         if pn.err != nil {
100                 return nil, pn.err
101         }
102         return pn.inode.Readdir()
103 }
104
105 func (pn *projectnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
106         pn.staleChecker.DoIfStale(pn.load, pn.FS().(*customFileSystem).Stale)
107         if pn.err != nil {
108                 return nil, pn.err
109         }
110         if replace == nil {
111                 // lookup
112                 return pn.inode.Child(name, nil)
113         }
114         return pn.inode.Child(name, func(existing inode) (inode, error) {
115                 if repl, err := replace(existing); err != nil {
116                         return existing, err
117                 } else if repl == nil {
118                         if existing == nil {
119                                 return nil, nil
120                         }
121                         // rmdir
122                         // (TODO)
123                         return existing, ErrInvalidArgument
124                 } else if existing != nil {
125                         // clobber
126                         return existing, ErrInvalidArgument
127                 } else if repl.FileInfo().IsDir() {
128                         // mkdir
129                         // TODO: repl.SetParent(pn, name), etc.
130                         return existing, ErrInvalidArgument
131                 } else {
132                         // create file
133                         return existing, ErrInvalidArgument
134                 }
135         })
136 }