void DefaultFileSource::Impl::update(DefaultFileRequest& request) {
    if (request.getResponse()) {
        // We've at least obtained a cache value, potentially we also got a final response.
        // The observers have been notified already; send what we have to the new one as well.

        // Before returning the existing response, make sure that it is still fresh, or update the
        // `stale` flag.
        request.checkResponseFreshness();

        if (request.getResponse()->stale && !request.realRequest) {
            // We've returned a stale response; now make sure the requester also gets a fresh
            // response eventually. It's possible that there's already a request in progress.
            // Note that this will also trigger updates to all other existing listeners.
            // Since we already have data, we're going to verify
            startRealRequest(request);
        } else {
            // The response is still fresh (or there's already a request for refreshing the resource
            // in progress), so there's nothing we need to do.
        }
    } else if (!request.cacheRequest && !request.realRequest) {
        // There is no request in progress, and we don't have a response yet. This means we'll have
        // to start the request ourselves.
        if (cache) {
            startCacheRequest(request);
        } else {
            startRealRequest(request);
        }
    } else {
        // There is a request in progress. We just have to wait.
    }
}
void DefaultFileSource::Impl::networkIsReachableAgain() {
    for (auto& req : pending) {
        auto& request = req.second;
        auto& response = request.getResponse();
        if (!request.realRequest && response && response->error && response->error->reason == Response::Error::Reason::Connection) {
            // We need all requests to fail at least once before we are going to start retrying
            // them, and we only immediately restart request that failed due to connection issues.
            startRealRequest(request);
        }
    }
}
void OnlineFileSource::Impl::reschedule(OnlineFileRequestImpl& request) {
    if (request.realRequest) {
        // There's already a request in progress; don't start another one.
        return;
    }

    const Seconds timeout = request.getRetryTimeout();

    if (timeout == Seconds::zero()) {
        update(request);
    } else if (timeout > Seconds::zero()) {
        request.realRequestTimer.start(timeout, Duration::zero(), [this, &request] {
            assert(!request.realRequest);
            startRealRequest(request);
        });
    }
}
void OnlineFileSource::Impl::startCacheRequest(OnlineFileRequestImpl& request) {
    // Check the cache for existing data so that we can potentially
    // revalidate the information without having to redownload everything.
    request.cacheRequest =
        cache->get(request.resource, [this, &request](std::shared_ptr<Response> response) {
            request.cacheRequest = nullptr;
            if (response) {
                response->stale = response->isExpired();
                request.setResponse(response);
            }

            if (!response || response->stale) {
                // No response or stale cache. Run the real request.
                startRealRequest(request);
            }

            reschedule(request);
        });
}
void DefaultFileSource::Impl::reschedule(DefaultFileRequest& request) {
    if (request.realRequest) {
        // There's already a request in progress; don't start another one.
        return;
    }

    const auto timeout = request.getRetryTimeout();

    if (timeout == 0) {
        update(request);
    } else if (timeout > 0) {
        if (!request.timerRequest) {
            request.timerRequest = std::make_unique<uv::timer>(util::RunLoop::getLoop());
        }

        // timeout is in seconds, but the timer takes milliseconds.
        request.timerRequest->start(1000 * timeout, 0, [this, &request] {
            assert(!request.realRequest);
            startRealRequest(request);
        });
    }
}
void DefaultFileSource::Impl::startCacheRequest(DefaultFileRequest& request) {
    // Check the cache for existing data so that we can potentially
    // revalidate the information without having to redownload everything.
    request.cacheRequest =
        cache->get(request.resource, [this, &request](std::shared_ptr<Response> response) {
            request.cacheRequest = nullptr;
            if (response) {
                response->stale = response->isExpired();
                request.setResponse(response);
            }

            if (!response || response->stale) {
                // No response or stale cache. Run the real request.
                startRealRequest(request);
            }

            // Notify in all cases; requestors can decide whether they want to use stale responses.
            request.notify();

            reschedule(request);
        });
}