16827: Don't append '/' to requests with query params. Bump version
[arvados.git] / sdk / R / R / Collection.R
index fed222fdda9c994a4914f4ebc594dc0a1ae8362f..1440836547253bc96871c651e666ec8608af243a 100644 (file)
-source("./R/Subcollection.R")
-source("./R/ArvadosFile.R")
+# Copyright (C) The Arvados Authors. All rights reserved.
+#
+# SPDX-License-Identifier: Apache-2.0
 
-#' Arvados Collection Object
+#' Collection
 #'
-#' Update description
+#' Collection class provides interface for working with Arvados collections.
 #'
-#' @examples arv = Collection$new(api, uuid)
-#' @export Collection
+#' @section Usage:
+#' \preformatted{collection = Collection$new(arv, uuid)}
+#'
+#' @section Arguments:
+#' \describe{
+#'   \item{arv}{Arvados object.}
+#'   \item{uuid}{UUID of a collection.}
+#' }
+#'
+#' @section Methods:
+#' \describe{
+#'   \item{add(content)}{Adds ArvadosFile or Subcollection specified by content to the collection.}
+#'   \item{create(files)}{Creates one or more ArvadosFiles and adds them to the collection at specified path.}
+#'   \item{remove(fileNames)}{Remove one or more files from the collection.}
+#'   \item{move(content, destination)}{Moves ArvadosFile or Subcollection to another location in the collection.}
+#'   \item{copy(content, destination)}{Copies ArvadosFile or Subcollection to another location in the collection.}
+#'   \item{getFileListing()}{Returns collections file content as character vector.}
+#'   \item{get(relativePath)}{If relativePath is valid, returns ArvadosFile or Subcollection specified by relativePath, else returns NULL.}
+#' }
+#'
+#' @name Collection
+#' @examples
+#' \dontrun{
+#' arv <- Arvados$new("your Arvados token", "example.arvadosapi.com")
+#' collection <- Collection$new(arv, "uuid")
+#'
+#' createdFiles <- collection$create(c("main.cpp", lib.dll), "cpp/src/")
+#'
+#' collection$remove("location/to/my/file.cpp")
+#'
+#' collection$move("folder/file.cpp", "file.cpp")
+#'
+#' arvadosFile <- collection$get("location/to/my/file.cpp")
+#' arvadosSubcollection <- collection$get("location/to/my/directory/")
+#' }
+NULL
+
+#' @export
 Collection <- R6::R6Class(
 
     "Collection",
 
     public = list(
 
-        #Todo(Fudo): Encapsulate this?
-        uuid                     = NULL,
-        etag                     = NULL,
-        owner_uuid               = NULL,
-        created_at               = NULL,
-        modified_by_client_uuid  = NULL,
-        modified_by_user_uuid    = NULL,
-        modified_at              = NULL,
-        portable_data_hash       = NULL,
-        replication_desired      = NULL,
-        replication_confirmed_at = NULL,
-        replication_confirmed    = NULL,
-        updated_at               = NULL,
-        manifest_text            = NULL,
-        name                     = NULL,
-        description              = NULL,
-        properties               = NULL,
-        delete_at                = NULL,
-        file_names               = NULL,
-        trash_at                 = NULL,
-        is_trashed               = NULL,
-
-        initialize = function(api, uuid)
-        {
-            private$api <- api
-            result <- private$api$getCollection(uuid)
-
-            self$uuid                     <- result$uuid                               
-            self$etag                     <- result$etag                               
-            self$owner_uuid               <- result$owner_uuid                         
-            self$created_at               <- result$created_at                         
-            self$modified_by_client_uuid  <- result$modified_by_client_uuid            
-            self$modified_by_user_uuid    <- result$modified_by_user_uuid              
-            self$modified_at              <- result$modified_at                        
-            self$portable_data_hash       <- result$portable_data_hash                 
-            self$replication_desired      <- result$replication_desired                
-            self$replication_confirmed_at <- result$replication_confirmed_at           
-            self$replication_confirmed    <- result$replication_confirmed              
-            self$updated_at               <- result$updated_at                         
-            self$manifest_text            <- result$manifest_text                      
-            self$name                     <- result$name                               
-            self$description              <- result$description                        
-            self$properties               <- result$properties                         
-            self$delete_at                <- result$delete_at                          
-            self$file_names               <- result$file_names                         
-            self$trash_at                 <- result$trash_at                           
-            self$is_trashed               <- result$is_trashed                         
-
-            #Todo(Fudo): Replace this when you get access to webDAV server.
-            private$fileItems <- private$getCollectionContent()
-
-            private$fileTree <- private$generateTree(private$fileItems)
-        },
+               uuid = NULL,
 
-        printFileContent = function(pretty = TRUE)
+               initialize = function(api, uuid)
         {
-            if(pretty)
-                private$fileTree$printContent(0)
-            else
-                print(private$fileItems)
-
+            private$REST <- api$getRESTService()
+            self$uuid <- uuid
         },
 
-        get = function(relativePath)
+        add = function(content, relativePath = "")
         {
-            treeNode <- private$traverseInOrder(private$fileTree, function(node)
-            {
-                if(node$relativePath == relativePath)
-                    return(node)
-                else
-                    return(NULL)
-            })
+            if(is.null(private$tree))
+                private$generateCollectionTreeStructure()
 
-            if(!is.null(treeNode))
+            if(relativePath == ""  ||
+               relativePath == "." ||
+               relativePath == "./")
             {
-                return(private$createSubcollectionTree(treeNode))
+                subcollection <- private$tree$getTree()
             }
             else
             {
-                return(NULL)
+                relativePath <- trimFromEnd(relativePath, "/")
+                subcollection <- self$get(relativePath)
             }
-        }
-    ),
 
-    active = list(
-        items = function(value)
-        {
-            if(missing(value))
-                return(private$fileItems)
-            else
-                print("Value is read-only.")
+            if(is.null(subcollection))
+                stop(paste("Subcollection", relativePath, "doesn't exist."))
 
-            return(NULL)
-        }
-    ),
-    
-    private = list(
-
-        api       = NULL,
-        fileItems = NULL,
-        fileTree  = NULL,
-
-        createSubcollectionTree = function(treeNode)
-        {
-            if(treeNode$hasChildren())
+            if("ArvadosFile"   %in% class(content) ||
+               "Subcollection" %in% class(content))
             {
-                children = NULL
+                if(!is.null(content$getCollection()))
+                    stop("Content already belongs to a collection.")
 
-                for(child in treeNode$children)
-                {
-                    child <- private$createSubcollectionTree(child)
-                    children <- c(children, child)                   
-                }
+                if(content$getName() == "")
+                    stop("Content has invalid name.")
 
-                return(Subcollection$new(treeNode$name, treeNode$relativePath, children))
+                subcollection$add(content)
+                content
             }
             else
             {
-                if(treeNode$type == "file")
-                    return(ArvadosFile$new(treeNode$name, treeNode$relativePath, private$api, self))
-                else if(treeNode$type == "folder" || treeNode$type == "root")
-                    return(Subcollection$new(treeNode$name, treeNode$relativePath, NULL))
+                stop(paste0("Expected AravodsFile or Subcollection object, got ",
+                            paste0("(", paste0(class(content), collapse = ", "), ")"),
+                            "."))
             }
         },
 
-        createSubcollectionFromNode = function(treeNode, children)
-        {
-            subcollection = NULL
-            if(treeNode$type == "file")
-                subcollection = ArvadosFile$new(treeNode$name, treeNode$relativePath)
-            else if(treeNode$type == "folder" || treeNode$type == "root")
-                subcollection = Subcollection$new(treeNode$name, treeNode$relativePath, children)
-            
-            subcollection
-        },
-
-        getCollectionContent = function()
-        {
-            #TODO(Fudo): Use proper URL here.
-            uri <- URLencode(paste0(private$api$getWebDavHostName(), "c=", self$uuid))
-
-            # fetch directory listing via curl and parse XML response
-            h <- curl::new_handle()
-            curl::handle_setopt(h, customrequest = "PROPFIND")
-
-            #TODO(Fudo): Use proper token here.
-            curl::handle_setheaders(h, "Authorization" = paste("OAuth2", private$api$getToken()))
-            response <- curl::curl_fetch_memory(uri, h)
-
-            HttpParser$new()$parseWebDAVResponse(response, uri)
-        },
-
-        #Todo(Fudo): Move tree creation to another file.
-        generateTree = function(collectionContent)
+        create = function(files)
         {
-            treeBranches <- sapply(collectionContent, function(filePath)
-            {
-                splitPath <- unlist(strsplit(filePath, "/", fixed = TRUE))
-
-                pathEndsWithSlash <- substr(filePath, nchar(filePath), nchar(filePath)) == "/"
-                
-                branch = private$createBranch(splitPath, pathEndsWithSlash)      
-            })
-
-            root <- TreeNode$new("./", "root")
-            root$relativePath = ""
-
-            sapply(treeBranches, function(branch)
-            {
-                private$addNode(root, branch)
-            })
-
-            root
-        },
+            if(is.null(private$tree))
+                private$generateCollectionTreeStructure()
 
-        createBranch = function(splitPath, pathEndsWithSlash)
-        {
-            branch <- NULL
-            lastElementIndex <- length(splitPath)
-            
-            lastElementInPathType = "file"
-            if(pathEndsWithSlash)
-                lastElementInPathType = "folder"
-
-            for(elementIndex in lastElementIndex:1)
+            if(is.character(files))
             {
-                if(elementIndex == lastElementIndex)
-                {
-                    branch = TreeNode$new(splitPath[[elementIndex]], lastElementInPathType)
-                }
-                else
+                sapply(files, function(file)
                 {
-                    newFolder = TreeNode$new(splitPath[[elementIndex]], "folder")
-                    newFolder$addChild(branch)
-                    branch = newFolder
-                }
-
-                branch$relativePath <- paste(unlist(splitPath[1:elementIndex]), collapse = "/")
-            }
+                    childWithSameName <- self$get(file)
+                    if(!is.null(childWithSameName))
+                        stop("Destination already contains file with same name.")
 
-            branch
-        },
+                    newTreeBranch <- private$tree$createBranch(file)
+                    private$tree$addBranch(private$tree$getTree(), newTreeBranch)
 
-        addNode = function(container, node)
-        {
-            child = container$getChild(node$name)
+                    private$REST$create(file, self$uuid)
+                    newTreeBranch$setCollection(self)
+                })
 
-            if(is.null(child))
-            {
-                container$addChild(node)
+                "Created"
             }
             else
             {
-                private$addNode(child, node$getFirstChild())
+                stop(paste0("Expected character vector, got ",
+                            paste0("(", paste0(class(files), collapse = ", "), ")"),
+                            "."))
             }
         },
 
-        traverseInOrder = function(node, predicate)
+        remove = function(paths)
         {
-            if(node$hasChildren())
+            if(is.null(private$tree))
+                private$generateCollectionTreeStructure()
+
+            if(is.character(paths))
             {
-                result <- predicate(node)
+                sapply(paths, function(filePath)
+                {
+                    filePath <- trimFromEnd(filePath, "/")
+                    file <- self$get(filePath)
 
-                if(!is.null(result))
-                    return(result)               
+                    if(is.null(file))
+                        stop(paste("File", filePath, "doesn't exist."))
 
-                for(child in node$children)
-                {
-                    result <- private$traverseInOrder(child, predicate)
+                    parent <- file$getParent()
+
+                    if(is.null(parent))
+                        stop("You can't delete root folder.")
 
-                    if(!is.null(result))
-                        return(result)
-                }
+                    parent$remove(file$getName())
+                })
 
-                return(NULL)
+                "Content removed"
             }
             else
             {
-                return(predicate(node))
+                stop(paste0("Expected character vector, got ",
+                            paste0("(", paste0(class(paths), collapse = ", "), ")"),
+                            "."))
             }
-        }
-
-    ),
+        },
 
