links = $links ?? new LinkRepository(); } /** * Checks still allowed in the trailing hour. */ public function remaining(): int { $limit = (int) Settings::get( 'rate_limit_per_hour' ); return max( 0, $limit - $this->links->checked_since( Clock::now() - HOUR_IN_SECONDS ) ); } /** * Budget for one lane batch. The remaining hourly budget is split between lanes so that even * concurrently running lanes can never exceed the limit together. * * @param int $lane Lane index. * @param int $lanes Number of lanes. * @param int $batch_max Max URLs per batch. */ public function lane_budget( int $lane, int $lanes, int $batch_max ): int { $remaining = $this->remaining(); $lanes = max( 1, $lanes ); $share = intdiv( $remaining, $lanes ) + ( $lane < $remaining % $lanes ? 1 : 0 ); return max( 0, min( $batch_max, $share ) ); } }