Net::DownloadHandler *Net::DownloadManager::download(const DownloadRequest &downloadRequest)
{
    // Process download request
    const QNetworkRequest request = createNetworkRequest(downloadRequest);
    const ServiceID id = ServiceID::fromURL(request.url());
    const bool isSequentialService = m_sequentialServices.contains(id);

    auto downloadHandler = new DownloadHandlerImpl {downloadRequest, this};
    connect(downloadHandler, &DownloadHandler::finished, downloadHandler, &QObject::deleteLater);
    connect(downloadHandler, &QObject::destroyed, this, [this, id, downloadHandler]()
    {
        m_waitingJobs[id].removeOne(downloadHandler);
    });

    if (!isSequentialService || !m_busyServices.contains(id)) {
        qDebug("Downloading %s...", qUtf8Printable(downloadRequest.url()));
        if (isSequentialService)
            m_busyServices.insert(id);
        downloadHandler->assignNetworkReply(m_networkManager.get(request));
    }
    else {
        m_waitingJobs[id].enqueue(downloadHandler);
    }

    return downloadHandler;
}
Net::DownloadHandler::DownloadHandler(QNetworkReply *reply, DownloadManager *manager, const DownloadRequest &downloadRequest)
    : QObject(manager)
    , m_reply(reply)
    , m_manager(manager)
    , m_downloadRequest(downloadRequest)
{
    if (reply)
        assignNetworkReply(reply);
}
void Net::DownloadManager::handleReplyFinished(const QNetworkReply *reply)
{
    const ServiceID id = ServiceID::fromURL(reply->url());
    const auto waitingJobsIter = m_waitingJobs.find(id);
    if ((waitingJobsIter == m_waitingJobs.end()) || waitingJobsIter.value().isEmpty()) {
        m_busyServices.remove(id);
        return;
    }

    auto handler = static_cast<DownloadHandlerImpl *>(waitingJobsIter.value().dequeue());
    qDebug("Downloading %s...", qUtf8Printable(handler->url()));
    handler->assignNetworkReply(m_networkManager.get(createNetworkRequest(handler->downloadRequest())));
    handler->disconnect(this);
}