-    cloneable = FALSE
-)
+        move = function(content, destination)
+        {
+            if(is.null(private$tree))
+                private$generateCollectionTreeStructure()
 
-TreeNode <- R6::R6Class(
+            content <- trimFromEnd(content, "/")
 
-    "TreeNode",
+            elementToMove <- self$get(content)
 
-    public = list(
+            if(is.null(elementToMove))
+                stop("Content you want to move doesn't exist in the collection.")
 
-        name = NULL,
-        relativePath = NULL,
-        children = NULL,
-        parent = NULL,
-        type = NULL,
+            elementToMove$move(destination)
+        },
 
-        initialize = function(name, type)
+        copy = function(content, destination)
         {
-            if(type == "folder")
-                name <- paste0(name, "/")
+            if(is.null(private$tree))
+                private$generateCollectionTreeStructure()
 
-            self$name <- name
-            self$type <- type
-            self$children <- list()
-        },
+            content <- trimFromEnd(content, "/")
 
-        addChild = function(node)
-        {
-            self$children <- c(self$children, node)
-            node$setParent(self)
-            self
-        },
+            elementToCopy <- self$get(content)
 
-        setParent = function(parent)
-        {
-            self$parent = parent
+            if(is.null(elementToCopy))
+                stop("Content you want to copy doesn't exist in the collection.")
+
+            elementToCopy$copy(destination)
         },
 
-        getChild = function(childName)
+        refresh = function()
         {
-            for(child in self$children)
+            if(!is.null(private$tree))
             {
-                if(childName == child$name)
-                    return(child)
+                private$tree$getTree()$setCollection(NULL, setRecursively = TRUE)
+                private$tree <- NULL
             }
-
-            return(NULL)
         },
 
-        hasChildren = function()
+        getFileListing = function()
         {
-            if(length(self$children) != 0)
-                return(TRUE)
-            else
-                return(FALSE)
+            if(is.null(private$tree))
+                private$generateCollectionTreeStructure()
+
+            content <- private$REST$getCollectionContent(self$uuid)
+            content[order(tolower(content))]
         },
 
-        getFirstChild = function()
+        get = function(relativePath)
         {
-            if(!self$hasChildren())
-                return(NULL)
-            else
-                return(self$children[[1]])
+            if(is.null(private$tree))
+                private$generateCollectionTreeStructure()
+
+            private$tree$getElement(relativePath)
         },
 
-        printContent = function(depth)
+        getRESTService = function() private$REST,
+        setRESTService = function(newRESTService) private$REST <- newRESTService
+    ),
+
+    private = list(
+
+        REST        = NULL,
+        tree        = NULL,
+        fileContent = NULL,
+
+        generateCollectionTreeStructure = function()
         {
-            indentation <- paste(rep("....", depth), collapse = "")
-            print(paste0(indentation, self$name))
-            
-            for(child in self$children)
-                child$printContent(depth + 1)
+            if(is.null(self$uuid))
+                stop("Collection uuid is not defined.")
+
+            if(is.null(private$REST))
+                stop("REST service is not defined.")
+
+            private$fileContent <- private$REST$getCollectionContent(self$uuid)
+            private$tree <- CollectionTree$new(private$fileContent, self)
         }
     ),
 
     cloneable = FALSE
 )
+
+#' print.Collection
+#'
+#' Custom print function for Collection class
+#'
+#' @param x Instance of Collection class
+#' @param ... Optional arguments.
+#' @export
+print.Collection = function(x, ...)
+{
+    cat(paste0("Type: ", "\"", "Arvados Collection", "\""), sep = "\n")
+    cat(paste0("uuid: ", "\"", x$uuid,               "\""), sep = "\n")
+}