13111: Note safe use of append().
[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         // Note: the "filters" slice's backing array might be reused
48         // by append(filters,...) below. This isn't goroutine safe,
49         // but all accesses are in the same goroutine, so it's OK.
50         filters := []Filter{{"owner_uuid", "=", pn.uuid}}
51         params := ResourceListParams{
52                 Filters: filters,
53                 Order:   "uuid",
54         }
55         for {
56                 var resp CollectionList
57                 pn.err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/collections", nil, params)
58                 if pn.err != nil {
59                         return
60                 }
61                 if len(resp.Items) == 0 {
62                         break
63                 }
64                 for _, i := range resp.Items {
65                         coll := i
66                         if coll.Name == "" || coll.Name == "." || coll.Name == ".." {
67                                 continue
68                         }
69                         pn.inode.Child(coll.Name, func(inode) (inode, error) {
70                                 return deferredCollectionFS(fs, pn, coll), nil
71                         })
72                 }
73                 params.Filters = append(filters, Filter{"uuid", ">", resp.Items[len(resp.Items)-1].UUID})
74         }
75
76         filters = append(filters, Filter{"group_class", "=", "project"})
77         params.Filters = filters
78         for {
79                 var resp GroupList
80                 pn.err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/groups", nil, params)
81                 if pn.err != nil {
82                         return
83                 }
84                 if len(resp.Items) == 0 {
85                         break
86                 }
87                 for _, group := range resp.Items {
88                         if group.Name == "" || group.Name == "." || group.Name == ".." {
89                                 continue
90                         }
91                         pn.inode.Child(group.Name, func(inode) (inode, error) {
92                                 return fs.newProjectNode(pn, group.Name, group.UUID), nil
93                         })
94                 }
95                 params.Filters = append(filters, Filter{"uuid", ">", resp.Items[len(resp.Items)-1].UUID})
96         }
97         pn.err = nil
98 }
99
100 func (pn *projectnode) Readdir() ([]os.FileInfo, error) {
101         pn.staleChecker.DoIfStale(pn.load, pn.FS().(*customFileSystem).Stale)
102         if pn.err != nil {
103                 return nil, pn.err
104         }
105         return pn.inode.Readdir()
106 }
107
108 func (pn *projectnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
109         pn.staleChecker.DoIfStale(pn.load, pn.FS().(*customFileSystem).Stale)
110         if pn.err != nil {
111                 return nil, pn.err
112         }
113         if replace == nil {
114                 // lookup
115                 return pn.inode.Child(name, nil)
116         }
117         return pn.inode.Child(name, func(existing inode) (inode, error) {
118                 if repl, err := replace(existing); err != nil {
119                         return existing, err
120                 } else if repl == nil {
121                         if existing == nil {
122                                 return nil, nil
123                         }
124                         // rmdir
125                         // (TODO)
126                         return existing, ErrInvalidArgument
127                 } else if existing != nil {
128                         // clobber
129                         return existing, ErrInvalidArgument
130                 } else if repl.FileInfo().IsDir() {
131                         // mkdir
132                         // TODO: repl.SetParent(pn, name), etc.
133                         return existing, ErrInvalidArgument
134                 } else {
135                         // create file
136                         return existing, ErrInvalidArgument
137                 }
138         })
139 }