library(epwshiftr)
workflow_root <- file.path(tempdir(), "epwshiftr-downloader")
if (dir.exists(workflow_root)) {
unlink(workflow_root, recursive = TRUE)
}
dir.create(workflow_root, recursive = TRUE, showWarnings = FALSE)
options(
epwshiftr.dir_cache = file.path(workflow_root, "cache")
)
select_cols <- function(x, cols, n = 10L) {
x <- data.table::as.data.table(x)
keep <- intersect(cols, names(x))
if (length(keep)) {
x <- x[, keep, with = FALSE]
}
utils::head(x, n)
}Downloader is the transfer engine used by
shift_download(), EsgStore$download_files(),
and the command line interface. It is also a general downloader: a plan
only needs logical_file_id, filename, and
url columns. ESGF-specific columns such as
file_key, dataset_id, service, or
data_node enrich the manifest but are not required for
ordinary HTTPS files.
The main future EPW workflow
usually avoids full NetCDF downloads because
shift_extract() reads through OPeNDAP first. Use the
downloader when you intentionally need local files, offline work,
repeated extraction, resumable transfers, background jobs, or
operational visibility.
Use a persistent manifest when you want resumable sessions, task status, event logs, background jobs, node history, and typed transfer configuration.
download_root <- file.path(workflow_root, "general-downloads")
downloader <- Downloader$new(
dest = file.path(download_root, "files"),
temp = file.path(download_root, "tmp"),
manifest = file.path(download_root, "manifest.duckdb"),
retries = 2L,
timeout = 120L,
connect_timeout = 30L,
n_workers = 2L,
resource_policy = list(
disk_preflight = TRUE,
min_free_space = 0
)
)
downloader
downloader$manifest
#> [1] "/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T//Rtmp3Hed7X/epwshiftr-downloader/general-downloads/manifest.duckdb"The manifest stores configuration, sessions, tasks, candidate URLs, transfer events, background jobs, daemon state, control messages, and data-node history.
download() is the shortcut API for a single URL. It can
run without a persistent session, but using it on the same downloader
keeps the destination, temporary directory, retry, timeout, and checksum
behavior consistent.
single_path <- downloader$download(
url = "https://raw.githubusercontent.com/r-lib/actions/v2/README.md",
filename = "r-lib-actions-readme.md",
subdir = "single",
progress = FALSE,
overwrite = FALSE,
resume = TRUE
)
single_path
#> [1] "/private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/single/r-lib-actions-readme.md"
file.info(single_path)[, c("size", "mtime")]
#> size
#> /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/single/r-lib-actions-readme.md 8700
#> mtime
#> /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/single/r-lib-actions-readme.md 2026-06-25 02:14:07
readLines(single_path, n = 6L)
#> [1] "# GitHub Actions for the R language"
#> [2] ""
#> [3] "[](https://www.bestpractices.dev/projects/8907)"
#> [4] "[](https://securityscorecards.dev/viewer/?uri=github.com/r-lib/actions)"
#> [5] "[](https://github.com/r-lib/actions/actions?workflow=R-CMD-check)"
#> [6] "[](https://community.rstudio.com/new-topic?category=Package%20development&tags=github-actions)"A persistent plan can contain multiple URLs and candidate mirrors.
The minimal required columns are logical_file_id,
filename, and url; subdir,
checksum, checksum_type, size,
service, and data_node are optional.
This live example downloads several small HTTPS files into the article-local temporary directory.
small_plan <- data.frame(
logical_file_id = c(
"r-lib-setup-r",
"r-lib-setup-r-dependencies",
"r-lib-check-r-package"
),
filename = c(
"setup-r-action.yml",
"setup-r-dependencies-action.yaml",
"check-r-package-action.yaml"
),
subdir = "batch",
url = c(
"https://raw.githubusercontent.com/r-lib/actions/v2/setup-r/action.yml",
"https://raw.githubusercontent.com/r-lib/actions/v2/setup-r-dependencies/action.yaml",
"https://raw.githubusercontent.com/r-lib/actions/v2/check-r-package/action.yaml"
),
checksum = NA_character_,
checksum_type = "sha256",
size = NA_real_,
service = "HTTPS",
data_node = "raw.githubusercontent.com",
stringsAsFactors = FALSE
)
select_cols(small_plan, c("logical_file_id", "filename", "subdir", "url", "service", "data_node"))
#> logical_file_id filename subdir
#> <char> <char> <char>
#> 1: r-lib-setup-r setup-r-action.yml batch
#> 2: r-lib-setup-r-dependencies setup-r-dependencies-action.yaml batch
#> 3: r-lib-check-r-package check-r-package-action.yaml batch
#> url
#> <char>
#> 1: https://raw.githubusercontent.com/r-lib/actions/v2/setup-r/action.yml
#> 2: https://raw.githubusercontent.com/r-lib/actions/v2/setup-r-dependencies/action.yaml
#> 3: https://raw.githubusercontent.com/r-lib/actions/v2/check-r-package/action.yaml
#> service data_node
#> <char> <char>
#> 1: HTTPS raw.githubusercontent.com
#> 2: HTTPS raw.githubusercontent.com
#> 3: HTTPS raw.githubusercontent.comPreflight is read-only. It estimates task count, known byte requirements, free space, and whether disk preflight would block the run.
downloader$preflight(plan = small_plan)
#> task_count needs_download required_bytes size_unknown_count dest_free_bytes
#> 1 3 3 0 3 298211315712
#> tmp_free_bytes min_free_space dest_disk_ok tmp_disk_ok disk_ok
#> 1 298211315712 0 TRUE TRUE NA
#> disk_would_block disk_preflight
#> 1 FALSE TRUEEnqueue writes a session, tasks, and candidates to the manifest. It
does not download until run() is called.
session_id <- downloader$enqueue(
small_plan,
session_label = "small-live-files"
)
downloader$sessions()
#> session_id label status created_at
#> 1 20260624-181407-0d0cb568 small-live-files queued 2026-06-24 18:14:07
#> updated_at completed_at
#> 1 2026-06-24 18:14:07 <NA>
select_cols(downloader$tasks(session_id = session_id), c(
"task_id", "logical_file_id", "filename", "status",
"target_path", "size"
), n = 6L)
#> task_id
#> <char>
#> 1: 4daf72ca754d11bc2e55dc3f372d2045b448d358c070b59da56a7e11af457e04
#> 2: 58534ff425504fe4271b159646fbdb17c4311cc742add3116da3e81656111269
#> 3: bc9b902b34cf54a38362043d473288417182d98dab7400dbf39ac28908d646e9
#> logical_file_id filename status
#> <char> <char> <char>
#> 1: r-lib-check-r-package check-r-package-action.yaml queued
#> 2: r-lib-setup-r-dependencies setup-r-dependencies-action.yaml queued
#> 3: r-lib-setup-r setup-r-action.yml queued
#> target_path
#> <char>
#> 1: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/check-r-package-action.yaml
#> 2: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-dependencies-action.yaml
#> 3: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-action.yml
#> size
#> <num>
#> 1: NA
#> 2: NA
#> 3: NA
run_rows <- downloader$run(
session_id = session_id,
progress = FALSE,
overwrite = FALSE,
resume = TRUE
)
select_cols(run_rows, c(
"logical_file_id", "filename", "status",
"bytes_done", "target_path", "selected_url"
), n = 6L)
#> logical_file_id filename status
#> <char> <char> <char>
#> 1: r-lib-check-r-package check-r-package-action.yaml done
#> 2: r-lib-setup-r-dependencies setup-r-dependencies-action.yaml done
#> 3: r-lib-setup-r setup-r-action.yml done
#> bytes_done
#> <num>
#> 1: 4031
#> 2: 15578
#> 3: 4260
#> target_path
#> <char>
#> 1: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/check-r-package-action.yaml
#> 2: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-dependencies-action.yaml
#> 3: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-action.yml
#> selected_url
#> <char>
#> 1: https://raw.githubusercontent.com/r-lib/actions/v2/check-r-package/action.yaml
#> 2: https://raw.githubusercontent.com/r-lib/actions/v2/setup-r-dependencies/action.yaml
#> 3: https://raw.githubusercontent.com/r-lib/actions/v2/setup-r/action.ymlThe manifest is useful after the R session that started the download has gone away. It records what was queued, what completed, and which URL was selected.
downloader$sessions()
#> session_id label status created_at
#> 1 20260624-181407-0d0cb568 small-live-files done 2026-06-24 18:14:07
#> updated_at completed_at
#> 1 2026-06-24 18:14:11 2026-06-24 18:14:11
select_cols(downloader$status(session_id = session_id), c(
"logical_file_id", "status", "bytes_done", "target_path",
"selected_url", "last_error"
), n = 10L)
#> logical_file_id status bytes_done
#> <char> <char> <num>
#> 1: r-lib-check-r-package done 4031
#> 2: r-lib-setup-r-dependencies done 15578
#> 3: r-lib-setup-r done 4260
#> target_path
#> <char>
#> 1: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/check-r-package-action.yaml
#> 2: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-dependencies-action.yaml
#> 3: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-action.yml
#> selected_url
#> <char>
#> 1: https://raw.githubusercontent.com/r-lib/actions/v2/check-r-package/action.yaml
#> 2: https://raw.githubusercontent.com/r-lib/actions/v2/setup-r-dependencies/action.yaml
#> 3: https://raw.githubusercontent.com/r-lib/actions/v2/setup-r/action.yml
#> last_error
#> <char>
#> 1: <NA>
#> 2: <NA>
#> 3: <NA>
select_cols(downloader$events(session_id = session_id), c(
"event", "message", "task_id", "created_at"
), n = 12L)
#> event
#> <char>
#> 1: enqueue
#> 2: start
#> 3: start
#> 4: done
#> 5: start
#> 6: done
#> 7: session_done
#> 8: done
#> message
#> <char>
#> 1: Queued 3 download task(s).
#> 2: https://raw.githubusercontent.com/r-lib/actions/v2/check-r-package/action.yaml
#> 3: https://raw.githubusercontent.com/r-lib/actions/v2/setup-r-dependencies/action.yaml
#> 4: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/check-r-package-action.yaml
#> 5: https://raw.githubusercontent.com/r-lib/actions/v2/setup-r/action.yml
#> 6: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-dependencies-action.yaml
#> 7: done
#> 8: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-action.yml
#> task_id
#> <char>
#> 1: <NA>
#> 2: 4daf72ca754d11bc2e55dc3f372d2045b448d358c070b59da56a7e11af457e04
#> 3: 58534ff425504fe4271b159646fbdb17c4311cc742add3116da3e81656111269
#> 4: 4daf72ca754d11bc2e55dc3f372d2045b448d358c070b59da56a7e11af457e04
#> 5: bc9b902b34cf54a38362043d473288417182d98dab7400dbf39ac28908d646e9
#> 6: 58534ff425504fe4271b159646fbdb17c4311cc742add3116da3e81656111269
#> 7: <NA>
#> 8: bc9b902b34cf54a38362043d473288417182d98dab7400dbf39ac28908d646e9
#> created_at
#> <POSc>
#> 1: 2026-06-24 18:14:07
#> 2: 2026-06-24 18:14:08
#> 3: 2026-06-24 18:14:08
#> 4: 2026-06-24 18:14:10
#> 5: 2026-06-24 18:14:10
#> 6: 2026-06-24 18:14:10
#> 7: 2026-06-24 18:14:11
#> 8: 2026-06-24 18:14:11
list.files(file.path(download_root, "files"), recursive = TRUE)
#> [1] "batch/check-r-package-action.yaml"
#> [2] "batch/setup-r-action.yml"
#> [3] "batch/setup-r-dependencies-action.yaml"
#> [4] "single/r-lib-actions-readme.md"verify() checks completed files. When a task has no
checksum, successful file existence is enough. When a checksum exists,
the recorded algorithm is used.
select_cols(downloader$verify(session_id = session_id), c(
"logical_file_id", "status", "checksum_ok", "target_path"
), n = 10L)
#> logical_file_id status checksum_ok
#> <char> <char> <lgcl>
#> 1: r-lib-check-r-package done TRUE
#> 2: r-lib-setup-r-dependencies done TRUE
#> 3: r-lib-setup-r done TRUE
#> target_path
#> <char>
#> 1: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/check-r-package-action.yaml
#> 2: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-dependencies-action.yaml
#> 3: /private/var/folders/8f/t8sk2pps6135xbp47cs8qb2r0000gn/T/Rtmp3Hed7X/epwshiftr-downloader/general-downloads/files/batch/setup-r-action.yml
downloader$cleanup_tmp(all = TRUE)
#> [1] 0Retry and resume become important after interrupted jobs, failed checksums, cancelled tasks, or a process exit during transfer. In a successful article run there is nothing to requeue, so the operational calls are shown here without executing them:
downloader$retry(session_id = session_id)
downloader$resume(session_id = session_id, progress = TRUE)
downloader$cancel(session_id = session_id)Long-running sessions can be submitted in the background. The job itself is stored in the manifest, and logs are written next to the manifest.
job <- downloader$start(
session_id = session_id,
overwrite = FALSE,
resume = TRUE,
mode = "process"
)
downloader$jobs()
downloader$job_status(job$job_id)
downloader$job_logs(job$job_id, tail = 50L)
downloader$stop_job(job$job_id)Daemon mode keeps a persistent downloader loop alive for repeated submissions:
downloader$daemon_start(port = 0L, heartbeat_interval = 5)
downloader$daemon_status()
downloader$start(session_id = session_id, mode = "daemon")
downloader$daemon_stop()Those examples are intentionally shown as ordinary code snippets instead of executed article chunks because starting detached processes during documentation builds can leave local background work behind.
ESGF File results produce downloader-ready plans with service URLs, candidate ranking fields, file keys, sizes, checksums, and data-node metadata. This live example builds an ESGF plan but does not download the large NetCDF files.
esgf_query <- esg_query("https://esgf-data.dkrz.de")$
activity_id("ScenarioMIP")$
experiment_id("ssp585")$
source_id("MPI-ESM1-2-LR")$
variant_label("r1i1p1f1")$
frequency("mon")$
variable_id("tas")$
data_node("esgf.ceda.ac.uk")$
params(table_id = "Amon")$
limit(3L)
esgf_files <- esgf_query$collect(
type = "File",
fields = "*",
all = FALSE,
limit = TRUE
)
if (!esgf_files$count()) {
stop("The live ESGF query returned no File records.", call. = FALSE)
}
esgf_files_2060 <- esgf_files$filter_time(
"2060-01-01T00:00:00Z",
"2060-12-31T23:59:59Z",
method = "drs"
)
if (!esgf_files_2060$count()) {
stop("The live ESGF query returned no File records covering 2060.", call. = FALSE)
}
esgf_files_2060$count()
#> [1] 1
esgf_plan <- esgf_files_2060$download_plan(
replica = "current",
service = "HTTPServer",
probe = FALSE,
strategy = "stable"
)
select_cols(esgf_plan, c(
"logical_file_id", "filename", "service", "data_node",
"size", "url", "checksum", "checksum_type"
), n = 10L)
#> logical_file_id
#> <char>
#> 1: master:CMIP6.ScenarioMIP.MPI-M.MPI-ESM1-2-LR.ssp585.r1i1p1f1.Amon.tas.gn.tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_205501-207412.nc
#> filename service
#> <char> <char>
#> 1: tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_205501-207412.nc HTTPServer
#> data_node size
#> <char> <num>
#> 1: esgf.ceda.ac.uk 8011675
#> url
#> <char>
#> 1: https://esgf.ceda.ac.uk/thredds/fileServer/esg_cmip6/CMIP6/ScenarioMIP/MPI-M/MPI-ESM1-2-LR/ssp585/r1i1p1f1/Amon/tas/gn/v20190710/tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_205501-207412.nc
#> checksum
#> <char>
#> 1: f1419fb9621b49e5bde363dd2d79c27f63d692ff3e7a2b81a5910fb5356f3294
#> checksum_type
#> <char>
#> 1: sha256probe = FALSE keeps this article fast and avoids
touching candidate URLs just to rank them. In operational runs,
probe = TRUE can record latency and availability in the
downloader’s download_node table.
For store-managed ESGF work, prefer store$downloader().
The downloader manifest, temporary files, final files, and logs then
live under the store, and completed tasks can be synced back into the
store catalog.
store <- EsgStore$new(file.path(workflow_root, "store"))
store_query_id <- store$add_query(
esgf_query,
label = "tas-ssp585-2060-plan",
track = TRUE
)
store_downloader <- store$downloader(
n_workers = 2L,
retries = 1L,
timeout = 120L,
resource_policy = list(min_free_space = 0)
)
preflight <- store$download_preflight(
query_id = store_query_id,
downloader = store_downloader,
replica = "current",
service = "HTTPServer",
probe = FALSE,
all = FALSE,
limit = TRUE
)
preflight$summary
#> query_id
#> <char>
#> 1: d31b46c91e03aa9eb71ac76be90cd1dcaebc742db41b1a3485d426a8ecb82f6c
#> label file_total current_count candidate_count bytes_total
#> <char> <int> <int> <int> <num>
#> 1: tas-ssp585-2060-plan 3 3 3 24059358
#> local_available needs_download no_httpserver cooldown_nodes
#> <int> <int> <int> <int>
#> 1: 0 3 0 0
#> target_path_collision_count missing_layout_field_count required_bytes
#> <int> <int> <num>
#> 1: 0 0 24059358
#> size_unknown_count dest_free_bytes tmp_free_bytes min_free_space disk_ok
#> <int> <num> <num> <num> <lgcl>
#> 1: 0 298203377664 298203377664 0 TRUE
#> disk_would_block disk_preflight
#> <lgcl> <lgcl>
#> 1: FALSE TRUE
select_cols(preflight$files, c(
"file_key", "filename", "variable_id", "size", "local_path", "status"
), n = 10L)
#> file_key
#> <char>
#> 1: master:CMIP6.ScenarioMIP.MPI-M.MPI-ESM1-2-LR.ssp585.r1i1p1f1.Amon.tas.gn.tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_201501-203412.nc
#> 2: master:CMIP6.ScenarioMIP.MPI-M.MPI-ESM1-2-LR.ssp585.r1i1p1f1.Amon.tas.gn.tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_203501-205412.nc
#> 3: master:CMIP6.ScenarioMIP.MPI-M.MPI-ESM1-2-LR.ssp585.r1i1p1f1.Amon.tas.gn.tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_205501-207412.nc
#> filename variable_id
#> <char> <char>
#> 1: tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_201501-203412.nc tas
#> 2: tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_203501-205412.nc tas
#> 3: tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_205501-207412.nc tas
#> size local_path status
#> <num> <char> <char>
#> 1: 8025316 <NA> current
#> 2: 8022367 <NA> current
#> 3: 8011675 <NA> current
select_cols(preflight$candidates, c(
"filename", "service", "data_node", "size", "url"
), n = 10L)
#> filename service
#> <char> <char>
#> 1: tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_201501-203412.nc HTTPServer
#> 2: tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_203501-205412.nc HTTPServer
#> 3: tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_205501-207412.nc HTTPServer
#> data_node size
#> <char> <num>
#> 1: esgf.ceda.ac.uk 8025316
#> 2: esgf.ceda.ac.uk 8022367
#> 3: esgf.ceda.ac.uk 8011675
#> url
#> <char>
#> 1: https://esgf.ceda.ac.uk/thredds/fileServer/esg_cmip6/CMIP6/ScenarioMIP/MPI-M/MPI-ESM1-2-LR/ssp585/r1i1p1f1/Amon/tas/gn/v20190710/tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_201501-203412.nc
#> 2: https://esgf.ceda.ac.uk/thredds/fileServer/esg_cmip6/CMIP6/ScenarioMIP/MPI-M/MPI-ESM1-2-LR/ssp585/r1i1p1f1/Amon/tas/gn/v20190710/tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_203501-205412.nc
#> 3: https://esgf.ceda.ac.uk/thredds/fileServer/esg_cmip6/CMIP6/ScenarioMIP/MPI-M/MPI-ESM1-2-LR/ssp585/r1i1p1f1/Amon/tas/gn/v20190710/tas_Amon_MPI-ESM1-2-LR_ssp585_r1i1p1f1_gn_205501-207412.ncTo actually enqueue and run those ESGF downloads, use:
session_id <- store$download_query(
query_id = store_query_id,
downloader = store_downloader,
replica = "current",
service = "HTTPServer",
probe = TRUE,
run = TRUE,
progress = TRUE
)
store$sync_downloads(store_downloader)For command-line operation, see CLI operations. For how query records, downloads, extraction outputs, and artifacts are tied together, see ESG stores.