Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions R/CONSTANTS.R
Original file line number Diff line number Diff line change
Expand Up @@ -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/"
Expand Down
127 changes: 127 additions & 0 deletions R/download_folder_asset.R
Original file line number Diff line number Diff line change
@@ -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
)
}



114 changes: 114 additions & 0 deletions R/download_folder_assets_fr_df.R
Original file line number Diff line number Diff line change
@@ -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()
}



54 changes: 54 additions & 0 deletions R/download_folder_zip.R
Original file line number Diff line number Diff line change
@@ -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)
}



Loading