diff --git a/NAMESPACE b/NAMESPACE index 613ec1a..765a14b 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -4,10 +4,14 @@ export("%>%") export(HHMMSSmmm_to_ms) export(assign_constants) export(check_ssl_certs) +export(download_folder_asset) +export(download_folder_assets_fr_df) +export(download_folder_zip) export(download_session_asset) export(download_session_assets_fr_df) export(download_session_csv) export(download_session_zip) +export(download_single_folder_asset_fr_df) export(download_single_session_asset_fr_df) export(download_video) export(download_volume_zip) diff --git a/R/CONSTANTS.R b/R/CONSTANTS.R index 000da2e..f6d676b 100644 --- a/R/CONSTANTS.R +++ b/R/CONSTANTS.R @@ -32,6 +32,8 @@ API_SESSION_DOWNLOAD_LINK <- "/volumes/%s/sessions/%s/download-link/" API_SESSION_CSV_DOWNLOAD_LINK <- "/volumes/%s/sessions/%s/csv-download-link/" API_FOLDER_DETAIL <- "/volumes/%s/folders/%s/" API_FOLDER_FILES <- "/volumes/%s/folders/%s/files/" +API_FOLDER_DOWNLOAD_LINK <- "/volumes/%s/folders/%s/download-link/" +API_FOLDER_FILE_DOWNLOAD_LINK <- "/volumes/%s/folders/%s/files/%s/download-link/" API_VOLUME_DOWNLOAD_LINK <- "/volumes/%s/download-link/" API_VOLUME_CSV_DOWNLOAD_LINK <- "/volumes/%s/csv-download-link/" API_SEARCH_VOLUMES <- "/search/volumes/" diff --git a/R/download_folder_asset.R b/R/download_folder_asset.R new file mode 100644 index 0000000..9ad295d --- /dev/null +++ b/R/download_folder_asset.R @@ -0,0 +1,127 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Download a Folder Asset via Signed Link. +#' +#' @description +#' Databrary serves folder-scoped assets through signed URLs. This helper +#' requests the signed link for a folder asset and streams the file to the +#' specified directory. +#' +#' @param vol_id Integer. Volume identifier containing the folder. Default is 1. +#' @param folder_id Integer. Folder identifier within the volume. Default is 1. +#' @param asset_id Integer. Asset identifier within the folder. Default is 1. +#' @param file_name Optional character string. File name to use when saving the +#' asset. Defaults to the API-provided file name. +#' @param target_dir Character string. Directory where the file will be saved. +#' Default is `tempdir()`. +#' @param rq An `httr2` request object. Default is `NULL`, in which case a +#' default authenticated request is generated. +#' @param timeout_secs Numeric. Timeout (seconds) applied to the download +#' request. Default is `REQUEST_TIMEOUT`. +#' +#' @returns The path to the downloaded file (character string) or `NULL` if the +#' download fails. +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' download_folder_asset() # Default public asset in folder 1 of volume 1 +#' download_folder_asset(vol_id = 1, folder_id = 2, asset_id = 3, +#' file_name = "example.mp4") +#' } +#' } +#' +#' @export +download_folder_asset <- function(vol_id = 1, + folder_id = 1, + asset_id = 1, + file_name = NULL, + target_dir = tempdir(), + timeout_secs = REQUEST_TIMEOUT, + vb = options::opt("vb"), + rq = NULL) { + assertthat::assert_that(length(vol_id) == 1) + assertthat::assert_that(is.numeric(vol_id)) + assertthat::assert_that(vol_id >= 1) + + assertthat::assert_that(length(folder_id) == 1) + assertthat::assert_that(is.numeric(folder_id)) + assertthat::assert_that(folder_id >= 1) + + assertthat::assert_that(length(asset_id) == 1) + assertthat::assert_that(is.numeric(asset_id)) + assertthat::assert_that(asset_id >= 1) + + if (!is.null(file_name)) { + assertthat::assert_that(length(file_name) == 1) + assertthat::assert_that(is.character(file_name)) + } + + assertthat::assert_that(length(target_dir) == 1) + assertthat::assert_that(is.character(target_dir)) + assertthat::assert_that(dir.exists(target_dir)) + + assertthat::is.number(timeout_secs) + assertthat::assert_that(length(timeout_secs) == 1) + assertthat::assert_that(timeout_secs > 0) + + assertthat::assert_that(length(vb) == 1) + assertthat::assert_that(is.logical(vb)) + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + path <- sprintf(API_FOLDER_FILE_DOWNLOAD_LINK, vol_id, folder_id, asset_id) + link <- request_signed_download_link(path = path, rq = rq, vb = vb) + + if (is.null(link)) { + return(NULL) + } + + resolved_name <- if (!is.null(file_name)) { + file_name + } else if (!is.null(link$file_name)) { + link$file_name + } else { + paste0( + folder_id, + "-", + asset_id, + "-", + format(Sys.time(), "%F-%H%M-%S"), + ".bin" + ) + } + + dest_path <- file.path(target_dir, resolved_name) + + if (file.exists(dest_path)) { + dest_path <- file.path( + target_dir, + paste0( + tools::file_path_sans_ext(resolved_name), + "-", + format(Sys.time(), "%F-%H%M-%S"), + ifelse( + nzchar(tools::file_ext(resolved_name)), + paste0(".", tools::file_ext(resolved_name)), + "" + ) + ) + ) + } + + download_signed_file( + download_url = link$download_url, + dest_path = dest_path, + timeout_secs = timeout_secs, + vb = vb + ) +} + + + diff --git a/R/download_folder_assets_fr_df.R b/R/download_folder_assets_fr_df.R new file mode 100644 index 0000000..ae1ef3d --- /dev/null +++ b/R/download_folder_assets_fr_df.R @@ -0,0 +1,114 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Download Multiple Assets From a Folder Data Frame. +#' +#' @description +#' Iterates over a data frame of folder assets, requesting signed download links +#' for each asset and saving them to disk. Designed to work with +#' `list_folder_assets()` output. +#' +#' @param folder_df Data frame describing assets. Must include `vol_id`, +#' `folder_id`, `asset_id`, and `asset_name` columns. +#' @param target_dir Character string. Base directory for downloads. Defaults to +#' `tempdir()`. +#' @param add_folder_subdir Logical. When `TRUE`, creates a subdirectory per +#' folder inside `target_dir`. +#' @param overwrite Logical. When `FALSE`, the function aborts if the target +#' directory already exists. +#' @param make_portable_fn Logical. When `TRUE`, filenames are sanitized via +#' `make_fn_portable()`. +#' @param timeout_secs Numeric. Timeout applied to each download request. +#' @param rq An optional `httr2` request object reused when requesting signed +#' links. +#' +#' @returns Character vector of downloaded file paths or `NULL` if the request +#' fails before any downloads start. +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' assets <- list_folder_assets(folder_id = 1, vol_id = 1) +#' download_folder_assets_fr_df(assets, vb = TRUE) +#' } +#' } +#' +#' @export +download_folder_assets_fr_df <- + function(folder_df = list_folder_assets(vol_id = 1), + target_dir = tempdir(), + add_folder_subdir = TRUE, + overwrite = TRUE, + make_portable_fn = FALSE, + timeout_secs = REQUEST_TIMEOUT_VERY_LONG, + vb = options::opt("vb"), + rq = NULL) { + assertthat::assert_that(is.data.frame(folder_df)) + required_cols <- c("vol_id", "folder_id", "asset_id", "asset_name") + missing_cols <- setdiff(required_cols, names(folder_df)) + if (length(missing_cols) > 0) { + stop( + "folder_df is missing required columns: ", + paste(missing_cols, collapse = ", "), + call. = FALSE + ) + } + + assertthat::assert_that(length(target_dir) == 1) + assertthat::assert_that(is.character(target_dir)) + if (dir.exists(target_dir)) { + if (!overwrite) { + if (vb) { + message("`overwrite` is FALSE. Cannot continue.") + } + return(NULL) + } + } else { + dir.create(target_dir, recursive = TRUE, showWarnings = FALSE) + } + assertthat::is.writeable(target_dir) + + assertthat::assert_that(length(add_folder_subdir) == 1) + assertthat::assert_that(is.logical(add_folder_subdir)) + + assertthat::assert_that(length(overwrite) == 1) + assertthat::assert_that(is.logical(overwrite)) + + assertthat::assert_that(length(make_portable_fn) == 1) + assertthat::assert_that(is.logical(make_portable_fn)) + + assertthat::is.number(timeout_secs) + assertthat::assert_that(length(timeout_secs) == 1) + assertthat::assert_that(timeout_secs > 0) + + assertthat::assert_that(length(vb) == 1) + assertthat::assert_that(is.logical(vb)) + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + if (vb) { + message("Downloading n=", nrow(folder_df), " files to ", target_dir) + } + + purrr::map( + seq_len(nrow(folder_df)), + download_single_folder_asset_fr_df, + folder_df = folder_df, + target_dir = target_dir, + add_folder_subdir = add_folder_subdir, + overwrite = overwrite, + make_portable_fn = make_portable_fn, + timeout_secs = timeout_secs, + vb = vb, + rq = rq, + .progress = vb + ) |> + purrr::list_c() + } + + + diff --git a/R/download_folder_zip.R b/R/download_folder_zip.R new file mode 100644 index 0000000..4323306 --- /dev/null +++ b/R/download_folder_zip.R @@ -0,0 +1,54 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Request a Signed ZIP Download for a Folder. +#' +#' @description +#' Folder-level ZIP archives are prepared asynchronously by the Django API. +#' Calling `download_folder_zip()` queues the job and returns a processing task +#' descriptor. When the archive is ready, Databrary emails a signed download +#' link to the authenticated user. +#' +#' @param vol_id Volume identifier for the folder. +#' @param folder_id Folder identifier scoped within the specified volume. +#' @param rq An `httr2` request object. Default is `NULL`, in which case a +#' default authenticated request is generated. +#' +#' @returns A list describing the processing task (`status`, `message`, +#' `task_id`) or `NULL` when the request fails. +#' +#' @inheritParams options_params +#' +#' @examples +#' \donttest{ +#' \dontrun{ +#' download_folder_zip(vol_id = 1, folder_id = 1) +#' } +#' } +#' +#' @export +download_folder_zip <- function(vol_id = 1, + folder_id = 1, + vb = options::opt("vb"), + rq = NULL) { + assertthat::assert_that(length(vol_id) == 1) + assertthat::assert_that(is.numeric(vol_id)) + assertthat::assert_that(vol_id >= 1) + + assertthat::assert_that(length(folder_id) == 1) + assertthat::assert_that(is.numeric(folder_id)) + assertthat::assert_that(folder_id >= 1) + + assertthat::assert_that(length(vb) == 1) + assertthat::assert_that(is.logical(vb)) + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + path <- sprintf(API_FOLDER_DOWNLOAD_LINK, vol_id, folder_id) + request_processing_task(path = path, rq = rq, vb = vb) +} + + + diff --git a/R/download_session_asset.R b/R/download_session_asset.R index 8700039..89217ea 100644 --- a/R/download_session_asset.R +++ b/R/download_session_asset.R @@ -3,175 +3,121 @@ #' NULL -#' Download Asset From Databrary. +#' Download an Asset via Signed Link. #' -#' @description Databrary stores file types (assets) of many types. This -#' function downloads an asset based on its system-unique integer identifer -#' (asset_id) and system-unique session (slot) identifier (session_id). +#' @description +#' Databrary serves assets through short-lived, signed URLs. This helper +#' requests the signed link for a session asset and streams the file to the +#' requested directory. #' -#' @param asset_id An integer. Asset id for target file. Default is 1. -#' @param session_id An integer. Slot/session number where target file is -#' stored. Default is 9807. -#' @param file_name A character string. Name for downloaded file. Default is NULL. +#' @param vol_id Integer. Volume identifier. Default is 1. +#' @param session_id Integer. Session identifier. Default is 9807. +#' @param asset_id Integer. Asset identifier within the session. Default is 1. +#' @param file_name Optional character string. Target file name. Defaults to the +#' API-provided file name. +#' @param target_dir Character string. Directory where the file will be saved. +#' Default is `tempdir()`. +#' @param rq An `httr2` request object. Default is `NULL`, in which case a +#' default authenticated request is generated. +#' @param timeout_secs Numeric. Timeout (seconds) applied to the download +#' request. Default is `REQUEST_TIMEOUT`. #' -#' @param target_dir A character string. Directory to save the downloaded file. -#' Default is a temporary directory given by a call to `tempdir()`. -#' @param rq A list in the form of an `httr2` request object. Default is NULL. -#' @param timeout_secs An integer constant. The default value, defined in -#' CONSTANTS.R is REQUEST_TIMEOUT. This value determines the default timeout -#' value for the httr2 request object. When downloading large files, it can be -#' useful to set this value to a large number. -#' -#' @returns Full file name to the asset or NULL. +#' @returns The path to the downloaded file (character string) or `NULL` if the +#' download fails. #' #' @inheritParams options_params #' #' @examples #' \donttest{ #' \dontrun{ -#' download_session_asset() # Download's 'numbers' file from volume 1. -#' download_session_asset(asset_id = 11643, session_id = 9825, file_name = "rdk.mp4") -#' # Downloads a display with a random dot kinematogram (RDK). +#' download_session_asset() # Default public asset in volume 1 +#' download_session_asset(vol_id = 1, session_id = 9825, asset_id = 11643, +#' file_name = "rdk.mp4") #' } #' } #' @export -download_session_asset <- function(asset_id = 1, +download_session_asset <- function(vol_id = 1, session_id = 9807, + asset_id = 1, file_name = NULL, - #target_dir = paste0("./", session_id), target_dir = tempdir(), timeout_secs = REQUEST_TIMEOUT, vb = options::opt("vb"), rq = NULL) { - # Check parameters + assertthat::assert_that(length(vol_id) == 1) + assertthat::assert_that(is.numeric(vol_id)) + assertthat::assert_that(vol_id >= 1) + + assertthat::assert_that(length(session_id) == 1) + assertthat::assert_that(is.numeric(session_id)) + assertthat::assert_that(session_id >= 1) + assertthat::assert_that(length(asset_id) == 1) assertthat::assert_that(is.numeric(asset_id)) assertthat::assert_that(asset_id >= 1) - - assertthat::assert_that(is.numeric(session_id)) - assertthat::assert_that(length(session_id) == 1) - assertthat::assert_that(session_id >= 1) - - assertthat::assert_that(is.character(target_dir)) + + if (!is.null(file_name)) { + assertthat::assert_that(length(file_name) == 1) + assertthat::assert_that(is.character(file_name)) + } + assertthat::assert_that(length(target_dir) == 1) + assertthat::assert_that(is.character(target_dir)) assertthat::assert_that(dir.exists(target_dir)) - + assertthat::is.number(timeout_secs) assertthat::assert_that(length(timeout_secs) == 1) assertthat::assert_that(timeout_secs > 0) - + assertthat::assert_that(length(vb) == 1) assertthat::assert_that(is.logical(vb)) - - assertthat::assert_that(is.null(rq) | - ("httr2_request" %in% class(rq))) - - # Handle NULL rq - if (is.null(rq)) { - if (vb) { - message("NULL request object. Will generate default.") - message("Not logged in. Only public information will be returned.") - } - rq <- databraryr::make_default_request() + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + path <- sprintf(API_FILES_DOWNLOAD_LINK, vol_id, session_id, asset_id) + link <- request_signed_download_link(path = path, rq = rq, vb = vb) + + if (is.null(link)) { + return(NULL) } - - this_rq <- rq %>% - httr2::req_url(sprintf(DOWNLOAD_FILE, session_id, asset_id)) %>% - httr2::req_progress() - - if (vb) - message( - "Attempting to download file with asset_id ", - asset_id, - " from session_id ", + + resolved_name <- if (!is.null(file_name)) { + file_name + } else if (!is.null(link$file_name)) { + link$file_name + } else { + paste0( session_id, - "." + "-", + asset_id, + "-", + format(Sys.time(), "%F-%H%M-%S"), + ".bin" ) - - resp <- tryCatch( - httr2::req_perform(this_rq), - httr2_error = function(cnd) { - if (vb) - message( - "Error downloading file with asset_id ", - asset_id, - " from session_id ", - session_id, - "." - ) - NULL - } - - ) - - if (is.null(resp)) { - message("Cannot access requested resource on Databrary. Exiting.") - return(resp) - } - - # Gather asset format info - format_mimetype <- NULL - format_extension <- NULL - this_file_extension <- list_asset_formats(vb = vb) %>% - dplyr::filter(httr2::resp_content_type(resp) == format_mimetype) %>% - dplyr::select(format_extension) %>% - as.character() - - # Check file name or generate - if (is.null(this_file_extension)) { - if (vb) - message("No matching file extension for ", - httr2::resp_content_type(resp)) - return(NULL) - } - - if (is.null(file_name)) { - if (vb) - message("Missing file name, creating temporary file name.") - file_name <- tempfile(paste0(session_id, "_", asset_id, "_"), - fileext = paste0(".", this_file_extension)) } - assertthat::is.string(file_name) - - if (file.exists(file_name)) { - if (vb) - message("File exists. Generating new unique name.\n") - file_name <- file.path(dirname(file_name), - paste0( - session_id, - "-", - asset_id, - "-", - format(Sys.time(), "%F-%H%M-%S"), - paste0(".", this_file_extension) - )) - } - - if (!(this_file_extension == xfun::file_ext(file_name))) { - if (vb) - message("File name ", - file_name, - " doesn't match extension ", - this_file_extension) - return(NULL) + + dest_path <- file.path(target_dir, resolved_name) + + if (file.exists(dest_path)) { + dest_path <- file.path( + target_dir, + paste0( + tools::file_path_sans_ext(resolved_name), + "-", + format(Sys.time(), "%F-%H%M-%S"), + ifelse( + nzchar(tools::file_ext(resolved_name)), + paste0(".", tools::file_ext(resolved_name)), + "" + ) + ) + ) } - - write_file <- tryCatch( - error = function(cnd) { - if (vb) - message("Failure writing file ", file_name) - NULL - }, - { - file_con <- file(file_name, "wb") - writeBin(resp$body, file_con) - close(file_con) - } + + download_signed_file( + download_url = link$download_url, + dest_path = dest_path, + timeout_secs = timeout_secs, + vb = vb ) - - if (!is.null(write_file)) { - file_name - } else { - write_file - } } diff --git a/R/download_session_assets_fr_df.R b/R/download_session_assets_fr_df.R index 4e2c1bd..b34b5fc 100644 --- a/R/download_session_assets_fr_df.R +++ b/R/download_session_assets_fr_df.R @@ -3,38 +3,37 @@ #' NULL -#' Download Asset From A Databrary Session. +#' Download Multiple Assets From a Session Data Frame. #' -#' @description Databrary stores file types (assets) of many types. This -#' function downloads assets in a data frame generated by list_session_assets(). +#' @description +#' Iterates over a data frame of session assets, requesting signed download +#' links for each asset and saving them to disk. Designed to work with +#' `list_session_assets()` or `list_volume_session_assets()` output. #' -#' @param session_df A data frame as generated by list_session_assets_2(). -#' @param target_dir A character string. Directory to save the downloaded file. -#' Default is directory named after the session_id. -#' @param add_session_subdir A logical value. Add add the session name to the -#' file path so that files are in a subdirectory specific to the session. Default -#' is TRUE. -#' @param overwrite A logical value. Overwrite an existing file. Default is TRUE. -#' @param make_portable_fn A logical value. Replace characters in file names -#' that are not broadly portable across file systems. Default is FALSE. -#' @param timeout_secs An integer. The seconds an httr2 request will run before -#' timing out. Default is 600 (10 min). This is to handle very large files. -#' @param rq A list in the form of an `httr2` request object. Default is NULL. +#' @param session_df Data frame describing assets. Must include `vol_id`, +#' `session_id`, `asset_id`, and `asset_name` columns. +#' @param target_dir Character string. Base directory for downloads. Defaults to +#' `tempdir()`. +#' @param add_session_subdir Logical. When `TRUE`, creates a subdirectory per +#' session inside `target_dir`. +#' @param overwrite Logical. When `FALSE`, the function aborts if the target +#' directory already exists. +#' @param make_portable_fn Logical. When `TRUE`, filenames are sanitized via +#' `make_fn_portable()`. +#' @param timeout_secs Numeric. Timeout applied to each download request. +#' @param rq An optional `httr2` request object reused when requesting signed +#' links. #' -#' @returns Full file names to the downloaded assets or NULL. +#' @returns Character vector of downloaded file paths or `NULL` if the request +#' fails before any downloads start. #' #' @inheritParams options_params #' #' @examples #' \donttest{ #' \dontrun{ -#' download_session_assets_fr_df() # Downloads all of the files from session -#' 9807 in Databrary volume 1. -#' -#' # Just the CSVs -#' v1 <- list_session_assets() -#' v1_csv <- dplyr::filter(v1, format_extension == "csv") -#' download_session_assets_fr_df(v1_csv, vb = TRUE) +#' assets <- list_session_assets(vol_id = 1, session_id = 9807) +#' download_session_assets_fr_df(assets, vb = TRUE) #' } #' } #' @export @@ -47,61 +46,57 @@ download_session_assets_fr_df <- timeout_secs = REQUEST_TIMEOUT_VERY_LONG, vb = options::opt("vb"), rq = NULL) { - # Check parameters assertthat::assert_that(is.data.frame(session_df)) - assertthat::assert_that("session_id" %in% names(session_df)) - assertthat::assert_that("session_id" %in% names(session_df)) - assertthat::assert_that("asset_id" %in% names(session_df)) - assertthat::assert_that("format_extension" %in% names(session_df)) - assertthat::assert_that("asset_name" %in% names(session_df)) - + required_cols <- c("vol_id", "session_id", "asset_id", "asset_name") + missing_cols <- setdiff(required_cols, names(session_df)) + if (length(missing_cols) > 0) { + stop( + "session_df is missing required columns: ", + paste(missing_cols, collapse = ", "), + call. = FALSE + ) + } + assertthat::assert_that(length(target_dir) == 1) assertthat::assert_that(is.character(target_dir)) - if (!dir.exists(target_dir)) { - if (vb) { - message("Target directory not found: ", target_dir) - message("Creating: ", target_dir) - } - dir.create(target_dir, recursive = TRUE) - } else { - if (vb) - message("Target directory exists: ", target_dir) - if (overwrite) { - if (vb) - message("`overwrite` is TRUE. Overwriting directory: ", target_dir) - } else { - if (vb) + if (dir.exists(target_dir)) { + if (!overwrite) { + if (vb) { message("`overwrite` is FALSE. Cannot continue.") + } return(NULL) } + } else { + dir.create(target_dir, recursive = TRUE, showWarnings = FALSE) } assertthat::is.writeable(target_dir) - + assertthat::assert_that(length(add_session_subdir) == 1) assertthat::assert_that(is.logical(add_session_subdir)) - + assertthat::assert_that(length(overwrite) == 1) assertthat::assert_that(is.logical(overwrite)) - + assertthat::assert_that(length(make_portable_fn) == 1) assertthat::assert_that(is.logical(make_portable_fn)) - + assertthat::is.number(timeout_secs) assertthat::assert_that(length(timeout_secs) == 1) assertthat::assert_that(timeout_secs > 0) - + assertthat::assert_that(length(vb) == 1) assertthat::assert_that(is.logical(vb)) - - assertthat::assert_that(is.null(rq) | - ("httr2_request" %in% class(rq))) - - if (vb) - message("Downloading n=", dim(session_df)[1], " files to /", target_dir) + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + if (vb) { + message("Downloading n=", nrow(session_df), " files to ", target_dir) + } + purrr::map( - 1:dim(session_df)[1], + seq_len(nrow(session_df)), download_single_session_asset_fr_df, - session_df, + session_df = session_df, target_dir = target_dir, add_session_subdir = add_session_subdir, overwrite = overwrite, @@ -109,7 +104,7 @@ download_session_assets_fr_df <- timeout_secs = timeout_secs, vb = vb, rq = rq, - .progress = TRUE + .progress = vb ) |> purrr::list_c() } diff --git a/R/download_session_csv.R b/R/download_session_csv.R index 84dd924..87e6f40 100644 --- a/R/download_session_csv.R +++ b/R/download_session_csv.R @@ -3,108 +3,61 @@ #' NULL -#' Download Session Spreadsheet As CSV +#' Request a Session or Volume CSV Export. #' -#' @description Databrary generates a CSV-formated spreadsheet that summarizes -#' information about individual sessions. This command downloads that CSV file -#' as a temporary file or with a name specified by the user. +#' @description +#' The Django API generates CSV reports asynchronously. This function queues a +#' CSV export for a specific session when `session_id` is supplied, or for the +#' entire volume when `session_id` is `NULL`. The API delivers the final signed +#' download link via email once the export is ready. #' -#' @param vol_id An integer. Target volume number. Default is 1. -#' @param file_name A character string. Name for the output file. -#' Default is 'test.csv'. -#' @param target_dir A character string. Directory to save downloaded file. -#' Default is `tempdir()`. -#' @param as_df A logical value. Convert the data from a list to a data frame. -#' Default is FALSE. -#' @param rq An `httr2` request object. Default is NULL. +#' @param vol_id Integer. Target volume identifier. Default is 1. +#' @param session_id Optional integer. When provided, requests a session-level +#' CSV export. When `NULL`, a volume-level CSV export is requested. +#' @param rq An `httr2` request object. Default is `NULL`, meaning a default +#' authenticated request is generated. #' -#' @returns A character string that is the name of the downloaded file or a -#' data frame if `as_df` is TRUE. +#' @returns A list describing the processing task (`status`, `message`, +#' `task_id`) or `NULL` if the request fails. #' #' @inheritParams options_params #' #' @examples #' \donttest{ #' \dontrun{ -#' download_session_csv() # Downloads "session" CSV for volume 1 +#' # Request a volume-wide CSV export +#' download_session_csv(vol_id = 1) +#' +#' # Request a session-specific CSV export +#' download_session_csv(vol_id = 1, session_id = 9807) #' } #' } #' #' @export download_session_csv <- function(vol_id = 1, - file_name = "test.csv", - target_dir = tempdir(), - as_df = FALSE, + session_id = NULL, vb = options::opt("vb"), rq = NULL) { - # Check parameters assertthat::assert_that(length(vol_id) == 1) assertthat::assert_that(is.numeric(vol_id)) assertthat::assert_that(vol_id >= 1) - - assertthat::assert_that(length(file_name) == 1) - assertthat::assert_that(is.character(file_name)) - - assertthat::assert_that(length(target_dir) == 1) - assertthat::assert_that(is.character(target_dir)) - - assertthat::assert_that(length(as_df) == 1) - assertthat::assert_that(is.logical(as_df)) - + + if (!is.null(session_id)) { + assertthat::assert_that(length(session_id) == 1) + assertthat::assert_that(is.numeric(session_id)) + assertthat::assert_that(session_id >= 1) + } + assertthat::assert_that(length(vb) == 1) assertthat::assert_that(is.logical(vb)) - - assertthat::assert_that(is.null(rq) | - ("httr2_request" %in% class(rq))) - - # Handle NULL request - if (is.null(rq)) { - if (vb) { - message("NULL request object. Will generate default.") - message("Not logged in. Only public information will be returned.") - } - rq <- databraryr::make_default_request() - } - this_rq <- rq %>% - httr2::req_url(sprintf(GET_SESSION_CSV, vol_id)) - - if (vb) - message(paste0("Downloading spreadsheet from vol_id ", vol_id, '.')) - resp <- tryCatch( - httr2::req_perform(this_rq), - httr2_error = function(cnd) { - if (vb) - message("Error retrieving spreadsheet from vol_id ", vol_id, ".") - NULL - } - ) - - if (is.null(resp)) { - message("Cannot access requested resource on Databrary. Exiting.") - return(resp) - } - - if (vb) - message("Valid CSV downloaded from ", sprintf(GET_SESSION_CSV, vol_id)) - - resp_txt <- httr2::resp_body_string(resp) - df <- - readr::read_csv( - resp_txt, - show_col_types = FALSE, - col_types = readr::cols(.default = readr::col_character()) - ) %>% - # Replace dashes in column names with underscores - dplyr::rename_with( ~ gsub("-", "_", .x, fixed = TRUE)) - if (as_df == TRUE) { - df + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + path <- if (is.null(session_id)) { + sprintf(API_VOLUME_CSV_DOWNLOAD_LINK, vol_id) } else { - if (vb) - message("Saving CSV.") - assertthat::is.writeable(target_dir) - full_fn <- file.path(target_dir, file_name) - assertthat::is.string(full_fn) - readr::write_csv(df, full_fn) - full_fn + sprintf(API_SESSION_CSV_DOWNLOAD_LINK, vol_id, session_id) } + + request_processing_task(path = path, rq = rq, vb = vb) } diff --git a/R/download_session_zip.R b/R/download_session_zip.R index 18f9a44..440a1be 100644 --- a/R/download_session_zip.R +++ b/R/download_session_zip.R @@ -3,125 +3,49 @@ #' NULL -#' Download Zip Archive From Databrary Session. +#' Request a Signed ZIP Download for a Session. #' -#' @param vol_id Volume number. -#' @param session_id Slot/session number. -#' @param out_dir Directory to save output file. -#' @param file_name Name for downloaded file, default is 'test.zip'. -#' @param rq An `httr2` request object. Default is NULL. +#' @description +#' The Django API prepares session-level ZIP archives asynchronously. Calling +#' `download_session_zip()` triggers the job and returns a processing task +#' summary. Once the archive is ready, Databrary emails a signed download link +#' to the authenticated user. #' -#' @returns Full filename of the downloaded file. +#' @param vol_id Volume identifier that owns the session. +#' @param session_id Session identifier within the volume. +#' @param rq An `httr2` request object. Default is `NULL`, in which case a +#' default authenticated request is generated. +#' +#' @returns A list describing the processing task (`status`, `message`, +#' `task_id`) or `NULL` when the request fails. #' #' @inheritParams options_params #' #' @examples #' \donttest{ #' \dontrun{ -#' download_session_zip() # Downloads Zip Archive from volume 31, session 9803 +#' download_session_zip(vol_id = 31, session_id = 9803) #' } #' } #' #' @export download_session_zip <- function(vol_id = 31, session_id = 9803, - out_dir = tempdir(), - file_name = "test.zip", vb = options::opt("vb"), rq = NULL) { - # Check parameters assertthat::assert_that(length(vol_id) == 1) assertthat::assert_that(is.numeric(vol_id)) assertthat::assert_that(vol_id >= 1) - + assertthat::assert_that(length(session_id) == 1) assertthat::assert_that(is.numeric(session_id)) assertthat::assert_that(session_id >= 1) - - assertthat::assert_that(length(out_dir) == 1) - assertthat::assert_that(is.character(out_dir)) - - assertthat::assert_that(length(file_name) == 1) - assertthat::assert_that(is.character(file_name)) - + assertthat::assert_that(length(vb) == 1) assertthat::assert_that(is.logical(vb)) - - assertthat::assert_that(is.null(rq) | - ("httr2_request" %in% class(rq))) - - if (is.null(rq)) { - if (vb) { - message("NULL request object. Will generate default.") - message("Not logged in. Only public information will be returned.") - } - rq <- databraryr::make_default_request() - } - rq <- rq %>% - httr2::req_url(sprintf(GET_SESSION_ZIP, vol_id, session_id)) - - resp <- tryCatch( - httr2::req_perform(rq), - httr2_error = function(cnd) { - if (vb) - message("Error downloading zip from sprintf(GET_SESSION_ZIP, vol_id, - session_id)") - NULL - } - ) - - if (is.null(resp)) { - message("Cannot access requested resource on Databrary. Exiting.") - return(NULL) - } - - bin <- NULL - bin <- httr2::resp_body_raw(resp) - - if (is.null(bin)) { - if (vb) - message("Null file returned") - return(NULL) - } - - if (file_name == "test.zip") { - if (vb) { - if (vb) - message("File name unspecified. Generating unique name.") - } - file_name <- make_zip_fn_sess(out_dir, vol_id, session_id) - } - if (vb) { - if (vb) - message(paste0("Downloading zip file as: \n'", file_name, "'.")) - } - writeBin(bin, file_name) - file_name -} -#------------------------------------------------------------------------------- -make_zip_fn_sess <- function(out_dir, vol_id, session_id) { - # Check parameters - assertthat::is.string(out_dir) - assertthat::is.writeable(out_dir) - assertthat::assert_that(length(out_dir) == 1) - - assertthat::assert_that(length(vol_id) == 1) - assertthat::assert_that(is.numeric(vol_id)) - assertthat::assert_that(vol_id >= 1) - - assertthat::assert_that(length(session_id) == 1) - assertthat::assert_that(is.numeric(session_id)) - assertthat::assert_that(session_id >= 1) - - paste0( - out_dir, - "/vol-", - vol_id, - "-sess-", - session_id, - "-", - format(Sys.time(), "%F-%H%M-%S"), - ".zip" - ) + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + path <- sprintf(API_SESSION_DOWNLOAD_LINK, vol_id, session_id) + request_processing_task(path = path, rq = rq, vb = vb) } diff --git a/R/download_single_folder_asset_fr_df.R b/R/download_single_folder_asset_fr_df.R new file mode 100644 index 0000000..807c594 --- /dev/null +++ b/R/download_single_folder_asset_fr_df.R @@ -0,0 +1,150 @@ +#' @eval options::as_params() +#' @name options_params +#' +NULL + +#' Download a Single Folder Asset From a Data Frame Row. +#' +#' @description +#' Helper used by `download_folder_assets_fr_df()` to fetch a single asset via +#' the signed-download workflow. +#' +#' @param i Integer. Index of the asset within `folder_df`. +#' @param folder_df Data frame containing folder asset metadata. +#' @param target_dir Base directory for downloads. +#' @param add_folder_subdir Logical. When `TRUE`, creates a subdirectory per +#' folder inside `target_dir`. +#' @param overwrite Logical. When `FALSE`, existing files are saved with a +#' timestamped suffix. +#' @param make_portable_fn Logical. When `TRUE`, filenames are sanitized via +#' `make_fn_portable()`. +#' @param timeout_secs Numeric. Timeout applied to the signed download request. +#' @param rq Optional `httr2` request object reused to request signed links. +#' +#' @returns Path to the downloaded asset or `NULL` if the download fails. +#' +#' @inheritParams options_params +#' +#' @export +download_single_folder_asset_fr_df <- function(i = NULL, + folder_df = NULL, + target_dir = tempdir(), + add_folder_subdir = TRUE, + overwrite = TRUE, + make_portable_fn = FALSE, + timeout_secs = REQUEST_TIMEOUT_VERY_LONG, + vb = options::opt("vb"), + rq = NULL) { + assertthat::assert_that(length(i) == 1) + assertthat::is.number(i) + assertthat::assert_that(i > 0) + + assertthat::assert_that(is.data.frame(folder_df)) + required_cols <- c("vol_id", "folder_id", "asset_id", "asset_name") + missing_cols <- setdiff(required_cols, names(folder_df)) + if (length(missing_cols) > 0) { + stop( + "folder_df is missing required columns: ", + paste(missing_cols, collapse = ", "), + call. = FALSE + ) + } + + assertthat::assert_that(length(target_dir) == 1) + assertthat::is.string(target_dir) + assertthat::assert_that(dir.exists(target_dir) || dir.create(target_dir, recursive = TRUE, showWarnings = FALSE)) + assertthat::is.writeable(target_dir) + + assertthat::assert_that(length(add_folder_subdir) == 1) + assertthat::assert_that(is.logical(add_folder_subdir)) + + assertthat::assert_that(length(overwrite) == 1) + assertthat::assert_that(is.logical(overwrite)) + + assertthat::assert_that(length(make_portable_fn) == 1) + assertthat::assert_that(is.logical(make_portable_fn)) + + assertthat::is.number(timeout_secs) + assertthat::assert_that(length(timeout_secs) == 1) + assertthat::assert_that(timeout_secs > 0) + + assertthat::assert_that(length(vb) == 1) + assertthat::assert_that(is.logical(vb)) + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + this_asset <- folder_df[i, , drop = FALSE] + if (nrow(this_asset) == 0) { + if (vb) { + message("No asset for index ", i) + } + return(NULL) + } + + dest_dir <- if (isTRUE(add_folder_subdir)) { + file.path(target_dir, this_asset$folder_id) + } else { + target_dir + } + dir.create(dest_dir, recursive = TRUE, showWarnings = FALSE) + assertthat::assert_that(dir.exists(dest_dir)) + assertthat::is.writeable(dest_dir) + + base_name <- this_asset$asset_name + if (is.null(base_name) || is.na(base_name) || base_name == "") { + base_name <- paste0("asset-", this_asset$asset_id) + } + + extension <- "" + if ("format_extension" %in% names(this_asset)) { + ext_value <- this_asset$format_extension + if (!is.null(ext_value) && !is.na(ext_value) && nzchar(ext_value)) { + if (tools::file_ext(base_name) != ext_value) { + extension <- paste0(".", ext_value) + } + } + } + + candidate_name <- paste0(base_name, extension) + + if (make_portable_fn) { + if (vb) { + message("Making file name '", candidate_name, "' portable.") + } + candidate_name <- make_fn_portable(candidate_name, vb = vb) + } + + dest_file <- file.path(dest_dir, candidate_name) + if (file.exists(dest_file) && !overwrite) { + if (vb) { + message("Generating new unique (time-stamped) file name.") + } + candidate_name <- paste0( + this_asset$folder_id, + "-", + this_asset$asset_id, + "-", + format(Sys.time(), "%F-%H%M-%S"), + ifelse( + nzchar(tools::file_ext(candidate_name)), + paste0(".", tools::file_ext(candidate_name)), + "" + ) + ) + dest_file <- file.path(dest_dir, candidate_name) + } + + download_folder_asset( + vol_id = this_asset$vol_id, + folder_id = this_asset$folder_id, + asset_id = this_asset$asset_id, + file_name = candidate_name, + target_dir = dest_dir, + timeout_secs = timeout_secs, + vb = vb, + rq = rq + ) +} + + + diff --git a/R/download_single_session_asset_fr_df.R b/R/download_single_session_asset_fr_df.R index 8ace4fe..ce4fe68 100644 --- a/R/download_single_session_asset_fr_df.R +++ b/R/download_single_session_asset_fr_df.R @@ -3,48 +3,28 @@ #' NULL -#' Download Single Asset From Databrary +#' Download a Single Asset From a Session Data Frame Row. #' -#' @description Databrary stores file types (assets) of many types. This -#' function downloads an asset based on its system-unique integer identifer -#' (asset_id) and system-unique session (slot) identifier (session_id). It -#' is designed to work with download_session_assets_fr_df() so that multiple -#' files can be downloaded simultaneously. +#' @description +#' Helper used by `download_session_assets_fr_df()` to fetch a single asset via +#' the signed-download workflow. #' -#' @param i An integer. Index into a row of the session asset data frame. -#' Default is NULL. -#' @param session_df A row from a data frame from `list_session_assets()` -#' or `list_volume_assets()`. Default is NULL> -#' @param target_dir A character string. Directory to save the downloaded file. -#' Default is a temporary directory given by a call to `tempdir()`. -#' @param add_session_subdir A logical value. Add add the session name to the -#' file path so that files are in a subdirectory specific to the session. Default -#' is TRUE. -#' @param overwrite A logical value. Overwrite an existing file. Default is TRUE. -#' @param make_portable_fn A logical value. Replace characters in file names -#' that are not broadly portable across file systems. Default is FALSE. -#' @param timeout_secs An integer. The seconds an httr2 request will run before -#' timing out. Default is 600 (10 min). This is to handle very large files. -#' @param rq A list in the form of an `httr2` request object. Default is NULL. +#' @param i Integer. Index of the asset within `session_df`. +#' @param session_df Data frame containing asset metadata. +#' @param target_dir Base directory for downloads. +#' @param add_session_subdir Logical. When `TRUE`, creates a subdirectory per +#' session inside `target_dir`. +#' @param overwrite Logical. When `FALSE`, existing files are saved with a +#' timestamped suffix. +#' @param make_portable_fn Logical. When `TRUE`, filenames are sanitized via +#' `make_fn_portable()`. +#' @param timeout_secs Numeric. Timeout applied to the signed download request. +#' @param rq Optional `httr2` request object reused to request signed links. #' -#' @returns Full file name to the asset or NULL. -#' -#' @inheritParams options_params +#' @returns Path to the downloaded asset or `NULL` if the download fails. #' -#' @examples -#' \donttest{ -#' \dontrun{ -#' vol_1 <- list_session_assets(session_id = 9807) -#' a_1 <- vol_1[1,] -#' tmp_dir <- tempdir() -#' fn <- file.path(tmp_dir, paste0(a_1$asset_name, ".", a_1$format_extension)) -#' download_single_session_asset_fr_df(a_1$asset_id, -#' fn, -#' session_id = a_1$session_id, -#' vb = TRUE) +#' @inheritParams options_params #' -#' } -#' } #' @export download_single_session_asset_fr_df <- function(i = NULL, session_df = NULL, @@ -55,183 +35,113 @@ download_single_session_asset_fr_df <- function(i = NULL, timeout_secs = REQUEST_TIMEOUT_VERY_LONG, vb = options::opt("vb"), rq = NULL) { - # Check parameters assertthat::assert_that(length(i) == 1) assertthat::is.number(i) assertthat::assert_that(i > 0) - + assertthat::assert_that(is.data.frame(session_df)) - assertthat::assert_that("session_id" %in% names(session_df)) - assertthat::assert_that("asset_id" %in% names(session_df)) - assertthat::assert_that("format_extension" %in% names(session_df)) - assertthat::assert_that("asset_name" %in% names(session_df)) - + required_cols <- c("vol_id", "session_id", "asset_id", "asset_name") + missing_cols <- setdiff(required_cols, names(session_df)) + if (length(missing_cols) > 0) { + stop( + "session_df is missing required columns: ", + paste(missing_cols, collapse = ", "), + call. = FALSE + ) + } + assertthat::assert_that(length(target_dir) == 1) assertthat::is.string(target_dir) + assertthat::assert_that(dir.exists(target_dir) || dir.create(target_dir, recursive = TRUE, showWarnings = FALSE)) assertthat::is.writeable(target_dir) - + assertthat::assert_that(length(add_session_subdir) == 1) assertthat::assert_that(is.logical(add_session_subdir)) - + assertthat::assert_that(length(overwrite) == 1) assertthat::assert_that(is.logical(overwrite)) - + assertthat::assert_that(length(make_portable_fn) == 1) assertthat::assert_that(is.logical(make_portable_fn)) - + assertthat::is.number(timeout_secs) assertthat::assert_that(length(timeout_secs) == 1) assertthat::assert_that(timeout_secs > 0) - + assertthat::assert_that(length(vb) == 1) assertthat::assert_that(is.logical(vb)) - - assertthat::assert_that(is.null(rq) | - ("httr2_request" %in% class(rq))) - - this_asset <- session_df[i, ] - if (is.null(this_asset)) { - if (vb) + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + this_asset <- session_df[i, , drop = FALSE] + if (nrow(this_asset) == 0) { + if (vb) { message("No asset for index ", i) + } return(NULL) } - - if (add_session_subdir) { - full_fn <- file.path( - target_dir, - this_asset$session_id, - paste0(this_asset$asset_name, ".", this_asset$format_extension) - ) - if (vb) - message("`add_session_subdir` is TRUE.") + + dest_dir <- if (isTRUE(add_session_subdir)) { + file.path(target_dir, this_asset$session_id) } else { - full_fn <- file.path(target_dir, - paste0(this_asset$asset_name, ".", this_asset$format_extension)) - if (vb) - message("`add_session_subdir` is FALSE.") + target_dir } - - if (file.exists(full_fn)) { - if (vb) - message("File exists: ", full_fn) - if (!overwrite) { - if (vb) - message("Generating new unique (time-stamped) file name.") - full_fn <- file.path( - dirname(full_fn), - paste0( - this_asset$session_id, - "-", - this_asset$asset_id, - "-", - format(Sys.time(), "%F-%H%M-%S"), - paste0(".", this_asset$format_extension) - ) - ) - } else { - if (vb) - message("Will overwrite existing file.") + dir.create(dest_dir, recursive = TRUE, showWarnings = FALSE) + assertthat::assert_that(dir.exists(dest_dir)) + assertthat::is.writeable(dest_dir) + + base_name <- this_asset$asset_name + if (is.null(base_name) || is.na(base_name) || base_name == "") { + base_name <- paste0("asset-", this_asset$asset_id) + } + + extension <- "" + if ("format_extension" %in% names(this_asset)) { + ext_value <- this_asset$format_extension + if (!is.null(ext_value) && !is.na(ext_value) && nzchar(ext_value)) { + if (tools::file_ext(base_name) != ext_value) { + extension <- paste0(".", ext_value) + } } } - + + candidate_name <- paste0(base_name, extension) + if (make_portable_fn) { - if (vb) - message("Making file name '", full_fn, "' portable.") - full_fn <- make_fn_portable(full_fn, vb = vb) - } - assertthat::is.string(full_fn) - - if (!dir.exists(dirname(full_fn))) { if (vb) { - message("Target directory not found: ", dirname(full_fn)) - message("Creating: ", dirname(full_fn)) - } - dir.create(dirname(full_fn), recursive = TRUE) - } else { - if (vb) - message("Target directory exists: ", dirname(full_fn)) - if (overwrite) { - if (vb) - message("Overwriting directory: ", dirname(full_fn)) - } else { - if (vb) - message("`overwrite` is FALSE. Skipping.") - return(NULL) + message("Making file name '", candidate_name, "' portable.") } + candidate_name <- make_fn_portable(candidate_name, vb = vb) } - assertthat::is.writeable(dirname(full_fn)) - - # Handle NULL rq - if (is.null(rq)) { + + dest_file <- file.path(dest_dir, candidate_name) + if (file.exists(dest_file) && !overwrite) { if (vb) { - message("NULL request object. Will generate default.") - message("Not logged in. Only public information will be returned.") + message("Generating new unique (time-stamped) file name.") } - rq <- databraryr::make_default_request() - } - - if (vb) - message( - "Downloading file with asset_id ", + candidate_name <- paste0( + this_asset$session_id, + "-", this_asset$asset_id, - " from session_id ", - this_asset$session_id + "-", + format(Sys.time(), "%F-%H%M-%S"), + ifelse( + nzchar(tools::file_ext(candidate_name)), + paste0(".", tools::file_ext(candidate_name)), + "" + ) ) - - # Up default timeout for possibly big files - rq <- - httr2::req_timeout(rq, seconds = timeout_secs) - - this_rq <- rq %>% - httr2::req_url(sprintf(DOWNLOAD_FILE, this_asset$session_id, this_asset$asset_id)) %>% - httr2::req_progress() - - resp <- tryCatch( - httr2::req_perform(this_rq), - httr2_error = function(cnd) - if (vb) - message( - "Error downloading asset ", - this_asset$asset_name, - " from session_id ", - this_asset$session_id - ), - NULL - ) - - if (is.null(resp)) { - message("Cannot access requested resource on Databrary. Exiting.") - return(resp) + dest_file <- file.path(dest_dir, candidate_name) } - - # if (is.null(resp)) { - # if (vb) - # message( - # "Download request for session ", - # this_asset$session_id, - # " asset ", - # this_asset$asset_id, - # " returned NULL. Skipping." - # ) - # return(NULL) - # } - - write_file <- tryCatch( - error = function(cnd) { - if (vb) - message("Failure writing file ", full_fn) - NULL - }, - { - file_con <- file(full_fn, "wb") - writeBin(resp$body, file_con) - close(file_con) - } + + download_session_asset( + vol_id = this_asset$vol_id, + session_id = this_asset$session_id, + asset_id = this_asset$asset_id, + file_name = candidate_name, + target_dir = dest_dir, + timeout_secs = timeout_secs, + vb = vb, + rq = rq ) - - if (!is.null(write_file)) { - full_fn - } else { - write_file - } } diff --git a/R/download_utils.R b/R/download_utils.R new file mode 100644 index 0000000..caea1b2 --- /dev/null +++ b/R/download_utils.R @@ -0,0 +1,101 @@ +# Internal helpers for the Django signed-download workflow. + +#' @noRd +request_processing_task <- function(path, rq = NULL, vb = FALSE) { + task <- perform_api_get( + path = path, + rq = rq, + vb = vb, + normalize = TRUE + ) + + if (is.null(task)) { + if (vb) { + message("Cannot access requested resource on Databrary. Exiting.") + } + return(NULL) + } + + if (vb && !is.null(task$message)) { + message(task$message) + } + + class(task) <- unique(c("databrary_processing_task", class(task))) + task +} + +#' @noRd +request_signed_download_link <- function(path, rq = NULL, vb = FALSE) { + link <- perform_api_get( + path = path, + rq = rq, + vb = vb, + normalize = TRUE + ) + + if (is.null(link)) { + if (vb) { + message("Cannot access requested resource on Databrary. Exiting.") + } + return(NULL) + } + + if (is.null(link$download_url)) { + if (vb) { + message("Download link payload missing 'download_url'.") + } + return(NULL) + } + + link$download_url <- ensure_absolute_url(link$download_url) + class(link) <- unique(c("databrary_signed_download", class(link))) + link +} + +#' @noRd +ensure_absolute_url <- function(url) { + assertthat::assert_that(assertthat::is.string(url)) + if (startsWith(url, "http://") || startsWith(url, "https://")) { + return(url) + } + paste0(DATABRARY_BASE_URL, ensure_leading_slash(url)) +} + +#' @noRd +download_signed_file <- function(download_url, + dest_path, + timeout_secs = REQUEST_TIMEOUT_VERY_LONG, + vb = FALSE) { + assertthat::assert_that(assertthat::is.string(download_url)) + assertthat::assert_that(assertthat::is.string(dest_path)) + assertthat::is.number(timeout_secs) + assertthat::assert_that(timeout_secs > 0) + + parent_dir <- dirname(dest_path) + if (!dir.exists(parent_dir)) { + dir.create(parent_dir, recursive = TRUE, showWarnings = FALSE) + } + assertthat::is.writeable(parent_dir) + + req <- httr2::request(download_url) | + httr2::req_timeout(seconds = timeout_secs) + + if (vb) { + message("Saving download to '", dest_path, "'.") + } + + tryCatch( + { + httr2::req_perform(req, path = dest_path) + dest_path + }, + httr2_error = function(cnd) { + if (vb) { + message("Download failed: ", conditionMessage(cnd)) + } + NULL + } + ) +} + + diff --git a/R/download_video.R b/R/download_video.R index d2a1711..8e24dae 100644 --- a/R/download_video.R +++ b/R/download_video.R @@ -3,119 +3,77 @@ #' NULL -#' Download Video From Databrary. +#' Download a Video Asset via Signed URL. #' -#' @param asset_id Asset id for target file. -#' @param session_id Slot/session number where target file is stored. -#' @param file_name Name for downloaded file. -#' @param target_dir Directory to save the downloaded file. -#' Default is a temporary directory given by a call to `tempdir()`. -#' @param rq An `httr2` request object. +#' @param vol_id Volume identifier containing the session. +#' @param session_id Session identifier containing the asset. +#' @param asset_id Asset identifier for the video file. +#' @param file_name Optional explicit file name. Defaults to the API-provided +#' value. +#' @param target_dir Directory to save the downloaded file. Defaults to +#' `tempdir()`. +#' @param rq Optional `httr2` request object reused when requesting the signed +#' link. #' -#' @returns Full file name to the asset. +#' @returns Path to the downloaded video or `NULL` on failure. #' #' @inheritParams options_params #' #' @examples #' \donttest{ #' \dontrun{ -#' download_video() # Download's 'numbers' file from volume 1. -#' download_video(asset_id = 11643, session_id = 9825, file_name = "rdk.mp4") -#' #' # Downloads a display with a random dot kinematogram (RDK). +#' download_video() # Default public video from volume 1 +#' download_video(vol_id = 1, session_id = 9825, asset_id = 11643, +#' file_name = "rdk.mp4") #' } #' } #' #' @export -download_video <- function(asset_id = 1, +download_video <- function(vol_id = 1, session_id = 9807, - file_name = tempfile(paste0(session_id, "_", - asset_id, "_"), - fileext = ".mp4"), + asset_id = 1, + file_name = NULL, target_dir = tempdir(), vb = options::opt("vb"), rq = NULL) { - # Check parameters assertthat::assert_that(length(asset_id) == 1) assertthat::assert_that(is.numeric(asset_id)) assertthat::assert_that(asset_id >= 1) - + assertthat::assert_that(length(session_id) == 1) assertthat::assert_that(is.numeric(session_id)) assertthat::assert_that(session_id >= 1) - - assertthat::assert_that(length(file_name) == 1) - assertthat::assert_that(is.character(file_name)) - + + assertthat::assert_that(length(vol_id) == 1) + assertthat::assert_that(is.numeric(vol_id)) + assertthat::assert_that(vol_id >= 1) + + if (!is.null(file_name)) { + assertthat::assert_that(length(file_name) == 1) + assertthat::assert_that(is.character(file_name)) + if (!endsWith(tolower(file_name), ".mp4")) { + stop("file_name must end with '.mp4' when provided.", call. = FALSE) + } + } + assertthat::assert_that(length(target_dir) == 1) assertthat::assert_that(is.character(target_dir)) - assertthat::assert_that(dir.exists(target_dir)) - + assertthat::assert_that(dir.exists(target_dir) || dir.create(target_dir, recursive = TRUE, showWarnings = FALSE)) + assertthat::is.writeable(target_dir) + assertthat::assert_that(length(vb) == 1) assertthat::assert_that(is.logical(vb)) - - assertthat::assert_that(is.null(rq) | - ("httr2_request" %in% class(rq))) - - if (is.null(rq)) { - if (vb) { - message("NULL request object. Will generate default.") - message("Not logged in. Only public information will be returned.") - } - rq <- databraryr::make_default_request() - } - - this_rq <- rq %>% - httr2::req_url(sprintf(DOWNLOAD_FILE, session_id, asset_id)) %>% - httr2::req_progress() - - if (file.exists(file_name)) { - if (vb) - message("File exists. Generating new unique name.\n") - file_name <- file.path(tempdir(), - paste0( - session_id, - "-", - asset_id, - "-", - format(Sys.time(), "%F-%H%M-%S"), - ".mp4" - )) - } - - if (vb) - message("Attempting to download video with asset_id ", - asset_id, - " from session_id ", - session_id) - - resp <- tryCatch( - httr2::req_perform(this_rq), - httr2_error = function(cnd) { - if (vb) - message( - message( - "Error retrieving video with asset_id ", - asset_id, - " from session_id ", - session_id - ) - ) - NULL - } + + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) + + download_session_asset( + vol_id = vol_id, + session_id = session_id, + asset_id = asset_id, + file_name = file_name, + target_dir = target_dir, + vb = vb, + rq = rq, + timeout_secs = REQUEST_TIMEOUT_VERY_LONG ) - - if (is.null(resp)) { - message("Cannot access requested resource on Databrary. Exiting.") - return(resp) - } - - if (httr2::resp_content_type(resp) == "video/mp4") { - file_con <- file(file_name, "wb") - writeBin(resp$body, file_con) - close(file_con) - file_name - } else { - message("Content type is ", httr2::resp_content_type(resp)) - NULL - } } diff --git a/R/download_volume_zip.R b/R/download_volume_zip.R index a8bfcf1..8d44338 100644 --- a/R/download_volume_zip.R +++ b/R/download_volume_zip.R @@ -3,112 +3,43 @@ #' NULL -#' Download Zip Archive of All Data in a Volume. +#' Request a Signed ZIP Download for a Volume. #' -#' @param vol_id Volume number. -#' @param out_dir Directory to save output file. -#' @param file_name Name for downloaded file, default is 'test.mp4'. -#' @param rq An `httr2` request object. Default is NULL. +#' @description +#' Volume-level ZIP archives are prepared asynchronously by the Django API. +#' Calling `download_volume_zip()` queues the job and returns a processing task +#' descriptor. When the archive is ready, Databrary emails a signed download +#' link to the authenticated user. +#' +#' @param vol_id Volume identifier. +#' @param rq An `httr2` request object. Default is `NULL`, in which case a +#' default authenticated request is generated. +#' +#' @returns A list describing the processing task (`status`, `message`, +#' `task_id`) or `NULL` when the request fails. #' -#' @returns Full filename of the downloaded file. -#' #' @inheritParams options_params -#' +#' #' @examples #' \donttest{ #' \dontrun{ -#' download_volume_zip() # Zip file of all data from volume 31, the default. +#' download_volume_zip(vol_id = 31) #' } #' } #' #' @export download_volume_zip <- function(vol_id = 31, - out_dir = tempdir(), - file_name = "test.zip", vb = options::opt("vb"), rq = NULL) { - # Check parameters assertthat::assert_that(length(vol_id) == 1) assertthat::assert_that(is.numeric(vol_id)) assertthat::assert_that(vol_id >= 1) - - assertthat::assert_that(length(out_dir) == 1) - assertthat::assert_that(is.character(out_dir)) - - assertthat::assert_that(length(file_name) == 1) - assertthat::assert_that(is.character(file_name)) - + assertthat::assert_that(length(vb) == 1) assertthat::assert_that(is.logical(vb)) - - assertthat::assert_that(is.null(rq) | - ("httr2_request" %in% class(rq))) - - # Handle NULL request - if (is.null(rq)) { - if (vb) { - message("\nNULL request object. Will generate default.") - message("Not logged in. Only public information will be returned.") - } - rq <- databraryr::make_default_request() - } - rq <- rq %>% - httr2::req_url(sprintf(GET_VOLUME_ZIP, vol_id)) - - resp <- tryCatch( - httr2::req_perform(rq), - httr2_error = function(cnd) { - if (vb) message("Error downloading zip archive from vol_id ", vol_id) - NULL - } - ) - - if (is.null(resp)) { - message("Cannot access requested resource on Databrary. Exiting.") - return(resp) - } - - bin <- NULL - bin <- httr2::resp_body_raw(resp) - if (is.null(bin)) { - if (vb) message("Null file returned") - return(NULL) - } - - if (file_name == "test.zip") { - if (vb) { - if (vb) - message("File name unspecified. Generating unique name.") - } - file_name <- make_zip_fn_vol(out_dir, vol_id) - } - if (vb) { - if (vb) - message(paste0("Downloading zip file as: \n'", file_name, "'.")) - } - writeBin(bin, file_name) - file_name -} + assertthat::assert_that(is.null(rq) || ("httr2_request" %in% class(rq))) -#------------------------------------------------------------------------------- -make_zip_fn_vol <- function(out_dir, vol_id) { - - # Check parameters - assertthat::is.string(out_dir) - assertthat::is.writeable(out_dir) - assertthat::assert_that(length(out_dir) == 1) - - assertthat::assert_that(length(vol_id) == 1) - assertthat::assert_that(is.numeric(vol_id)) - assertthat::assert_that(vol_id >= 1) - - paste0( - out_dir, - "/vol-", - vol_id, - "-", - format(Sys.time(), "%F-%H%M-%S"), - ".zip" - ) + path <- sprintf(API_VOLUME_DOWNLOAD_LINK, vol_id) + request_processing_task(path = path, rq = rq, vb = vb) } diff --git a/R/list_folder_assets.R b/R/list_folder_assets.R index b701f33..ccbac12 100644 --- a/R/list_folder_assets.R +++ b/R/list_folder_assets.R @@ -79,6 +79,7 @@ list_folder_assets <- function(folder_id = 1, asset_mime_type = format$mimetype, asset_format_id = format$id, asset_format_name = format$name, + format_extension = format$extension, asset_duration = file$duration, asset_created_at = file$created_at, asset_updated_at = file$updated_at, diff --git a/R/list_session_assets.R b/R/list_session_assets.R index dc24c69..270658e 100644 --- a/R/list_session_assets.R +++ b/R/list_session_assets.R @@ -84,6 +84,7 @@ list_session_assets <- function(session_id = 9807, asset_mime_type = format$mimetype, asset_format_id = format$id, asset_format_name = format$name, + format_extension = format$extension, asset_duration = file$duration, asset_created_at = file$created_at, asset_updated_at = file$updated_at, diff --git a/man/download_folder_asset.Rd b/man/download_folder_asset.Rd new file mode 100644 index 0000000..5c6e01a --- /dev/null +++ b/man/download_folder_asset.Rd @@ -0,0 +1,57 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/download_folder_asset.R +\name{download_folder_asset} +\alias{download_folder_asset} +\title{Download a Folder Asset via Signed Link.} +\usage{ +download_folder_asset( + vol_id = 1, + folder_id = 1, + asset_id = 1, + file_name = NULL, + target_dir = tempdir(), + timeout_secs = REQUEST_TIMEOUT, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{vol_id}{Integer. Volume identifier containing the folder. Default is 1.} + +\item{folder_id}{Integer. Folder identifier within the volume. Default is 1.} + +\item{asset_id}{Integer. Asset identifier within the folder. Default is 1.} + +\item{file_name}{Optional character string. File name to use when saving the +asset. Defaults to the API-provided file name.} + +\item{target_dir}{Character string. Directory where the file will be saved. +Default is \code{tempdir()}.} + +\item{timeout_secs}{Numeric. Timeout (seconds) applied to the download +request. Default is \code{REQUEST_TIMEOUT}.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Default is \code{NULL}, in which case a +default authenticated request is generated.} +} +\value{ +The path to the downloaded file (character string) or \code{NULL} if the +download fails. +} +\description{ +Databrary serves folder-scoped assets through signed URLs. This helper +requests the signed link for a folder asset and streams the file to the +specified directory. +} +\examples{ +\donttest{ +\dontrun{ +download_folder_asset() # Default public asset in folder 1 of volume 1 +download_folder_asset(vol_id = 1, folder_id = 2, asset_id = 3, + file_name = "example.mp4") +} +} + +} diff --git a/man/download_folder_assets_fr_df.Rd b/man/download_folder_assets_fr_df.Rd new file mode 100644 index 0000000..7399287 --- /dev/null +++ b/man/download_folder_assets_fr_df.Rd @@ -0,0 +1,58 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/download_folder_assets_fr_df.R +\name{download_folder_assets_fr_df} +\alias{download_folder_assets_fr_df} +\title{Download Multiple Assets From a Folder Data Frame.} +\usage{ +download_folder_assets_fr_df( + folder_df = list_folder_assets(vol_id = 1), + target_dir = tempdir(), + add_folder_subdir = TRUE, + overwrite = TRUE, + make_portable_fn = FALSE, + timeout_secs = REQUEST_TIMEOUT_VERY_LONG, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{folder_df}{Data frame describing assets. Must include \code{vol_id}, +\code{folder_id}, \code{asset_id}, and \code{asset_name} columns.} + +\item{target_dir}{Character string. Base directory for downloads. Defaults to +\code{tempdir()}.} + +\item{add_folder_subdir}{Logical. When \code{TRUE}, creates a subdirectory per +folder inside \code{target_dir}.} + +\item{overwrite}{Logical. When \code{FALSE}, the function aborts if the target +directory already exists.} + +\item{make_portable_fn}{Logical. When \code{TRUE}, filenames are sanitized via +\code{make_fn_portable()}.} + +\item{timeout_secs}{Numeric. Timeout applied to each download request.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An optional \code{httr2} request object reused when requesting signed +links.} +} +\value{ +Character vector of downloaded file paths or \code{NULL} if the request +fails before any downloads start. +} +\description{ +Iterates over a data frame of folder assets, requesting signed download links +for each asset and saving them to disk. Designed to work with +\code{list_folder_assets()} output. +} +\examples{ +\donttest{ +\dontrun{ +assets <- list_folder_assets(folder_id = 1, vol_id = 1) +download_folder_assets_fr_df(assets, vb = TRUE) +} +} + +} diff --git a/man/download_folder_zip.Rd b/man/download_folder_zip.Rd new file mode 100644 index 0000000..84e1714 --- /dev/null +++ b/man/download_folder_zip.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/download_folder_zip.R +\name{download_folder_zip} +\alias{download_folder_zip} +\title{Request a Signed ZIP Download for a Folder.} +\usage{ +download_folder_zip( + vol_id = 1, + folder_id = 1, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{vol_id}{Volume identifier for the folder.} + +\item{folder_id}{Folder identifier scoped within the specified volume.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{An \code{httr2} request object. Default is \code{NULL}, in which case a +default authenticated request is generated.} +} +\value{ +A list describing the processing task (\code{status}, \code{message}, +\code{task_id}) or \code{NULL} when the request fails. +} +\description{ +Folder-level ZIP archives are prepared asynchronously by the Django API. +Calling \code{download_folder_zip()} queues the job and returns a processing task +descriptor. When the archive is ready, Databrary emails a signed download +link to the authenticated user. +} +\examples{ +\donttest{ +\dontrun{ +download_folder_zip(vol_id = 1, folder_id = 1) +} +} + +} diff --git a/man/download_session_asset.Rd b/man/download_session_asset.Rd index 2c95e5a..5f6369b 100644 --- a/man/download_session_asset.Rd +++ b/man/download_session_asset.Rd @@ -2,11 +2,12 @@ % Please edit documentation in R/download_session_asset.R \name{download_session_asset} \alias{download_session_asset} -\title{Download Asset From Databrary.} +\title{Download an Asset via Signed Link.} \usage{ download_session_asset( - asset_id = 1, + vol_id = 1, session_id = 9807, + asset_id = 1, file_name = NULL, target_dir = tempdir(), timeout_secs = REQUEST_TIMEOUT, @@ -15,39 +16,41 @@ download_session_asset( ) } \arguments{ -\item{asset_id}{An integer. Asset id for target file. Default is 1.} +\item{vol_id}{Integer. Volume identifier. Default is 1.} + +\item{session_id}{Integer. Session identifier. Default is 9807.} -\item{session_id}{An integer. Slot/session number where target file is -stored. Default is 9807.} +\item{asset_id}{Integer. Asset identifier within the session. Default is 1.} -\item{file_name}{A character string. Name for downloaded file. Default is NULL.} +\item{file_name}{Optional character string. Target file name. Defaults to the +API-provided file name.} -\item{target_dir}{A character string. Directory to save the downloaded file. -Default is a temporary directory given by a call to \code{tempdir()}.} +\item{target_dir}{Character string. Directory where the file will be saved. +Default is \code{tempdir()}.} -\item{timeout_secs}{An integer constant. The default value, defined in -CONSTANTS.R is REQUEST_TIMEOUT. This value determines the default timeout -value for the httr2 request object. When downloading large files, it can be -useful to set this value to a large number.} +\item{timeout_secs}{Numeric. Timeout (seconds) applied to the download +request. Default is \code{REQUEST_TIMEOUT}.} \item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} -\item{rq}{A list in the form of an \code{httr2} request object. Default is NULL.} +\item{rq}{An \code{httr2} request object. Default is \code{NULL}, in which case a +default authenticated request is generated.} } \value{ -Full file name to the asset or NULL. +The path to the downloaded file (character string) or \code{NULL} if the +download fails. } \description{ -Databrary stores file types (assets) of many types. This -function downloads an asset based on its system-unique integer identifer -(asset_id) and system-unique session (slot) identifier (session_id). +Databrary serves assets through short-lived, signed URLs. This helper +requests the signed link for a session asset and streams the file to the +requested directory. } \examples{ \donttest{ \dontrun{ -download_session_asset() # Download's 'numbers' file from volume 1. -download_session_asset(asset_id = 11643, session_id = 9825, file_name = "rdk.mp4") -# Downloads a display with a random dot kinematogram (RDK). +download_session_asset() # Default public asset in volume 1 +download_session_asset(vol_id = 1, session_id = 9825, asset_id = 11643, + file_name = "rdk.mp4") } } } diff --git a/man/download_session_assets_fr_df.Rd b/man/download_session_assets_fr_df.Rd index f954865..48c7b46 100644 --- a/man/download_session_assets_fr_df.Rd +++ b/man/download_session_assets_fr_df.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/download_session_assets_fr_df.R \name{download_session_assets_fr_df} \alias{download_session_assets_fr_df} -\title{Download Asset From A Databrary Session.} +\title{Download Multiple Assets From a Session Data Frame.} \usage{ download_session_assets_fr_df( session_df = list_session_assets(), @@ -16,44 +16,42 @@ download_session_assets_fr_df( ) } \arguments{ -\item{session_df}{A data frame as generated by list_session_assets_2().} +\item{session_df}{Data frame describing assets. Must include \code{vol_id}, +\code{session_id}, \code{asset_id}, and \code{asset_name} columns.} -\item{target_dir}{A character string. Directory to save the downloaded file. -Default is directory named after the session_id.} +\item{target_dir}{Character string. Base directory for downloads. Defaults to +\code{tempdir()}.} -\item{add_session_subdir}{A logical value. Add add the session name to the -file path so that files are in a subdirectory specific to the session. Default -is TRUE.} +\item{add_session_subdir}{Logical. When \code{TRUE}, creates a subdirectory per +session inside \code{target_dir}.} -\item{overwrite}{A logical value. Overwrite an existing file. Default is TRUE.} +\item{overwrite}{Logical. When \code{FALSE}, the function aborts if the target +directory already exists.} -\item{make_portable_fn}{A logical value. Replace characters in file names -that are not broadly portable across file systems. Default is FALSE.} +\item{make_portable_fn}{Logical. When \code{TRUE}, filenames are sanitized via +\code{make_fn_portable()}.} -\item{timeout_secs}{An integer. The seconds an httr2 request will run before -timing out. Default is 600 (10 min). This is to handle very large files.} +\item{timeout_secs}{Numeric. Timeout applied to each download request.} \item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} -\item{rq}{A list in the form of an \code{httr2} request object. Default is NULL.} +\item{rq}{An optional \code{httr2} request object reused when requesting signed +links.} } \value{ -Full file names to the downloaded assets or NULL. +Character vector of downloaded file paths or \code{NULL} if the request +fails before any downloads start. } \description{ -Databrary stores file types (assets) of many types. This -function downloads assets in a data frame generated by list_session_assets(). +Iterates over a data frame of session assets, requesting signed download +links for each asset and saving them to disk. Designed to work with +\code{list_session_assets()} or \code{list_volume_session_assets()} output. } \examples{ \donttest{ \dontrun{ -download_session_assets_fr_df() # Downloads all of the files from session -9807 in Databrary volume 1. - -# Just the CSVs -v1 <- list_session_assets() -v1_csv <- dplyr::filter(v1, format_extension == "csv") -download_session_assets_fr_df(v1_csv, vb = TRUE) +assets <- list_session_assets(vol_id = 1, session_id = 9807) +download_session_assets_fr_df(assets, vb = TRUE) } } } diff --git a/man/download_session_csv.Rd b/man/download_session_csv.Rd index 039448f..74eea14 100644 --- a/man/download_session_csv.Rd +++ b/man/download_session_csv.Rd @@ -2,46 +2,44 @@ % Please edit documentation in R/download_session_csv.R \name{download_session_csv} \alias{download_session_csv} -\title{Download Session Spreadsheet As CSV} +\title{Request a Session or Volume CSV Export.} \usage{ download_session_csv( vol_id = 1, - file_name = "test.csv", - target_dir = tempdir(), - as_df = FALSE, + session_id = NULL, vb = options::opt("vb"), rq = NULL ) } \arguments{ -\item{vol_id}{An integer. Target volume number. Default is 1.} +\item{vol_id}{Integer. Target volume identifier. Default is 1.} -\item{file_name}{A character string. Name for the output file. -Default is 'test.csv'.} - -\item{target_dir}{A character string. Directory to save downloaded file. -Default is \code{tempdir()}.} - -\item{as_df}{A logical value. Convert the data from a list to a data frame. -Default is FALSE.} +\item{session_id}{Optional integer. When provided, requests a session-level +CSV export. When \code{NULL}, a volume-level CSV export is requested.} \item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} -\item{rq}{An \code{httr2} request object. Default is NULL.} +\item{rq}{An \code{httr2} request object. Default is \code{NULL}, meaning a default +authenticated request is generated.} } \value{ -A character string that is the name of the downloaded file or a -data frame if \code{as_df} is TRUE. +A list describing the processing task (\code{status}, \code{message}, +\code{task_id}) or \code{NULL} if the request fails. } \description{ -Databrary generates a CSV-formated spreadsheet that summarizes -information about individual sessions. This command downloads that CSV file -as a temporary file or with a name specified by the user. +The Django API generates CSV reports asynchronously. This function queues a +CSV export for a specific session when \code{session_id} is supplied, or for the +entire volume when \code{session_id} is \code{NULL}. The API delivers the final signed +download link via email once the export is ready. } \examples{ \donttest{ \dontrun{ -download_session_csv() # Downloads "session" CSV for volume 1 +# Request a volume-wide CSV export +download_session_csv(vol_id = 1) + +# Request a session-specific CSV export +download_session_csv(vol_id = 1, session_id = 9807) } } diff --git a/man/download_session_zip.Rd b/man/download_session_zip.Rd index c7c5c9b..ab5bb17 100644 --- a/man/download_session_zip.Rd +++ b/man/download_session_zip.Rd @@ -2,40 +2,39 @@ % Please edit documentation in R/download_session_zip.R \name{download_session_zip} \alias{download_session_zip} -\title{Download Zip Archive From Databrary Session.} +\title{Request a Signed ZIP Download for a Session.} \usage{ download_session_zip( vol_id = 31, session_id = 9803, - out_dir = tempdir(), - file_name = "test.zip", vb = options::opt("vb"), rq = NULL ) } \arguments{ -\item{vol_id}{Volume number.} +\item{vol_id}{Volume identifier that owns the session.} -\item{session_id}{Slot/session number.} - -\item{out_dir}{Directory to save output file.} - -\item{file_name}{Name for downloaded file, default is 'test.zip'.} +\item{session_id}{Session identifier within the volume.} \item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} -\item{rq}{An \code{httr2} request object. Default is NULL.} +\item{rq}{An \code{httr2} request object. Default is \code{NULL}, in which case a +default authenticated request is generated.} } \value{ -Full filename of the downloaded file. +A list describing the processing task (\code{status}, \code{message}, +\code{task_id}) or \code{NULL} when the request fails. } \description{ -Download Zip Archive From Databrary Session. +The Django API prepares session-level ZIP archives asynchronously. Calling +\code{download_session_zip()} triggers the job and returns a processing task +summary. Once the archive is ready, Databrary emails a signed download link +to the authenticated user. } \examples{ \donttest{ \dontrun{ -download_session_zip() # Downloads Zip Archive from volume 31, session 9803 +download_session_zip(vol_id = 31, session_id = 9803) } } diff --git a/man/download_single_folder_asset_fr_df.Rd b/man/download_single_folder_asset_fr_df.Rd new file mode 100644 index 0000000..ebf17e1 --- /dev/null +++ b/man/download_single_folder_asset_fr_df.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/download_single_folder_asset_fr_df.R +\name{download_single_folder_asset_fr_df} +\alias{download_single_folder_asset_fr_df} +\title{Download a Single Folder Asset From a Data Frame Row.} +\usage{ +download_single_folder_asset_fr_df( + i = NULL, + folder_df = NULL, + target_dir = tempdir(), + add_folder_subdir = TRUE, + overwrite = TRUE, + make_portable_fn = FALSE, + timeout_secs = REQUEST_TIMEOUT_VERY_LONG, + vb = options::opt("vb"), + rq = NULL +) +} +\arguments{ +\item{i}{Integer. Index of the asset within \code{folder_df}.} + +\item{folder_df}{Data frame containing folder asset metadata.} + +\item{target_dir}{Base directory for downloads.} + +\item{add_folder_subdir}{Logical. When \code{TRUE}, creates a subdirectory per +folder inside \code{target_dir}.} + +\item{overwrite}{Logical. When \code{FALSE}, existing files are saved with a +timestamped suffix.} + +\item{make_portable_fn}{Logical. When \code{TRUE}, filenames are sanitized via +\code{make_fn_portable()}.} + +\item{timeout_secs}{Numeric. Timeout applied to the signed download request.} + +\item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} + +\item{rq}{Optional \code{httr2} request object reused to request signed links.} +} +\value{ +Path to the downloaded asset or \code{NULL} if the download fails. +} +\description{ +Helper used by \code{download_folder_assets_fr_df()} to fetch a single asset via +the signed-download workflow. +} diff --git a/man/download_single_session_asset_fr_df.Rd b/man/download_single_session_asset_fr_df.Rd index 72d3a79..0bdd37a 100644 --- a/man/download_single_session_asset_fr_df.Rd +++ b/man/download_single_session_asset_fr_df.Rd @@ -2,7 +2,7 @@ % Please edit documentation in R/download_single_session_asset_fr_df.R \name{download_single_session_asset_fr_df} \alias{download_single_session_asset_fr_df} -\title{Download Single Asset From Databrary} +\title{Download a Single Asset From a Session Data Frame Row.} \usage{ download_single_session_asset_fr_df( i = NULL, @@ -17,53 +17,31 @@ download_single_session_asset_fr_df( ) } \arguments{ -\item{i}{An integer. Index into a row of the session asset data frame. -Default is NULL.} +\item{i}{Integer. Index of the asset within \code{session_df}.} -\item{session_df}{A row from a data frame from \code{list_session_assets()} -or \code{list_volume_assets()}. Default is NULL>} +\item{session_df}{Data frame containing asset metadata.} -\item{target_dir}{A character string. Directory to save the downloaded file. -Default is a temporary directory given by a call to \code{tempdir()}.} +\item{target_dir}{Base directory for downloads.} -\item{add_session_subdir}{A logical value. Add add the session name to the -file path so that files are in a subdirectory specific to the session. Default -is TRUE.} +\item{add_session_subdir}{Logical. When \code{TRUE}, creates a subdirectory per +session inside \code{target_dir}.} -\item{overwrite}{A logical value. Overwrite an existing file. Default is TRUE.} +\item{overwrite}{Logical. When \code{FALSE}, existing files are saved with a +timestamped suffix.} -\item{make_portable_fn}{A logical value. Replace characters in file names -that are not broadly portable across file systems. Default is FALSE.} +\item{make_portable_fn}{Logical. When \code{TRUE}, filenames are sanitized via +\code{make_fn_portable()}.} -\item{timeout_secs}{An integer. The seconds an httr2 request will run before -timing out. Default is 600 (10 min). This is to handle very large files.} +\item{timeout_secs}{Numeric. Timeout applied to the signed download request.} \item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} -\item{rq}{A list in the form of an \code{httr2} request object. Default is NULL.} +\item{rq}{Optional \code{httr2} request object reused to request signed links.} } \value{ -Full file name to the asset or NULL. +Path to the downloaded asset or \code{NULL} if the download fails. } \description{ -Databrary stores file types (assets) of many types. This -function downloads an asset based on its system-unique integer identifer -(asset_id) and system-unique session (slot) identifier (session_id). It -is designed to work with download_session_assets_fr_df() so that multiple -files can be downloaded simultaneously. -} -\examples{ -\donttest{ -\dontrun{ -vol_1 <- list_session_assets(session_id = 9807) -a_1 <- vol_1[1,] -tmp_dir <- tempdir() -fn <- file.path(tmp_dir, paste0(a_1$asset_name, ".", a_1$format_extension)) -download_single_session_asset_fr_df(a_1$asset_id, - fn, - session_id = a_1$session_id, - vb = TRUE) - -} -} +Helper used by \code{download_session_assets_fr_df()} to fetch a single asset via +the signed-download workflow. } diff --git a/man/download_video.Rd b/man/download_video.Rd index f958cf8..a779662 100644 --- a/man/download_video.Rd +++ b/man/download_video.Rd @@ -2,43 +2,48 @@ % Please edit documentation in R/download_video.R \name{download_video} \alias{download_video} -\title{Download Video From Databrary.} +\title{Download a Video Asset via Signed URL.} \usage{ download_video( - asset_id = 1, + vol_id = 1, session_id = 9807, - file_name = tempfile(paste0(session_id, "_", asset_id, "_"), fileext = ".mp4"), + asset_id = 1, + file_name = NULL, target_dir = tempdir(), vb = options::opt("vb"), rq = NULL ) } \arguments{ -\item{asset_id}{Asset id for target file.} +\item{vol_id}{Volume identifier containing the session.} + +\item{session_id}{Session identifier containing the asset.} -\item{session_id}{Slot/session number where target file is stored.} +\item{asset_id}{Asset identifier for the video file.} -\item{file_name}{Name for downloaded file.} +\item{file_name}{Optional explicit file name. Defaults to the API-provided +value.} -\item{target_dir}{Directory to save the downloaded file. -Default is a temporary directory given by a call to \code{tempdir()}.} +\item{target_dir}{Directory to save the downloaded file. Defaults to +\code{tempdir()}.} \item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} -\item{rq}{An \code{httr2} request object.} +\item{rq}{Optional \code{httr2} request object reused when requesting the signed +link.} } \value{ -Full file name to the asset. +Path to the downloaded video or \code{NULL} on failure. } \description{ -Download Video From Databrary. +Download a Video Asset via Signed URL. } \examples{ \donttest{ \dontrun{ -download_video() # Download's 'numbers' file from volume 1. -download_video(asset_id = 11643, session_id = 9825, file_name = "rdk.mp4") -#' # Downloads a display with a random dot kinematogram (RDK). +download_video() # Default public video from volume 1 +download_video(vol_id = 1, session_id = 9825, asset_id = 11643, + file_name = "rdk.mp4") } } diff --git a/man/download_volume_zip.Rd b/man/download_volume_zip.Rd index 180a1cb..c603472 100644 --- a/man/download_volume_zip.Rd +++ b/man/download_volume_zip.Rd @@ -2,37 +2,32 @@ % Please edit documentation in R/download_volume_zip.R \name{download_volume_zip} \alias{download_volume_zip} -\title{Download Zip Archive of All Data in a Volume.} +\title{Request a Signed ZIP Download for a Volume.} \usage{ -download_volume_zip( - vol_id = 31, - out_dir = tempdir(), - file_name = "test.zip", - vb = options::opt("vb"), - rq = NULL -) +download_volume_zip(vol_id = 31, vb = options::opt("vb"), rq = NULL) } \arguments{ -\item{vol_id}{Volume number.} - -\item{out_dir}{Directory to save output file.} - -\item{file_name}{Name for downloaded file, default is 'test.mp4'.} +\item{vol_id}{Volume identifier.} \item{vb}{Show verbose messages. (Defaults to \code{FALSE}, overwritable using option 'databraryr.vb' or environment variable 'R_DATABRARYR_VB')} -\item{rq}{An \code{httr2} request object. Default is NULL.} +\item{rq}{An \code{httr2} request object. Default is \code{NULL}, in which case a +default authenticated request is generated.} } \value{ -Full filename of the downloaded file. +A list describing the processing task (\code{status}, \code{message}, +\code{task_id}) or \code{NULL} when the request fails. } \description{ -Download Zip Archive of All Data in a Volume. +Volume-level ZIP archives are prepared asynchronously by the Django API. +Calling \code{download_volume_zip()} queues the job and returns a processing task +descriptor. When the archive is ready, Databrary emails a signed download +link to the authenticated user. } \examples{ \donttest{ \dontrun{ -download_volume_zip() # Zip file of all data from volume 31, the default. +download_volume_zip(vol_id = 31) } } diff --git a/tests/testthat/test-download_folder_asset.R b/tests/testthat/test-download_folder_asset.R new file mode 100644 index 0000000..279a2b6 --- /dev/null +++ b/tests/testthat/test-download_folder_asset.R @@ -0,0 +1,70 @@ +# download_folder_asset ------------------------------------------------------- +test_that("download_folder_asset rejects bad input parameters", { + expect_error(download_folder_asset(vol_id = -1)) + expect_error(download_folder_asset(vol_id = 0)) + expect_error(download_folder_asset(vol_id = "a")) + expect_error(download_folder_asset(vol_id = list(a = 1, b = 2))) + expect_error(download_folder_asset(vol_id = TRUE)) + + expect_error(download_folder_asset(folder_id = -1)) + expect_error(download_folder_asset(folder_id = 0)) + expect_error(download_folder_asset(folder_id = "a")) + expect_error(download_folder_asset(folder_id = list(a = 1, b = 2))) + expect_error(download_folder_asset(folder_id = TRUE)) + + expect_error(download_folder_asset(asset_id = -1)) + expect_error(download_folder_asset(asset_id = 0)) + expect_error(download_folder_asset(asset_id = "a")) + expect_error(download_folder_asset(asset_id = list(a = 1, b = 2))) + expect_error(download_folder_asset(asset_id = TRUE)) + + expect_error(download_folder_asset(file_name = 3)) + expect_error(download_folder_asset(file_name = list(a = 1, b = 2))) + expect_error(download_folder_asset(file_name = TRUE)) + + expect_error(download_folder_asset(target_dir = 3)) + expect_error(download_folder_asset(target_dir = list(a = 1, b = 2))) + expect_error(download_folder_asset(target_dir = TRUE)) + + expect_error(download_folder_asset(timeout_secs = -1)) + expect_error(download_folder_asset(timeout_secs = 0)) + expect_error(download_folder_asset(timeout_secs = list(1, 2))) + + expect_error(download_folder_asset(vb = -1)) + expect_error(download_folder_asset(vb = 3)) + expect_error(download_folder_asset(vb = "a")) + expect_error(download_folder_asset(vb = list(a = 1, b = 2))) + + expect_error(download_folder_asset(rq = "a")) + expect_error(download_folder_asset(rq = -1)) + expect_error(download_folder_asset(rq = c(2, 3))) + expect_error(download_folder_asset(rq = list(a = 1, b = 2))) +}) + +test_that("download_folder_asset fetches signed link", { + tmp_dir <- tempdir() + fake_link <- list(download_url = "https://example.com/file.bin", file_name = "example.bin") + class(fake_link) <- c("databrary_signed_download", "list") + + captured_path <- NULL + captured_dest <- NULL + + result <- with_mocked_bindings( + download_folder_asset(vol_id = 1, folder_id = 2, asset_id = 3, target_dir = tmp_dir), + request_signed_download_link = function(path, rq = NULL, vb = FALSE) { + captured_path <<- path + fake_link + }, + download_signed_file = function(download_url, dest_path, timeout_secs = REQUEST_TIMEOUT, vb = FALSE) { + captured_dest <<- dest_path + dest_path + } + ) + + expect_true(grepl("example.bin$", result)) + expect_equal(result, captured_dest) + expect_equal(captured_path, sprintf("/volumes/%s/folders/%s/files/%s/download-link/", 1, 2, 3)) +}) + + + diff --git a/tests/testthat/test-download_folder_assets_fr_df.R b/tests/testthat/test-download_folder_assets_fr_df.R new file mode 100644 index 0000000..ccb4485 --- /dev/null +++ b/tests/testthat/test-download_folder_assets_fr_df.R @@ -0,0 +1,65 @@ +# download_folder_assets_fr_df ----------------------------------------------- +test_that("download_folder_assets_fr_df rejects bad input parameters", { + expect_error(download_folder_assets_fr_df(folder_df = 3)) + expect_error(download_folder_assets_fr_df(folder_df = "a")) + expect_error(download_folder_assets_fr_df(folder_df = TRUE)) + + missing_cols <- data.frame(vol_id = 1, folder_id = 1, asset_id = 1) + expect_error(download_folder_assets_fr_df(folder_df = missing_cols)) + + expect_error(download_folder_assets_fr_df(target_dir = 3)) + expect_error(download_folder_assets_fr_df(target_dir = list(a = 1, b = 2))) + expect_error(download_folder_assets_fr_df(target_dir = TRUE)) + + expect_error(download_folder_assets_fr_df(add_folder_subdir = -1)) + expect_error(download_folder_assets_fr_df(add_folder_subdir = 3)) + expect_error(download_folder_assets_fr_df(add_folder_subdir = "a")) + expect_error(download_folder_assets_fr_df(add_folder_subdir = list(a = 1, b = 2))) + + expect_error(download_folder_assets_fr_df(overwrite = -1)) + expect_error(download_folder_assets_fr_df(overwrite = 3)) + expect_error(download_folder_assets_fr_df(overwrite = "a")) + expect_error(download_folder_assets_fr_df(overwrite = list(a = 1, b = 2))) + + expect_error(download_folder_assets_fr_df(make_portable_fn = -1)) + expect_error(download_folder_assets_fr_df(make_portable_fn = 3)) + expect_error(download_folder_assets_fr_df(make_portable_fn = "a")) + expect_error(download_folder_assets_fr_df(make_portable_fn = list(a = 1, b = 2))) + + expect_error(download_folder_assets_fr_df(timeout_secs = -1)) + expect_error(download_folder_assets_fr_df(timeout_secs = TRUE)) + expect_error(download_folder_assets_fr_df(timeout_secs = "a")) + expect_error(download_folder_assets_fr_df(timeout_secs = list(a = 1, b = 2))) + + expect_error(download_folder_assets_fr_df(vb = -1)) + expect_error(download_folder_assets_fr_df(vb = 3)) + expect_error(download_folder_assets_fr_df(vb = "a")) + expect_error(download_folder_assets_fr_df(vb = list(a = 1, b = 2))) + + expect_error(download_folder_assets_fr_df(rq = "a")) + expect_error(download_folder_assets_fr_df(rq = -1)) + expect_error(download_folder_assets_fr_df(rq = c(1, 2))) + expect_error(download_folder_assets_fr_df(rq = list(a = 1, b = 2))) +}) + +test_that("download_folder_assets_fr_df iterates rows", { + folder_df <- tibble::tibble( + vol_id = c(1, 1), + folder_id = c(2, 2), + asset_id = c(3, 4), + asset_name = c("file_a", "file_b") + ) + + calls <- list() + results <- with_mocked_bindings( + download_folder_assets_fr_df(folder_df = folder_df, target_dir = tempdir(), vb = FALSE), + download_single_folder_asset_fr_df = function(i, folder_df, ...) { + calls[[length(calls) + 1]] <<- list(i = i, folder_df = folder_df) + paste0("path-", i) + } + ) + + expect_equal(results, c("path-1", "path-2")) + expect_equal(vapply(calls, function(x) x$i, numeric(1)), c(1, 2)) +}) + diff --git a/tests/testthat/test-download_folder_zip.R b/tests/testthat/test-download_folder_zip.R new file mode 100644 index 0000000..519da6c --- /dev/null +++ b/tests/testthat/test-download_folder_zip.R @@ -0,0 +1,41 @@ +# download_folder_zip --------------------------------------------------------- +test_that("download_folder_zip rejects bad input parameters", { + expect_error(download_folder_zip(vol_id = -1)) + expect_error(download_folder_zip(vol_id = 0)) + expect_error(download_folder_zip(vol_id = "a")) + expect_error(download_folder_zip(vol_id = list(a = 1, b = 2))) + expect_error(download_folder_zip(vol_id = TRUE)) + + expect_error(download_folder_zip(folder_id = -1)) + expect_error(download_folder_zip(folder_id = 0)) + expect_error(download_folder_zip(folder_id = "a")) + expect_error(download_folder_zip(folder_id = list(a = 1, b = 2))) + expect_error(download_folder_zip(folder_id = TRUE)) + + expect_error(download_folder_zip(vb = -1)) + expect_error(download_folder_zip(vb = 3)) + expect_error(download_folder_zip(vb = "a")) + expect_error(download_folder_zip(vb = list(a = 1, b = 2))) + + expect_error(download_folder_zip(rq = "a")) + expect_error(download_folder_zip(rq = -1)) + expect_error(download_folder_zip(rq = c(1, 2))) +}) + +test_that("download_folder_zip returns processing task", { + captured_path <- NULL + fake_task <- list(status = "processing", message = "queued", task_id = "xyz") + task <- with_mocked_bindings( + download_folder_zip(vol_id = 2, folder_id = 5), + request_processing_task = function(path, rq = NULL, vb = FALSE) { + captured_path <<- path + fake_task + } + ) + + expect_identical(task, fake_task) + expect_equal(captured_path, sprintf("/volumes/%s/folders/%s/download-link/", 2, 5)) +}) + + + diff --git a/tests/testthat/test-download_session_asset.R b/tests/testthat/test-download_session_asset.R index ec24187..b61aa70 100644 --- a/tests/testthat/test-download_session_asset.R +++ b/tests/testthat/test-download_session_asset.R @@ -1,32 +1,42 @@ # download_session_asset --------------------------------------------------------- test_that("download_session_asset rejects bad input parameters", { + expect_error(download_session_asset(vol_id = -1)) + expect_error(download_session_asset(vol_id = 0)) + expect_error(download_session_asset(vol_id = "a")) + expect_error(download_session_asset(vol_id = list(a = 1, b = 2))) + expect_error(download_session_asset(vol_id = TRUE)) + expect_error(download_session_asset(asset_id = -1)) expect_error(download_session_asset(asset_id = 0)) expect_error(download_session_asset(asset_id = "a")) - expect_error(download_session_asset(asset_id = list(a=1, b=2))) + expect_error(download_session_asset(asset_id = list(a = 1, b = 2))) expect_error(download_session_asset(asset_id = TRUE)) - + expect_error(download_session_asset(session_id = -1)) expect_error(download_session_asset(session_id = 0)) expect_error(download_session_asset(session_id = "a")) - expect_error(download_session_asset(session_id = list(a=1, b=2))) + expect_error(download_session_asset(session_id = list(a = 1, b = 2))) expect_error(download_session_asset(session_id = TRUE)) - + expect_error(download_session_asset(file_name = 3)) - expect_error(download_session_asset(file_name = list(a=1, b=2))) + expect_error(download_session_asset(file_name = list(a = 1, b = 2))) expect_error(download_session_asset(file_name = TRUE)) - + expect_error(download_session_asset(target_dir = 3)) - expect_error(download_session_asset(target_dir = list(a=1, b=2))) + expect_error(download_session_asset(target_dir = list(a = 1, b = 2))) expect_error(download_session_asset(target_dir = TRUE)) - + + expect_error(download_session_asset(timeout_secs = -1)) + expect_error(download_session_asset(timeout_secs = 0)) + expect_error(download_session_asset(timeout_secs = list(1, 2))) + expect_error(download_session_asset(vb = -1)) expect_error(download_session_asset(vb = 3)) expect_error(download_session_asset(vb = "a")) - expect_error(download_session_asset(vb = list(a=1, b=2))) - + expect_error(download_session_asset(vb = list(a = 1, b = 2))) + expect_error(download_session_asset(rq = "a")) expect_error(download_session_asset(rq = -1)) - expect_error(download_session_asset(rq = c(2,3))) - expect_error(download_session_asset(rq = list(a=1, b=2))) + expect_error(download_session_asset(rq = c(2, 3))) + expect_error(download_session_asset(rq = list(a = 1, b = 2))) }) diff --git a/tests/testthat/test-download_session_assets_fr_df.R b/tests/testthat/test-download_session_assets_fr_df.R index 275f393..fed17e6 100644 --- a/tests/testthat/test-download_session_assets_fr_df.R +++ b/tests/testthat/test-download_session_assets_fr_df.R @@ -1,43 +1,43 @@ # download_session_assets_fr_df --------------------------------------------------------- -test_that("download_session_assets_fr_df rejects bad input parameters", - { - expect_error(download_session_assets_fr_df(session_asset_entry = 3)) - expect_error(download_session_assets_fr_df(session_asset_entry = "a")) - expect_error(download_session_assets_fr_df(session_asset_entry = TRUE)) - expect_error(download_session_assets_fr_df(session_asset_entry = list(a = 1, b = - 2))) - expect_error(download_session_assets_fr_df(target_dir = 3)) - expect_error(download_session_assets_fr_df(target_dir = list(a = 1, b = - 2))) - expect_error(download_session_assets_fr_df(target_dir = TRUE)) - - expect_error(download_session_assets_fr_df(add_session_subdir = -1)) - expect_error(download_session_assets_fr_df(add_session_subdir = 3)) - expect_error(download_session_assets_fr_df(add_session_subdir = "a")) - expect_error(download_session_assets_fr_df(add_session_subdir = list(a = 1, b = 2))) - - expect_error(download_session_assets_fr_df(overwrite = -1)) - expect_error(download_session_assets_fr_df(overwrite = 3)) - expect_error(download_session_assets_fr_df(overwrite = "a")) - expect_error(download_session_assets_fr_df(overwrite = list(a = 1, b = 2))) - - expect_error(download_session_assets_fr_df(make_portable_fn = -1)) - expect_error(download_session_assets_fr_df(make_portable_fn = 3)) - expect_error(download_session_assets_fr_df(make_portable_fn = "a")) - expect_error(download_session_assets_fr_df(make_portable_fn = list(a = 1, b = 2))) - - expect_error(download_session_assets_fr_df(timeout_secs = -1)) - expect_error(download_session_assets_fr_df(timeout_secs = TRUE)) - expect_error(download_session_assest_fr_df(timeout_secs = "a")) - expect_error(download_session_assets_fr_df(timeout_secs = list(a = 1, b = 2))) - - expect_error(download_session_assets_fr_df(vb = -1)) - expect_error(download_session_assets_fr_df(vb = 3)) - expect_error(download_session_assets_fr_df(vb = "a")) - expect_error(download_session_assets_fr_df(vb = list(a = 1, b = 2))) - - expect_error(download_session_assets_fr_df(rq = "a")) - expect_error(download_session_assets_fr_df(rq = -1)) - expect_error(download_session_assets_fr_df(rq = c(2, 3))) - expect_error(download_session_assets_fr_df(rq = list(a = 1, b = 2))) - }) +test_that("download_session_assets_fr_df rejects bad input parameters", { + expect_error(download_session_assets_fr_df(session_df = 3)) + expect_error(download_session_assets_fr_df(session_df = "a")) + expect_error(download_session_assets_fr_df(session_df = TRUE)) + + missing_cols <- data.frame(vol_id = 1, session_id = 1, asset_id = 1) + expect_error(download_session_assets_fr_df(session_df = missing_cols)) + + expect_error(download_session_assets_fr_df(target_dir = 3)) + expect_error(download_session_assets_fr_df(target_dir = list(a = 1, b = 2))) + expect_error(download_session_assets_fr_df(target_dir = TRUE)) + + expect_error(download_session_assets_fr_df(add_session_subdir = -1)) + expect_error(download_session_assets_fr_df(add_session_subdir = 3)) + expect_error(download_session_assets_fr_df(add_session_subdir = "a")) + expect_error(download_session_assets_fr_df(add_session_subdir = list(a = 1, b = 2))) + + expect_error(download_session_assets_fr_df(overwrite = -1)) + expect_error(download_session_assets_fr_df(overwrite = 3)) + expect_error(download_session_assets_fr_df(overwrite = "a")) + expect_error(download_session_assets_fr_df(overwrite = list(a = 1, b = 2))) + + expect_error(download_session_assets_fr_df(make_portable_fn = -1)) + expect_error(download_session_assets_fr_df(make_portable_fn = 3)) + expect_error(download_session_assets_fr_df(make_portable_fn = "a")) + expect_error(download_session_assets_fr_df(make_portable_fn = list(a = 1, b = 2))) + + expect_error(download_session_assets_fr_df(timeout_secs = -1)) + expect_error(download_session_assets_fr_df(timeout_secs = TRUE)) + expect_error(download_session_assets_fr_df(timeout_secs = "a")) + expect_error(download_session_assets_fr_df(timeout_secs = list(a = 1, b = 2))) + + expect_error(download_session_assets_fr_df(vb = -1)) + expect_error(download_session_assets_fr_df(vb = 3)) + expect_error(download_session_assets_fr_df(vb = "a")) + expect_error(download_session_assets_fr_df(vb = list(a = 1, b = 2))) + + expect_error(download_session_assets_fr_df(rq = "a")) + expect_error(download_session_assets_fr_df(rq = -1)) + expect_error(download_session_assets_fr_df(rq = c(2, 3))) + expect_error(download_session_assets_fr_df(rq = list(a = 1, b = 2))) +}) diff --git a/tests/testthat/test-download_session_csv.R b/tests/testthat/test-download_session_csv.R index 2ee0759..8e7eaa0 100644 --- a/tests/testthat/test-download_session_csv.R +++ b/tests/testthat/test-download_session_csv.R @@ -3,24 +3,51 @@ test_that("download_session_csv rejects bad input parameters", { expect_error(download_session_csv(vol_id = -1)) expect_error(download_session_csv(vol_id = 0)) expect_error(download_session_csv(vol_id = "a")) - expect_error(download_session_csv(vol_id = list(a=1, b=2))) + expect_error(download_session_csv(vol_id = list(a = 1, b = 2))) expect_error(download_session_csv(vol_id = TRUE)) - - expect_error(download_session_csv(file_name = 3)) - expect_error(download_session_csv(file_name = list(a=1, b=2))) - expect_error(download_session_csv(file_name = TRUE)) - - expect_error(download_session_csv(target_dir = 3)) - expect_error(download_session_csv(target_dir = list(a=1, b=2))) - expect_error(download_session_csv(target_dir = TRUE)) - expect_error(download_session_csv(as_df = -1)) - expect_error(download_session_csv(as_df = 3)) - expect_error(download_session_csv(as_df = "a")) - expect_error(download_session_csv(as_df = list(a=1, b=2))) - + expect_error(download_session_csv(session_id = -1)) + expect_error(download_session_csv(session_id = 0)) + expect_error(download_session_csv(session_id = "a")) + expect_error(download_session_csv(session_id = list(a = 1, b = 2))) + expect_error(download_session_csv(session_id = TRUE)) + expect_error(download_session_csv(vb = -1)) expect_error(download_session_csv(vb = 3)) expect_error(download_session_csv(vb = "a")) - expect_error(download_session_csv(vb = list(a=1, b=2))) + expect_error(download_session_csv(vb = list(a = 1, b = 2))) + + expect_error(download_session_csv(rq = "a")) + expect_error(download_session_csv(rq = -1)) + expect_error(download_session_csv(rq = c(1, 2))) +}) + +test_that("download_session_csv returns volume processing task", { + captured_path <- NULL + fake_task <- list(status = "processing", message = "queued", task_id = "abc") + task <- with_mocked_bindings( + download_session_csv(), + request_processing_task = function(path, rq = NULL, vb = FALSE) { + captured_path <<- path + fake_task + } + ) + + expect_identical(task, fake_task) + expect_equal(captured_path, sprintf("/volumes/%s/csv-download-link/", 1)) +}) + +test_that("download_session_csv returns session processing task", { + captured_path <- NULL + fake_task <- list(status = "processing", message = "queued", task_id = "def") + task <- with_mocked_bindings( + download_session_csv(vol_id = 2, session_id = 11), + request_processing_task = function(path, rq = NULL, vb = FALSE) { + captured_path <<- path + fake_task + } + ) + + expect_identical(task, fake_task) + expect_equal(captured_path, sprintf("/volumes/%s/sessions/%s/csv-download-link/", 2, 11)) }) diff --git a/tests/testthat/test-download_session_zip.R b/tests/testthat/test-download_session_zip.R index 91bd6f6..3403885 100644 --- a/tests/testthat/test-download_session_zip.R +++ b/tests/testthat/test-download_session_zip.R @@ -2,29 +2,38 @@ test_that("download_session_zip rejects bad input parameters", { expect_error(download_session_zip(vol_id = -1)) expect_error(download_session_zip(vol_id = "a")) - expect_error(download_session_zip(vol_id = list(a=1, b=2))) + expect_error(download_session_zip(vol_id = list(a = 1, b = 2))) expect_error(download_session_zip(vol_id = TRUE)) - + expect_error(download_session_zip(session_id = -1)) expect_error(download_session_zip(session_id = "a")) - expect_error(download_session_zip(session_id = list(a=1, b=2))) + expect_error(download_session_zip(session_id = list(a = 1, b = 2))) expect_error(download_session_zip(session_id = TRUE)) - - expect_error(download_session_zip(out_dir = -1)) - expect_error(download_session_zip(out_dir = list(a=1, b=2))) - expect_error(download_session_zip(out_dir = TRUE)) - - expect_error(download_session_zip(file_name = -1)) - expect_error(download_session_zip(file_name = list(a=1, b=2))) - expect_error(download_session_zip(file_name = TRUE)) - + expect_error(download_session_zip(vb = -1)) expect_error(download_session_zip(vb = 3)) expect_error(download_session_zip(vb = "a")) - expect_error(download_session_zip(vb = list(a=1, b=2))) + expect_error(download_session_zip(vb = list(a = 1, b = 2))) + + expect_error(download_session_zip(rq = "a")) + expect_error(download_session_zip(rq = -1)) + expect_error(download_session_zip(rq = c(1, 2))) }) -test_that("download_session_zip returns string", { - testthat::skip("Download route still under migration to Django signed-link workflow") - expect_true(is.character(download_session_zip())) +test_that("download_session_zip returns processing task", { + captured_path <- NULL + fake_task <- list(status = "processing", message = "queued", task_id = "abc") + task <- with_mocked_bindings( + download_session_zip(), + request_processing_task = function(path, rq = NULL, vb = FALSE) { + captured_path <<- path + fake_task + } + ) + + expect_identical(task, fake_task) + expect_equal( + captured_path, + sprintf("/volumes/%s/sessions/%s/download-link/", 31, 9803) + ) }) diff --git a/tests/testthat/test-download_single_folder_asset_fr_df.R b/tests/testthat/test-download_single_folder_asset_fr_df.R new file mode 100644 index 0000000..91b31a2 --- /dev/null +++ b/tests/testthat/test-download_single_folder_asset_fr_df.R @@ -0,0 +1,79 @@ +# download_single_folder_asset_fr_df ---------------------------------------- +test_that("download_single_folder_asset_fr_df rejects bad input parameters", { + expect_error(download_single_folder_asset_fr_df(i = 0)) + expect_error(download_single_folder_asset_fr_df(i = -1)) + expect_error(download_single_folder_asset_fr_df(i = "a")) + + expect_error(download_single_folder_asset_fr_df(i = 1, folder_df = 3)) + expect_error(download_single_folder_asset_fr_df(i = 1, folder_df = "a")) + expect_error(download_single_folder_asset_fr_df(i = 1, folder_df = TRUE)) + + missing_cols <- data.frame(vol_id = 1, folder_id = 1, asset_id = 1) + expect_error(download_single_folder_asset_fr_df(i = 1, folder_df = missing_cols)) + + expect_error(download_single_folder_asset_fr_df(i = 1, target_dir = 3)) + expect_error(download_single_folder_asset_fr_df(i = 1, target_dir = list(a = 1, b = 2))) + expect_error(download_single_folder_asset_fr_df(i = 1, target_dir = TRUE)) + + expect_error(download_single_folder_asset_fr_df(i = 1, add_folder_subdir = -1)) + expect_error(download_single_folder_asset_fr_df(i = 1, add_folder_subdir = 3)) + expect_error(download_single_folder_asset_fr_df(i = 1, add_folder_subdir = "a")) + expect_error(download_single_folder_asset_fr_df(i = 1, add_folder_subdir = list(a = 1, b = 2))) + + expect_error(download_single_folder_asset_fr_df(i = 1, overwrite = -1)) + expect_error(download_single_folder_asset_fr_df(i = 1, overwrite = 3)) + expect_error(download_single_folder_asset_fr_df(i = 1, overwrite = "a")) + expect_error(download_single_folder_asset_fr_df(i = 1, overwrite = list(a = 1, b = 2))) + + expect_error(download_single_folder_asset_fr_df(i = 1, make_portable_fn = -1)) + expect_error(download_single_folder_asset_fr_df(i = 1, make_portable_fn = 3)) + expect_error(download_single_folder_asset_fr_df(i = 1, make_portable_fn = "a")) + expect_error(download_single_folder_asset_fr_df(i = 1, make_portable_fn = list(a = 1, b = 2))) + + expect_error(download_single_folder_asset_fr_df(i = 1, timeout_secs = -1)) + expect_error(download_single_folder_asset_fr_df(i = 1, timeout_secs = TRUE)) + expect_error(download_single_folder_asset_fr_df(i = 1, timeout_secs = "a")) + expect_error(download_single_folder_asset_fr_df(i = 1, timeout_secs = list(a = 1, b = 2))) + + expect_error(download_single_folder_asset_fr_df(i = 1, vb = -1)) + expect_error(download_single_folder_asset_fr_df(i = 1, vb = 3)) + expect_error(download_single_folder_asset_fr_df(i = 1, vb = "a")) + expect_error(download_single_folder_asset_fr_df(i = 1, vb = list(a = 1, b = 2))) + + expect_error(download_single_folder_asset_fr_df(i = 1, rq = "a")) + expect_error(download_single_folder_asset_fr_df(i = 1, rq = -1)) + expect_error(download_single_folder_asset_fr_df(i = 1, rq = c(2, 3))) + expect_error(download_single_folder_asset_fr_df(i = 1, rq = list(a = 1, b = 2))) +}) + +test_that("download_single_folder_asset_fr_df delegates to download_folder_asset", { + folder_df <- tibble::tibble( + vol_id = 1, + folder_id = 2, + asset_id = 3, + asset_name = "demo", + format_extension = "txt" + ) + + captured <- NULL + result <- with_mocked_bindings( + download_single_folder_asset_fr_df(i = 1, folder_df = folder_df, target_dir = tempdir()), + download_folder_asset = function(vol_id, folder_id, asset_id, file_name, target_dir, ...) { + captured <<- list( + vol_id = vol_id, + folder_id = folder_id, + asset_id = asset_id, + file_name = file_name, + target_dir = target_dir + ) + "downloaded-path" + } + ) + + expect_equal(result, "downloaded-path") + expect_equal(captured$vol_id, 1) + expect_equal(captured$folder_id, 2) + expect_equal(captured$asset_id, 3) + expect_true(grepl("demo.txt$", captured$file_name)) +}) + diff --git a/tests/testthat/test-download_single_session_asset_fr_df.R b/tests/testthat/test-download_single_session_asset_fr_df.R index 8cec8e3..263cccb 100644 --- a/tests/testthat/test-download_single_session_asset_fr_df.R +++ b/tests/testthat/test-download_single_session_asset_fr_df.R @@ -1,44 +1,47 @@ # download_single_session_asset_fr_df --------------------------------------------------------- -test_that("download_single_session_asset_fr_df rejects bad input parameters", - { - expect_error(download_single_session_asset_fr_df(session_asset_entry = 3)) - expect_error(download_single_session_asset_fr_df(session_asset_entry = "a")) - expect_error(download_single_session_asset_fr_df(session_asset_entry = TRUE)) - expect_error(download_single_session_asset_fr_df(session_asset_entry = list(a = 1, b = - 2))) - - expect_error(download_single_session_asset_fr_df(target_dir = 3)) - expect_error(download_single_session_asset_fr_df(target_dir = list(a = 1, b = - 2))) - expect_error(download_single_session_asset_fr_df(target_dir = TRUE)) - - expect_error(download_single_session_asset_fr_df(add_session_subdir = -1)) - expect_error(download_single_session_asset_fr_df(add_session_subdir = 3)) - expect_error(download_single_session_asset_fr_df(add_session_subdir = "a")) - expect_error(download_single_session_asset_fr_df(add_session_subdir = list(a = 1, b = 2))) - - expect_error(download_single_session_asset_fr_df(overwrite = -1)) - expect_error(download_single_session_asset_fr_df(overwrite = 3)) - expect_error(download_single_session_asset_fr_df(overwrite = "a")) - expect_error(download_single_session_asset_fr_df(overwrite = list(a = 1, b = 2))) - - expect_error(download_single_session_asset_fr_df(make_portable_fn = -1)) - expect_error(download_single_session_asset_fr_df(make_portable_fn = 3)) - expect_error(download_single_session_asset_fr_df(make_portable_fn = "a")) - expect_error(download_single_session_asset_fr_df(make_portable_fn = list(a = 1, b = 2))) - - expect_error(download_single_session_asset_fr_df(timeout_secs = -1)) - expect_error(download_single_session_asset_fr_df(timeout_secs = TRUE)) - expect_error(download_single_session_asset_fr_df(timeout_secs = "a")) - expect_error(download_single_session_asset_fr_df(timeout_secs = list(a = 1, b = 2))) - - expect_error(download_single_session_asset_fr_df(vb = -1)) - expect_error(download_single_session_asset_fr_df(vb = 3)) - expect_error(download_single_session_asset_fr_df(vb = "a")) - expect_error(download_single_session_asset_fr_df(vb = list(a = 1, b = 2))) - - expect_error(download_single_session_asset_fr_df(rq = "a")) - expect_error(download_single_session_asset_fr_df(rq = -1)) - expect_error(download_single_session_asset_fr_df(rq = c(2, 3))) - expect_error(download_single_session_asset_fr_df(rq = list(a = 1, b = 2))) - }) +test_that("download_single_session_asset_fr_df rejects bad input parameters", { + expect_error(download_single_session_asset_fr_df(i = 0)) + expect_error(download_single_session_asset_fr_df(i = -1)) + expect_error(download_single_session_asset_fr_df(i = "a")) + + expect_error(download_single_session_asset_fr_df(session_df = 3)) + expect_error(download_single_session_asset_fr_df(session_df = "a")) + expect_error(download_single_session_asset_fr_df(session_df = TRUE)) + + missing_cols <- data.frame(vol_id = 1, session_id = 1, asset_id = 1) + expect_error(download_single_session_asset_fr_df(i = 1, session_df = missing_cols)) + + expect_error(download_single_session_asset_fr_df(i = 1, target_dir = 3)) + expect_error(download_single_session_asset_fr_df(i = 1, target_dir = list(a = 1, b = 2))) + expect_error(download_single_session_asset_fr_df(i = 1, target_dir = TRUE)) + + expect_error(download_single_session_asset_fr_df(i = 1, add_session_subdir = -1)) + expect_error(download_single_session_asset_fr_df(i = 1, add_session_subdir = 3)) + expect_error(download_single_session_asset_fr_df(i = 1, add_session_subdir = "a")) + expect_error(download_single_session_asset_fr_df(i = 1, add_session_subdir = list(a = 1, b = 2))) + + expect_error(download_single_session_asset_fr_df(i = 1, overwrite = -1)) + expect_error(download_single_session_asset_fr_df(i = 1, overwrite = 3)) + expect_error(download_single_session_asset_fr_df(i = 1, overwrite = "a")) + expect_error(download_single_session_asset_fr_df(i = 1, overwrite = list(a = 1, b = 2))) + + expect_error(download_single_session_asset_fr_df(i = 1, make_portable_fn = -1)) + expect_error(download_single_session_asset_fr_df(i = 1, make_portable_fn = 3)) + expect_error(download_single_session_asset_fr_df(i = 1, make_portable_fn = "a")) + expect_error(download_single_session_asset_fr_df(i = 1, make_portable_fn = list(a = 1, b = 2))) + + expect_error(download_single_session_asset_fr_df(i = 1, timeout_secs = -1)) + expect_error(download_single_session_asset_fr_df(i = 1, timeout_secs = TRUE)) + expect_error(download_single_session_asset_fr_df(i = 1, timeout_secs = "a")) + expect_error(download_single_session_asset_fr_df(i = 1, timeout_secs = list(a = 1, b = 2))) + + expect_error(download_single_session_asset_fr_df(i = 1, vb = -1)) + expect_error(download_single_session_asset_fr_df(i = 1, vb = 3)) + expect_error(download_single_session_asset_fr_df(i = 1, vb = "a")) + expect_error(download_single_session_asset_fr_df(i = 1, vb = list(a = 1, b = 2))) + + expect_error(download_single_session_asset_fr_df(i = 1, rq = "a")) + expect_error(download_single_session_asset_fr_df(i = 1, rq = -1)) + expect_error(download_single_session_asset_fr_df(i = 1, rq = c(2, 3))) + expect_error(download_single_session_asset_fr_df(i = 1, rq = list(a = 1, b = 2))) +}) diff --git a/tests/testthat/test-download_video.R b/tests/testthat/test-download_video.R index 90af42e..41f11d7 100644 --- a/tests/testthat/test-download_video.R +++ b/tests/testthat/test-download_video.R @@ -1,29 +1,41 @@ # download_video --------------------------------------------------------- test_that("download_video rejects bad input parameters", { - expect_error(download_asset(asset_id = -1)) - expect_error(download_asset(asset_id = 0)) - expect_error(download_asset(asset_id = "a")) - expect_error(download_asset(asset_id = list(a=1, b=2))) - expect_error(download_asset(asset_id = TRUE)) - - expect_error(download_asset(session_id = -1)) - expect_error(download_asset(session_id = 0)) - expect_error(download_asset(session_id = "a")) - expect_error(download_asset(session_id = list(a=1, b=2))) - expect_error(download_asset(session_id = TRUE)) + expect_error(download_video(vol_id = -1)) + expect_error(download_video(vol_id = 0)) + expect_error(download_video(vol_id = "a")) + expect_error(download_video(vol_id = list(a = 1, b = 2))) + expect_error(download_video(vol_id = TRUE)) + + expect_error(download_video(asset_id = -1)) + expect_error(download_video(asset_id = 0)) + expect_error(download_video(asset_id = "a")) + expect_error(download_video(asset_id = list(a = 1, b = 2))) + expect_error(download_video(asset_id = TRUE)) + + expect_error(download_video(session_id = -1)) + expect_error(download_video(session_id = 0)) + expect_error(download_video(session_id = "a")) + expect_error(download_video(session_id = list(a = 1, b = 2))) + expect_error(download_video(session_id = TRUE)) expect_error(download_video(file_name = 3)) - expect_error(download_video(file_name = list(a=1, b=2))) + expect_error(download_video(file_name = list(a = 1, b = 2))) expect_error(download_video(file_name = TRUE)) - + expect_error(download_video(file_name = "not_mp3")) + expect_error(download_video(target_dir = 3)) - expect_error(download_video(target_dir = list(a=1, b=2))) + expect_error(download_video(target_dir = list(a = 1, b = 2))) expect_error(download_video(target_dir = TRUE)) - + expect_error(download_video(vb = -1)) expect_error(download_video(vb = 3)) expect_error(download_video(vb = "a")) - expect_error(download_video(vb = list(a=1, b=2))) + expect_error(download_video(vb = list(a = 1, b = 2))) + + expect_error(download_video(rq = "a")) + expect_error(download_video(rq = -1)) + expect_error(download_video(rq = c(1, 2))) + expect_error(download_video(rq = list(a = 1, b = 2))) }) # Removing 2023-10-09 until Databrary system responds more quickly diff --git a/tests/testthat/test-download_volume_zip.R b/tests/testthat/test-download_volume_zip.R index 8c0bc58..35c5e25 100644 --- a/tests/testthat/test-download_volume_zip.R +++ b/tests/testthat/test-download_volume_zip.R @@ -2,25 +2,31 @@ test_that("download_volume_zip rejects bad input parameters", { expect_error(download_volume_zip(vol_id = -1)) expect_error(download_volume_zip(vol_id = "a")) - expect_error(download_volume_zip(vol_id = list(a=1, b=2))) + expect_error(download_volume_zip(vol_id = list(a = 1, b = 2))) expect_error(download_volume_zip(vol_id = TRUE)) - - expect_error(download_volume_zip(out_dir = -1)) - expect_error(download_volume_zip(out_dir = list(a=1, b=2))) - expect_error(download_volume_zip(out_dir = TRUE)) - - expect_error(download_volume_zip(file_name = -1)) - expect_error(download_volume_zip(file_name = list(a=1, b=2))) - expect_error(download_volume_zip(file_name = TRUE)) - + expect_error(download_volume_zip(vb = -1)) expect_error(download_volume_zip(vb = 3)) expect_error(download_volume_zip(vb = "a")) - expect_error(download_volume_zip(vb = list(a=1, b=2))) + expect_error(download_volume_zip(vb = list(a = 1, b = 2))) + + expect_error(download_volume_zip(rq = "a")) + expect_error(download_volume_zip(rq = -1)) + expect_error(download_volume_zip(rq = c(1, 2))) }) -test_that("download_volume_zip returns string", { - testthat::skip("Download route still under migration to Django signed-link workflow") - expect_true(is.character(download_volume_zip())) +test_that("download_volume_zip returns processing task", { + captured_path <- NULL + fake_task <- list(status = "processing", message = "queued", task_id = "abc") + task <- with_mocked_bindings( + download_volume_zip(), + request_processing_task = function(path, rq = NULL, vb = FALSE) { + captured_path <<- path + fake_task + } + ) + + expect_identical(task, fake_task) + expect_equal(captured_path, sprintf("/volumes/%s/download-link/", 31)) })