Nothing
# Cache Management for mobdb
#' Get mobdb cache directory path
#'
#' Returns the cache directory path, checking environment variables and
#' options before falling back to the default R user directory.
#'
#' Priority order:
#' 1. MOBDB_CACHE_PATH environment variable
#' 2. mobdb.cache_path R option
#' 3. tools::R_user_dir("mobdb", "cache") (default)
#'
#' @return Character string with cache directory path
#' @noRd
get_mobdb_cache_path <- function() {
# Priority 1: Environment variable
env_path <- Sys.getenv("MOBDB_CACHE_PATH", unset = "")
if (nzchar(env_path)) {
return(path.expand(env_path))
}
# Priority 2: R options
opt_path <- getOption("mobdb.cache_path")
if (!is.null(opt_path)) {
return(path.expand(opt_path))
}
# Priority 3: Default R user directory (CRAN-compliant)
tools::R_user_dir("mobdb", which = "cache")
}
#' Ensure cache directory exists
#'
#' Creates the cache directory if it doesn't exist.
#'
#' @return Character string with cache directory path (invisibly)
#' @noRd
ensure_cache_dir <- function() {
cache_dir <- get_mobdb_cache_path()
if (!dir.exists(cache_dir)) {
dir.create(cache_dir, recursive = TRUE, showWarnings = FALSE)
}
invisible(cache_dir)
}
#' Generate cache key from parameters
#'
#' Creates a unique cache key by hashing the provided parameters.
#'
#' @param ... Parameters to include in cache key
#' @param prefix Character prefix for cache key (default: "mobdb")
#' @return Character string with cache key (filename)
#' @noRd
generate_cache_key <- function(..., prefix = "mobdb") {
params <- list(...)
# Remove NULL values
params <- params[!vapply(params, is.null, logical(1))]
# Sort for consistency (same params = same key regardless of order)
if (length(params) > 0 && !is.null(names(params))) {
params <- params[order(names(params))]
}
# Generate hash
param_hash <- digest::digest(params, algo = "md5")
paste0(prefix, "_", param_hash, ".rds")
}
#' Read from cache
#'
#' @param cache_key Cache file name
#' @param max_age Maximum age in hours (NULL = no limit)
#' @return Cached data or NULL if not found/expired
#' @noRd
read_from_cache <- function(cache_key, max_age = NULL) {
cache_dir <- get_mobdb_cache_path()
cache_file <- file.path(cache_dir, cache_key)
if (!file.exists(cache_file)) {
return(NULL)
}
# Check age if max_age specified
if (!is.null(max_age)) {
file_info <- file.info(cache_file)
age_hours <- as.numeric(difftime(Sys.time(), file_info$mtime, units = "hours"))
if (age_hours > max_age) {
cli::cli_inform(c("i" = "Cache expired (age: {round(age_hours, 1)}h > max: {max_age}h)"))
return(NULL)
}
}
cli::cli_inform(c("v" = "Using cached data"))
readRDS(cache_file)
}
#' Write to cache
#'
#' @param data Data to cache
#' @param cache_key Cache file name
#' @return Character. Path to the cache file (invisibly).
#' @noRd
write_to_cache <- function(data, cache_key) {
cache_dir <- ensure_cache_dir()
cache_file <- file.path(cache_dir, cache_key)
saveRDS(data, cache_file, compress = TRUE)
invisible(cache_file)
}
#' Get default cache TTL by endpoint type
#'
#' @param endpoint_type Type of endpoint
#' @return TTL in hours
#' @noRd
get_cache_ttl <- function(endpoint_type = c("feeds", "search", "datasets", "historical")) {
endpoint_type <- match.arg(endpoint_type)
ttls <- list(
feeds = 1, # Feed status changes relatively often
search = 0.5, # Search results should be fresh
datasets = 24, # Historical datasets don't change
historical = 24 # Historical data is immutable
)
ttls[[endpoint_type]]
}
# User-facing cache management functions ----
#' Set or show mobdb cache directory
#'
#' Configure the directory where mobdb caches API responses. By default,
#' mobdb uses \code{tools::R_user_dir("mobdb", "cache")}.
#'
#' @param path Optional. Directory path for cache. If NULL (default), shows
#' current cache path without changing it.
#' @param install Logical. If TRUE, adds MOBDB_CACHE_PATH to .Renviron for
#' persistence across R sessions. Default: FALSE
#' @param overwrite Logical. If TRUE, overwrites existing MOBDB_CACHE_PATH in
#' .Renviron. Default: FALSE
#'
#' @return Character string with cache path (invisibly)
#' @export
#'
#' @examples
#' # Show current cache path
#' mobdb_cache_path()
#'
#' \donttest{
#' # Set for current session only
#' mobdb_cache_path(file.path(tempdir(), "mobdb_cache_example"))
#' }
#'
#' \dontrun{
#' # Set permanently in .Renviron
#' mobdb_cache_path("~/my_mobdb_cache", install = TRUE)
#' }
mobdb_cache_path <- function(path = NULL, install = FALSE, overwrite = FALSE) {
# If no path provided, just show current path
if (is.null(path)) {
current_path <- get_mobdb_cache_path()
cli::cli_inform(c("i" = "Current cache path: {current_path}"))
return(invisible(current_path))
}
# Expand and validate path
path <- path.expand(path)
# Create directory if it doesn't exist
if (!dir.exists(path)) {
dir.create(path, recursive = TRUE, showWarnings = FALSE)
cli::cli_inform(c("v" = "Created cache directory: {path}"))
}
# Set for current session
Sys.setenv("MOBDB_CACHE_PATH" = path)
cli::cli_inform(c("v" = "Cache path set to: {path}"))
# Make permanent if requested
if (install) {
if (interactive()) {
answer <- utils::menu(
c("Yes", "No"),
title = "This will write MOBDB_CACHE_PATH to ~/.Renviron. Proceed?"
)
if (answer != 1) {
cli::cli_inform(c(
"v" = "Cache path set for current session only.",
"i" = "Path was not written to .Renviron."
))
return(invisible(path))
}
}
home <- Sys.getenv("HOME")
renv <- file.path(home, ".Renviron")
# Check if already set
if (file.exists(renv)) {
oldenv <- readLines(renv, warn = FALSE)
if (!overwrite && any(grepl("MOBDB_CACHE_PATH", oldenv))) {
cli::cli_abort(c(
"MOBDB_CACHE_PATH already set in .Renviron",
"i" = "Use {.code overwrite = TRUE} to replace"
))
}
# Remove old entry
newenv <- oldenv[!grepl("MOBDB_CACHE_PATH", oldenv)]
writeLines(newenv, renv)
}
# Append new setting
write(paste0("MOBDB_CACHE_PATH='", path, "'"),
renv,
sep = "\n",
append = TRUE)
cli::cli_inform(c(
"v" = "Added MOBDB_CACHE_PATH to .Renviron",
"i" = "Restart R for permanent effect"
))
}
invisible(path)
}
#' Show cache information
#'
#' Displays information about the mobdb cache including location,
#' number of files, and total size.
#'
#' @return List with cache information (invisibly):
#' \item{path}{Cache directory path}
#' \item{files}{Number of cached files}
#' \item{size_mb}{Total size in megabytes}
#' \item{exists}{Whether cache directory exists}
#'
#' @export
#'
#' @examples
#' # Show cache info
#' mobdb_cache_info()
mobdb_cache_info <- function() {
cache_path <- get_mobdb_cache_path()
# Get cache statistics
if (dir.exists(cache_path)) {
files <- list.files(cache_path, pattern = "\\.rds$", full.names = TRUE)
n_files <- length(files)
if (n_files > 0) {
total_size <- sum(file.size(files), na.rm = TRUE)
size_mb <- round(total_size / 1024^2, 2)
} else {
size_mb <- 0
}
} else {
n_files <- 0
size_mb <- 0
}
cli::cli_inform(c(
"mobdb Cache Information",
"i" = "Path: {cache_path}",
"i" = "Files: {n_files}",
"i" = "Size: {size_mb} MB",
"i" = "Exists: {dir.exists(cache_path)}"
))
invisible(list(
path = cache_path,
files = n_files,
size_mb = size_mb,
exists = dir.exists(cache_path)
))
}
#' List cached files
#'
#' Returns a tibble with information about all cached files, including
#' file name, size, modification time, and age.
#'
#' @return Tibble with columns:
#' \item{file}{File name}
#' \item{size_mb}{File size in megabytes}
#' \item{modified}{Last modification time}
#' \item{age_hours}{Age in hours}
#'
#' @export
#'
#' @examples
#' # List all cached files
#' mobdb_cache_list()
mobdb_cache_list <- function() {
cache_dir <- get_mobdb_cache_path()
if (!dir.exists(cache_dir)) {
cli::cli_warn("Cache directory does not exist: {cache_dir}")
return(tibble::tibble())
}
files <- list.files(cache_dir, pattern = "\\.rds$", full.names = TRUE)
if (length(files) == 0) {
cli::cli_inform(c("i" = "Cache is empty"))
return(tibble::tibble())
}
file_info <- file.info(files)
tibble::tibble(
file = basename(files),
size_mb = round(file_info$size / 1024^2, 2),
modified = file_info$mtime,
age_hours = round(as.numeric(difftime(Sys.time(), file_info$mtime, units = "hours")), 1)
) |>
dplyr::arrange(dplyr::desc(.data$modified))
}
#' Clear mobdb cache
#'
#' Removes cached files from the cache directory. Can remove all files
#' or only those older than a specified number of days.
#'
#' @param older_than Optional. Remove only files older than this many days.
#' If NULL (default), removes all cached files.
#'
#' @return Invisibly returns NULL. Called for its side effect of removing cached files (and, if requested via older_than, only those older than the given number of days)
#' @export
#'
#' @examples
#' \donttest{
#' # Clear all cache
#' mobdb_cache_clear()
#'
#' # Clear only files older than 7 days
#' mobdb_cache_clear(older_than = 7)
#' }
mobdb_cache_clear <- function(older_than = NULL) {
cache_dir <- get_mobdb_cache_path()
if (!dir.exists(cache_dir)) {
cli::cli_inform(c("i" = "No cache directory found"))
return(invisible())
}
files <- list.files(cache_dir, pattern = "\\.rds$", full.names = TRUE)
if (length(files) == 0) {
cli::cli_inform(c("i" = "Cache is already empty"))
return(invisible())
}
# Filter by age if specified
if (!is.null(older_than)) {
file_info <- file.info(files)
age_days <- as.numeric(difftime(Sys.time(), file_info$mtime, units = "days"))
files <- files[age_days > older_than]
if (length(files) == 0) {
cli::cli_inform(c("i" = "No files older than {older_than} day{?s}"))
return(invisible())
}
}
file.remove(files)
cli::cli_inform(c("v" = "Removed {length(files)} cached file{?s}"))
invisible()
}
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.