20846: Merge branch '19213-ubuntu2204-support' into 20846-ubuntu2204
[arvados.git] / sdk / R / R / Collection.R
1 # Copyright (C) The Arvados Authors. All rights reserved.
2 #
3 # SPDX-License-Identifier: Apache-2.0
4
5 #' R6 Class Representing Arvados Collection
6 #'
7 #' @description
8 #' Collection class provides interface for working with Arvados collections,
9 #' for exaplme actions like creating, updating, moving or removing are possible.
10 #'
11 #' @seealso
12 #' https://git.arvados.org/arvados.git/tree/HEAD:/sdk/R
13 #'
14 #' @export
15
16 Collection <- R6::R6Class(
17
18     "Collection",
19
20     public = list(
21
22         #' @field uuid Autentic for Collection UUID.
23         uuid = NULL,
24
25         #' @description
26         #' Initialize new enviroment.
27         #' @param api Arvados enviroment.
28         #' @param uuid The UUID Autentic for Collection UUID.
29         #' @return A new `Collection` object.
30         #' @examples
31         #' \dontrun{
32         #' collection <- Collection$new(arv, CollectionUUID)
33         #' }
34         initialize = function(api, uuid)
35         {
36             private$REST <- api$getRESTService()
37             self$uuid <- uuid
38         },
39
40         #' @description
41         #' Adds ArvadosFile or Subcollection specified by content to the collection. Used only with ArvadosFile or Subcollection.
42         #' @param content Content to be added.
43         #' @param relativePath Path to add content.
44         add = function(content, relativePath = "")
45         {
46             if(is.null(private$tree))
47                 private$generateCollectionTreeStructure()
48
49             if(relativePath == ""  ||
50                relativePath == "." ||
51                relativePath == "./")
52             {
53                 subcollection <- private$tree$getTree()
54             }
55             else
56             {
57                 relativePath <- trimFromEnd(relativePath, "/")
58                 subcollection <- self$get(relativePath)
59             }
60
61             if(is.null(subcollection))
62                 stop(paste("Subcollection", relativePath, "doesn't exist."))
63
64             if("ArvadosFile"   %in% class(content) ||
65                "Subcollection" %in% class(content))
66             {
67                 if(!is.null(content$getCollection()))
68                     stop("Content already belongs to a collection.")
69
70                 if(content$getName() == "")
71                     stop("Content has invalid name.")
72
73                 subcollection$add(content)
74                 content
75             }
76             else
77             {
78                 stop(paste0("Expected AravodsFile or Subcollection object, got ",
79                             paste0("(", paste0(class(content), collapse = ", "), ")"),
80                             "."))
81             }
82         },
83
84         #' @description
85         #' Read file content.
86         #' @param file Name of the file.
87         #' @param col Collection from which the file is read.
88         #' @param sep  Separator used in reading tsv, csv file format.
89         #' @param istable Used in reading txt file to check if the file is table or not.
90         #' @param fileclass Used in reading fasta file to set file class.
91         #' @param Ncol Used in reading binary file to set numbers of columns in data.frame.
92         #' @param Nrow Used in reading binary file to set numbers of rows in data.frame size.
93         #' @examples
94         #' \dontrun{
95         #' collection <- Collection$new(arv, collectionUUID)
96         #' readFile <- collection$readArvFile(arvadosFile, istable = 'yes')                    # table
97         #' readFile <- collection$readArvFile(arvadosFile, istable = 'no')                     # text
98         #' readFile <- collection$readArvFile(arvadosFile)                                     # xlsx, csv, tsv, rds, rdata
99         #' readFile <- collection$readArvFile(arvadosFile, fileclass = 'fasta')                # fasta
100         #' readFile <- collection$readArvFile(arvadosFile, Ncol= 4, Nrow = 32)                 # binary, only numbers
101         #' readFile <- collection$readArvFile(arvadosFile, Ncol = 5, Nrow = 150, istable = "factor") # binary with factor or text
102         #' }
103         readArvFile = function(file, con, sep = ',', istable = NULL, fileclass = "SeqFastadna", Ncol = NULL, Nrow = NULL, wantedFunction = NULL)
104         {
105             arvFile <- self$get(file)
106             FileName <- arvFile$getName()
107             FileName <- tolower(FileName)
108             FileFormat <- gsub(".*\\.", "", FileName)
109
110             # set enviroment
111             ARVADOS_API_TOKEN <- Sys.getenv("ARVADOS_API_TOKEN")
112             ARVADOS_API_HOST <- Sys.getenv("ARVADOS_API_HOST")
113             my_collection <- self$uuid
114             key <- gsub("/", "_", ARVADOS_API_TOKEN)
115
116             Sys.setenv(
117                 "AWS_ACCESS_KEY_ID" = key,
118                 "AWS_SECRET_ACCESS_KEY" = key,
119                 "AWS_DEFAULT_REGION" = "collections",
120                 "AWS_S3_ENDPOINT" = gsub("api[.]", "", ARVADOS_API_HOST))
121
122             if (FileFormat == "txt") {
123                 if (is.null(istable)){
124                     stop(paste('You need to paste whether it is a text or table file'))
125                 } else if (istable == 'no') {
126                     fileContent <- arvFile$read("text") # used to read
127                     fileContent <- gsub("[\r\n]", " ", fileContent)
128                 } else if (istable == 'yes') {
129                     arvConnection <- arvFile$connection("r") # used to make possible use different function later
130                     fileContent <- read.table(arvConnection)
131                 }
132             }
133             else if (FileFormat  == "xlsx") {
134                 fileContent <- aws.s3::s3read_using(FUN = openxlsx::read.xlsx, object = file, bucket = my_collection)
135             }
136             else if (FileFormat == "csv" || FileFormat == "tsv") {
137                 arvConnection <- arvFile$connection("r")
138                 if (FileFormat == "tsv"){
139                     mytable <- read.table(arvConnection, sep = '\t')
140                 } else if (FileFormat == "csv" & sep == '\t') {
141                     mytable <- read.table(arvConnection, sep = '\t')
142                 } else if (FileFormat == "csv") {
143                     mytable <- read.table(arvConnection, sep = ',')
144                 } else {
145                     stop(paste('File format not supported, use arvadosFile$connection() and customise it'))
146                 }
147             }
148             else if (FileFormat == "fasta") {
149                 fastafile <- aws.s3::s3read_using(FUN = seqinr::read.fasta, as.string = TRUE, object = file, bucket = my_collection)
150             }
151             else if (FileFormat == "dat" || FileFormat == "bin") {
152                 fileContent <- gzcon(arvFile$connection("rb"))
153
154                 # function to precess data to binary format
155                 read_bin.file <- function(fileContent) {
156                     # read binfile
157                     column.names <- readBin(fileContent, character(), n = Ncol)
158                     bindata <- readBin(fileContent, numeric(), Nrow*Ncol+Ncol)
159                     # check
160                     res <- which(bindata < 0.0000001)
161                     if (is.list(res)) {
162                         bindata <- bindata[-res]
163                     } else {
164                         bindata <- bindata
165                     }
166                     # make a dataframe
167                     data <- data.frame(matrix(data = NA, nrow = Nrow, ncol = Ncol))
168                     for (i in 1:Ncol) {
169                         data[,i] <- bindata[(1+Nrow*(i-1)):(Nrow*i)]
170                     }
171                     colnames(data) = column.names
172
173                     len <- which(is.na(data[,Ncol])) # error if sth went wrong
174                     if (length(len) == 0) {
175                         data
176                     } else {
177                         stop(paste("there is a factor or text in the table, customize the function by typing more arguments"))
178                     }
179                 }
180                 if (is.null(Nrow) | is.null(Ncol)){
181                     stop(paste('You need to specify numbers of columns and rows'))
182                 }
183                 if (is.null(istable)) {
184                     fileContent <- read_bin.file(fileContent) # call a function
185                 } else if (istable == "factor") { # if there is a table with col name
186                     fileContent <- read_bin.file(fileContent)
187                 }
188             }
189             else if (FileFormat == "rds" || FileFormat == "rdata") {
190                 arvConnection <- arvFile$connection("rb")
191                 mytable <- readRDS(gzcon(arvConnection))
192             }
193             else {
194                 stop(parse(('File format not supported, use arvadosFile$connection() and customise it')))
195             }
196         },
197
198         #' @description
199         #' Write file content
200         #' @param name Name of the file.
201         #' @param file File to be saved.
202         #' @param istable Used in writing txt file to check if the file is table or not.
203         #' @examples
204         #' \dontrun{
205         #' collection <- Collection$new(arv, collectionUUID)
206         #' writeFile <- collection$writeFile(name = "myoutput.csv", file = file, fileFormat = "csv", istable = NULL, collectionUUID = collectionUUID)             # csv
207         #' writeFile <- collection$writeFile(name = "myoutput.tsv", file = file, fileFormat = "tsv", istable = NULL, collectionUUID = collectionUUID)             # tsv
208         #' writeFile <- collection$writeFile(name = "myoutput.fasta", file = file, fileFormat = "fasta", istable = NULL, collectionUUID = collectionUUID)         # fasta
209         #' writeFile <- collection$writeFile(name = "myoutputtable.txt", file = file, fileFormat = "txt", istable = "yes", collectionUUID = collectionUUID)       # txt table
210         #' writeFile <- collection$writeFile(name = "myoutputtext.txt", file = file, fileFormat = "txt", istable = "no", collectionUUID = collectionUUID)         # txt text
211         #' writeFile <- collection$writeFile(name = "myoutputbinary.dat", file = file, fileFormat = "dat", collectionUUID = collectionUUID)                       # binary
212         #' writeFile <- collection$writeFile(name = "myoutputxlsx.xlsx", file = file, fileFormat = "xlsx", collectionUUID = collectionUUID)                       # xlsx
213         #' }
214         writeFile = function(name, file, collectionUUID, fileFormat, istable = NULL, seqName = NULL)
215         {
216             # set enviroment
217             ARVADOS_API_TOKEN <- Sys.getenv("ARVADOS_API_TOKEN")
218             ARVADOS_API_HOST <- Sys.getenv("ARVADOS_API_HOST")
219             my_collection <- self$uuid
220             key <- gsub("/", "_", ARVADOS_API_TOKEN)
221
222             Sys.setenv(
223                 "AWS_ACCESS_KEY_ID" = key,
224                 "AWS_SECRET_ACCESS_KEY" = key,
225                 "AWS_DEFAULT_REGION" = "collections",
226                 "AWS_S3_ENDPOINT" = gsub("api[.]", "", ARVADOS_API_HOST))
227
228             # save file
229             if (fileFormat == "txt") {
230                 if (istable == "yes") {
231                     aws.s3::s3write_using(file, FUN = write.table, object = name, bucket = collectionUUID)
232                 } else if (istable == "no") {
233                     aws.s3::s3write_using(file, FUN = writeChar, object = name, bucket = collectionUUID)
234                 } else {
235                     stop(paste("Specify parametr istable"))
236                 }
237             } else if (fileFormat == "csv") {
238                 aws.s3::s3write_using(file, FUN = write.csv, object = name, bucket = collectionUUID)
239             } else if (fileFormat == "tsv") {
240                 aws.s3::s3write_using(file, FUN = write.table, row.names = FALSE, sep = "\t", object = name, bucket = collectionUUID)
241             } else if (fileFormat == "fasta") {
242                 aws.s3::s3write_using(file, FUN = seqinr::write.fasta, name = seqName, object = name, bucket = collectionUUID)
243             } else if (fileFormat == "xlsx") {
244                 aws.s3::s3write_using(file, FUN = openxlsx::write.xlsx, object = name, bucket = collectionUUID)
245             } else if (fileFormat == "dat" || fileFormat == "bin") {
246                 aws.s3::s3write_using(file, FUN = writeBin, object = name, bucket = collectionUUID)
247             } else {
248                 stop(parse(('File format not supported, use arvadosFile$connection() and customise it')))
249             }
250         },
251
252         #' @description
253         #' Creates one or more ArvadosFiles and adds them to the collection at specified path.
254         #' @param files Content to be created.
255         #' @examples
256         #' \dontrun{
257         #' collection <- arv$collections_create(name = collectionTitle, description = collectionDescription, owner_uuid = collectionOwner, properties = list("ROX37196928443768648" = "ROX37742976443830153"))
258         #' }
259         create = function(files)
260         {
261             if(is.null(private$tree))
262                 private$generateCollectionTreeStructure()
263
264             if(is.character(files))
265             {
266                 sapply(files, function(file)
267                 {
268                     childWithSameName <- self$get(file)
269                     if(!is.null(childWithSameName))
270                         stop("Destination already contains file with same name.")
271
272                     newTreeBranch <- private$tree$createBranch(file)
273                     private$tree$addBranch(private$tree$getTree(), newTreeBranch)
274
275                     private$REST$create(file, self$uuid)
276                     newTreeBranch$setCollection(self)
277                     newTreeBranch
278                 })
279             }
280             else
281             {
282                 stop(paste0("Expected character vector, got ",
283                             paste0("(", paste0(class(files), collapse = ", "), ")"),
284                             "."))
285             }
286         },
287
288         #' @description
289         #' Remove one or more files from the collection.
290         #' @param paths Content to be removed.
291         #' @examples
292         #' \dontrun{
293         #' collection$remove(fileName.format)
294         #' }
295         remove = function(paths)
296         {
297             if(is.null(private$tree))
298                 private$generateCollectionTreeStructure()
299
300             if(is.character(paths))
301             {
302                 sapply(paths, function(filePath)
303                 {
304                     filePath <- trimFromEnd(filePath, "/")
305                     file <- self$get(filePath)
306
307                     if(is.null(file))
308                         stop(paste("File", filePath, "doesn't exist."))
309
310                     parent <- file$getParent()
311
312                     if(is.null(parent))
313                         stop("You can't delete root folder.")
314
315                     parent$remove(file$getName())
316                 })
317
318                 "Content removed"
319             }
320             else
321             {
322                 stop(paste0("Expected character vector, got ",
323                             paste0("(", paste0(class(paths), collapse = ", "), ")"),
324                             "."))
325             }
326         },
327
328         #' @description
329         #' Moves ArvadosFile or Subcollection to another location in the collection.
330         #' @param content Content to be moved.
331         #' @param destination Path to move content.
332         #' @examples
333         #' \dontrun{
334         #' collection$move("fileName.format", path)
335         #' }
336         move = function(content, destination)
337         {
338             if(is.null(private$tree))
339                 private$generateCollectionTreeStructure()
340
341             content <- trimFromEnd(content, "/")
342
343             elementToMove <- self$get(content)
344
345             if(is.null(elementToMove))
346                 stop("Content you want to move doesn't exist in the collection.")
347
348             elementToMove$move(destination)
349         },
350
351         #' @description
352         #' Copies ArvadosFile or Subcollection to another location in the collection.
353         #' @param content Content to be moved.
354         #' @param destination Path to move content.
355         #' @examples
356         #' \dontrun{
357         #' copied <- collection$copy("oldName.format", "newName.format")
358         #' }
359         copy = function(content, destination)
360         {
361             if(is.null(private$tree))
362                 private$generateCollectionTreeStructure()
363
364             content <- trimFromEnd(content, "/")
365
366             elementToCopy <- self$get(content)
367
368             if(is.null(elementToCopy))
369                 stop("Content you want to copy doesn't exist in the collection.")
370
371             elementToCopy$copy(destination)
372         },
373
374         #' @description
375         #' Refreshes the environment.
376         #' @examples
377         #' \dontrun{
378         #' collection$refresh()
379         #' }
380         refresh = function()
381         {
382             if(!is.null(private$tree))
383             {
384                 private$tree$getTree()$setCollection(NULL, setRecursively = TRUE)
385                 private$tree <- NULL
386             }
387         },
388
389         #' @description
390         #' Returns collections file content as character vector.
391         #' @examples
392         #' \dontrun{
393         #' list <- collection$getFileListing()
394         #' }
395         getFileListing = function()
396         {
397             if(is.null(private$tree))
398                 private$generateCollectionTreeStructure()
399
400             content <- private$REST$getCollectionContent(self$uuid)
401             content[order(tolower(content))]
402         },
403
404         #' @description
405         #' If relativePath is valid, returns ArvadosFile or Subcollection specified by relativePath, else returns NULL.
406         #' @param relativePath Path from content is taken.
407         #' @examples
408         #' \dontrun{
409         #' arvadosFile <- collection$get(fileName)
410         #' }
411         get = function(relativePath)
412         {
413             if(is.null(private$tree))
414                 private$generateCollectionTreeStructure()
415
416             private$tree$getElement(relativePath)
417         },
418
419         getRESTService = function() private$REST,
420         setRESTService = function(newRESTService) private$REST <- newRESTService
421     ),
422     private = list(
423
424         REST        = NULL,
425         #' @tree beautiful tree of sth
426         tree        = NULL,
427         fileContent = NULL,
428
429         generateCollectionTreeStructure = function(relativePath = NULL)
430         {
431             if(is.null(self$uuid))
432                 stop("Collection uuid is not defined.")
433
434             if(is.null(private$REST))
435                 stop("REST service is not defined.")
436
437             private$fileContent <- private$REST$getCollectionContent(self$uuid, relativePath)
438             private$tree <- CollectionTree$new(private$fileContent, self)
439         }
440     ),
441
442     cloneable = FALSE
443 )
444
445 #' print.Collection
446 #'
447 #' Custom print function for Collection class
448 #'
449 #' @param x Instance of Collection class
450 #' @param ... Optional arguments.
451 #' @export
452 print.Collection = function(x, ...)
453 {
454     cat(paste0("Type: ", "\"", "Arvados Collection", "\""), sep = "\n")
455     cat(paste0("uuid: ", "\"", x$uuid,               "\""), sep = "\n")
456 }