Merge branch '19088-s3-properties-tags'
[arvados.git] / sdk / go / arvados / fs_project.go
index 776c8a3365178e62874c24d1ac237691e9f60a9a..bea1f76e24f24faffa38fbccd7b6b880ffb2d22e 100644 (file)
 package arvados
 
 import (
-       "os"
-       "sync"
-       "time"
+       "log"
+       "strings"
 )
 
-// projectnode exposes an Arvados project as a filesystem directory.
-type projectnode struct {
-       inode
-       uuid string
-       err  error
-
-       loadLock  sync.Mutex
-       loadStart time.Time
+func (fs *customFileSystem) defaultUUID(uuid string) (string, error) {
+       if uuid != "" {
+               return uuid, nil
+       }
+       var resp User
+       err := fs.RequestAndDecode(&resp, "GET", "arvados/v1/users/current", nil, nil)
+       if err != nil {
+               return "", err
+       }
+       return resp.UUID, nil
 }
 
-func (pn *projectnode) load() {
-       fs := pn.FS().(*siteFileSystem)
-
-       pn.loadLock.Lock()
-       defer pn.loadLock.Unlock()
-       if !fs.Stale(pn.loadStart) {
-               return
+// loadOneChild loads only the named child, if it exists.
+func (fs *customFileSystem) projectsLoadOne(parent inode, uuid, name string) (inode, error) {
+       uuid, err := fs.defaultUUID(uuid)
+       if err != nil {
+               return nil, err
        }
-       pn.loadStart = time.Now()
 
-       if pn.uuid == "" {
-               var resp User
-               pn.err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/users/current", nil, nil)
-               if pn.err != nil {
-                       return
-               }
-               pn.uuid = resp.UUID
-       }
-       filters := []Filter{{"owner_uuid", "=", pn.uuid}}
-       params := ResourceListParams{
-               Filters: filters,
-               Order:   "uuid",
-       }
-       for {
-               var resp CollectionList
-               pn.err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/collections", nil, params)
-               if pn.err != nil {
-                       return
+       var contents CollectionList
+       for _, subst := range []string{"/", fs.forwardSlashNameSubstitution} {
+               contents = CollectionList{}
+               err = fs.RequestAndDecode(&contents, "GET", "arvados/v1/groups/"+uuid+"/contents", nil, ResourceListParams{
+                       Count: "none",
+                       Filters: []Filter{
+                               {"name", "=", strings.Replace(name, subst, "/", -1)},
+                               {"uuid", "is_a", []string{"arvados#collection", "arvados#group"}},
+                               {"groups.group_class", "=", "project"},
+                       },
+                       Select: []string{"uuid", "name", "modified_at", "properties"},
+               })
+               if err != nil {
+                       return nil, err
                }
-               if len(resp.Items) == 0 {
+               if len(contents.Items) > 0 || fs.forwardSlashNameSubstitution == "/" || fs.forwardSlashNameSubstitution == "" || !strings.Contains(name, fs.forwardSlashNameSubstitution) {
                        break
                }
-               for _, i := range resp.Items {
-                       coll := i
-                       if coll.Name == "" {
-                               continue
-                       }
-                       pn.inode.Child(coll.Name, func(inode) (inode, error) {
-                               return deferredCollectionFS(fs, pn, coll), nil
-                       })
-               }
-               params.Filters = append(filters, Filter{"uuid", ">", resp.Items[len(resp.Items)-1].UUID})
+               // If the requested name contains the configured "/"
+               // replacement string and didn't match a
+               // project/collection exactly, we'll try again with
+               // "/" in its place, so a lookup of a munged name
+               // works regardless of whether the directory listing
+               // has been populated with escaped names.
+               //
+               // Note this doesn't handle items whose names contain
+               // both "/" and the substitution string.
        }
-
-       filters = append(filters, Filter{"group_class", "=", "project"})
-       params.Filters = filters
-       for {
-               var resp GroupList
-               pn.err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/groups", nil, params)
-               if pn.err != nil {
-                       return
-               }
-               if len(resp.Items) == 0 {
-                       break
-               }
-               for _, group := range resp.Items {
-                       if group.Name == "" || group.Name == "." || group.Name == ".." {
-                               continue
-                       }
-                       pn.inode.Child(group.Name, func(inode) (inode, error) {
-                               return fs.newProjectNode(pn, group.Name, group.UUID), nil
-                       })
-               }
-               params.Filters = append(filters, Filter{"uuid", ">", resp.Items[len(resp.Items)-1].UUID})
+       if len(contents.Items) == 0 {
+               return nil, nil
        }
-       pn.err = nil
-}
+       coll := contents.Items[0]
 
-func (pn *projectnode) Readdir() ([]os.FileInfo, error) {
-       pn.load()
-       if pn.err != nil {
-               return nil, pn.err
+       if strings.Contains(coll.UUID, "-j7d0g-") {
+               // Group item was loaded into a Collection var -- but
+               // we only need the Name and UUID anyway, so it's OK.
+               return fs.newProjectNode(parent, coll.Name, coll.UUID, nil), nil
+       } else if strings.Contains(coll.UUID, "-4zz18-") {
+               return deferredCollectionFS(fs, parent, coll), nil
+       } else {
+               log.Printf("group contents: unrecognized UUID in response: %q", coll.UUID)
+               return nil, ErrInvalidArgument
        }
-       return pn.inode.Readdir()
 }
 
-func (pn *projectnode) Child(name string, replace func(inode) (inode, error)) (inode, error) {
-       pn.load()
-       if pn.err != nil {
-               return nil, pn.err
+func (fs *customFileSystem) projectsLoadAll(parent inode, uuid string) ([]inode, error) {
+       uuid, err := fs.defaultUUID(uuid)
+       if err != nil {
+               return nil, err
        }
-       if replace == nil {
-               // lookup
-               return pn.inode.Child(name, nil)
-       }
-       return pn.inode.Child(name, func(existing inode) (inode, error) {
-               if repl, err := replace(existing); err != nil {
-                       return existing, err
-               } else if repl == nil {
-                       if existing == nil {
-                               return nil, nil
+
+       var inodes []inode
+
+       // When #17424 is resolved, remove the outer loop here and use
+       // []string{"arvados#collection", "arvados#group"} directly as the uuid
+       // filter.
+       for _, class := range []string{"arvados#collection", "arvados#group"} {
+               // Note: the "filters" slice's backing array might be reused
+               // by append(filters,...) below. This isn't goroutine safe,
+               // but all accesses are in the same goroutine, so it's OK.
+               filters := []Filter{
+                       {"uuid", "is_a", class},
+               }
+               if class == "arvados#group" {
+                       filters = append(filters, Filter{"group_class", "=", "project"})
+               }
+
+               params := ResourceListParams{
+                       Count:   "none",
+                       Filters: filters,
+                       Order:   "uuid",
+                       Select:  []string{"uuid", "name", "modified_at", "properties"},
+               }
+
+               for {
+                       // The groups content endpoint returns Collection and Group (project)
+                       // objects. This function only accesses the UUID and Name field. Both
+                       // collections and groups have those fields, so it is easier to just treat
+                       // the ObjectList that comes back as a CollectionList.
+                       var resp CollectionList
+                       err = fs.RequestAndDecode(&resp, "GET", "arvados/v1/groups/"+uuid+"/contents", nil, params)
+                       if err != nil {
+                               return nil, err
                        }
-                       // rmdir
-                       // (TODO)
-                       return existing, ErrInvalidArgument
-               } else if existing != nil {
-                       // clobber
-                       return existing, ErrInvalidArgument
-               } else if repl.FileInfo().IsDir() {
-                       // mkdir
-                       // TODO: repl.SetParent(pn, name), etc.
-                       return existing, ErrInvalidArgument
-               } else {
-                       // create file
-                       return existing, ErrInvalidArgument
+                       if len(resp.Items) == 0 {
+                               break
+                       }
+                       for _, i := range resp.Items {
+                               if fs.forwardSlashNameSubstitution != "" {
+                                       i.Name = strings.Replace(i.Name, "/", fs.forwardSlashNameSubstitution, -1)
+                               }
+                               if !permittedName(i.Name) {
+                                       continue
+                               }
+                               if strings.Contains(i.UUID, "-j7d0g-") {
+                                       inodes = append(inodes, fs.newProjectNode(parent, i.Name, i.UUID, &Group{
+                                               UUID:       i.UUID,
+                                               Name:       i.Name,
+                                               ModifiedAt: i.ModifiedAt,
+                                               Properties: i.Properties,
+                                       }))
+                               } else if strings.Contains(i.UUID, "-4zz18-") {
+                                       inodes = append(inodes, deferredCollectionFS(fs, parent, i))
+                               } else {
+                                       log.Printf("group contents: unrecognized UUID in response: %q", i.UUID)
+                                       return nil, ErrInvalidArgument
+                               }
+                       }
+                       params.Filters = append(filters, Filter{"uuid", ">", resp.Items[len(resp.Items)-1].UUID})
                }
-       })
+       }
+       return inodes, nil
 }