R/rate_limiting.R

Defines functions reset_rate_limit get_rate_limit_status rate_limit is_rate_limit_initialized init_rate_limit

Documented in get_rate_limit_status init_rate_limit is_rate_limit_initialized rate_limit reset_rate_limit

#' Rate Limiting for VirusTotal API
#'
#' @description
#' Modern rate limiting implementation that properly manages API request limits.
#' VirusTotal public API allows 4 requests per minute.
#'
#' @name rate-limiting
#' @keywords internal
#' @family rate limiting
NULL

.virustotal_state <- new.env(parent = emptyenv())

#' Initialize rate limiting state
#'
#' @keywords internal
init_rate_limit <- function() {
  .virustotal_state$requests <- numeric(0)
  .virustotal_state$window_size <- 60
  .virustotal_state$max_requests <- 4
  .virustotal_state$initialized <- TRUE
}

#' Check if rate limiting is properly initialized
#'
#' @keywords internal
is_rate_limit_initialized <- function() {
  !is.null(.virustotal_state$initialized) &&
    !is.null(.virustotal_state$requests) &&
    !is.null(.virustotal_state$window_size) &&
    !is.null(.virustotal_state$max_requests)
}

#' Modern rate limiting implementation
#'
#' Uses a sliding window approach to track requests and enforce limits.
#' This replaces the old environment variable-based approach.
#'
#' @param force_wait Logical. If TRUE, will wait even if under limit
#' @return Invisible TRUE
#' @keywords internal
#' @family rate limiting
rate_limit <- function(force_wait = FALSE) {
  if (!is_rate_limit_initialized()) {
    init_rate_limit()
  }

  current_time <- as.numeric(Sys.time())
  window_size <- .virustotal_state$window_size
  if (is.null(window_size)) window_size <- 60

  window_start <- current_time - window_size

  requests <- .virustotal_state$requests
  if (is.null(requests)) requests <- numeric(0)

  if (length(requests) > 0) {
    active_requests <- requests[requests > window_start]
  } else {
    active_requests <- numeric(0)
  }
  .virustotal_state$requests <- active_requests

  max_requests <- .virustotal_state$max_requests
  if (is.null(max_requests)) max_requests <- 4

  if (length(.virustotal_state$requests) >= max_requests || force_wait) {
    if (length(.virustotal_state$requests) > 0) {
      oldest_request <- min(active_requests)
      wait_time <- max(0, window_size - (current_time - oldest_request))

      if (wait_time > 0) {
        message(sprintf("Rate limit reached. Waiting %.1f seconds...",
                        wait_time))
        Sys.sleep(wait_time + 0.1)

        current_time <- as.numeric(Sys.time())
        window_start <- current_time - window_size

        requests_after_wait <- .virustotal_state$requests
        if (!is.null(requests_after_wait) && length(requests_after_wait) > 0) {
          keep <- requests_after_wait > window_start
          active_after <- requests_after_wait[keep]
        } else {
          active_after <- numeric(0)
        }
        .virustotal_state$requests <- active_after
      }
    }
  }

  .virustotal_state$requests <- c(.virustotal_state$requests, current_time)

  invisible(TRUE)
}

#' Get current rate limit status
#'
#' @return List with current status information
#' @keywords internal
#' @family rate limiting
get_rate_limit_status <- function() {
  if (!is_rate_limit_initialized()) {
    init_rate_limit()
  }

  current_time <- as.numeric(Sys.time())

  window_size <- .virustotal_state$window_size
  if (is.null(window_size)) window_size <- 60

  max_requests <- .virustotal_state$max_requests
  if (is.null(max_requests)) max_requests <- 4

  requests <- .virustotal_state$requests
  if (is.null(requests)) requests <- numeric(0)

  window_start <- current_time - window_size

  if (length(requests) > 0) {
    active_requests <- requests[requests > window_start]
  } else {
    active_requests <- numeric(0)
  }

  list(
    requests_used = length(active_requests),
    max_requests = max_requests,
    window_size = window_size,
    requests_remaining = max_requests - length(active_requests),
    window_reset_time = if (length(active_requests) > 0) {
      min(active_requests) + window_size
    } else {
      current_time
    }
  )
}

#' Reset rate limiting state
#'
#' Clears all rate limiting history. Useful for testing.
#'
#' @keywords internal
#' @family rate limiting
reset_rate_limit <- function() {
  init_rate_limit()
  message("Rate limiting state reset.")
  invisible(TRUE)
}

Try the virustotal package in your browser

Any scripts or data that you put into this service are public.

virustotal documentation built on April 13, 2026, 9:07 a.m.